電商課題V 分布式鎖

2021-08-27 13:01:10 字數 3678 閱讀 8028

@鄭昀彙總

併發控制

防止併發

distributed lock

distributed lock manager

電商目的:

保證整個(分布式)系統內對乙個重要事物(訂單,賬戶等)的有效操作執行緒 ,同一時間內有且只有乙個。比如交易中心有n臺伺服器,訂單中心有m臺伺服器,如何保證乙個訂單的同一筆支付處理,乙個賬戶的同一筆充值操作是原子性的。

基於哪些服務實現分布式鎖?

基於memcache的分布式鎖

memcache的所有命令都是原子性的(

internally atomic),所以利用它的add命令即可。

鄭昀列出一段簡單但埋下了問題的偽碼:

if (cache.add("lock:", currenttimestamp, expiredtime)) catch

cache.delete("lock.")

} else

上面**所暴露的常見性問題

1)如持有鎖的執行緒異常退出或宕機,鎖並沒有釋放

2)設定了key的expire,那麼如果有新執行緒在key過期後拿到了新的鎖,原來超時的執行緒回來時,如果不經判斷會誤認為那是它持有的鎖,會誤刪鎖

解決思路

1)強制釋放

在鍵值上做文章,存入的是current unix time+lock timeout+1

2)釋放自己持有的鎖時,先檢查是否已超時

持有鎖的執行緒在解鎖之前應該再檢查一次自己的鎖是否已經超時,再去做delete操作,因為可能客戶端因為某個耗時的操作而掛起,操作完的時候鎖因為超時已經被別人獲得,這時就不必解鎖了。

繼續解決

上面的辦法會引入新問題:

如果多個執行緒檢測到鎖超時,都嘗試去釋放鎖,那麼就會出現

競態條件(

race condition)。

場景是,

c0操作超時了,但它還持有著鎖,c1和c2讀取lock.檢查時間戳,先後發現超時了。

c1 傳送delete lock.,

c1 傳送set lock. 且成功。

c2 傳送delete lock.,

c2 傳送set lock. 且成功。

這樣,c1和c2都認為自己拿到了鎖。

如果比較在意這種競態條件,那麼推薦使用基於zookeeper或redis的解決方案。

基於zookeeper的分布式鎖

這主要得益於zookeeper為我們保證了資料的強一致性,即使用者只要完全相信每時每刻,zk集群中任意節點(乙個zk server)上的相同znode的資料一定是相同的。鎖服務可以分為兩類,乙個是保持獨佔,另乙個是控制時序。所謂保持獨佔,就是所有試圖來獲取這個鎖的客戶端,最終只有乙個可以成功獲得這把鎖。通常的做法是把zk上的乙個znode看作是一把鎖,通過 create znode的方式來實現。所有客戶端都去建立 /distributed_lock 節點,最終成功建立的那個客戶端也就擁有了這把鎖。

控制時序,就是所有試圖獲取這個鎖的客戶端,最終都是會被安排執行,只是有個全域性時序。做法和上面基本類似,只是這裡 /distributed_lock 已經預先存在,客戶端在它下面建立臨時有序節點(這個可以通過節點的屬性控制:createmode.ephemeral_sequential來指 定)。zk的父節點(/distributed_lock)維持乙份sequence,保證子節點建立的時序性,從而形成了每個客戶端的全域性時序。

zookeeper 裡實現分布式鎖的基本邏輯:

客戶端呼叫create()方法建立名為「_locknode_/guid-lock-」的節點,需要注意的是,這裡節點的建立型別需要設定為ephemeral_sequential。

客戶端呼叫getchildren(「_locknode_」)方法來獲取所有已經建立的子節點,同時在這個節點上註冊上子節點變更通知的watcher。

客戶端獲取到所有子節點path之後,如果發現自己在步驟1中建立的節點是所有節點中序號最小的,那麼就認為這個客戶端獲得了鎖。

如果在步驟3中發現自己並非是所有子節點中最小的,說明自己還沒有獲取到鎖,就開始等待,直到下次子節點變更通知的時候,再進行子節點的獲取,判斷是否獲取鎖。

釋放鎖的過程相對比較簡單,就是刪除自己建立的那個子節點即可。

基於redis的分布式鎖

接著前面的競態條件說,同樣的場景下,使用redis的setnx(即set if not exists,類似於memcache的add)和

getset(先寫新值,返回舊值,原子性操作,可以用於分辨是不是首次操作)命令便可迎刃而解:

c3傳送setnx lock. 想要獲得鎖,由於c0還持有鎖,所以redis返回給c3乙個0,

c3傳送get lock. 以檢查鎖是否超時了,如果沒超時,則等待或重試。

反之,如果已超時,c3通過下面的操作來嘗試獲得鎖:

getset lock.通過getset,c3拿到的時間戳如果仍然是超時的,那就說明,c3如願以償拿到鎖了。

如果在c3之前,有個叫c4的客戶端比c3快一步執行了上面的操作,那麼c3拿到的時間戳是個未超時的值,這時,c3沒有如期獲得鎖,需要再次等待或重試。留意一下,儘管c3沒拿到鎖,但它改寫了c4設定的鎖的超時值,不過這一點非常微小的誤差帶來的影響可以忽略不計。

jeffkit的偽碼參考:

# get lock

lock =0

while!=1

:

timestamp = current unix

time1

lock = 

setnx

orderid

if1or(now

()>

(get

orderid

)and

now(

)>

(getset

orderid

)):

break

else

:

sleep

(10ms

)do_your_job

() # release lock if

()<

orderid:

del orderid

1,jeffkit,

用redis實現分布式鎖,基於redis;

2,rdc.taobao,

zookeeper典型使用場景一覽;

3,ilya sterin,

distributed locking made easy,基於zookeeper;

4,遲炯,

解讀google分布式鎖服務;

5,**rdc,2012,

zookeeper分布式鎖避免羊群效應(herd effect);

分布式 分布式鎖

本質是利用redis的setnx 方法的特性來加鎖,setnx 即key不存在則設定key,否則直接返回false,要求在分布式系統中使用同乙個redis服務,以下提供兩種解決方案 1 直接使用redistemplate 這其實並不能完全保證高併發下的安全問題,因為可能在鎖過期之後該執行緒尚未執行完...

分布式專題 分布式鎖

在傳統的單體應用架構中,遇到併發安全性問題時我們可以通過同步鎖synchronized,同步 塊,reentrantlock等方式都可以解決,但隨著業務的發展,單體應用架構不能滿足龐大的使用者請求量,於是分布式系統應用而生,在分布式系統中,由於每個系統都執行在不同的伺服器上,有著不同的jvm,所以j...

分布式電商專案一 安裝軟體

使用配置好的虛擬機器進行docker安裝,並使用docker安裝mysql和redis。可以參考docker的官方文件docker官網.刪除系統已經有的docker yum remove docker docker client docker client latest docker common ...