CLOVER🍀

That was when it all began.

WildFly Swarm+Hystrix(Circuit Breaker)を試す

WildFly Swarmには、Netflix OSSとの統合機能があります。

Stability Patterns / Circuit Breaker

目次のタイトルが「Stability Patterns / Circuit Breaker」なのに、ページのタイトルが「NetflixOSS」というのは
どうなんでしょう?というのはありますが、RibbonやHystrixと統合する機能のようです。

WildFly Swarmのチュートリアルにも登場します。

Fault tolerance and resilience | Thorntail

ここではConsulとともに使われているようですが、今回はRibbonもConsulも置いておいて、単純にWildFly Swarmの提供する
Hystrix向けのFraction経由でHystrixを使うことに着目したいと思います。まあ、個人的にちょっとHystrixを把握して
おきたいということがありまして。

Hystrixとは

Netflix OSSの提供する、Circuit Breakerです。

GitHub - Netflix/Hystrix: Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

Circuit Breakerとは、主にMicroservicesで外部サービスを呼び出す際に、呼び出し先が障害などでエラーになったりタイムアウト
なるような時にリクエスト(というか呼び出し)を遮断してしまうものになります。これで、外部サービスの障害に自サービスが引っ張られて
遅延していくといったことを防ぎます。

遮断された呼び出しについては、一定時間後などでまた再開するようになります。もちろん、相手側が復旧していればですが。

Hystrixのドキュメントとしては、こちらを見るとよいでしょう。

Getting Started · Netflix/Hystrix Wiki · GitHub

How To Use · Netflix/Hystrix Wiki · GitHub

WildFly Swarm+Hystrix Fraction

続いて、WildFly SwarmのHystrix向けのFractionについてです。

