AkkaのCircuitBreakerとNetflixのHystrixの違い

# この記事はなに？

マイクロサービス等の文脈で登場する[CircuitBreaker](http://martinfowler.com/bliki/CircuitBreaker.html)。
Scalaで使えるものの代表(要出典)として以下の2つを比較する。

- [Circuit Breaker - Akka Documentation](http://doc.akka.io/docs/akka/current/common/circuitbreaker.html)
  - シンプルだが単機能
- [Netflix/Hystrix](https://github.com/Netflix/Hystrix/)
  - 高機能

ちなみに個人的な好みはAkkaの方。
Scalaから使いやすいのと導入が簡単。

# CircuitBreakerってどんなもの？

以前書いたので貼っておきます。
[[Akka]CircuitBreakerはどう動くのか - Qiita](http://qiita.com/petitviolet/items/777be810e3c5756a393d)

# AkkaのCircuitBreaker

[公式サンプル](http://doc.akka.io/docs/akka/current/common/circuitbreaker.html#Examples)がある。
ちなみに独自の[サンプル実装](https://github.com/petitviolet/supervisor/blob/master/example/src/main/scala/net/petitviolet/ex/AkkaCircuitBreakerPrac.scala)はここ。

`akka.pattern.CircuitBreaker`に状態遷移の条件に当たるもの(`maxFailures`, `callTimeout`, `resetTimeout`)を渡してインスタンス化する。
そして`CircuitBreaker#withCircuitBreaker`に`scala.concurrent.Future`を引数として与えるだけ。
一行抜粋するとこんな感じ。

```scala
breaker.withCircuitBreaker(Future(dangerousCall)) pipeTo sender()
```

`withCircuitBreaker`で`Future`の実行を監視し、成功したら結果を`Future`として取得する。
規定回失敗したら`Open`に遷移して...というCircuitBreakerらしい機能を提供している。

これの嬉しい点は引数として`scala.concurrent.Future`を要求するところで、監視対象の処理はAkkaやActorには依存しない。
`CircuitBreaker`のコンストラクタに`akka.actor.Scheduler`を要求する程度。

機能もシンプルなCircuitBreakerという程度で、`Future`の監視以外は何も提供されていない。
ThreadPoolはコンストラクタの`ExecutionContext`として設定することになる。

# Hystrix

[公式サンプル](https://github.com/Netflix/Hystrix/#hello-world)はここ。
独自な[サンプル実装](https://github.com/petitviolet/supervisor/blob/master/example/src/main/scala/net/petitviolet/ex/HystrixPrac.scala)はここ。

AkkaのCircuitBreakerと違って、実行を監視する存在がいるのではなく、監視対象となる処理自体を`HystrixCommand`として定義・実装する点が大きく違う。
一行抜粋するとこんな感じ。

```java
String s = new CommandHelloWorld("Bob").execute();
```

`execute`以外にも`java.util.concurrent.Future`を返却する`queue()`や`rx.Observable`を返却する`observer()`がある。

```java
Future<String> s = new CommandHelloWorld("Bob").queue();
Observable<String> s = new CommandHelloWorld("Bob").observe();
```

`getFallback`を定義しておくことで`execute`等が失敗した際のfallback値を返却する機能もある。

処理そのものを`HystrixCommand`として定義するのはすっきりして良い。
外部からは`execute`のような処理のkickしか出来ないので、必要な情報を詰め込んでおける。

ここでは詳しくあげないが、Command実行時の[Metrics](https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring)を取得できたり[ダッシュボード](https://github.com/Netflix/Hystrix/wiki/Dashboard)が用意してあったり[キャッシュ](https://github.com/Netflix/Hystrix/wiki/How-To-Use#request-cache)も組み込んであったりと色々出来る。
高機能過ぎる...。

あと、そもそもCircuitBreakerで監視したい対象は、外部サービスを非同期に呼び出す処理だったりすることが多い。
なので非同期呼び出し前提として`execute`は無くても良さそう。

## CircuitBreakerとしてのHystrix

機能が盛り沢山なHystrixだが、今回はCircuitBreakerとして見てみる。
設定出来る項目は以下を参照。
[Configuration · Netflix/Hystrix Wiki](https://github.com/Netflix/Hystrix/wiki/Configuration#circuit-breaker)

実際に設定を適用するにはこんな感じ。

```scala
object MyCommand {
  val key = HystrixCommandGroupKey.Factory.asKey("my-command")

  private val circuitBreakerSetter =
    HystrixCommandProperties.Setter()
      .withCircuitBreakerEnabled(true)
      .withCircuitBreakerRequestVolumeThreshold(10)
      .withCircuitBreakerErrorThresholdPercentage(30)
      .withCircuitBreakerSleepWindowInMilliseconds(50)

  val setter: HystrixCommand.Setter =
    HystrixCommand.Setter
      .withGroupKey(key)
      .andCommandPropertiesDefaults(circuitBreakerSetter)
}

class MyCommand(num: Long) extends HystrixCommand[Result](MyCommand.setter) {
  ???
}
```

Akkaの何回失敗したら、という設定と異なり、一定時間内にどの程度(%)失敗したらOpenになる、という設定の仕方。

# ざっくり比較

AkkaのCircuitBreakerは非常にシンプルだが機能は少ない。
繰り返しになるが単なる`scala.concurrent.Future`を監視出来る手軽さは強力かなと。
設定出来る項目も少なく、学習コストは低いと思われる。
導入も捨てるのも簡単なので総合的にお手軽。

それに対してHystrixは高機能だがやや複雑な印象。
ダッシュボードやキャッシュといった機能だけでなくThreadPoolやタイムアウトの設定なども細かく出来る。
使い込めばかなり便利そう。
もちろん、単純に`HystrixCommand`を実装したコマンドを使うだけなら簡単。

とりあえずCircuitBreakerとしての機能が必要ならAkkaで、
ダッシュボード等の機能も必要であればHystrixを使う。

ちなみにAkkaの方でもこんなIssueが立ってたりで面白い。
[CircuitBreaker should provide statistics like Netflix Hystrix does · Issue #16617 · akka/akka](https://github.com/akka/akka/issues/16617)

## Scala的な観点

あまり言いたくないがHystrixの難点はJavaであることで、Scalaから使うという観点に立つと`Observable`はともかく`java.util.concurrent.Future`は使いづらい。
`scala.concurrent.Promise`使うとか`java.util.concurrent.Future#get`を`scala.concurrent.Future#apply`で非同期にするとか...？

多少頑張ればScalaからでもいい感じに使えそう。
[HystrixをScala / Playアプリケーションから使ってみる - たけぞう瀕死ブログ](http://takezoe.hatenablog.com/entry/2016/06/18/021820)

ちなみに`Observable`は[Scala2.12のSAM](http://www.scala-lang.org/news/2.12.0#lambda-syntax-for-sam-types)のおかげで以下のように簡潔に使えるようになっているのが嬉しい。

```scala
command.observe().subscribe(
  (r: Result) => doSomething(r), // onNext
  (t: Throwable) => t.printStackTrace(),  // onError
  () => println("complete")  // onComplete
)
```