@鄭昀彙總
併發控制
防止併發
distributed lock
distributed lock manager
電商目的:
保證整個(分布式)系統內對乙個重要事物(訂單,賬戶等)的有效操作執行緒 ,同一時間內有且只有乙個。比如交易中心有n臺伺服器,訂單中心有m臺伺服器,如何保證乙個訂單的同一筆支付處理,乙個賬戶的同一筆充值操作是原子性的。
基於哪些服務實現分布式鎖?
基於memcache的分布式鎖
memcache的所有命令都是原子性的(
internally atomic),所以利用它的add命令即可。
鄭昀列出一段簡單但埋下了問題的偽碼:
if (cache.add("lock:", currenttimestamp, expiredtime)) catchcache.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 ...