この記事はなに?
マイクロサービス等の文脈で登場する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
)