「如何設計」:redis
分佈式環境下,鎖定全局惟一公共資源 表現爲:bash
第一步是上鎖的資源目標,是鎖定全局惟一公共資源,只有是全局惟一的資源才存在多個線程或服務競爭的狀況,互斥性表現爲一個資源的隔離級別串行化,若是對照單機事務ACID的隔離型來講,互斥性的事務隔離級別是SERLALIZABLE,屬於最高的隔離級別。restful
(事務隔離級別:DEFAULT,READ_UNCOMMITTED,READ_COMMITED,REPEATABLE_READ,SERLALIZABLE)網絡
例子:分佈式
1. 防止用戶重複下單 共享資源進行上鎖的對象 : 【用戶id】
2. 訂單生成後發送MQ給消費者進行積分的添加 尋找上鎖的對象 :【訂單id】
3. 用戶已經建立訂單,準備對訂單進行支付,同時商家在對這個訂單進行改價 尋找上鎖對象 : 【訂單id】
複製代碼
redis單線程串行處理自然就是解決串行化問題,用來解決分佈式鎖是再適合不過。微服務
實現方式:
setnx key value Expire_time
獲取到鎖 返回 1 , 獲取失敗 返回 0post
存在問題:性能
redis 只能在setnx指定一個鎖的超時時間,假設初始設定鎖的時間是10秒鐘,可是業務獲取到鎖跑了20秒鐘,在10秒鐘以後,若是又有一個業務能夠獲取到相同的一把鎖,這個時候可能就存在兩個相同的業務都獲取獲得鎖的問題,而且兩個業務處在並行階段。也就是第一個獲取鎖的業務沒法對自身的鎖進行續租。spa
redis 的client與server端並無維持心跳的機制,若是在鏈接出現問題,client會獲得一個超時的回饋。
redis的集羣實際上在CAP模式中是處在與AP的模型,保證可用性。在主從複製中「主」有數據,但可能「從」尚未數據,這個時候,一旦主掛掉或者網絡抖動等各類緣由,可能會切換到「從」節點,這個時候有可能會致使兩個業務線程同時的獲取到兩把鎖。
上述的問題其實並非redis的缺陷,只是redis採用了AP模型,它自己沒法確保咱們對一致性的要求。redis官方推薦redlock算法來保證,問題是redlock至少須要三個redis主從實例來實現,維護成本比較高,至關於redlock使用三個redis集羣實現了本身的另外一套一致性算法,比較繁瑣,在業界也使用得比較少。
能不能使用redis做爲分佈式鎖,這個自己就不是redis的問題,仍是取決於業務場景,咱們先要本身確認咱們的場景是適合 AP 仍是 CP , 若是在社交發帖等場景下,咱們並無很是強的事務一致性問題,redis提供給咱們高性能的AP模型是很是適合的,但若是是交易類型,對數據一致性很是敏感的場景,咱們可能要尋在一種更加適合的 CP 模型
redis可能做爲高可用的分佈式鎖並不合適,咱們須要確立高可用分佈式鎖的設計目標
- | redis | zookeeper | etcd |
---|---|---|---|
一致性算法 | 無 | zab | raft |
CAP | AP | CP | CP |
高可用 | 主從 | N+1 | N+1 |
實現 | setnx | create臨時有序節點 | restful |
剛剛也分析過,redis其實沒法確保數據的一致性,先來看zookeeper是否合適做爲咱們須要的分佈式鎖,首先zk的模式是CP模型,也就是說,當zk鎖提供給咱們進行訪問的時候,在zk集羣中能確保這把鎖在zk的每個節點都存在。
(這個其實是zk的leader經過二階段提交寫請求來保證的,這個也是zk的集羣規模大了的一個瓶頸點)
說zk的鎖問題以前先看看zookeeper中幾個特性,這幾個特性構建了zk的一把分佈式鎖 特性:
有序節點
當在一個父目錄下如 /lock 下建立 有序節點,節點會按照嚴格的前後順序建立出自節點 lock000001,lock000002,lock0000003,以此類推,有序節點能嚴格保證各個自節點按照排序命名生成。
臨時節點
客戶端創建了一個臨時節點,在客戶端的會話結束或會話超時,zookepper會自動刪除該解ID那。
事件監聽
在讀取數據時,咱們能夠對節點設置監聽,當節點的數據發生變化(1 節點建立 2 節點刪除 3 節點數據變成 4 自節點變成)時,zookeeper會通知客戶端。
結合這幾個特色,來看下zk是怎麼組合分佈式鎖。
zk官方提供的客戶端並不支持分佈式鎖的直接實現,咱們須要本身寫代碼去利用zk的這幾個特性去進行實現。
客戶端建立了臨時有序節點並創建了事件監聽,就可讓業務線程與zk維持心跳,這個心跳也就是這把鎖的租期。當客戶端的業務線程完成了執行就把節點進行刪除,也就釋放了這把鎖,不過中間也可能存在問題
客戶端掛掉
由於註冊的是臨時節點,客戶端掛掉,zk會進行感知,也就會把這個臨時節點刪除,鎖也就隨着釋放
業務線程假死
業務線程並無消息,而是一個假死狀態,(例如死循環,死鎖,超長gc),這個時候鎖會被一直霸佔不能釋放,這個問題須要從兩個方面進行解決。
第一個是自己業務代碼的問題,爲什麼會出現死循環,死鎖等問題。
第二個是對鎖的異常監控問題,這個其實也是微服務治理的一個方面。
剛剛說了zk鎖的維持是靠zk和客戶端的心跳進行維持,若是客戶端出現了長時間的GC會出現什麼情況
etcd實現分佈式鎖比zk要簡單不少,就是使用key value的方式進行寫入,在集羣中,若是存在key的話就不能寫入,也就意味着不能獲取到鎖,若是集羣中,能夠寫入key,就意味着獲取獲得鎖。
etc到使用了raft保證了集羣的一致性,也就是在外界看來,只要etcd集羣中某一臺機器存在了鎖,全部的機器也就存在了鎖,這個跟zk同樣屬於強一致性,而且數據是能夠進行持久化,默認數據一更新就持久化。
etcd 並不存在一個心跳的機制,因此跟redis同樣獲取鎖的時候就要對其進行expire的指定,這個時候就存在一個鎖的租期問題。
租期問題有幾種思路能夠去解決,這裏討論其中一種:
在獲取到鎖的業務線程,能夠開啓一個子線程去維護和輪訓這把鎖的有效時間,並定時的對這把鎖進行續租
假設業務線程獲取到一把鎖,鎖的expire時間爲10s,業務線程會開啓一個子線程經過輪訓的方式每2秒鐘去把這把鎖進行續租,每次都將鎖的expire還原到10s,當業務線程執行完業務時,會把這把鎖進行刪除,事件完畢。
這種思路同樣會存在問題:
首先得了解清楚咱們使用分佈式鎖的場景,爲什麼使用分佈式鎖,用它來幫咱們解決什麼問題,先聊場景後聊分佈式鎖的技術選型。
不管是redis,zk,etcd其實在各個場景下或多或少都存在一些問題,例如redis的AP模型會限制不少使用場景,但它卻擁有了幾者中最高的性能,zookeeper的分佈式鎖要比redis可靠不少,但他繁瑣的實現機制致使了它的性能不如redis,並且zk會隨着集羣的擴大而性能更加降低。etcd 看似是一種折中的方案,不過像鎖的租期續約都要本身去實現。
簡單來講,先了解業務場景,後進行技術選型。