分布式鎖定
1)為什麼(以及何時)需要分布式鎖定
分布式鎖定是一種機制,可確保群集多個節點之間臨界部分的互操作性。典型任務:- 背景任務/sheduler的領導力(領導力)。
- 將單一執行者限制為共享資源(移動文件、遷移模式、獨家支付步驟)。
- 如果無法實現等效性/排序,則順序處理單元(wallet/order)。
- 如果可以進行等效的upsert,CAS(匹配和設置)或按鍵排隊(按鍵排列)。
- 如果資源允許交換操作(CRDT,計數器)。
- 如果問題由單個存儲中的事務解決。
2)威脅模型和屬性
故障和復雜性:- 網絡:延遲、分離(分區)、數據包丟失。
- 過程:GC暫停,停止世界,鎖定後崩潰。
- 時間:時鐘漂移和位移打破TTL方法。
- 重新擁有:網絡後的「僵屍」過程可能認為它仍然擁有城堡。
- 安全:不超過一個有效所有者(安全)。
- 活著:城堡在主人失靈(liveness)時被釋放。
- 正義:沒有饑餓。
- 時鐘獨立性:正確性獨立於時鐘(或由時鐘調整來補償)。
3)主要型號
3.1 Lease(租賃鎖)
TTL發出了鎖。業主有義務將其延長至到期(心跳/保持活力)。
優點:在裂縫中自負。
風險:如果所有者「卷曲」並繼續工作,但失去了延期,則可能會出現雙重所有權。
3.2 Fencing token(護欄令牌)
每次成功捕獲時,都會發出單調增長的數字。資源用戶(DB,隊列,文件存儲)檢查令牌並拒絕使用舊編號的操作。
這在TTL/租賃和網絡拆分中至關重要-可以防止「舊」所有者。
3.3 Quorum鎖(CP系統)
使用分布式共識(Raft/Paxos;etcd/ZooKeeper/Consul),記錄與共識日誌相關聯→在大多數節點上沒有分裂中斷。
另外:強大的安全保障。
減:對法定人數的敏感性(如果失去法定人數,其生存能力將減弱)。
3.4個AP鎖(內存/緩存+復制)
例如,Redis群集。高可用性和速度,但沒有嚴格的網絡分離安全保證。需要在合成器側面進行彎曲。
4)平臺和模式
4.1 etcd/ZooKeeper/Consul(建議使用強鎖)
短暫節點(ZK)或會話/leases(etcd):關鍵在會話現場時存在。
會期保持性;法定人數的損失→會議到期→鎖被釋放。
等候隊列的序節點(ZK'EPHEMERAL_SEQUENTIAL) →公平性。
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 key value NX PX ttl」。
問題:- 復制/傳送器可以允許同時擁有者。
- 多個實例的雷德洛克(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群集(過程/會話)中的鎖。
好吧,當所有關鍵部分都處於一個DB中時。
SQL:
sql
SELECT pg_try_advisory_lock(42); -- take
SELECT pg_advisory_unlock(42); -- let go
4.4個文件/雲「鎖」
S3/GCS+具有「If-Match」(ETag)條件的lock對象元數據→本質上是CAS。
適合備用/遷移。
5)安全鎖設計
5.1所有者身份
存儲「owner_id」(節點#process #pid#start_time)+unlock時用於對賬的隨機令牌。
重復解鎖不應拆除別人的鎖。
5.2 TTL和延期
TTL <T_fail_detect(故障檢測時間)和≥ p99操作關鍵部分×庫存。
擴展-定期(例如,每個「TTL/3」),具有截止日期。
5.3 Fencing token on shinka
改變外部資源的部分必須傳遞「fencing_token」。
Sink(DB/緩存/存儲)存儲「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中-「領導者」是獨家的epemer鍵;其余的訂閱更改。
領導者寫心跳;損失-連任。
所有領導者操作都伴隨著fencing token(時代/修訂號)。
7)錯誤及其處理
客戶拿起城堡,但在→規範之前沒有人會受到傷害。TTL/會議將騰空。
城堡在工作的中間到期:- 看門狗是強制性的:如果延期失敗,則中斷關鍵部分並回滾/補償。
- 沒有「下車」:沒有鎖,關鍵部分就無法繼續。
一個漫長的停頓(GC/stop-the-world)→擴展沒有發生,另一個接管了城堡。工作流必須檢測所有權的損失(保持通道)並中斷。
8)Dedlocks,優先事項和倒置
在分布式世界中,Dedlocks很少見(鎖通常是一個鎖定),但是如果鎖有幾個鎖定,則堅持單個取回順序(鎖定命令)。
優先級倒置:低優先級所有者在高優先級等待時保留資源。解決方案:TTL限制,提前(如果業務允許),共享資源。
禁食:使用等待隊列(ZK子順序節點)實現公平。
9)可觀察性
度量標準:- `lock_acquire_total{status=ok|timeout|error}`
- `lock_hold_seconds{p50,p95,p99}`
- 「fencing_token_valu」(單調)
- `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.
- 「Orfen」鎖(沒有心跳)。
- 等待隊列膨脹。
10)實例
10.1安全的Redis鎖定與fencing(偽)
1.將令牌計數器存儲在可靠的堆棧中(例如Postgres/etcd)。
2.成功的「SET NX PX」會讀取/嵌入令牌,並且所有資源更改都將通過DB/服務中的令牌驗證來完成。
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, Curator)
java
LeaderSelector selector = new LeaderSelector(client, "/leaders/cron", listener);
selector. autoRequeue();
selector. start(); // listener. enterLeadership() с try-finally и heartbeat
10.4 Postgres advisory lock with dedline (SQL+app)
sql
SELECT pg_try_advisory_lock(128765); -- attempt without blocking
-- if false --> return via backoff + jitter
11)花花公子(Game Days)
失去法定人數:禁用1-2個etcd節點→嘗試取鎖不應通過。
GC暫停/停止世界:人為地延遲所有者的流動,→檢查看門狗正在中斷工作。
Split-brain:模仿城堡所有者和看門狗之間的網絡鴻溝→新主人獲得更高的折騰,舊的-被合成器拒絕。
Clock skew/Drift:從所有者那裏移走手表(對於Redis/lease) →確保通過令牌/檢查確保正確性。
發布前崩潰:TTL/會話釋放 →鎖的過程下降。
12)反模式
幹凈的TTL鎖,當訪問外部資源時,不帶扣。
依靠本地時間來正確無誤(沒有HLC/fencing)。
通過一個Redis向導在帶有操縱器的環境中分發鎖,而無需確認副本。
無限關鍵部分(TTL「世紀」)。
在沒有「owner_id」/token對賬的情況下拆除「外星人」鎖。
缺少backoff+Jitter →嘗試的「風暴」。
一站式全球鎖-一袋沖突;在鑰匙上縫合更好。
13)實施支票
- 定義了資源的類型以及是否可以通過CAS/隊列/冪等來實現。
- 選擇了一種機制:etcd/ZK/Consul for CP;Redis/緩存-僅帶調試。
- 實現:「owner_id」,TTL+擴展,看門狗,正確解鎖。
- 外部資源會檢查fencing token(單調性)。
- 有領導和失敗的策略。
- 設置指標、異常值、令牌和修訂版本。
- 在acquire上提供backoff+jitter和taymout。
- 舉行比賽日:法定人數,分裂大腦,GC暫停,時鐘跳躍。
- 關於采取多個鎖的順序的文件(如果需要)。
- 退化計劃(brownout):在無法進入城堡的情況下該怎麼辦。
14) FAQ
Q: Redis鎖定是否足夠「SET NX PX」?
A:只有當資源檢查了fencing token時。否則,在網絡共享中,兩個所有者是可能的。
Q: 默認情況下選擇什麼?
答:對於嚴格的保證是etcd/ZooKeeper/Consul (CP)。對於一個DB內的輕量級任務-advisory locks Postgres。Redis只是一個騙局。
問:什麼是TTL上市?
答:「TTL ≥ p99的關鍵部分持續時間× 2」,並且足夠短,可以快速清理「僵屍」。擴展-每個「TTL/3」。
問:如何避免饑餓?
A:順序等待隊列(ZK序列)或公平算法;嘗試限制和公平規劃。
Q: 是否需要時間同步?
答:要正確無誤,請使用fencing。對於可操作的可預測性-是的(NTP/PTP),但不要依賴鎖邏輯中的墻時鐘。
15)結果
強大的分布式鎖定是在具有lease+keepalive的法定支架(etcd/ZK/Consul)上構建的,並且必須通過在可變資源級別上的鎖定令牌來補充。任何沒有圍欄的TTL/Redis方法都是破裂的風險。首先考慮低成本和偶然性,在沒有低成本的情況下使用鎖定,測量,測試故障模式-並且「關鍵部分」僅在意義上而不是事件數量上仍然至關重要。