为什么多个线程不可能同时抢到一把锁_分布式为什么一定要有高可用的分布式锁?看完就知道了...
分布式鎖定義
分布式鎖在分布式環(huán)境下,鎖定全局唯一公共資源,表現(xiàn)為:
- 請求串行化
- 互斥性
第一步是上鎖的資源目標,是鎖定全局唯一公共資源,只有是全局唯一的資源才存在多個線程或服務競爭的情況。
互斥性表現(xiàn)為一個資源的隔離級別串行化,如果對照單機事務 ACID 的隔離性來說,互斥性的事務隔離級別是 SERLALIZABLE,屬于最高的隔離級別。
事務隔離級別:
- DEFAULT
- READ_UNCOMMITTED
- READ_COMMITED
- REPEATABLE_READ
- SERLALIZABLE
分布式鎖目的
分布式鎖的目的如下:
- 解決業(yè)務層冪等性
- 解決 MQ 消費端多次接受同一消息
- 確保串行|隔離級別
- 多臺機器同時執(zhí)行定時任務
尋找唯一資源進行上鎖
例子:
1. 防止用戶重復下單 共享資源進行上鎖的對象 : 【用戶id】2. 訂單生成后發(fā)送MQ給消費者進行積分的添加 尋找上鎖的對象 :【訂單id】3. 用戶已經創(chuàng)建訂單,準備對訂單進行支付,同時商家在對這個訂單進行改價 尋找上鎖對象 : 【訂單id】基于 Redis 分布式鎖
Redis 單線程串行處理天然就是解決串行化問題,用來解決分布式鎖是再適合不過。
實現(xiàn)方式:
setnx key value Expire_time 獲取到鎖 返回 1 , 獲取失敗 返回 0存在問題如下:
鎖時間不可控
Redis 只能在 Setnx 指定一個鎖的超時時間,假設初始設定鎖的時間是 10 秒鐘,但是業(yè)務獲取到鎖跑了 20 秒鐘,在 10 秒鐘之后,如果又有一個業(yè)務可以獲取到相同的一把鎖。
這個時候可能就存在兩個相同的業(yè)務都獲取得到鎖的問題,并且兩個業(yè)務處在并行階段。也就是第一個獲取鎖的業(yè)務無法對自身的鎖進行續(xù)租。
單點連接超時問題
Redis 的 Client 與 Server 端并沒有維持心跳的機制,如果在連接中出現(xiàn)問題,Client 會得到一個超時的回饋。
主從問題
Redis 的集群實際上在 CAP 模式中是處在與 AP 的模型,保證可用性。在主從復制中“主”有數(shù)據(jù),但可能“從”還沒有數(shù)據(jù)。這個時候,一旦主掛掉或者網(wǎng)絡抖動等各種原因,可能會切換到“從”節(jié)點。
這個時候有可能會導致兩個業(yè)務線程同時的獲取到兩把鎖:
①業(yè)務線程-1:向主節(jié)點請求鎖
②業(yè)務線程-1:獲取鎖
③業(yè)務線程-1:獲取到鎖并開始執(zhí)行業(yè)務
④這個時候 Redis 剛生成的鎖在主從之間還未進行同步
⑤Redis 這時候主節(jié)點掛掉了
⑥Redis 的從節(jié)點升級為主節(jié)點
⑦業(yè)務線程-2:向新的主節(jié)點請求鎖
⑧業(yè)務線程-2:獲取到新的主節(jié)點返回的鎖
⑨業(yè)務線程-2:獲取到鎖開始執(zhí)行業(yè)務
⑩這個時候業(yè)務線程-1和業(yè)務線程-2同時在執(zhí)行任務
Redlock
上述的問題其實并不是 Redis 的缺陷,只是 Redis 采用了 AP 模型,它本身無法確保我們對一致性的要求。
Redis 官方推薦 Redlock 算法來保證,問題是 Redlock 至少需要三個 Redis 主從實例來實現(xiàn),維護成本比較高。
相當于 Redlock 使用三個 Redis 集群實現(xiàn)了自己的另一套一致性算法,比較繁瑣,在業(yè)界也使用得比較少。
能不能使用 Redis 作為分布式鎖
能不能使用 Redis 作為分布式鎖,這個本身就不是 Redis 的問題,還是取決于業(yè)務場景,我們先要自己確認我們的場景是適合 AP 還是 CP。
如果在社交發(fā)帖等場景下,我們并沒有非常強的事務一致性問題,Redis 提供給我們高性能的 AP 模型是非常適合的。
但如果是交易類型,對數(shù)據(jù)一致性非常敏感的場景,我們可能要尋找一種更加適合的 CP 模型。
Redis 可能作為高可用的分布式鎖并不合適,我們需要確立高可用分布式鎖的設計目標。
高可用分布式鎖設計目標
高可用分布式鎖的設計目標如下:
- 強一致性,是 CP 模型
- 服務高可用,不存在單點問題
- 鎖能夠續(xù)租和自動釋放
- 業(yè)務接入簡單
三種分布式鎖方案對比
常用的三種分布式鎖方案對比如下圖:
基于 Zookeeper 分布式鎖
剛剛也分析過,Redis 其實無法確保數(shù)據(jù)的一致性,先來看 Zookeeper 是否合適作為我們需要的分布式鎖。
首先 ZK 的模式是 CP 模型,也就是說,當 ZK 鎖提供給我們進行訪問的時候,在 ZK 集群中能確保這把鎖在 ZK 的每一個節(jié)點都存在。
這個實際上是 ZK 的 Leader 通過二階段提交寫請求來保證的,這個也是 ZK 的集群規(guī)模大了的一個瓶頸點。
ZK 鎖實現(xiàn)的原理
說 ZK 的鎖問題之前先看看 Zookeeper 中的幾個特性,這幾個特性構建了 ZK 的一把分布式鎖。
Zookeeper 中的幾個特性如下:
- 有序節(jié)點,當在一個父目錄下如 /lock 下創(chuàng)建有序節(jié)點,節(jié)點會按照嚴格的先后順序創(chuàng)建出自節(jié)點 lock000001,lock000002,lock0000003,以此類推,有序節(jié)點能嚴格保證各個自節(jié)點按照排序命名生成。
- 臨時節(jié)點,客戶端建立了一個臨時節(jié)點,在客戶端的會話結束或會話超時,Zookeeper 會自動刪除該節(jié)點 ID。
- 事件監(jiān)聽,在讀取數(shù)據(jù)時,我們可以對節(jié)點設置監(jiān)聽,當節(jié)點的數(shù)據(jù)發(fā)生變化(1 節(jié)點創(chuàng)建,2 節(jié)點刪除,3 節(jié)點數(shù)據(jù)變動,4 子節(jié)點變動)時,Zookeeper 會通知客戶端。
結合這幾個特點,來看下 ZK 是怎么組合分布式鎖:
- 業(yè)務線程-1,業(yè)務線程-2 分別向 ZK 的 /lock 目錄下,申請創(chuàng)建有序的臨時節(jié)點。
- 業(yè)務線程-1 搶到 /lock0001 的文件,也就是在整個目錄下最小序的節(jié)點,也就是線程-1 獲取到了鎖。
- 業(yè)務線程-2 只能搶到 /lock0002 的文件,并不是最小序的節(jié)點,線程 2 未能獲取鎖。
- 業(yè)務線程-1 與 lock0001 建立了連接,并維持了心跳,維持的心跳也就是這把鎖的租期。
- 當業(yè)務線程-1 完成了業(yè)務,將釋放掉與 ZK 的連接,也就是釋放了這把鎖。
ZK 分布式鎖的代碼實現(xiàn)
ZK 官方提供的客戶端并不支持分布式鎖的直接實現(xiàn),我們需要自己寫代碼去利用 ZK 的這幾個特性去進行實現(xiàn):
ZK 分布式鎖客戶端假死的問題
客戶端創(chuàng)建了臨時有序節(jié)點并建立了事件監(jiān)聽,就可以讓業(yè)務線程與 ZK 維持心跳,這個心跳也就是這把鎖的租期。
當客戶端的業(yè)務線程完成了執(zhí)行就把節(jié)點進行刪除,也就釋放了這把鎖,不過中間也可能存在問題:
- 客戶端掛掉。因為注冊的是臨時節(jié)點,客戶端掛掉,ZK 會進行感知,也就會把這個臨時節(jié)點刪除,鎖也就隨著釋放。
- 業(yè)務線程假死。業(yè)務線程并沒有消息,而是一個假死狀態(tài),(例如死循環(huán),死鎖,超長 GC),這個時候鎖會被一直霸占不能釋放,這個問題需要從兩個方面進行解決。
總結
以上是生活随笔為你收集整理的为什么多个线程不可能同时抢到一把锁_分布式为什么一定要有高可用的分布式锁?看完就知道了...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python生成柱状图 不显示_pyth
- 下一篇: springboot初始化加载数据_Sp