まず、今回用意したpom.xmlを記載しましょう。
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>circuit-breaker-hystrix</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <scala.major.version>2.12</scala.major.version>
        <scala.version>${scala.major.version}.1</scala.version>
        <scala.maven.plugin.version>3.2.2</scala.maven.plugin.version>

        <failOnMissingWebXml>false</failOnMissingWebXml>

        <wildfly.swarm.version>2017.1.1</wildfly.swarm.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.wildfly.swarm</groupId>
                <artifactId>bom-all</artifactId>
                <version>${wildfly.swarm.version}</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>

        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.wildfly.swarm</groupId>
            <artifactId>jaxrs</artifactId>
        </dependency>
        <dependency>
          <groupId>org.wildfly.swarm</groupId>
          <artifactId>hystrix</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>${scala.maven.plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <scalaVersion>${scala.version}</scalaVersion>
                    <args>
                        <arg>-Xlint</arg>
                        <arg>-unchecked</arg>
                        <arg>-deprecation</arg>
                        <arg>-feature</arg>
                    </args>
                    <recompileMode>incremental</recompileMode>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.wildfly.swarm</groupId>
                <artifactId>wildfly-swarm-plugin</artifactId>
                <version>${wildfly.swarm.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

注目点は、ここですね。

        <dependency>
          <groupId>org.wildfly.swarm</groupId>
          <artifactId>hystrix</artifactId>
        </dependency>

これでHystrix向けのFractionが追加されるわけですが、Hystrixへのライブラリ依存関係以外に何が増えるんだろう?と思って見てみたら
HystrixMetricsStreamServletが追加されるようです。
https://github.com/wildfly-swarm/wildfly-swarm/blob/2017.1.1/fractions/netflix/hystrix/src/main/java/org/wildfly/swarm/netflix/hystrix/runtime/HystrixArchivePreparer.java

https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-metrics-event-stream

これは、HystrixのDashboardを使う時に必要になります。

呼び出し先の外部サービス

では、アプリケーションを書いていきましょう。

今回は、Spring Boot CLIで簡単なRestControllerを作ることにします。単純に「HelloWorld!!」と返すだけのRestControllerです。
hello-message.groovy

@RestController
class HelloMessageController {
    @RequestMapping("hello-message")
    def message() {
        "HelloWorld!!"
    }
}

このアプリケーションは、ポート9000でリッスンすることにしましょう。次のコマンドで起動します。

$ spring run hello-message.groovy -- --server.port=9000

以上で、呼び出し先の外部サービスの準備はおしまいです。

WildFly SwarmでHystrixを使う

続いて、WildFly Swarm+Hystrixを使ったアプリケーションを書きます。

Getting Startedの「Hello World!」を見ると、HystixCommandというクラスを継承して実装するようです。

Getting Started / Hello World!

そして、実際にどんな処理をするかは、runメソッドをオーバーライドすればよい、と。

Fail Fast

で、最初にこんなのを用意。
src/main/scala/org/littlewings/wildflyswarm/hystrix/FailFastMessageCommand.scala

package org.littlewings.wildflyswarm.hystrix

import javax.ws.rs.client.ClientBuilder

import com.netflix.hystrix.{HystrixCommand, HystrixCommandGroupKey}
import org.jboss.logging.Logger

class FailFastMessageCommand extends HystrixCommand[String](HystrixCommandGroupKey.Factory.asKey("FailFastMessageCommand")) {
  val logger: Logger = Logger.getLogger(classOf[FailFastMessageCommand])

  override def run(): String = {
    logger.infof("start fail-fast request")

    val client =
      ClientBuilder
        .newClient

    try {
      val response =
        client
          .target("http://localhost:9000/hello-message")
          .request
          .get

      val message = response.readEntity(classOf[String])

      response.close()

      logger.infof("end fail-fast request")

      message
    } finally {
      client.close()
    }
  }
}

HystrixCommandのコンストラクタには、HystrixCommandGroupKeyを渡す必要があるようです。今回は、クラスの単純名から
キーを作成しました。

class FailFastMessageCommand extends HystrixCommand[String](HystrixCommandGroupKey.Factory.asKey("FailFastMessageCommand")) {

で、クラス名がなぜかFailFastになっていますが、これはドキュメントの次の箇所で、Fail Fastなパターンとして
紹介されているからです。

How To Use / Common Patterns / Fail Fast

この実装方法を取ると、runメソッドの呼び出しに失敗する(例外がスローされる)とHystrixRuntimeExceptionにその原因が
包まれてスローされるようです。それが、呼び出し元まで伝播してくる、と。

試してみましょう。こんなJAX-RSリソースクラスを用意。
src/main/scala/org/littlewings/wildflyswarm/hystrix/MessageResource.scala

package org.littlewings.wildflyswarm.hystrix

import javax.ws.rs.core.MediaType
import javax.ws.rs.{GET, Path, Produces}

import org.jboss.logging.Logger

@Path("message")
class MessageResource {
  val logger: Logger = Logger.getLogger(classOf[MessageResource])

  @GET
  @Path("fail-fast")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def failFast: String = {
    logger.infof("start resource")

    val command = new FailFastMessageCommand
    val message = command.execute()

    logger.infof("end resource")

    message + System.lineSeparator
  }
}

ポイントは、オーバーライドしたrunメソッドではなくexecuteメソッドを呼び出すことですね。

    val command = new FailFastMessageCommand
    val message = command.execute()

アプリケーションを起動します。

$ mvn wildfly-swarm:run

確認。

$ curl -i http://localhost:8080/message/fail-fast
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/plain
Content-Length: 13
Date: Wed, 18 Jan 2017 11:39:12 GMT

HelloWorld!!

では、続いてSpring Boot CLIで書いたアプリケーションを落としてアクセス。

$ curl -i http://localhost:8080/message/fail-fast
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Type: text/html;charset=UTF-8
Content-Length: 9359
Date: Wed, 18 Jan 2017 11:39:47 GMT

<html><head><title>ERROR</title><style>body {
    font-family: "Lucida Grande", "Lucida Sans Unicode", "Trebuchet MS", Helvetica, Arial, Verdana, sans-serif;
    margin: 5px;
}

〜省略〜

エラーになります。

サーバーWildFly Swarmアプリケーション)裏では、HystrixRuntimeExceptionがスローされています。さらにその原因をたどると
接続エラーなわけですが。

Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: FailFastMessageCommand failed and no fallback available.

落としたSpring Boot CLIアプリケーションを復帰させると、アクセス可能になります。

$ curl -i http://localhost:8080/message/fail-fast
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/plain
Content-Length: 13
Date: Wed, 18 Jan 2017 11:41:45 GMT

HelloWorld!!
Fail Silent

続いて、Fail Silentと紹介されているパターン。

How To Use / Common Patterns / Fail Silent

このケースで用意したクラス。
src/main/scala/org/littlewings/wildflyswarm/hystrix/FailSilentCommand.scala

package org.littlewings.wildflyswarm.hystrix

import javax.ws.rs.client.ClientBuilder

import com.netflix.hystrix.{HystrixCommand, HystrixCommandGroupKey}
import org.jboss.logging.Logger

class FailSilentCommand extends HystrixCommand[String](HystrixCommandGroupKey.Factory.asKey("FailSilentCommand")) {
  val logger: Logger = Logger.getLogger(classOf[FailSilentCommand])

  override def run(): String = {
    logger.infof("start fail-silent request")

    val client =
      ClientBuilder
        .newClient

    try {
      val response =
        client
          .target("http://localhost:9000/hello-message")
          .request
          .get

      val message = response.readEntity(classOf[String])

      response.close()

      logger.infof("end fail-silent request")

      message
    } finally {
      client.close()
    }
  }

  override def getFallback: String = "Fallback Message!!"
}

Fail Fastで紹介したクラスとほとんど同じですが、今回はHystrixCommand#getFallbackメソッドをオーバーライドしています。

  override def getFallback: String = "Fallback Message!!"

JAX-RSリソースクラスにもアクセス先を追加。

@Path("message")
class MessageResource {
  val logger: Logger = Logger.getLogger(classOf[MessageResource])

  @GET
  @Path("fail-fast")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def failFast: String = {
    logger.infof("start resource")

    val command = new FailFastMessageCommand
    val message = command.execute()

    logger.infof("end resource")

    message + System.lineSeparator
  }

  @GET
  @Path("fail-silent")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def failSilent: String = {
    logger.infof("start resource")

    val command = new FailSilentCommand
    val message = command.execute()

    logger.infof("end resource")

    message + System.lineSeparator
  }

WildFly Swarmに乗せたアプリケーションを動かして、確認。

$ curl -i http://localhost:8080/message/fail-silent
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/plain
Content-Length: 13
Date: Wed, 18 Jan 2017 11:57:05 GMT

HelloWorld!!

Spring Boot CLIで作ったアプリケーションを落として確認。

$ curl -i http://localhost:8080/message/fail-silent
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/plain
Content-Length: 19
Date: Wed, 18 Jan 2017 11:57:40 GMT

Fallback Message!!

オーバーライドしたgetFallbackメソッドで設定した値が返ってきました。

ちなみに、サーバー側では例外が飛んだりはしていません。

2017-01-18 20:57:40,472 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-9) start resource
2017-01-18 20:57:40,473 INFO  [org.littlewings.wildflyswarm.hystrix.FailSilentCommand] (hystrix-FailSilentCommand-3) start fail-silent request
2017-01-18 20:57:40,492 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-9) end resource

というわけで、エラー発生時に代替の値を返す実装方法ですと。なので、「Silent」なわけですね。

HystrixCommandの呼び出し方法

少し、追加説明を。

今回、HystrixCommandのサブクラスを、すべてexecuteメソッドで呼び出しています。

    val command = new FailFastMessageCommand
    val message = command.execute()

これは、「Synchronous Execution」、同期呼び出しになります。

How To Use / Synchronous Execution

他にはHystrixCommand#queueで呼び出す「Asynchronous Execution」、この場合はFutureが返ります。

How To Use / Asynchronous Execution

Observable(※RxJava)を返す「Reactive Execution」(HystrixCommand#observe、HystrixCommand#toObservable)。

How To Use / Reactive Execution

そしてHystrixObservableCommandを使っての「Reactive Commands」があります。

How To Use / Reactive Commands

呼び出しパターンとして覚えておきましょう。

設定を変更する

今回はデフォルトの設定でHystrixを動かしていますが、設定を行うこともできます。

ドキュメントはこちら。

Configuration · Netflix/Hystrix Wiki · GitHub

今回は、「circuitBreaker.requestVolumeThreshold」というプロパティを変えてみましょう。

Configuration / circuitBreaker.requestVolumeThreshold

実は、ここまでの動作確認では、Circuit Breakerがその名の通りの動作をしていることを確認していません。

「circuitBreaker.requestVolumeThreshold」というプロパティは、ある時間あたり、何回リクエストが失敗するとCircuitが
オープンするかを設定します。デフォルトは20で、オープンした後はCircuitがオープンした時間または最後にリクエスト呼び出しを
行った時間に、「circuitBreaker.sleepWindowInMilliseconds」で指定した時間が経過したのち、Circuitがクローズします。
デフォルトは5000(5秒)です。

今回、「circuitBreaker.requestVolumeThreshold」を5に設定したCommandを用意してみます。
src/main/scala/org/littlewings/wildflyswarm/hystrix/ConfiguredMessageCommand.scala

package org.littlewings.wildflyswarm.hystrix

import javax.ws.rs.client.ClientBuilder

import com.netflix.hystrix.{HystrixCommand, HystrixCommandGroupKey, HystrixCommandProperties}
import org.jboss.logging.Logger

class ConfiguredMessageCommand
  extends HystrixCommand[String](
    HystrixCommand.Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ConfiguredMessageCommand"))
      .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(5))
  ) {
  val logger: Logger = Logger.getLogger(classOf[ConfiguredMessageCommand])

  override def run(): String = {
    logger.infof("start configured-message request")

    val client =
      ClientBuilder
        .newClient

    try {
      val response =
        client
          .target("http://localhost:9000/hello-message")
          .request
          .get

      val message = response.readEntity(classOf[String])

      response.close()

      logger.infof("end configured-message request")

      message
    } finally {
      client.close()
    }
  }

  override def getFallback: String = "Fallback Message!!"
}

ここまでのHystrixCommandの実装と異なり、HystrixCommand.SetterでGroupKeyを指定しつつ、プロパティを設定しています。

  extends HystrixCommand[String](
    HystrixCommand.Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ConfiguredMessageCommand"))
      .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(5))
  ) {

なお、Fail Silentで実装しています。

「Confiugration」のドキュメントでいくと、「How to Set Instance Default」と書かれている方法ですね。というわけで、
このインスタンス単位の設定になります。

JAX-RSリソースクラス側に、このCommandの呼び出しを追加。

  @GET
  @Path("configured")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def configured: String = {
    logger.infof("start configured-message resource")

    val command = new ConfiguredMessageCommand
    val message = command.execute()

    logger.infof("end configured-message resource")

    message + System.lineSeparator
  }

まずは、Spring Boot CLIアプリケーションが起動している状態でアクセス。

$ curl http://localhost:8080/message/configured
HelloWorld!!

この時、WildFly Swarm側で動かしているアプリケーションには、こんなログが出力されます。

2017-01-18 21:26:26,933 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-2) start configured-message resource
2017-01-18 21:26:26,935 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-2) start configured-message request
2017-01-18 21:26:26,995 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-2) end configured-message request
2017-01-18 21:26:26,997 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-2) end configured-message resource

