分佈式鎖,是控制分佈式系統之間同步訪問共享資源的一種方式。在分佈式系統中,經常須要協調他們的動做。若是不一樣的系統或是同一個系統的不一樣主機之間共享了一個或一組資源,那麼訪問這些資源的時候,每每須要互斥來防止彼此干擾來保證一致性,在這種狀況下,便須要使用到分佈式鎖。redis
使用setnx、getset、expire、del這4個redis命令實現
1.setnx 是『SET if Not eXists』(若是不存在,則 SET)的簡寫。 命令格式:SETNX key value;使用:只在鍵 key 不存在的狀況下,將鍵 key 的值設置爲 value 。若鍵 key 已經存在, 則 SETNX 命令不作任何動做。返回值:命令在設置成功時返回 1 ,設置失敗時返回 0 服務器
2.getset 命令格式:GETSET key value,將鍵 key 的值設爲 value ,並返回鍵 key 在被設置以前的舊的value。返回值:若是鍵 key 沒有舊值, 也便是說, 鍵 key 在被設置以前並不存在, 那麼命令返回 nil 。當鍵 key 存在但不是字符串類型時,命令返回一個錯誤。分佈式
3.expire 命令格式:EXPIRE key seconds,使用:爲給定 key 設置生存時間,當 key 過時時(生存時間爲 0 ),它會被自動刪除。返回值:設置成功返回 1 。 當 key 不存在或者不能爲 key 設置生存時間時(好比在低於 2.1.3 版本的 Redis 中你嘗試更新 key 的生存時間),返回 0 。優化
4.del 命令格式:DEL key [key …],使用:刪除給定的一個或多個 key ,不存在的 key 會被忽略。返回值:被刪除 key 的數量。
blog
過程分析:
1.A嘗試去獲取鎖lockkey,經過setnx(lockkey,currenttime+timeout)命令,對lockkey進行setnx,將value值設置爲當前時間+鎖超時時間;
2.若是返回值爲1,說明redis服務器中尚未lockkey,也就是沒有其餘用戶擁有這個鎖,A就能獲取鎖成功;
3.在進行相關業務執行以前,先執行expire(lockkey),對lockkey設置有效期,防止死鎖。由於若是不設置有效期的話,lockkey將一直存在於redis中,其餘用戶嘗試獲取鎖時,執行到setnx(lockkey,currenttime+timeout)時,將不能成功獲取到該鎖;
4.執行相關業務;
5.釋放鎖,A完成相關業務以後,要釋放擁有的鎖,也就是刪除redis中該鎖的內容,del(lockkey),接下來的用戶才能進行從新設置鎖新值。
缺陷:
若是A在setnx成功後,A成功獲取鎖了,也就是鎖已經存到Redis裏面了,此時服務器異常關閉或是重啓,將不會執行closeOrder,也就不會設置鎖的有效期,這樣的話鎖就不會釋放了,就會產生死鎖。進程
爲了解決原理1中會出現的死鎖問題,提出原理2雙重防死鎖,能夠更好解決死鎖問題。資源
過程分析:
1.當A經過setnx(lockkey,currenttime+timeout)命令能成功設置lockkey時,即返回值爲1,過程與原理1一致;
2.當A經過setnx(lockkey,currenttime+timeout)命令不能成功設置lockkey時,這是不能直接判定獲取鎖失敗;由於咱們在設置鎖時,設置了鎖的超時時間timeout,噹噹前時間大於redis中存儲鍵值爲lockkey的value值時,能夠認爲上一任的擁有者對鎖的使用權已經失效了,A就能夠強行擁有該鎖;具體斷定過程以下;
3.A經過get(lockkey),獲取redis中的存儲鍵值爲lockkey的value值,即獲取鎖的相對時間lockvalueA
4.lockvalueA!=null && currenttime>lockvalue,A經過當前的時間與鎖設置的時間作比較,若是當前時間已經大於鎖設置的時間臨界,便可以進一步判斷是否能夠獲取鎖,不然說明該鎖還在被佔用,A就還不能獲取該鎖,結束,獲取鎖失敗;
5.步驟4返回結果爲true後,經過getSet設置新的超時時間,並返回舊值lockvalueB,以做判斷,由於在分佈式環境,在進入這裏時可能另外的進程獲取到鎖並對值進行了修改,只有舊值與返回的值一致才能說明中間未被其餘進程獲取到這個鎖
6.lockvalueB == null || lockvalueA==lockvalueB,判斷:若果lockvalueB爲null,說明該鎖已經被釋放了,此時該進程能夠獲取鎖;舊值與返回的lockvalueB一致說明中間未被其餘進程獲取該鎖,能夠獲取鎖;不然不能獲取鎖,結束,獲取鎖失敗。字符串