Redis 併發, 鎖, 競爭鎖問題.

Redis併發問題

Redis爲單進程單線程模式,採用隊列模式將併發訪問變爲串行訪問。Redis自己沒有鎖的概念,Redis對於多個客戶端鏈接並不存在競爭,可是在Jedis客戶端對Redis進行併發訪問時會發生鏈接超時、數據轉換錯誤、阻塞、客戶端關閉鏈接等問題,這些問題均是因爲客戶端鏈接混亂形成。對此有2種解決方法:

1.客戶端角度,爲保證每一個客戶端間正常有序與Redis進行通訊,對鏈接進行池化,同時對客戶端讀寫Redis操做採用內部鎖synchronized。

2.服務器角度,利用setnx實現鎖。

對於第一種,須要應用程序本身處理資源的同步,可使用的方法比較通俗,可使用synchronized也可使用lock;第二種須要用到Redis的setnx命令,可是須要注意一些問題。

SETNX命令(SET if Not eXists)

語法:
SETNX key value

功能:
將 key 的值設爲 value ,當且僅當 key 不存在;若給定的 key 已經存在,則 SETNX 不作任何動做。

時間複雜度:
O(1)
返回值:
設置成功,返回 1 。
設置失敗,返回 0 。

模式:將 SETNX 用於加鎖(locking)

SETNX 能夠用做加鎖原語(locking primitive)。好比說,要對關鍵字(key) foo 加鎖,客戶端能夠嘗試如下方式:

SETNX lock.foo <current Unix time + lock timeout + 1>

若是 SETNX 返回 1 ,說明客戶端已經得到了鎖, key 設置的unix時間則指定了鎖失效的時間。以後客戶端能夠經過 DEL lock.foo 來釋放鎖。

若是 SETNX 返回 0 ,說明 key 已經被其餘客戶端上鎖了。若是鎖是非阻塞(non blocking lock)的,咱們能夠選擇返回調用,或者進入一個重試循環,直到成功得到鎖或重試超時(timeout)。

可是已經證明僅僅使用SETNX加鎖帶有競爭條件,在特定的狀況下會形成錯誤。

處理死鎖(deadlock)

上面的鎖算法有一個問題:若是由於客戶端失敗、崩潰或其餘緣由致使沒有辦法釋放鎖的話,怎麼辦?

這種情況能夠經過檢測發現——由於上鎖的 key 保存的是 unix 時間戳,假如 key 值的時間戳小於當前的時間戳,表示鎖已經再也不有效。

可是,當有多個客戶端同時檢測一個鎖是否過時並嘗試釋放它的時候,咱們不能簡單粗暴地刪除死鎖的 key ,再用 SETNX 上鎖,由於這時競爭條件(race condition)已經造成了:

C1 和 C2 讀取 lock.foo 並檢查時間戳, SETNX 都返回 0 ,由於它已經被 C3 鎖上了,但 C3 在上鎖以後就崩潰(crashed)了。
C1 向 lock.foo 發送 DEL 命令。
C1 向 lock.foo 發送 SETNX 併成功。
C2 向 lock.foo 發送 DEL 命令。
C2 向 lock.foo 發送 SETNX 併成功。
出錯:由於競爭條件的關係,C1 和 C2 兩個都得到了鎖。



幸虧,如下算法能夠避免以上問題。來看看咱們聰明的 C4 客戶端怎麼辦:

C4 向 lock.foo 發送 SETNX 命令。
由於崩潰掉的 C3 還鎖着 lock.foo ,因此 Redis 向 C4 返回 0 。
C4 向 lock.foo 發送 GET 命令,查看 lock.foo 的鎖是否過時。若是不,則休眠(sleep)一段時間,並在以後重試。
另外一方面,若是 lock.foo 內的 unix 時間戳比當前時間戳老,C4 執行如下命令:
GETSET lock.foo <current Unix timestamp + lock timeout + 1>


由於 GETSET 的做用,C4 能夠檢查看 GETSET 的返回值,肯定 lock.foo 以前儲存的舊值還是那個過時時間戳,若是是的話,那麼 C4 得到鎖。
若是其餘客戶端,好比 C5,比 C4 更快地執行了 GETSET 操做並得到鎖,那麼 C4 的 GETSET 操做返回的就是一個未過時的時間戳(C5 設置的時間戳)。C4 只好從第一步開始重試。
注意,即使 C4 的 GETSET 操做對 key 進行了修改,這對將來也沒什麼影響。

這裏假設鎖key對應的value沒有實際業務意義,不然會有問題,並且其實其value也確實不該該用在業務中。

爲了讓這個加鎖算法更健壯,得到鎖的客戶端應該經常檢查過時時間以避免鎖因諸如 DEL 等命令的執行而被意外解開,由於客戶端失敗的狀況很是複雜,不只僅是崩潰這麼簡單,還多是客戶端由於某些操做被阻塞了至關長時間,緊接着 DEL 命令被嘗試執行(但這時鎖卻在另外的客戶端手上)。


GETSET命令

語法:
GETSET key value

功能:
將給定 key 的值設爲 value ,並返回 key 的舊值(old value)。當 key 存在但不是字符串類型時,返回一個錯誤。

時間複雜度:
O(1)

返回值:
返回給定 key 的舊值;當 key 沒有舊值時,也便是, key 不存在時,返回 nil 。

ref by
http://blog.csdn.net/hpb21/article/details/7893013
http://redis.readthedocs.org/en/latest/string/setnx.html
相關文章
相關標籤/搜索