
Circuit Breaker(サーキットブレーカー)パターンは、
外部サービスの失敗がシステム全体に波及するのを防ぐパターンです。
一定回数以上の失敗を検知したら回路を切る処理を実行します。
要するに、呼び出しを止める役割ですね。
状態の流れ
| 状態 |
説明 |
| Closed(閉) |
通常状態。リクエストは全て実行される。 |
| Open(開) |
失敗が一定回数を超えたため「回路が遮断」。外部呼び出しを停止。 |
| Half-Open(半開) |
一定時間後、テスト的に少数のリクエストを試す。成功ならClosedへ戻す。失敗なら再びOpen。 |
構造イメージ
[Client] → [CircuitBreaker] → [ExternalService]
Closed → 通常通過
Open → 呼び出し拒否(即エラー返す)
Half-Open → 試験的に一部通過
サンプルコード
class CircuitBreaker {
constructor(fn, failureThreshold = 3, recoveryTime = 5000) {
this.fn = fn;
this.failureThreshold = failureThreshold;
this.recoveryTime = recoveryTime;
this.failureCount = 0;
this.state = "CLOSED";
this.nextTry = Date.now();
}
async call(...args) {
if (this.state === "OPEN") {
if (Date.now() > this.nextTry) {
this.state = "HALF_OPEN";
} else {
throw new Error("Circuit is OPEN. Call blocked.");
}
}
try {
const result = await this.fn(...args);
this._reset();
return result;
} catch (err) {
this._fail();
throw err;
}
}
_fail() {
this.failureCount++;
if (this.failureCount >= this.failureThreshold) {
this.state = "OPEN";
this.nextTry = Date.now() + this.recoveryTime;
console.warn("⚡ Circuit opened!");
}
}
_reset() {
this.failureCount = 0;
this.state = "CLOSED";
}
}
// === 疑似API関数 ===
async function unstableAPI() {
if (Math.random() < 0.7) throw new Error("API Error");
return "Success!";
}
// === 利用例 ===
const apiBreaker = new CircuitBreaker(unstableAPI);
setInterval(async () => {
try {
console.log(await apiBreaker.call());
} catch (e) {
console.log("Error", e.message, "| State:", apiBreaker.state);
}
}, 1000);
メリット
・外部サービス障害時に連鎖的な障害を防ぐ
・再試行タイミングを制御できる
・全体の安定性向上
デメリット
・状態管理(Open/Closed)の調整が難しい
・短期的なエラーでも遮断される可能性
・適切な「閾値・リトライ時間」設定が必要
よく使われる場所
Web APIクライアント
想定外の値が送られてきた時に、無限ループなどを防ぐために使われます。
外部決済連携
外部APIなどで、レスポンスコールバックが想定以上の時間が経過したなどの時に使われます。
分散システム / マイクロサービス環境
特に Netflix Hystrix, Resilience4j, Polly(.NET) などのサービスで、代表的実装されています。
Canvasデモ
Canvas上でCircuit Breakerの状態遷移
(Closed → Open → Half-Open → Closed)
をアニメーション表示するJavaScriptデモです。
<canvas id="breaker" width="500" height="250"></canvas>
<script>
const canvas = document.getElementById("breaker");
const ctx = canvas.getContext("2d");
const states = ["CLOSED", "OPEN", "HALF-OPEN"];
let current = 0;
let lastSwitch = Date.now();
function drawState() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 状態名
const state = states[current];
let color = "#4CAF50"; // CLOSED
if (state === "OPEN") color = "#F44336";
if (state === "HALF-OPEN") color = "#FFC107";
// 回路線
ctx.lineWidth = 6;
ctx.strokeStyle = "#333";
ctx.beginPath();
ctx.moveTo(50, 120);
ctx.lineTo(200, 120);
// 状態による開閉表現
if (state === "OPEN") {
ctx.moveTo(220, 100);
ctx.lineTo(220, 140);
} else if (state === "HALF-OPEN") {
ctx.moveTo(210, 110);
ctx.lineTo(220, 130);
} else {
ctx.moveTo(200, 120);
ctx.lineTo(250, 120);
}
ctx.stroke();
// 出力線
ctx.beginPath();
ctx.moveTo(250, 120);
ctx.lineTo(450, 120);
ctx.stroke();
// 状態ボックス
ctx.fillStyle = color;
ctx.fillRect(180, 160, 140, 50);
ctx.fillStyle = "#fff";
ctx.font = "16px sans-serif";
ctx.textAlign = "center";
ctx.fillText(state, 250, 190);
// 状態説明
ctx.fillStyle = "#222";
ctx.font = "14px sans-serif";
let desc = {
"CLOSED": "正常稼働中:全リクエスト通過",
"OPEN": "失敗多発:リクエスト遮断",
"HALF-OPEN": "試験中:一部通過"
}[state];
ctx.fillText(desc, 250, 230);
}
function loop() {
drawState();
if (Date.now() - lastSwitch > 2000) {
current = (current + 1) % states.length;
lastSwitch = Date.now();
}
requestAnimationFrame(loop);
}
loop();
</script>
あとがき
try~catchで、想定外エラーを取得して、プログラムの停止を防ぐのは、Javascriptではお作法として重要な書き方になりますが、
while処理をする際に、まあまあな確率で無限ループに陥ってしまうことがあるのも自分のプログラミング時のアルアルです。
そうした時に、このサーキットブレーカー処理を組み込んでおいて、想定外の処理をキチンと埋め込んでおくことが、安定したプログラム実行につながるし、しっかりとした設計に繋がりますね。
0 件のコメント:
コメントを投稿