概述
分布式系統有一個著名的理論CAP,指在一個分布式系統中,最多只能同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)這三項中的兩項。所以在設計系統時,往往需要權衡,在CAP中作選擇。當然,這個理論也並不一定完美,不同系統對CAP的要求級別不一樣,選擇需要考慮方方面面。
在微服務系統中,一個請求存在多級跨服務調用,往往需要犧牲強一致性老保證系統高可用,比如通過分布式事務,異步消息等手段完成。但還是有的場景,需要阻塞所有節點的所有線程,對共享資源的訪問。比如並發時“超賣”和“余額減為負數”等情況。
本地鎖可以通過語言本身支持,要實現分布式鎖,就必須依賴中間件,數據庫、redis、zookeeper等。
分布式鎖特性
不管使用什么中間件,有幾點是實現分布式鎖必須要考慮到的。
-
互斥:互斥好像是必須的,否則怎么叫鎖。
-
死鎖: 如果一個線程獲得鎖,然后掛了,並沒有釋放鎖,致使其他節點(線程)永遠無法獲取鎖,這就是死鎖。分布式鎖必須做到避免死鎖。
-
性能: 高並發分布式系統中,線程互斥等待會成為性能瓶頸,需要好的中間件和實現來保證性能。
-
鎖特性:考慮到復雜的場景,分布式鎖不能只是加鎖,然后一直等待。最好實現如Java Lock的一些功能如:鎖判斷,超時設置,可重入性等。
Redis實現之Redisson原理
redission實現了JDK中的Lock接口,所以使用方式一樣,只是Redssion的鎖是分布式的。如下:
好,Lock主要實現是RedissionLock。
先來看常用的Lock方法實現。
再看lockInterruptibly
方法:
總結lockInterruptibly
:獲取鎖,不成功則訂閱釋放鎖的消息,獲得消息前阻塞。得到釋放通知后再去循環獲取鎖。
下面重點看看如何獲取鎖:Long ttl = tryAcquire(leaseTime, unit, threadId)
已經在注釋中解釋了,需要注意的是,此處用到了Netty的Future-listen模型,可以看看我的另一篇對Future的簡單講解:給Future一個Promise。
下面就是最重要的redis獲取鎖的方法tryLockInnerAsync
:
這個方法主要就是調用redis執行eval lua,為什么使用eval,因為redis對lua腳本執行具有原子性。把這個方法翻譯一下:
這就是核心獲取鎖的方式,下面直接釋放鎖方法unlockInnerAsync
:
從釋放鎖代碼中看到,刪除key后會發送消息,所以上文提到獲取鎖失敗后,阻塞訂閱此消息。
另外,上文提到刷新過期時間方法scheduleExpirationRenewal
,指線程獲取鎖后需要不斷刷新失效時間,避免未執行完鎖就失效。這個方法的實現原理也類似,只是使用了Netty的TimerTask,每到過期時間1/3就去重新刷一次,如果key不存在則停止刷新。Timer實現大概如下:
參考列表:
-
一分鍾實現分布式鎖
我這里准備了一些
【Java核心技術資料】
【JAVA核心總結】
【524頁中高級XXXX】
【《JavaGuide面試突擊》v3.0肝出來了!】
【Java高級筆試寶典覆蓋近3年Java筆試中98%高頻知識點吊打100家大廠面試官】
【Java大廠面試題】
【阿里架構師花近十年時間整理出來的Java核心知識pdf(Java崗)】
【524頁《Java中高級程序員必備核心知識》總結,令人猶如醍醐灌頂】
等一系列的Java架構資料 需要的話掃一掃免費獲取 無套路