前言:
前面我們了解了什麼是分布式鎖,以及分布式鎖使用的場景和分布式實現的原理,那麼接下來我們就來看一下在實際開發中我們要向實現乙個分布式鎖,應該如何實現,常見的分布式所得實現方法有:基於資料庫實現、基於快取實現、基於zookeeper等,在這裡我們來看一下基於快取的分布式鎖的實現。
一、版本
一、基本實現:
前面我們分析了我們要向實現乙個分布式鎖,要不許滿足兩個條件:
多程序可見;
互斥、鎖的釋放;
1:多程序可見:
redis本身是基於jvm之外的第三方軟體,而我們所有的伺服器都會用到快取,所以它天然的滿足多程序可見這一條件;
2:互斥:
互斥就是說只能有乙個程序獲取到鎖的標記,而這個我們可以基於redis的setnx指令來實現,setnx指令的意思是,當多次執行該指令時,只有第一次執行的才會返回成功1,其他情況都會返回0,多個程序堆同乙個key執行setnx指令,肯定只有乙個能夠執行成功,其他一定會失敗,滿足互斥的需求。
3:釋放鎖:
前面我們利用redis的儲存特性可以用來獲取鎖,那麼我們獲取到所執行完**後就需要釋放鎖,釋放鎖我們則用到del命令,直接刪除;但是這裡存在另乙個問題如果多個伺服器其中乙個伺服器獲取到了值,但是卻宕機了,那麼鎖不久永遠無法刪除了嗎?
為了避免伺服器宕機引起鎖無法刪除的問題,我們可以再獲取鎖的時候,給鎖加乙個有效期,當時間超出,就會自動釋放,這樣就付匯造成死鎖了,但是setnx指令中並沒有設定時間的功能,因此我們要借助set指令的nx和px引數來實現。
這其中指定下面幾個引數:
ex:過期時間,單位是秒;
px:過期時長,單位是毫秒;
nx:等同於setnx;
因此獲取鎖和釋放鎖的基本步驟如下:
(1):通過set命令設定鎖;
(2):判斷返回值是否ok;
1):nil,獲取鎖失敗結束或者重試;
2):ok,獲取鎖成功,執行業務,釋放鎖;
(3):異常情況,伺服器宕機,超時後會自動釋放鎖;
二、版本二,互斥鎖:
上面我們分析並且實現了分布式鎖,但是在上面的最初版本中卻存在乙個問題,我們思考一下,釋放鎖用del語句吧所刪除,有沒有這樣一種可能;
1:如果有三個程序a、b、c,如果此時a獲取到了鎖且設定超時時間是十秒;
2:a開始執行任務由於某種原因業務阻塞了超過了十秒,此時把鎖釋放了;
3:b恰好這個時候獲取鎖,由於所已經被a釋放所以獲取到了鎖;
4:a此時執行完了任務開始呼叫del刪除所鎖,於是把b的鎖給釋放了,而b此時還在執行任務;
5:此時c嘗試獲取鎖,也成功了,因為a把b的鎖給刪除了;
問題出現了,b和c同時獲取到了鎖,違反了互斥的條件,那麼如何解決這個問題呢,我們應該在刪除鎖之前,判斷這個鎖是不是屬於自己的,如果不是就不要刪除鎖了,那麼問題來了如何的值當前所是不是屬於自己的?
要想知道當前所是不是自己的,我們可以在set鎖的時候,存入當前執行緒的唯一標識,刪除鎖前判斷一下值是不是與自己的唯一標識一致,如果不一致就說明不是自己的鎖,那就不要刪除了。
流程如圖:
三、版本三,重入鎖:
接下來我們來看一下分布式鎖的第三個特性,如果我們在獲取鎖後,執行**過程中再次嘗試獲取鎖,執行setnx坑定會失敗,這樣就可能會導致死鎖,這樣的鎖是重入的,那如何解決這一問題呢。
我們都知道synchronized是乙個重入鎖,他的實現原理就是當現成執行synchronized**時,會有乙個引數記錄當前執行的執行緒名稱,當有重入鎖出現時,會判斷第二次拿鎖的執行緒是否和第一次相同,如果相同則就放行。
知道了synchronized重入鎖的實現原理,那麼在這裡我們也可以借用synchronized實現重入鎖的方式來實現分布式鎖的重入鎖;
那麼來看一下如何實現:
1、獲取鎖:首次嘗試獲取鎖,如果失敗判斷這個鎖是否是自己的,如果是則允許再次獲取鎖,而且必須記錄重複獲取鎖此時加1
2、釋放鎖:在這裡釋放鎖不能直接刪除,因為鎖是可重入的,如果鎖進了多次在最內層刪除了鎖,導致外部的業務在沒有鎖的情況下執行,會有安全問題,因此必須獲取鎖重入的次數,釋放鎖時減去重入的次數,如果減到0,則可以刪除。
因此在儲存鎖的結構裡必須包含的資訊有,key、執行緒表示、鎖的重如次數,不能在使用簡單的key-value方式,在這裡我們要使用hash介面的儲存方式,key:
分析了重入鎖的實現來看一下實現重入鎖的步驟,這其中需要用的一些redis的命令,如下:
1:exists key:判斷乙個key是否存在;
2:hexists key field:判斷乙個hash的field是否存在;
3:hset key field value:給乙個hash的field設定乙個值;
4:hincrby key field increment:給乙個hash的field值增加指定的數值;
5:expire key seconds:給乙個key設定過期時間;
6:del key:刪除指定的key;
獲取鎖步驟:
1:判斷lock是否存在,exists lock;
(1)、存在,說明有人獲取到了鎖,下面判斷是不是自己;
判斷當前執行緒id作為hashkey是否存在,hexists lock threadid;
不存在,說明所已經有了,且不是自己獲取的,獲取鎖失敗,end;
存在,說明是自己獲取到的鎖,重如次數+1,hincrby lock threadid 1;
(2)、不存在,說明可以獲取鎖,hset key threadid 1;
(3)、設定鎖自動釋放時間,expire lock 20;
釋放鎖步驟:
1、判斷當前執行緒id作為hashkey是否存在,hexists lock threadid;
不存在,說明鎖已經失效,不用管;
存在,說明鎖還在,重如次數-1,hincrby lock threadid -1;
2、判斷重入次數是否為0:
為0,則說明重入此時全部釋放到了最外層,刪除key,del lock;
不為0,說明鎖還在使用,重置有效時間,expire lock 20;
分布式鎖 使用Redis實現分布式鎖
關於分布式鎖的實現,我的前一篇文章講解了如何使用zookeeper實現分布式鎖。關於分布式鎖的背景此處不再做贅述,我們直接討論下如何使用redis實現分布式鎖。關於redis,筆主不打算做長篇大論的介紹,只介紹下redis優秀的特性。支援豐富的資料型別,如string list map set zs...
Redis 分布式鎖(二)
在不同程序需要互斥地訪問共享資源時,分布式鎖是一種非常有用的技術手段。實現高效的分布式鎖有三個屬性需要考慮 安全屬性 互斥,不管什麼時候,只有乙個客戶端持有鎖 效率屬性a 不會死鎖 效率屬性b 容錯,只要大多數redis節點能夠正常工作,客戶端端都能獲取和釋放鎖。解鈴還須繫鈴人。加鎖和解鎖必須是同乙...
redis實現分布式鎖
隨便 系統越來越大,各功能模組除了垂直切割以外,同時也得做集群處理,那麼問題來了,在多執行緒情況下對於資源的競爭就需要乙個統一的訪問限制。以選課系統為例子,集群中各節點對課程可選數量同時操作,這裡就需要同步了,否則會導致最後選到的數量比可選的數量大,這裡我們的分布式鎖就派上用場了。利用redis來實...