2017-01-31

Expeditorコードリーディング

cookpadさんのExpeditorのコードリーディングをしてみました。

Expeditorの概要

非同期に処理を実行したり、依存処理や失敗時のfallbackを簡潔に書けるライブラリです。一番ベーシックな使い方はこんな感じです。
command = Expeditor::Command.new do
  # some code
end

command.start # start execute asynchronously
command.get # wait for value

コードリーディングの前の前提知識

ライブラリのベースはconcurrent-rubyなので、まずはconcurrent-rubyの説明から。

Concurrent::IVar

ImmutableなVarで以下の処理をすることができます
a = Concurrent::IVar.new
a.add_observer do
  puts "hoge"
end

a.fulfilled? # false
a.set 1 # set and call observer
a.fulfilled? # true
a.set 2 # error

Concurrent::Future

非同期に処理を実行することができるようになるクラス。FutureはConcurrent::IVarを継承しています。

使い方は色々ありますが、Expeditorの場合は以下のようにイニシャライズ時にブロックを渡して、後でexecuteするスタイルです。

require 'concurrent/future'

future = Concurrent::Future.new do
  # some code
end

futute.complete? # false
future.execute # execute asynchronously
future.value
future.complete? # true

最も基本的な実行ケースを追ってみる

Expeditor::Commandをイニシャライズ。@service = Expeditor::Services.defaultとblock以外はセットされない

Dependenciesがある場合

ベーシックな使い方との差分は

fallback_blockがある場合

サーキットブレーカー

内外のAPIを叩くときに対象サービスが落ちていたり、ネットワーク障害などでHTTPリクエストが失敗したときなど、立て続けにエラーになるケースでは、そのサービスが復旧するまではリクエストを送らずにクライアント側でエラーにするのが効率的です。このように、エラーがある閾値を越えたときに一定時間対象の処理を行わないようにする機構をサーキットブレーカーと呼びます。Expeditorにはこのサーキットブレーカーの機構も備えています。
service = Expeditor::Service.new(
  period: 10,          # retention period of the service metrics (success, failure, timeout, ...)
  sleep: 1,            # if once the circuit is opened, the circuit is still open until sleep time is passed even though failure rate is less than threshold
  threshold: 0.5,      # if the failure rate is more than or equal to threshold, the circuit is opened
  non_break_count: 100 # if the total count of metrics is not more than non_break_count, the circuit is not opened even though failure rate is more than threshold
)

command = Expeditor::Command.new(service: service) do
  ...
end

その他

このエントリーをはてなブックマークに追加