GH GambleHub

分散ロック

1)分散ロックが必要な理由

分散ブロッキングは、複数のクラスタノード間の重要なセクションの相互排除を保証するメカニズムです。典型的なタスク:
  • バックグラウンドタスク/シェーダーのリーダー選挙。
  • 共有リソース(ファイル移動、スキーママイグレーション、排他的支払いステップ)上の単一の実行者の制限。
  • idempotency/orderを達成できない場合は、集計(ウォレット/オーダー)のシーケンシャル処理。
ロックを使用しない方が良い場合:
  • idempotent upsertを作成できる場合、CAS (compare-and-set)またはキーごとの順序。
  • リソースが可換演算(CRDT、 counters)を許可する場合。
  • 問題が1つの店のトランザクションによって解決されれば。

2)脅威モデルとプロパティ

障害と合併症:
  • ネットワーク:遅延、パーティション、パケット損失。
  • プロセス:GCの一時停止、世界停止、ロックキャプチャ後のクラッシュ。
  • 時間:クロックドリフトと変位ブレークTTLアプローチ。
  • 回収:ネットの後の「ゾンビ」プロセスは、それがまだ城を所有していると思うかもしれません。
希望するプロパティ:
  • 安全:複数の有効な所有者(安全)。
  • 生存可能性:所有者が失敗したときにロックが解放されます(生きている)。
  • 正義:断食はありません。
  • クロックの独立性:正しさはウォールクロック(またはフェンシングトークンによって補償)に依存しません。

3)主要なモデル

3.1リース(レンタルロック)

ロックはTTLで発行されます。所有者は有効期限(heartbeat/keepalive)の前にそれを更新する義務があります。

長所:自己吸収をクラッシュ。
リスク:所有者が「立ち往生」し、働き続けるが、延長を失った場合、二重所有権が発生する可能性があります。

3.2フェンシングトークン

キャプチャが成功するごとに、単調に増加する数が発行されます。リソース消費者(データベース、キュー、ファイルストレージ)は、トークンをチェックし、古い番号で操作を拒否します。
これはTTL/リースとネットワークパーティションにとって非常に重要です。

3.3クォーラムロック(CPシステム)

分散コンセンサスが使用されます(いかだ/Paxos;etcd/ZooKeeper/Consul)、レコードはコンセンサスログに関連付けられています→ほとんどのノードで分割された脳はありません。

プラス:強力なセキュリティ保証。
マイナス:クォーラムに対する感度(それが失われたとき、生存性はラメである)。

3.APロック×4(インメモリ/キャッシュ+レプリケーション)

たとえば、Redisクラスタです。高可用性と高速、しかしネットワークパーティションのための強力なセキュリティ保証なし。傷口の側面で囲うことを要求して下さい。

4)プラットホームおよびパターン

4.1 etcd/ZooKeeper/領事館(強いロックにおすすめ)

一時的なノード(ZK)またはセッション/リース(etcd):セッションが生きている間にキーが存在します。
セッションのkeepalive;quorum loss→セッションの期限切れ→ロックが解放されます。
waitキュー→fairのシーケンスノード(ZK 'EPHEMERAL_SEQUENTIAL')。

etcd (Go)のスケッチ:
go cli, _:= clientv3. New(...)
lease, _:= cli. Grant(ctx, 10)            // 10s lease sess, _:= concurrency. NewSession(cli, concurrency. WithLease(lease. ID))
m:= concurrency. NewMutex(sess, "/locks/orders/42")
if err:= m. Lock(ctx); err!= nil { / handle / }
defer m. Unlock(ctx)

4.2 Redis(きちんとした)

クラシック-'SETキー値NX PX ttl'。

問題:
  • レプリケーション/feiloverは同時所有者を許可する場合があります。
  • 複数のインスタンスからのRedlockはリスクを軽減しますが、排除しません。信頼できないネットワークを持つ環境で物議を醸しています。

迅速な調整レイヤーとしてRedisを使用する方が安全ですが、常にターゲットリソースのフェンシングトークンを補完します。

例(Lua-unlock):
lua
-- release only if value matches if redis. call("GET", KEYS[1]) == ARGV[1] then return redis. call("DEL", KEYS[1])
else return 0 end

4.3つのDBロック

PostgreSQLアドバイザリロック:Postgresクラスタ内のロック(プロセス/セッション)。
すべてのクリティカルセクションがすでに同じデータベースにある場合、それは良いことです。

SQL:
sql
SELECT pg_try_advisory_lock(42); -- take
SELECT pg_advisory_unlock(42); -- let go

4.4ファイル/クラウドロック

S3/GCS+オブジェクトメタデータロック「If-Match」 (ETag)条件→本質的にCAS。
バックアップ/移行に適しています。

