在普通的單機程序中,我們?yōu)榱吮苊赓Y源競爭,通常會使用synchronize、lock 等方式進行加鎖防止并發(fā)問題。不過在分布式系統(tǒng)中,請求是并發(fā)的在多臺機器上執(zhí)行,這時候就需要使用分布式鎖來防止資源競爭問題。
提到分布式鎖,我們最常見的方案就是基于redis實現(xiàn),本文將依次解釋
- 如何用redis實現(xiàn)一個基本的分布式鎖
- 基本方案中還有哪些問題?如何解決?
redis分布式鎖
我們知道redis由于其單線程的模式,可以保證各命令按順序、原子的執(zhí)行。
Redis的分布式鎖主要使用了setnx命令。
- 加鎖。使用
setnx key value,當(dāng)key不存在時,設(shè)置成功表示獲取到鎖,否則認(rèn)為獲取鎖失敗。 - 解鎖。使用
del key,刪除緩存對應(yīng)的key表示成功釋放鎖。
一般來說,為了防止發(fā)生由于服務(wù)問題導(dǎo)致解鎖命令未執(zhí)行而造成鎖一致無法被釋放(死鎖)的情況。會在加鎖時同時使用expire key timeout命令設(shè)置一個默認(rèn)的超時時間。由于setnx 和 expire 是兩個命令,為了保證原子性,可以通過lua腳本的方式進行執(zhí)行。
上述方案已經(jīng)可以實現(xiàn)一個基本的分布式鎖,但是還是會有一些特殊的場景及問題需要我們?nèi)リP(guān)注并解決。
分布式鎖特殊場景
-
鎖錯誤刪除
舉一個特殊場景的例子。線程A獲取鎖L,然后設(shè)置超時時間10s。然后線程B在12s時成功獲取鎖,然后開始執(zhí)行到18s。然后線程A在15秒時執(zhí)行完,就會去解鎖(刪除緩存key)。此時就發(fā)生了錯誤解鎖的問題,即釋放了線程B持有的鎖。

這種場景,可以通過每個線程設(shè)置value都是與之唯一對應(yīng)的value來解決(例如UUID)。然后在del的時候做一個value校驗來防止誤刪除。
-
超時解鎖導(dǎo)致并發(fā)問題
上圖中除了鎖誤刪的情況,還因為 鎖超時自動釋放 進而導(dǎo)致了線程A和線程B并發(fā)問題。針對這種情況,一般的解決方案是為線程創(chuàng)建一個守護進程,來進行不斷的續(xù)約操作,避免鎖超時釋放。

注意守護進程也需要進行value校驗,防止錯誤的續(xù)約操作。當(dāng)主線程執(zhí)行完業(yè)務(wù)后,同時關(guān)閉掉守護線程即可。(如果主線程意外關(guān)閉了,守護線程也會自動關(guān)閉)
其他問題
除了上述問題,redis本身在主從切換、腦裂問題等極端情況導(dǎo)致的數(shù)據(jù)不一致問題,也會導(dǎo)致分布式鎖的錯誤。
參考資料
https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/