JAX-RSリソースクラスのログと、間にHystrixCommandnのログが挟まっている感じですね。

では、Spring Boot CLIで作ったアプリケーションを落として、連続で6回アクセスしてみます。速すぎると切り替わらなかったので、
5回に1回で1秒スリープを…。

$ for i in {1..6}; do curl http://localhost:8080/message/configured; if [ $(( $i % 5 )) -eq 0 ]; then sleep 1; fi done
Fallback Message!!
Fallback Message!!
Fallback Message!!
Fallback Message!!
Fallback Message!!
Fallback Message!!

この時のログは、こんな感じ。

2017-01-18 21:41:34,953 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:41:34,961 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-12) end configured-message resource
2017-01-18 21:41:34,978 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-13) start configured-message resource
2017-01-18 21:41:34,979 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:41:34,985 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-13) end configured-message resource
2017-01-18 21:41:34,997 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-14) start configured-message resource
2017-01-18 21:41:34,998 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:41:35,002 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-14) end configured-message resource
2017-01-18 21:41:35,016 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-15) start configured-message resource
2017-01-18 21:41:35,016 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:41:35,025 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-15) end configured-message resource
2017-01-18 21:41:35,039 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-16) start configured-message resource
2017-01-18 21:41:35,039 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:41:35,045 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-16) end configured-message resource
2017-01-18 21:41:36,060 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-17) start configured-message resource
2017-01-18 21:41:36,061 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-17) end configured-message resource