5)安全ロックの設計

5.1所有者のID

'owner_id'(#process#pid#start_time node)+ランダムトークンを格納してロック解除検証を行います。
繰り返されたロック解除は、他の誰かのロックを削除するべきではありません。

5.2 TTLと拡張

TTL <T_fail_detect(故障検出時間)およびp99 ≥の重要なセクション操作×スペア。
更新-定期的に(例えば、すべての'TTL/3')、期限付き。

5.3傷のフェンシングトークン

外部リソースを変更するセクションは'fencing_token'を渡す必要があります。

Sink (DB/cache/storage)は「last_token」を格納し、より小さなものを拒否します:
sql
UPDATE wallet
SET balance = balance +:delta, last_token =:token
WHERE id =:id AND:token > last_token;

5.4待ち行列と正義

ZK-'EPHEMERAL_SEQUENTIAL'およびオブザーバー:クライアントは最も近い前任者のリリースを待っています。
etcdで-リビジョン/バージョン管理のキー;'mod_revision'で指定します。

5.5スプリットブレイン行動

CPアプローチ:クォーラムなしでは、ロックを取ることはできません-安全性を破るよりも立った方が良いです。
APアプローチ:分割された島で進捗が許可されます→フェンシングが必要です。

6)リーダー選挙

etcd/ZKでは「、リーダー」は排他的なエペメリック・キーです。残りは変更のためにサインアップされます。
リーダーはハートビートを書きます;損失-再選。
すべてのリーダー操作にフェンシングトークン(era/revision number)を同行します。

7)エラーとその処理

クライアントはロックを取りましたが、クラッシュして作業→標準、誰も苦しむことはありません。TTL/セッションを公開します。

作業中にロックが切れました:
  • 必須ウォッチドッグ:拡張に失敗した場合は、クリティカルセクションを中断し、ロールバック/補償します。
  • No 「finish later」:ロックなしでは、クリティカルセクションを続行できません。

長い一時停止(GC/stop-the-world)→拡張は起こらなかった、他はロックを取りました。ワークフローは、所有権の喪失(キープアライブチャネル)を検出し、中止する必要があります。

8) Dedloki、優先順位と反転

分散世界のデドロキは珍しい(通常は1つの城がある)が、いくつかの城がある場合は、単一の受注(ロック注文)に準拠します。
優先度反転:優先度の低い所有者はリソースを保持し、優先度の高い所有者は待機します。ソリューション:TTL制限、プリエンプション(ビジネスが許可している場合)、リソースのシャーディング。
断食:フェアネスのために待ちキュー(ZK-sub-orderノード)を使用します。

9)観測可能性

メトリクス:
  • 'lock_acquire_total {status=ok' timeout 'error}'
  • 'lock_hold_seconds {p50、 p95、 p99}'
  • 'fencing_token_value'(単調)
  • 'lease_renew_fail_total'
  • 'split_brain_prevented_total'(クォーラム不足のために拒否された試行回数)
  • 'preemptions_total'、 'wait_queue_len'
ログ/トレース:
  • 'lock_name'、 'owner_id'、 'token'、 'ttl'、 'attempt'、' wait_time_ms'、 'path' (ZK)、 'mod_revision' (etcd)。
  • 「acquire→critical section→release」は結果と重なります。
アラート:
  • 成長'lease_renew_fail_total'。
  • 'lock_hold_seconds {p99}'> SLO。
  • 「孤児」ロック(ハートビートなし)。
  • 膨らんだ待機リスト。

10)ケーススタディ

10.1フェンシング付きセキュアRedisロック(擬似)

1.信頼できるストア(Postgres/etcdなど)にトークンカウンターを保存します。
2.'SET NX PX'が成功すると、トークンを読み取り/増分し、データベース/サービスでトークンをチェックした状態でリソースにすべての変更を加えます。

python acquire token = db. next_token ("locks/orders/42") # monotone ok = redis. set("locks:orders:42", owner, nx=True, px=ttl_ms)
if not ok:
raise Busy()

critical op guarded by token db. exec("UPDATE orders SET... WHERE id=:id AND:token > last_token",...)
release (compare owner)

10.2 etcd Mutex+watchdog (Go)

go ctx, cancel:= context. WithCancel(context. Background())
sess, _:= concurrency. NewSession(cli, concurrency. WithTTL(10))
m:= concurrency. NewMutex(sess, "/locks/job/cleanup")
if err:= m. Lock(ctx); err!= nil { /... / }

// Watchdog go func() {
<-sess. Done ()//loss of session/quorum cancel ()//stop working
}()

doCritical (ctx )//must respond to ctx. Done()
_ = m. Unlock(context. Background())
_ = sess. Close()

