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

キューと非同期API

重複受信の防御を実装する

TT Labで続きを見る

目標

二重請求を自分で作ってみてから冪等キーで防ぎ、同時リクエストと本文の不一致まで処理して、実際の決済APIの水準の防御を完成させます。

なぜ重要なのか

ネットワークをまたぐ配信で、純粋な意味のちょうど1回は、不可能に近いです。送った側は、確認応答が来なかったとき、「届かなかった」と「届いたが応答だけが消えた」を区別できないからです。そのため、実務の正解は決まっています。少なくとも1回で送り、受け取る側で重複を消します。このラボで最も微妙なのは、ステップ5です。キーを参照して、なければ保存するという素朴な実装は、2つのリクエストが同時に届くと、両方が通ってしまいます。RedisのSET ... NXの1回で、参照と先取りをアトミックに合わせる必要があります。この1行の違いが、プロモーションのときに二重請求の報告が入るかどうかを分けます。

ステップ

  1. /root/idem/pay.pyを127.0.0.1:8130で起動します。起動時にRedisのキーbalanceを10000に初期化し、POST /payは{"amount":100}を受け取って残高を減らし、{"charged":100,"balance":<잔액>}を返します(プレースホルダーは残高です)。
  2. 冪等性なしで同じリクエストを2回送った結果を/root/idem/double.outにbalance_before=10000 balance_after=9800の形式で書きます。
  3. pay.pyがIdempotency-Keyヘッダーを要求するように変えます。ヘッダーがなければ400を返します。
  4. 同じキーで2回送ると、最初の応答がそのまま繰り返され、残高は1回だけ減ります。/root/idem/dedup.outにbalance_after=9900 calls=2を書きます。
  5. 同じキーで同時に2件を送っても、1回だけ処理されます。/root/idem/concurrent.outにprocessed=1 balance_delta=100を書きます。
  6. 同じキーに別の金額を送ると、422を返します。/root/idem/mismatch.outにstatus=422を書きます。
  7. 冪等キーの残りの寿命が0より大きく、86400以下である必要があります。/root/idem/ttl.txtにttl=<초>を書きます(プレースホルダーは秒数です)。

参考

決済サービスを起動する

/root/idem/pay.pyを127.0.0.1:8130で起動してください。起動時にRedisのキーbalanceを10000に初期化し、POST /payは{"amount":100}を受け取って残高を減らし、{"charged":100,"balance":<잔액>}を返します(プレースホルダーは残高です)。

残高をRedisに置けば、再起動しても状態が保たれます。最初の残高を決まった値に初期化してください。

二重請求を再現する

冪等性なしで同じリクエストを2回送った結果を/root/idem/double.outにbalance_before=10000 balance_after=9800の形式で書いてください。

同じ決済リクエストを2回送ると、残高が2回減ります。これが防ぐべき状態です。

冪等キーのヘッダーを受け取る

pay.pyがIdempotency-Keyヘッダーを要求するように変えてください。ヘッダーがなければ400を返します。

ヘッダーがなければ400で拒否するほうが安全です。クライアントにキーを作らせることが契約です。

同じキーは1回だけ処理する

同じキーで2回送ると、最初の応答がそのまま繰り返され、残高は1回だけ減るようにしてください。/root/idem/dedup.outにbalance_after=9900 calls=2を書きます。

キーに対して保存しておいた最初の応答を、そのまま返します。2回目のリクエストで、残高が変わってはいけません。

同時リクエストでも1回だけ処理する

同じキーで同時に2件を送っても、1回だけ処理されるようにしてください。/root/idem/concurrent.outにprocessed=1 balance_delta=100を書きます。

参照してから保存すると、その間にほかのリクエストが割り込みます。1回のアトミックなコマンドで先取りする必要があります。

キーは同じなのに本文が違う場合は拒否する

同じキーに別の金額を送ると、422を返すようにしてください。/root/idem/mismatch.outにstatus=422を書きます。

最初のリクエストの本文のハッシュも一緒に保存しておけば、比較できます。ステータスコードは422です。

キーの期限切れを設定する

冪等キーの残りの寿命が0より大きく、86400以下になるようにしてください。/root/idem/ttl.txtにttl=<초>を書きます(プレースホルダーは秒数です)。

キーを永遠に保管すると、メモリが際限なく増えます。24時間が慣行です。残りの寿命を取得して確認してください。