分散式鎖本質上要實現的目標就是在 Redis 裡面佔一個“茅坑”,當別的程式也要來佔 時,發現已經有人蹲在那裡了,就只好放棄或者稍後再試。
佔坑一般是使用 setnx(set if not exists) 指令,只允許被一個客戶端佔坑。先來先佔, 用 完了,再呼叫 del 指令釋放茅坑。
// 這裡的冒號:就是一個普通的字元,沒特別含義,它可以是任意其它字元,不要誤解 > setnx lock:codehole true OK ... do something critical ...
> del lock:codehole
(integer) 1
但是有個問題,如果邏輯執行到中間出現異常了,可能會導致 del 指令沒有被呼叫,這樣 就會陷入死鎖,鎖永遠得不到釋放。
於是我們在拿到鎖之後,再給鎖加上一個過期時間,比如 5s,這樣即使中間出現異常也 可以保證 5 秒之後鎖會自動釋放。
> setnx lock:codehole true OK
> expire lock:codehole 5 ...
do something critical ...
> del lock:codehole
(integer) 1
但是以上邏輯還有問題。如果在 setnx 和 expire 之間伺服器程式突然掛掉了,可能是因 為機器掉電或者是被人為殺掉的,就會導致 expire 得不到執行,也會造成死鎖。
這種問題的根源就在於 setnx 和 expire 是兩條指令而不是原子指令。如果這兩條指令可 以一起執行就不會出現問題。也許你會想到用 Redis 事務來解決。但是這裡不行,因為 expire是依賴於 setnx 的執行結果的,如果 setnx 沒搶到鎖,expire 是不應該執行的。事務裡沒有 if- else 分支邏輯,事務的特點是一口氣執行,要麼全部執行要麼一個都不執行。
為了解決這個疑難,Redis 開源社群湧現了一堆分散式鎖的 library,專門用來解決這個問 題。實現方法極為複雜,小白使用者一般要費很大的精力才可以搞懂。如果你需要使用分散式鎖, 意味著你不能僅僅使用 Jedis 或者 redis-py 就行了,還得引入分散式鎖的 library。
為了治理這個亂象,Redis 2.8 版本中作者加入了 set 指令的擴充套件引數,使得 setnx 和expire 指令可以一起執行,徹底解決了分散式鎖的亂象。從此以後所有的第三方分散式鎖library 可以休息了。
> set lock:codehole true ex 5 nx
OK ...
do something critical ...
> del lock:codehole
上面這個指令就是 setnx 和 expire 組合在一起的原子指令,它就是分散式鎖的 奧義所在。
摘自《redis深度歷險:核心原理和應用實踐》