10.3 ZKのリーダーシップ(Java、キュレーター)

java
LeaderSelector selector = new LeaderSelector(client, "/leaders/cron", listener);
selector. autoRequeue();
selector. start(); // listener. enterLeadership() с try-finally и heartbeat

10.4期限付きPostgresアドバイザリーロック(SQL+アプリ)

sql
SELECT pg_try_advisory_lock(128765); -- attempt without blocking
-- if false --> return via backoff + jitter

11)テストプレイブック(ゲーム日)

クォーラム損失:1-2 etcdノードを無効にする→ロックを取る試みは通過しないでください。
GC-pause/stop-the-world:オーナーのフローを人工的に遅らせる→ウォッチドッグが作業を中断していることを確認する。
スプリットブレイン:所有者と城の側の間のネットワーク分離のエミュレーション→新しい所有者はより高いフェンシングトークンを取得し、古いものは青で拒否されます。
クロックスキュー/ドリフト:所有者から時計を離します(Redis/リース用)→トークン/チェックによって正確さが確保されていることを確認します。
リリース前のクラッシュ:プロセスのクラッシュ→TTL/セッション経由でロックが解除されます。

12)アンチパターン

外部リソースにアクセスするときにフェンスなしでTTLロックをクリーンにします。
正確さのための現地時間に頼って下さい(HLC/囲うこと無し)。
feiloverとレプリカの確認なしの環境で1つのRedisマスタを介したロックの配布。
無限クリティカルセクション(TTL「年齢のための」)。
'owner_id '/tokenをチェックせずに他の誰かのロックを削除します。
バックオフの欠如+ジッタ→試みの嵐。
単一のグローバルロック「すべてのために」-紛争の袋。キーシャーディングの方がいい。

13)実装チェックリスト

  • リソースタイプが定義され、CAS/queue/idempotencyをディスペンスできます。
  • 選択されたメカニズム:etcd/ZK/Consul for CP;Redis/cache-フェンシングのみ。
  • 実装:'owner_id'、 TTL+extension、 watchdog、正しいロック解除。
  • 外部リソースはフェンショントークン(単調)をチェックします。
  • リーダーシップ戦略とフェイルオーバーがあります。
  • 設定されたメトリック、アラート、ロギングトークンおよびリビジョン。
  • バックオフ+ジッタと取得タイムアウトが提供されます。
  • ゲーム開催日:クォーラム、スプリットブレイン、GCは一時停止、クロックスキュー。
  • 複数のロックを取るための手順のドキュメント(必要に応じて)。
  • brownout plan-ロックが利用できない場合の対処方法。

14) FAQ

Q: 'SET NX PX' Redisロックで十分ですか?
A:リソースがフェンシングトークンをチェックする場合のみ。そうでなければ、ネットワークパーティショニングでは、2人の所有者が可能です。

Q: 何を「デフォルトで」選ぶためにか"?

A:厳密な保証のため-etcd/ZooKeeper/Consul (CP)。1つのデータベース内の簡単なタスクのために-アドバイザリはPostgresをロックします。レディス-フェンシングのみ。

Q:どのTTLを置くべきですか?
A: 'TTL ≥ p99クリティカルセクションの持続時間× 2'とすぐに"ゾンビをクリアするのに十分な短い。"リニューアル-すべての'TTL/3'。

Q:断食を避ける方法か?
A:順番待ちキュー(ZKシーケンシャル)または公平性アルゴリズム;試みおよび公正な計画の限界。

Q:私は時間の同期を必要としますか?
A:正しさのため-いいえ(使用囲うこと)。動作予測可能性については、はい(NTP/PTP)ですが、ロックロジックはウォールクロックに依存しません。

15)合計

信頼できる分散ロックは、リース+キープアライブでクォーラムレベル(etcd/ZK/Consul)に構築され、変更されるリソースのレベルでトークンをフェンシングすることによって必然的に補完されます。フェンシングなしのTTL/Redisアプローチは、スプリットブレインのリスクです。因果関係と偶像性について最初に考え、それがそれらなしで不可能であるロックを使用し、測定し、故障モードをテストします-そしてあなたの「重要なセクション」は、インシデントの数ではなく、意味でのみ重要です。

Contact

お問い合わせ

ご質問やサポートが必要な場合はお気軽にご連絡ください。いつでもお手伝いします!

統合を開始

Email は 必須。Telegram または WhatsApp は 任意

お名前 任意
Email 任意
件名 任意
メッセージ 任意
Telegram 任意
@
Telegram を入力いただいた場合、Email に加えてそちらにもご連絡します。
WhatsApp 任意
形式:+国番号と電話番号(例:+81XXXXXXXXX)。

ボタンを押すことで、データ処理に同意したものとみなされます。