在這個技術不斷更新迭代的情況下,分散式這個概念,在企業中的權重越來越高!談到分散式時,不可避免一定會提到分散式鎖定,現階段分散式鎖定的實現方式主流的有三種實現方式, Zookeeper
、DB
、 Redis
,我們這篇文章以Redis為例!
從我們的角度來看,這三個屬性是有效使用分散式鎖定所需的最低保證。
安全特性:互斥。在任何給定時刻,只有一個客戶端可以持有鎖。
活力屬性:無死鎖。最終,即使鎖定資源的客戶端崩潰或分區,也始終可以獲得鎖。
活動性:容錯能力。只要大多數Redis節點都處於運作狀態,客戶端就可以取得和釋放鎖定。
我們使用Redis鎖定資源最簡單的方法是:
在實例中建立鎖定。
鎖定通常使用Redis過期功能在有限時間存在,因此最終將被釋放,最終超過給定期限會被刪除。
當客戶端需要釋放資源時,它將刪除鎖定。
乍一看,似乎沒有什麼問題。但不妨我們深究一下,這種實作方案在redis單機環境下似乎並沒有什麼問題!但是如果節點宕了呢?好吧,那麼讓我們加入一個slave
節點!如果主伺服器宕機了,就使用這個節點吧!但是我們不妨來看看她真的能保證可用嗎?
在談論這個的致命缺陷時,我們需要了解一個知識點,Redis複製是異步的。
客戶端A取得主伺服器中的鎖定。
在將鎖定複製傳輸到從機之前,主機就會崩潰。
slave
晉升為
master
。
客戶端B取得鎖,因為從機並沒有該鎖的對象,取得成功!
顯然,這樣是不對的,主節點因為沒來得及同步資料就宕機了,所以從節點沒有該數據,從而造成分散式鎖的失效,那麼作者antirez
的觀點是如何解決這個呢?
作者認為,我們應該使用多個Redis
,這些節點是完全獨立的,不需要使用複製或任何協調資料的系統,多個redis系統取得鎖定的過程變成瞭如下步驟:
以毫秒為單位取得目前的伺服器時間
嘗試使用相同的key和隨機值來取得鎖,對每一個機器取得鎖時都應該有一個超時時間,例如鎖的過期時間為10s那麼取得單一節點鎖的超時時間就應該為5到50毫秒左右,他這樣做的目的是為了確保客戶端與故障的機器連接,耗費多餘的時間!超時間時間內未獲取資料就放棄該節點,從而去下一個節點獲取,直到將所有節點全部獲取一遍!
取得完成後,取得目前時間減去步驟一取得的時間,當且僅當客戶端半數以上取得成功且取得鎖定的時間小於鎖定超時時間,則證明該鎖生效!
取得鎖定之後,鎖定的逾時時間等於
設定的有效時間-取得鎖定花費的時間
如果取得鎖定的機器不滿足半數以上,或是鎖的逾時時間計算完畢後為負數等異常操作,則系統會嘗試解鎖所有實例,即使有些實例沒有取得鎖成功,依舊會被嘗試解鎖!
釋放鎖,只需在所有實例中釋放鎖,無論客戶端是否認為它能夠成功鎖定給定的實例。
Martin Kleppmann發表文章任務,Redlock並不能保證該鎖的安全性!
他認為鎖的用途無非兩種
提升效率,用鎖來保證一個任務沒有必要被執行兩次。例如(很昂貴的計算)
為確保準確性,使用鎖定機制以確保任務按照正常流程順序進行,以避免兩個節點同時對同一份資料進行操作,導致文件衝突和資料遺失。
對於第一種原因,我們對鎖是有一定寬容度的,就算發生了兩個節點同時工作,對系統的影響也僅僅是多付出了一些計算的成本,沒什麼額外的影響。這時候 使用單點的 Redis 就能很好的解決問題,沒有必要使用RedLock,維護那麼多的Redis實例,提升系統的維護成本。
但是對於第二種場景來說,就比較慎重了,因為很可能涉及到一些金錢交易,如果鎖定失敗,並且兩個節點同時處理同一數據,則結果將導致文件損壞,數據丟失,永久性不一致,或金錢方面的損失!
我們假設一種場景,我們有兩個客戶端,每個客戶端必須拿到鎖之後才能去保存資料到資料庫,我們使用RedLock演算法實作會出現什麼問題呢?在 RedLock中,為了防止死鎖,鎖是具有過期時間的,但是Martin
認為這是不安全的!該流程圖類似這樣!
客戶端1取得到鎖定成功後,開始執行,執行到一半系統發生Full GC ,系統服務被掛起,過段時間鎖逾時了。
客戶端2等待客戶端1的鎖定逾時後,成功的獲取到鎖,開始執行入庫操作,完成後,客戶端1完成了Full GC,又做了一次入庫操作!這是不安全的!如何解決呢?
Martin
提出來一種類似樂觀鎖定的實作機制,範例圖如下:
客戶端1長時間被掛起後,客戶端2取得到鎖,開始寫庫操作,同時攜帶令牌34
,寫庫完成後,客戶端1甦醒,開始進行入庫操作,但是因為攜帶的令牌為33 小於最新令牌,因此該次提交就被拒絕!
即使系統出現問題導致掛起,該想法似乎完備,可確保資料仍能正確處理。但仔細想一下:
如果只有當您的令牌大於所有過去的令牌時,資料儲存區才能始終接受寫入,則它是可線性化的儲存區,相當與使用資料庫來實現一個分散式鎖定係統,那麼RedLock的作用就變的微乎其微!甚至不在需要使用redis保證分散式鎖!
回想一下Redlock演算法
取得鎖定的幾個步驟,你會發現鎖定的有效性是與目前的系統時鐘強烈依賴,我們假設:
我們有,A B C D E 五個redis節點:
客戶端1取得節點A,B,C的鎖定。由於網路問題,無法存取D和E。
節點C上的時鐘向前跳,導致鎖定過期。
客戶端2取得節點C,D,E的鎖定。由於網路問題,無法存取A和B。
現在,客戶1和2都認為他們持有該鎖定。
如果C在將鎖定持久保存到磁碟之前崩潰並立即重新啟動,則可能會發生類似的問題。
Martin認為系統時間的階梯主要來自兩個面向(以及作者給出的解決方案):
人為修改。
#對於人為修改,能說啥呢?人要搞破壞沒辦法避免。
從NTP服務收到了一個跳躍時時鐘更新。
需要運維人員處理NTP接受階躍時脈更新的問題。需要將階躍的時間更新到伺服器的時候,應採取小步快跑的方式。多次修改,每次更新時間盡量小。
我們回顧1 觀點,深究抽像出現這個缺陷的根本原因,就是為了解決由於系統宕機帶來的鎖失效而給鎖強加了一個失效時間,異常情況下,程序(業務)執行的時間大於鎖失效時間從而造成的一系列的問題,我們能否從這方面去考慮,從而用程式來解決這個樣一個死局呢?
我們可以保證業務程式執行時間絕對小於鎖定逾時時間,這樣就可以避免鎖定失效時間小於業務時間的問題
java語言中redisson
實現了一種保證鎖失效時間絕對大於業務程序執行時間的機制。官方叫做看門狗機制(Watchdog),他的主要原理是,在程式成功取得鎖之後,會fork一條子線程去不斷的給該鎖續期,直至該鎖釋放為止!他的原理圖大概如下圖:
redisson使用守護線程來進行鎖的續期,(守護線程的作用:當主執行緒銷毀,會和主執行緒一起銷毀。)防止程式宕機後,線程依舊不斷續命,造成死鎖!
另外,Redisson也實作並且優化了 RedLock演算法、公平鎖、可重入鎖、連鎖等操作,讓Redis分散式鎖的實作方式更加簡單有效率!
#以上是怎麼使用Redis鎖定資源的詳細內容。更多資訊請關注PHP中文網其他相關文章!