使用Redis SETNX 命令實現分布式鎖」

2021-09-27 10:45:09 字數 2732 閱讀 9863

使用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,...