前面我們有聊過樂觀鎖和悲觀鎖的實現(xiàn),均是對于單體架構(gòu)的場景下的實現(xiàn)。那么現(xiàn)在我們來總結(jié)看下分布式情況下如何實現(xiàn)鎖機制。
常見場景
我們來看下一個場景,假設我現(xiàn)在在分布式系統(tǒng)下要做一個業(yè)務邏輯的消費動作,我如何保證我的消費動作只被消費一次不重復消費?有的同學第一時間就想到了MQ,諸如Zookeeper。我們今天暫不談MQ,那其實核心還是代碼執(zhí)行的鎖機制問題。
我們再來看一個場景,我們有個接口需要經(jīng)常查數(shù)據(jù)庫DB數(shù)據(jù),如果場景允許我們經(jīng)常會對其加一層緩存,并設定過期時間。假設在某一瞬間,緩存過期,但此時并發(fā)量又很大,會有大量的請求穿透去數(shù)據(jù)庫請求數(shù)據(jù),造成緩存雪崩效應。于是,我們就可以考慮加鎖機制,只讓一個請求去執(zhí)行查詢DB更新緩存的操作。
基本原理
回顧下我們之前聊到鎖的原理,分布式鎖也是一樣的,要實現(xiàn)它必須滿足:
互斥:任何時刻只能有一個客戶端對其加鎖;
避免死鎖:要充分考慮某客戶端在持有鎖的期間崩潰,也不能導致后續(xù)其他客戶端不能加鎖;
誰加鎖誰解鎖:加鎖和解鎖必須是同一個客戶端,否則容易出現(xiàn)A客戶端把B客戶端的鎖給解了,導致鎖機制失效。
示例實踐
我們僅以Redis實現(xiàn)分布式鎖為例來說明分布式鎖的實現(xiàn)。以單機單機部署Redis的情況為例,如果有分布式Redis集群部署的情況,可以參考Redlock算法的實現(xiàn)。下面我們進入Redis+Lua實現(xiàn)分布式鎖的實踐。
我們來看示例代碼。
加鎖
注意到代碼的每個細節(jié)了么?都是至關重要的。上面的set是封裝過的,那我們來簡單說明一下這個方法吧,該方法分別對應了上面的鎖需要滿足的條件。比如,NX操作保證了鎖的互斥,設置過期時間避免了死鎖,唯一請求ID用來標注客戶端,在解鎖的時候可以用來校驗是不是同一個客戶端自己的鎖。
解鎖
解鎖這個動作就有趣了,看似簡單卻暗藏玄機,也是很重要的環(huán)節(jié)。因為解鎖存在一個判斷是都本客戶端的鎖的操作,之后才執(zhí)行解鎖。而這個if判斷在高并發(fā)的情況下我們不得不考慮操作的原子性,這其實和PHP等其他語言代碼考慮高并發(fā)的原理是大相徑庭(有興趣的看官也可以思考下,為什么有判斷就要保證原子性呢,有哪些可能出現(xiàn)問題的場景)。那我們?nèi)绻WC操作的原子性呢?第一反應是想到事務?我們這里借助Lua腳本來保證原子性,Redis的eval命令執(zhí)行Lua腳本保證原子性。
我們來看下示例代碼
我們同樣來說明下面的解鎖代碼。其實很簡單,就是執(zhí)行了一個Lua腳本,這個腳本實現(xiàn)了或者當前鎖的值,即唯一請求ID值,判斷是否同一個客戶端的請求ID,如果是,則執(zhí)行Redis的del操作。
好了,關于Redis實現(xiàn)分布式的鎖例子就到這里了,這里只是簡單的示例便于理解,實際生產(chǎn)將需要考慮更多的場景和因素,比如集群,Zookeeper方式實現(xiàn),時間和能力有限,這里就不展開贅述。
-
死鎖
+關注
關注
0文章
25瀏覽量
8075 -
分布式
+關注
關注
1文章
895瀏覽量
74498 -
程序互斥
+關注
關注
0文章
3瀏覽量
6151
發(fā)布評論請先 登錄
相關推薦
評論