分散ロックの仕組み — 同じ仕事を、2台が重ねて始めないために
期限の後に戻ったAを、書き込み先で止める
まずは、期限が切れた後の場面を考える
注文を担当するAがロックを取った後、長く止まります。期限が切れてBが新しいロックを取り、書き込みを終えます。その後にAが動き出したら、古い処理の書き込みを拒む仕組みが必要になることがあります。
下のPython 3の例は、書き込み先が番号を確認する練習です。fence-demo.pyへ保存してpython fence-demo.pyで試します。
last_token = 0
value = None
def write(token, new_value):
global last_token, value
if token < last_token:
return False
last_token = token
value = new_value
return True
print(write(2, "Bの結果"))
print(write(1, "遅れて戻ったAの結果"))
print(value)
True
False
Bの結果
この例は判定のモデルです。本番ではトークンを信頼できる取得順で発行し、書き込み先で検査と更新を原子的に行います。同じ番号の処理の再試行もどう扱うか決めます。Pythonのこの変数だけで複数サーバーを調整できるわけではありません。
取得・解放・書き込み先の3か所を見る
| 場所 | 確認すること |
|---|---|
| 取得 | 誰が使えるかを原子的に決める |
| 解放 | 自分の取得かを確認して解放する |
| 書き込み先 | 期限後に戻った古い処理を拒む |
Redisの単一インスタンスの方式では、NXと期限を同じSETで指定し、取得ごとの一意な値を保存します。解放は値の比較と削除を原子的に行い、他の所有者のキーを消さないようにします。これは、Redisの障害や非同期複製への切替時も排他が保たれるという保証とは別です。
方式ごとの条件を確認する
Redlockは独立した複数のRedisと、過半数の取得・期限から取得時間等を差し引いた有効時間を使います。時計や停止への前提があり、公式資料も整合性について注意と議論を案内しています。ノードを増やしただけで、あらゆる停止に安全になるとは説明できません。
ZooKeeperのロックのレシピは、エフェメラルな連番ノード等を使います。通信が一瞬途切れたこととセッション失効は別です。etcdのロックもリース・所有キーに結び付きます。いずれも外部のストレージへの古い書き込みを、名前だけで防げるわけではありません。
業務の正しさから選ぶ
重複注文なら、DBの一意制約やトランザクション、処理済みID等が適する場合があります。ロックが取れても、応答を失って再試行する場面や、処理が一部だけ終わる場面は残ります。守りたい資源と失敗時の振る舞いから、排他・冪等性・フェンシングを組み合わせます。
🐧 ペンギン先生のまとめ:「分散ロック」って出てきたら「離れたプログラム同士で、同じ仕事を使う順番を調整する仕組み」と思えばだいたいOK!
重複の扱いはメッセージキューの仕組み、データの更新はSQL入門へ進めます。
参考資料
- Redis:Distributed Locks — 単一ロック・所有者確認・Redlockの期限と整合性・フェンシング
- etcd:Concurrency API — リースとロックの所有キー
- ZooKeeper:Recipes — エフェメラル連番ノードのロックレシピ
- ZooKeeper:Programmer Guide — 接続喪失とセッション失効の違い