この記事はなに?
マイクロサービス等の文脈で登場するCircuitBreaker。
Scalaで使えるものの代表(要出典)として以下の2つを比較する。
-
Circuit Breaker - Akka Documentation
- シンプルだが単機能
-
Netflix/Hystrix
- 高機能
ちなみに個人的な好みはAkkaの方。
Scalaから使いやすいのと導入が簡単。
CircuitBreakerってどんなもの?
以前書いたので貼っておきます。
[Akka]CircuitBreakerはどう動くのか - Qiita
AkkaのCircuitBreaker
akka.pattern.CircuitBreaker
に状態遷移の条件に当たるもの(maxFailures
, callTimeout
, resetTimeout
)を渡してインスタンス化する。
そしてCircuitBreaker#withCircuitBreaker
にscala.concurrent.Future
を引数として与えるだけ。
一行抜粋するとこんな感じ。
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
AkkaのCircuitBreakerと違って、実行を監視する存在がいるのではなく、監視対象となる処理自体をHystrixCommand
として定義・実装する点が大きく違う。
一行抜粋するとこんな感じ。
String s = new CommandHelloWorld("Bob").execute();
execute
以外にもjava.util.concurrent.Future
を返却するqueue()
やrx.Observable
を返却するobserver()
がある。
Future<String> s = new CommandHelloWorld("Bob").queue();
Observable<String> s = new CommandHelloWorld("Bob").observe();
getFallback
を定義しておくことでexecute
等が失敗した際のfallback値を返却する機能もある。
処理そのものをHystrixCommand
として定義するのはすっきりして良い。
外部からはexecute
のような処理のkickしか出来ないので、必要な情報を詰め込んでおける。
ここでは詳しくあげないが、Command実行時のMetricsを取得できたりダッシュボードが用意してあったりキャッシュも組み込んであったりと色々出来る。
高機能過ぎる...。
あと、そもそもCircuitBreakerで監視したい対象は、外部サービスを非同期に呼び出す処理だったりすることが多い。
なので非同期呼び出し前提としてexecute
は無くても良さそう。
CircuitBreakerとしてのHystrix
機能が盛り沢山なHystrixだが、今回はCircuitBreakerとして見てみる。
設定出来る項目は以下を参照。
Configuration · Netflix/Hystrix Wiki
実際に設定を適用するにはこんな感じ。
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
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アプリケーションから使ってみる - たけぞう瀕死ブログ
ちなみにObservable
はScala2.12のSAMのおかげで以下のように簡潔に使えるようになっているのが嬉しい。
command.observe().subscribe(
(r: Result) => doSomething(r), // onNext
(t: Throwable) => t.printStackTrace(), // onError
() => println("complete") // onComplete
)