6回目では、Command呼び出しそのものがなくなり、いきなりFallbackに移行しています。
JAX-RSリソース側のログしかない

2017-01-18 21:41:36,060 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-17) start configured-message resource
2017-01-18 21:41:36,061 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-17) end configured-message resource

これがオープンした状態ですね、と。時間を置いてアクセスする(ウィンドウをまたぐと)と、再度Command呼び出しを再開します。
この例ではまだ呼び出しに失敗する状態のままですが。

2017-01-18 21:42:05,728 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-18) start configured-message resource
2017-01-18 21:42:05,728 INFO  [org.littlewings.wildflyswarm.hystrix.ConfiguredMessageCommand] (hystrix-ConfiguredMessageCommand-10) start configured-message request
2017-01-18 21:42:05,733 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-18) end configured-message resource

先ほど実装した、単純なFail Silentの場合だと、デフォルト値なので20回まではCommandを呼び出し続けます。

$ for i in {1..21}; do curl http://localhost:8080/message/fail-silent; if [ $(( $i % 5 )) -eq 0 ]; then sleep 1; fi done

Commandの呼び出しがなくなるのは、21回目からですね。

2017-01-18 21:47:30,867 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-5) start resource
2017-01-18 21:47:30,868 INFO  [org.littlewings.wildflyswarm.hystrix.FailSilentCommand] (hystrix-FailSilentCommand-10) start fail-silent request
2017-01-18 21:47:30,872 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-5) end resource
2017-01-18 21:47:31,903 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-6) start resource
2017-01-18 21:47:31,904 INFO  [org.littlewings.wildflyswarm.hystrix.MessageResource] (default task-6) end resource

