在分佈式系統中,當多個服務實例競爭同一資源(如庫存扣減、訂單創建)時,需要分佈式鎖保證操作的原子性。與單機鎖(如Java的ReentrantLock)不同,分佈式鎖需解決跨節點、跨進程的互斥問題。本文將解析基於Redis、ZooKeeper和etcd的3種實現方案,及其背後的一致性原理。
一、分佈式鎖的核心要求
無論採用哪種方案,分佈式鎖需滿足4個核心特性:
- 互斥性:同一時間只能有一個實例持有鎖;
- 安全性:避免死鎖(如實例崩潰後鎖能自動釋放);
- 可用性:少數節點故障不影響鎖服務;
- 一致性:集羣環境下鎖狀態在各節點保持一致。
二、基於Redis的分佈式鎖
Redis憑藉高性能成為分佈式鎖的主流選擇,核心利用SET命令的原子性。
實現方案
// 基於Redisson的分佈式鎖實現(推薦)
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 獲取鎖(自動過期避免死鎖)
RLock lock = redisson.getLock("order:lock:1001");
try {
// 嘗試獲取鎖,最多等待10秒,15秒後自動釋放
boolean locked = lock.tryLock(10, 15, TimeUnit.SECONDS);
if (locked) {
// 執行業務邏輯(如扣減庫存)
updateInventory();
}
} finally {
// 釋放鎖
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
一致性原理
- 單節點:依賴Redis單實例的
SET key value NX PX命令(NX確保僅當鍵不存在時設置,PX設置過期時間),通過原子操作保證互斥; - 集羣環境:需使用Redisson的
RedLock算法,向多個獨立Redis節點申請鎖,只有超過半數節點成功才認為獲鎖,避免單點故障導致的鎖失效。
優缺點
- 優點:性能極高(單機QPS萬級),適合高併發場景;
- 缺點:集羣模式下一致性較弱(最終一致性),極端情況可能出現鎖丟失。
三、基於ZooKeeper的分佈式鎖
ZooKeeper基於ZAB協議提供強一致性,適合對可靠性要求高的場景。
實現方案
// 基於Curator的分佈式鎖實現
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("zk1:2181,zk2:2181,zk3:2181")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
// 創建分佈式鎖
InterProcessMutex lock = new InterProcessMutex(client, "/locks/order_1001");
try {
// 嘗試獲取鎖,最多等待10秒
if (lock.acquire(10, TimeUnit.SECONDS)) {
// 執行業務邏輯
updateInventory();
}
} finally {
// 釋放鎖
if (lock.isAcquiredInThisProcess()) {
lock.release();
}
}
一致性原理
- 利用ZooKeeper的臨時順序節點特性:
- 所有實例在
/locks目錄下創建臨時順序節點(如/locks/lock-00000001); - 只有序號最小的節點持有者獲鎖;
- 其他節點監聽前序節點,當前序節點刪除時(持有鎖的實例釋放或崩潰),觸發重新競爭。
- 臨時節點特性確保實例崩潰後鎖自動釋放(ZooKeeper檢測到會話超時後刪除節點)。
優缺點
- 優點:強一致性,無死鎖風險,適合數據一致性要求高的場景(如金融交易);
- 缺點:性能較低(單次操作百毫秒級),集羣部署複雜度高。
四、基於etcd的分佈式鎖
etcd基於Raft算法提供強一致性,兼具Redis的高性能和ZooKeeper的可靠性。
實現方案
// 基於etcd/clientv3的分佈式鎖實現(Go語言示例)
client, err := clientv3.New(clientv3.Config{
Endpoints: []string{"etcd1:2379", "etcd2:2379", "etcd3:2379"},
DialTimeout: 5 * time.Second,
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
// 創建鎖
lock := etcdclientv3.NewLock(client, "/locks/order_1001")
// 獲取鎖(自動續租避免超時)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
leaseId, err := lock.Lock(ctx)
if err != nil {
log.Fatal(err)
}
defer cancel()
// 執行業務邏輯
updateInventory()
// 釋放鎖
_, err = lock.Unlock(context.TODO(), leaseId)
if err != nil {
log.Fatal(err)
}
一致性原理
- 利用etcd的
Put命令和Lease機制:
- 通過
Put /locks/order_1001 <value> --lease=xxx創建帶租約的鍵,租約過期自動刪除; - 利用Raft算法保證鍵值對在集羣中的強一致性,確保只有一個實例能成功創建鍵;
- 獲鎖實例需定期續租(
KeepAlive),避免業務未完成時鎖過期。
優缺點
- 優點:強一致性(Raft協議),性能優於ZooKeeper,支持Watch機制實時感知鎖狀態;
- 缺點:相對Redis複雜度略高,客户端生態不如Redis成熟。
五、三種方案對比與選型建議
|
維度 |
Redis |
ZooKeeper |
etcd |
|
一致性 |
最終一致性(集羣) |
強一致性 |
強一致性 |
|
性能 |
極高(萬級QPS) |
中(千級QPS) |
高(萬級QPS) |
|
可靠性 |
依賴RedLock算法 |
天然可靠 |
天然可靠 |
|
適用場景 |
高併發,允許短暫不一致 |
金融級,強一致性要求 |
雲原生,平衡性能與一致性 |
選型建議:
- 秒殺、庫存等高頻場景首選Redis,搭配Redisson降低實現複雜度;
- 金融交易等強一致性場景選ZooKeeper或etcd;
- 雲原生環境(如Kubernetes)優先選etcd,與基礎設施天然集成。
總結
分佈式鎖的核心是解決跨節點互斥,三種方案分別基於不同的一致性模型:Redis依賴客户端算法保證最終一致性,ZooKeeper和etcd通過集羣協議提供強一致性。實際落地時,需結合業務對性能、一致性的要求選擇方案,同時注意鎖超時時間設置、自動續租、異常釋放等細節,避免出現鎖失效或死鎖問題。