使用redis的 setnx 命令可以實現分布式鎖,本文介紹其實現方法。
直接進入正題,現在分布式的應用場景很多,為了保持資料的一致性,經常碰到需要對資源加鎖的情形。 利用redis來實現分布式鎖就是其中的一種實現方案。
setnx命令簡介
命令格式
setnx key value
1將 key 的值設為 value ,當且僅當 key 不存在。
若給定的 key 已經存在,則 setnx 不做任何動作。
setnx 是『set if not exists』(如果不存在,則 set)的簡寫。
返回值設定成功,返回 1 。
設定失敗,返回 0 。
示例redis> exists job # job 不存在
(integer) 0
redis> setnx job "programmer" # job 設定成功
(integer) 1
redis> setnx job "code-farmer" # 嘗試覆蓋 job ,失敗
(integer) 0
redis> get job # 沒有被覆蓋
"programmer"
setnx分布式鎖實現方案
利用setnx的特性,很容易的想到,在需要加鎖的時候,呼叫setnx命令,如果返回了1,表示設定成功,獲得了當前鎖,之後做一些想要的操作,完成之後呼叫del命令釋放鎖。
redis> setnx lock true # 獲得鎖成功
(integer) 1
... do thing ...
redis> del lock # 釋放鎖
(integer) 1
但是這樣存在乙個問題,如果在執行del命令之前,當前程式發生錯誤,那麼這個鎖就永遠得不到釋放,其他程式也永遠無法加鎖成功。
於是我們可以在加鎖之後為這個鎖設定乙個過期時間,過期時間之後,如果沒有釋放,就自動刪除,防止鎖被一直占用。
redis> setnx lock true # 獲得鎖成功
(integer) 1
redis> expire lock 5 # 設定5秒的過期時間
(integer) 1
... do thing ...
redis> del lock # 釋放鎖
(integer) 1
但是這樣還是有問題,如果在setnx和expire之間程式又發生了錯誤,當前鎖又無法釋放。所以根本原因還是需要乙個原子的操作,在獲得鎖的同時能夠同時設定鎖的過期時間。
setnx設定鎖
在設定鎖的時候,我們可以利用鎖的值來實現過期的特性
setnx lock
1我們不是設定乙個簡單的值到lock中,而是將過期的時間寫入到lock中。 獲得鎖的判斷條件仍舊是跟之前一樣, 如果返回了1的話,表示獲得了鎖,可以進行下一步的操作。
判斷過期條件
正常情況下,操作完成之後,仍舊執行del操作將當前鎖釋放。那麼如果當前程式發生了錯誤退出了,當前鎖沒有正常釋放,其他的程序如何獲得鎖呢。
假設上乙個程序加鎖之後異常退出,沒有釋放鎖。當前的程序想要加鎖,在呼叫setnx的時候發現加鎖失敗,然後需要呼叫get命令獲得當前鎖的值,即上乙個程序寫入的過期時間。 如果獲得的過期時間未到,那麼當前程序繼續等待; 如果鎖的過期時間已經到了,很大的概率上乙個獲得鎖的程序已經發生了錯誤,因為我們這個過期時間一般會設定的比正常的執行時間要長。在這種情況下, 當前程序可以重新寫入這個鎖並進行後續的操作。
解決競爭條件
但是這樣又帶來乙個新的問題: 假設有p1和p2兩個程序同時想獲得鎖,他們都檢測到了當前的鎖已經過期了, 他們可以寫入,他們呼叫set命令寫入都會成功,那麼如果決定到底是哪個程序獲得了鎖呢。
所以在這邊重新寫入的時候不能簡單的呼叫set命令, 還有另乙個命令可以考慮: getset。getset命令在設定值的同時,會將設定之前的值返回。
仍舊考慮剛才的情形, p1和p2同時在競爭鎖,發現鎖的時間t已經過期了,然後他們同時呼叫getset命令設定新的鎖。假設p1先設定成功時間t1,那麼呼叫getset得到的值就是t; p2呼叫getset雖然將鎖的時間設定成了t2,但是他得到的值是t1。
通過判斷getset返回的值,就能判斷自己是否獲得了鎖。如果返回的值仍然是乙個過期的時間,那麼說明正確的加鎖了;否則的話,說明正好有別的程序已經設定了鎖,當前程序只是更新了一下鎖而已,就繼續等待。
可能會說這邊有乙個小問題,p1設定的鎖的過期時間被p2更改了。考慮到產生這種競態條件的時候肯定時間間隔是非常小的, 即使重新設定了過期時間,這種很短的時間修改在大多數情況下都可以忽略不計。
偽**所以,我們能夠得到最終的乙個過程,用偽**表示
while 1:
lock = redis.setnx(key, time.now() + timeout)
if lock == 1:
// 獲得鎖
break
lock_ts = redis.get(key)
if (lock_ts < time.now()) && (redis.getset(key, time.now() + timeout) < time.now()):
// 鎖已經過期,用getset重新寫鎖
// 返回的原來的時間仍舊過期,說明加鎖成功
break
else:
sleep
.... do something ...
// 完成之後釋放鎖
redis.del(key)
詳解使用Redis SETNX 命令實現分布式鎖
使用redis的 setnx 命令可以實現分布式鎖,下文介紹其實現方法。setnx命令簡介 命令格式 setnx key value 將 key 的值設為 value,當且僅當 key 不存在。若給定的 key 已經存在,則 setnx 不做任何動作。setnx 是set if not exists...
使用Redis SETNX 命令實現分布式鎖
使用redis的 setnx 命令可以實現分布式鎖,下文介紹其實現方法。setnx key value 將 key 的值設為 value,當且僅當 key 不存在。若給定的 key 已經存在,則 setnx 不做任何動作。setnx 是set if not exists的簡寫。返回整數,具體為 1,...
使用Redis SETNX 命令實現分布式鎖
使用redis的 setnx 命令可以實現分布式鎖,下文介紹其實現方法。setnx key value 將 key 的值設為 value,當且僅當 key 不存在。若給定的 key 已經存在,則 setnx 不做任何動作。setnx 是set if not exists的簡寫。返回整數,具體為 1,...