全体のデフォルト設定

今回、HystrixCommandのサブクラスの設定を個別に行いましたが、全体的にデフォルト値を設定することもできるようです。

設定例としては、こんな感じ。ConfigurationManagerというクラスを使えばよいみたいです。

ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.circuitBreaker.requestVolumeThreshold", 5);

また、インスタンス単位で設定する場合は、こちら。

ConfigurationManager.getConfigInstance().setProperty("hystrix.command.[HystrixCommandGroupKey名].circuitBreaker.requestVolumeThreshold", 5);

参考)
https://github.com/Netflix/Hystrix/blob/master/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java

ただ、これをWildFly Swarmでやろうとすると、実行時に依存関係(archaius-core)が見えてないっぽいです。

Caused by: java.lang.NoClassDefFoundError: com/netflix/config/ConfigurationManager

HystrixFractionを見ると、使えるモジュールが「com.netflix.hystrix」と「io.reactivex.rxjava」に絞られている
みたいなので、そうかーという感じですが。
https://github.com/wildfly-swarm/wildfly-swarm/blob/2017.1.1/fractions/netflix/hystrix/src/main/java/org/wildfly/swarm/netflix/hystrix/HystrixFraction.java#L30-L33

HystrixFractionではHystrixMetricsStreamServletのパスしか設定できないみたいなので、全体的なデフォルト値は
触ることができないっぽいですねぇ。

