TT Lab
はじめる
学ぶ 学習パス コース

マイクロサービスアーキテクチャ

サービス間REST呼び出しにタイムアウトとリトライを入れる

TT Labで続きを見る

目標

不安定なダウンストリームを相手に、タイムアウト、指数バックオフ、ジッター、リトライ禁止の条件、フォールバックを自分で実装して、同期呼び出しの基本を完成させます。

なぜ重要なのか

同期呼び出しでの事故は、たいてい「タイムアウトがなかったから」または「リトライを間違えたから」起きます。タイムアウトがないと、ダウンストリームが遅くなったときにアップストリームのコネクションプールが先に枯渇し、ダウンストリームと無関係なリクエストまで失敗します。逆に、固定の間隔で無制限にリトライすると、ダウンストリームが回復しようとする瞬間ごとに、同じ波が押し寄せます。実際の事例で、注文インスタンス20台が毎秒100件を処理しながらmaxAttempts 5でリトライしたところ、決済サービスが毎秒10,000件を受けました。リトライは負荷を足すのではなく、掛け算します。そのため、このラボでは値を入れる順序を重視します。先にタイムアウト、次にリトライの条件、次にバックオフとジッター、最後にフォールバックです。

ステップ

  1. /opt/app/flaky.pyを127.0.0.1:8110で起動してください。GET /fastが200でなければなりません。
  2. /root/rest/call.pyを作成して/fastを呼び出し、レスポンスの本文を/root/rest/fast.outに保存してください。
  3. /root/rest/timeout.pyを作成して、/slowを1.0秒のタイムアウトで呼び出してください。/root/rest/timeout.outの1行目にTIMEOUT、2行目にelapsed=<초>を書いてください(プレースホルダーは秒数です)。経過時間は2.0秒未満でなければなりません。
  4. /root/rest/retry.pyを作成して、/flaky?key=lab&fail=2を指数バックオフでリトライして、最終的に成功させてください。/root/rest/retry.logに、attempt=<n> wait=<초>の形式で、試行ごとに1行ずつ残して、合計3行にしてください(プレースホルダーは秒数です)。
  5. retry.pyの待ち時間の計算を、full jitterに変更してください。試行番号2の待ち時間を20回取り出して、/root/rest/jitter.txtに1行ずつ書いてください。互いに異なる値が15個以上なければなりません。
  6. /root/rest/retry400.pyで/badを呼び出してください。400はリトライしないので、/root/rest/retry400.logはちょうど1行でなければなりません。
  7. /root/rest/budget.txtに、instances=20、rps=100、max_attempts=5、worst_rps=10000の4行を書いてください。
  8. /root/rest/gateway.pyを127.0.0.1:8111で起動してください。GET /orderは、ダウンストリームの/always500を呼び出しますが、タイムアウトとリトライを適用し、最終的に失敗したときは、200と{"degraded":true}を返します。

参考

不安定なダウンストリームを起動する

/opt/app/flaky.pyを127.0.0.1:8110で起動してください。GET /fastが200でなければなりません。

/opt/app/flaky.pyを実行すると、8110で起動します。/fast、/slow、/flaky、/badの4つのパスの動作を、まず自分の目で確認してください。

正常な呼び出しで基準線を取る

/root/rest/call.pyを作成して/fastを呼び出し、レスポンスの本文を/root/rest/fast.outに保存してください。

httpxやurllibで/fastを呼び出して、本文をファイルに残します。まだタイムアウトは気にしなくて構いません。

遅いレスポンスをタイムアウトで切る

/root/rest/timeout.pyを作成して、/slowを1.0秒のタイムアウトで呼び出してください。/root/rest/timeout.outの1行目にTIMEOUT、2行目にelapsed=<초>を書いてください(プレースホルダーは秒数です)。経過時間は2.0秒未満でなければなりません。

/slowは3秒後に答えます。クライアントのタイムアウトを1秒にすると、例外が発生します。例外をキャッチして、結果と経過時間を一緒に記録してください。

指数バックオフのリトライを実装する

/root/rest/retry.pyを作成して、/flaky?key=lab&fail=2を指数バックオフでリトライして、最終的に成功させてください。/root/rest/retry.logに、attempt=<n> wait=<초>の形式で、試行ごとに1行ずつ残して、合計3行にしてください(プレースホルダーは秒数です)。

間隔を、試行のたびに2倍に増やします。各試行の番号と待ち時間を、ログに1行ずつ残さなければ採点されません。

ジッターでリトライのタイミングを散らす

retry.pyの待ち時間の計算を、full jitterに変更してください。試行番号2の待ち時間を20回取り出して、/root/rest/jitter.txtに1行ずつ書いてください。互いに異なる値が15個以上なければなりません。

full jitterは、0から計算されたバックオフまでの間の乱数だけ待ちます。同じ試行番号でも、値が毎回異なる必要があります。

確定的なエラーはリトライしない

/root/rest/retry400.pyで/badを呼び出してください。400はリトライしないので、/root/rest/retry400.logはちょうど1行でなければなりません。

400は、何回送っても400です。ステータスコードでリトライするかどうかを分ける分岐を入れてください。

リトライバジェットを計算する

/root/rest/budget.txtに、instances=20、rps=100、max_attempts=5、worst_rps=10000の4行を書いてください。

インスタンス数×毎秒のリクエスト数×最大試行回数が、ダウンストリームが受ける最悪の毎秒リクエスト数です。3つの値と結果を、それぞれ書いてください。

フォールバックのあるゲートウェイとして総合する

/root/rest/gateway.pyを127.0.0.1:8111で起動してください。GET /orderは、ダウンストリームの/always500を呼び出しますが、タイムアウトとリトライを適用し、最終的に失敗したときは、200と{"degraded":true}を返します。

ダウンストリームが落ちても、ユーザーには200を返しますが、レスポンスに品質低下の表示を残します。前のステップのタイムアウトとリトライを、併せて使います。