All Articles

Node.js のサーキットブレーカ opossum をさわってみた

マイクロサービスなどで利用されるサーキットブレーカと言えば、最近は Envoy (Istio) が最も有名です。

Envoy は別プロセスのプロキシ (コンテナのサイドカー) として導入してサーキットブレーカとして利用可能ですが、サーキットブレーカには各種言語のライブラリとして導入可能なものもあります。

Node.js ライブラリ形式のサーキットブレーカである opossum をさわってみたので、その概要や感想をまとめます。

opossum について

opossum は Node.js のサーキットブレーカライブラリです。

マイクロサービスにおけるサーキットブレーカと言えば外部サービスとの通信で利用するイメージが強いですが、opossum は任意の処理に対してサーキットブレーカを挿入することができます。

使い方は非常に簡単で、失敗する可能性のある関数を CircuitBreaker クラスの引数に渡すだけです。

function asyncFunctionThatCouldFail(x, y) {
  :
}

const options = {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 30000
};

const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options);

breaker.fire(x, y)
  .then(console.log)
  .catch(console.error);

npm の opossum のページ を眺めれば、なんとなく使えてしまうと思います。

サンプルコードの実装

実際にサーキットブレーカが動作する様子を見るため、TypeScript でコードを書いてみました。

※ ここには主要な処理を抜粋して掲載するので、コードの全体については GitHub のこちら を参照ください。

まずはテスト用に、await で指定した時間待てる wait 関数と、指定した時間遅延する slowEcho 関数を用意しました。

function wait(second: number) {
  return new Promise((resolve) => setTimeout(resolve, second * 1000))
}

async function slowEcho(message: string, waitSecond: number = 0) {
  await wait(waitSecond)
  return message
}

続いて、サーキットブレーカの設定を定義します。

オプションは色々ありますが、以下の例では

  • エラーと判断するタイムアウトの時間
  • 何 % がエラーになったらサーキットブレーカを開くか
  • サーキットブレーカが開いた場合、何秒後に再度利用可能になるか

を設定しています。

const options = {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 3000
}

サーキットブレーカを設定します。

const breaker = new CircuitBreaker(slowEcho, options)

breaker.on('timeout', () => {
  logWithTime('timeout')
})
breaker.on('open', () => {
  logWithTime('open')
})
breaker.on('halfOpen', () => {
  logWithTime('halfOpen')
})
breaker.on('close', () => {
  logWithTime('close')
})

動作を見てみるため、breaker.on を使い、いくつかのイベントでログ出力も設定しました。

あとは関数の実行の遅延が 0, 1, 2, … 秒となるように呼び出す処理を書きます。

// [0, 1, 2, ...]
const waitSeconds = [...Array(10).keys()]

for (let i = 0; i < waitSeconds.length; i++) {
  const waitSecond = waitSeconds[i]
  logWithTime(`waitSecond = ${waitSecond}`)


  try {
    const echoMessage = await breaker.fire('Hello!', waitSecond)
    logWithTime(echoMessage)
  } catch (e) {
    logWithTime('error catched!!!')
  }
}

実行結果

実装したコードを実行してみると、以下のようになりました。

※ ”#” 移行は実行結果についての説明です。

$ ts-node index.ts
[20:50:42] waitSecond = 0
[20:50:42] Hello!
[20:50:42] waitSecond = 1
[20:50:43] Hello!
[20:50:43] waitSecond = 2
[20:50:45] Hello!
[20:50:45] waitSecond = 3
[20:50:48] timeout
[20:50:48] error catched!!! # 3 秒遅延させた処理は、タイムアウトでエラーとなっています
[20:50:48] waitSecond = 4
[20:50:51] timeout
[20:50:51] error catched!!! # 4 秒遅延させた処理も、3 秒たった時点でタイムアウトでエラーとなっています
[20:50:51] waitSecond = 5
[20:50:54] timeout
[20:50:54] open             # エラーが 50% になったため、この時点でサーキットブレーカが開きました
[20:50:54] error catched!!! # 5 秒遅延させた処理も、3 秒たった時点でタイムアウトでエラーとなっています
[20:50:54] waitSecond = 6
[20:50:54] error catched!!! # 以後の処理は 3 秒待つこともなく、即座にエラーになっています
[20:50:54] waitSecond = 7
[20:50:54] error catched!!!
[20:50:54] waitSecond = 8
[20:50:54] error catched!!!
[20:50:54] waitSecond = 9
[20:50:54] error catched!!!

サーキットブレーカが動作しているのを確認できました。

その他のサーキットブレーカのライブラリについて

今回は opossum を試しましたが、他にも npm には

といったサーキットブレーカのライブラリがありました。

ただ、これらはすでにメンテナンスされていません。

また、Netflixy 製のサーキットブレーカの Java ライブラリ Hystrix もすでに活発な開発は行っておらず、resilience4j のようなアクティブなライブラリを利用することを推奨しているようです。

※ ちなみに、今回 opossum をさわってみたのは、書籍『マイクロサービスパターン』で Hystrix が紹介されていて気になったのがきっかけです。

Envoy などとの比較

ライブラリ形式のサーキットブレーカが少なく、かつメンテナンスされていないケースが多い理由の 1 つは、Envoy (Istio) を利用した別プロセスのプロキシによるサーキットブレーカが主流になっているためだと思います。

Kubernetes などを使い、Envoy などをサイドカーとして導入すれば、1 つ 1 つのアプリケーションにサーキットブレーカを仕込む手間がかかりません

これは複数の言語で実装されるマイクロサービスの場合には特に大きなメリットになります。

一方、今回ライブラリ形式のサーキットブレーカをさわってみて、Envoy などを導入するよりも簡単だなと感じました。

Istio などのサービスメッシュの導入は、事例は増えているものの、気軽に手を出せるものではないと思います。

簡易的にサーキットブレーカを導入したい場合などは、opossum などのライブラリ形式のサーキットブレーカも良い選択かもしれないと感じました。