Hystrix Dashboard

最後に、Hystrix Dashboardを使ってみます。最初の方で、Dashboardと連携するのに使う機能があるって書きましたしね。

Home · Netflix/Hystrix Wiki · GitHub

DashboardはWARとして提供されているので、こちらをダウンロードして使います。自分は、Maven Centralからダウンロードしました。

https://search.maven.org/#artifactdetails%7Ccom.netflix.hystrix%7Chystrix-dashboard%7C1.5.9%7Cwar

今回は、WildFly Swarmのmicroprofile-hollowswarm.jarを使います。

$ java -Dswarm.http.port=18080 -jar microprofile-2017.1.1-hollowswarm.jar hystrix-dashboard-1.5.9.war

リッスンポートは、18080としました。

この状態で、「http://localhost:18080/」にアクセスするとDashboardを見ることができます。

表示された赤枠のテキストフィールドに、今回作成したWildFly Swarmアプリケーションを起動した状態で
http://localhost:8080/hystrix.stream」をテキストフィールドに埋め込み、「Add Stream」ボタンを押します。

すると、追加したURLが現れるので、この状態で「Monitor Stream」をクリックします。

Dashboardに移行するので、あとはHystrixMetricsStreamServletによって送信されたメトリクスを確認することが
できます。

先にも書きましたが、WildFly SwarmのHystrix向けのFractionを依存関係に追加すると、HystrixMetricsStreamServletが追加されます。
https://github.com/wildfly-swarm/wildfly-swarm/blob/2017.1.1/fractions/netflix/hystrix/src/main/java/org/wildfly/swarm/netflix/hystrix/runtime/HystrixArchivePreparer.java

https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-metrics-event-stream

これで、SSE(Server-Sent Events)を使ってクライアント側にメトリクスを送り続ける仕組みになっているみたいです。
※Server-Sent Eventsよく知らないですが
Metrics and Monitoring / Metrics Event Stream

オマケ - HystrixConcurrencyStrategy

WildFly Swarmのチュートリアルでは、なにやら起動時にThreadFactoryの設定をしているコードがあります。
https://github.com/wildfly-swarm/tutorial/blob/master/microservice/everest/src/main/java/org/javaee7/wildfly/samples/everest/HystrixEEBootstrap.java
https://github.com/wildfly-swarm/tutorial/blob/master/microservice/everest/src/main/java/org/javaee7/wildfly/samples/everest/EEConcurrencyStrategy.java

このコードでは、@ResourceでインジェクションしたManagedThreadFactoryを使ってThreadPoolを作成し、HystrixPluginsに
HystrixConcurrencyStrategyとして登録します。

    @Resource
    ManagedThreadFactory threadFactory;

    @PostConstruct
    public void onStartup() {
        System.out.println("Initialising hystrix ...");
        HystrixPlugins.getInstance().registerConcurrencyStrategy(new EEConcurrencyStrategy(threadFactory));
    }

これは、Hystrixのプラグインの仕組みのひとつですね。

Plugins · Netflix/Hystrix Wiki · GitHub

Plugins / Concurrency Strategy

このチュートリアルのコードでは、Hystrixが使用するThreadPoolで使うThreadを、ManagedThreadFactoryを使って
作成するように変更しています。まあ、EE環境だからってことなのでしょうね。

まとめ

WildFly Swarm(自体はそこまで関係なかったですけど)を使って、HystrixのFractionを組み込み、基本的な使い方とDashboardの連携まで
見てみました。

Circuit Breakerを使ったのは初めてだったのですが、なかなか面白かったです。

今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/wildfly-swarm-scala-examples/tree/master/circuit-breaker-hystrix

参考)
マイクロサービスにレジリエンスをもたらすHystrixを試してみる - たけぞう瀕死ブログ

Spring BootでCircuit Breaker(Spring Cloud Netflix Hystrix)を試す - abcdefg.....