javascript
2. Spring Boot使用Apache Curator实现分布式锁(可重入排它锁)「第四章 ZooKeeper Curator应用场景实战」「架构之路ZooKeeper理论和实战」
?
相關歷史文章(閱讀本文前,您可能需要先看下之前的系列👇)
國內最全的Spring?Boot系列之四
享元模式:共享女友?-?第355篇
什么是?ZooKeeper?-?第347篇
ZooKeeper安裝?-?第348篇
ZooKeeper數據結構和實操??-?第349篇
ZooKeeper的watch機制?-?第350篇
ZooKeeper的acl權限控制??-?第351篇
ZooKeeper內存數據和持久化??-?第352篇
ZooKeeper集群搭建?-?第354篇
ZooKeeper?Java客戶端的基本使用?-?第356篇
ZooKeeper客戶端Curator?-?第358篇
ZooKeeper客戶端Curator的進階使用?-?第359篇
ZooKeeper客戶端Curator實現Watch事件監聽?-?第361篇
Spring Boot 使用 Curator 操作 ZooKeeper - 第363篇
Spring Boot使用Apache Curator實現服務的注冊和發現 - 第364篇
Spring Boot使用Apache Curator實現分布式鎖(可重入排它鎖) - 第365篇
Spring Boot使用Apache Curator實現leader選舉 - 第366篇
Spring Boot使用Apache Curator實現分布式計數器 - 367篇
ZooKeeper Session 基本原理 - 第369篇
ZooKeeper分桶策略實現高性能的會話管理 - 第371篇
ZooKeeper集群架構以及讀寫原理 - 第372篇
ZooKeeper Leader選舉原理也不過如此,看完這篇你不再懵逼了 - 第374篇
Zookeeper 集群節點為什么要部署成奇數呢?- 第376篇
ZooKeeper集群腦裂問題 - 第379篇
分布式一致性算法Paxos,ZooKeeper的ZAB協議 - 第381篇
?
在前面我們將Curator集成到了Spring Boot項目中,這一節我們看看Curator支持的應用場景之一:分布式鎖
一、基本概念
1.1 分布式鎖
為了防止分布式系統中的多個進程之間相互干擾,我們需要一種分布式協調技術來對這些進程進行調度。而這個分布式協調技術的核心就是來實現這個分布式鎖。
1.2 分布式鎖應該具備哪些條件
(1)在分布式系統環境下,一個方法在同一時間只能被一個機器的一個線程執行
(2)高可用的獲取鎖與釋放鎖
(3)高性能的獲取鎖與釋放鎖
(4)具備可重入特性(可理解為重新進入,由多于一個任務并發使用,而不必擔心數據錯誤)
(4)具備鎖失效機制,防止死鎖
(5)具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗
1.3分布式鎖的實現有哪些
(1)Memcached:利用 Memcached 的 add 命令。此命令是原子性操作,只有在 key 不存在的情況下,才能 add 成功,也就意味著線程得到了鎖。
(2)Redis:和 Memcached 的方式類似,利用 Redis 的 setnx 命令。此命令同樣是原子性操作,只有在 key 不存在的情況下,才能 set 成功。
(3)Zookeeper:利用 Zookeeper 的順序臨時節點,來實現分布式鎖和等待隊列。Zookeeper 設計的初衷,就是為了實現分布式鎖服務的。
(4)Chubby:Google 公司實現的粗粒度分布式鎖服務,底層利用了 Paxos 一致性算法。
二、Curator實現分布式鎖
2.1 說明
???????? 使用Curator實現分布式鎖非常的簡單,核心類InterProcessMutex,看官網的示例代碼:
2.2 例子業務場景說明
???????? 接下來我們實現一個下單減庫存的例子:
???????? 我們假設一個這樣的業務場景:
在電商中,用戶購買商品需要扣減商品庫存,一般有兩種扣減庫存方式:
(1)下單減庫存
優點:用戶體驗好,下單成功,庫存直接扣除,用戶支付不會出現庫存不足情況
缺點:用戶一直不付款,這個商品的庫存就會被占用,其他人就無法購買了。
(2)支付減庫存
優點:不會導致庫存被惡意鎖定,對商家有利。
缺點:用戶體驗不好,用戶支付時可能商品庫存不足了,會導致用戶交易失敗。
那么,我們一般為了用戶體驗,會采用下單減庫存。但是為了解決下單減庫存的缺陷,會創建一個定時任務,定時去清理超時未支付的訂單。
2.3 未使用分布式鎖的情況下
2.3.1 編碼說明
???????? 這塊就不進行具體展開說明了,但為了文章的完整性,這里簡單說明下:
(1)表:產品表product和訂單表order_info。
(2)實體類:Product和OrderInfo。
(3)持久化類:ProductRepository和OrderInfoRepository,這里使用的是Spring Data JPA進行數據庫的操作。
(4)服務層:主要是下單減庫存。
(5)使用多線程模擬發起下單請求:
?
?
2.3.2 測試說明
???????? 我們現在數據庫有一條數據:
????????? 庫存是10,然后發起請求進行測試一下,就會出現庫存為負數的了:
2.4 使用分布式鎖的情況下
???????? 使用分布式鎖的核心就是進行調用方法之前,獲取鎖才能進行往下操作,否則進行等待獲取鎖,在操作完成之后,記得釋放鎖資源。
?
long productId = 1; InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework, "/product_"+productId); try {interProcessMutex.acquire();//加鎖shopService.reduceStock(productId); } catch (Exception e) {e.printStackTrace(); }finally {try {interProcessMutex.release();} catch (Exception e) {e.printStackTrace();} }?
三、Zookeeper 分布式鎖加鎖原理
???????? 從上面我們可以對于分布式鎖的使用,簡單的很,那么這個具體是怎么一個邏輯吶?如果你也有這樣的疑問,請跟隨文章進行往下探索。
3.1 方式一:非公平鎖的實現
???????? 看如下的一個設計圖:
?
(1)獲取鎖:首先判斷當前鎖是否已經有其他事務創建,如果創建了的話,那么就進行監聽,等待被喚醒;如果沒有被其它事務進行創建的話,那么就會進入下一步。這里有個小問題就是如何判斷是否已經被其它事務創建吶?看過前面章節的就會關注到一個一個點,當執行create指令的時候,如果節點存在那么就會報錯,無法創建成功。
(2)創建/exclusive/lock:執行create指令創建節點,如果創建失敗,那么同樣的進入監聽等待;如果創建成功的話,那么就會獲得鎖,進行業務處理,業務處理完成之后,釋放鎖。
(3)其它節點監聽:這時候其它節點監聽就會收到收到釋放鎖的監聽信息,也就是delete 節點改變的通知,然后這些節點在回到(1)進行爭取鎖。
???????? 但是這種方式在并發的情況下存在嚴重的問題,性能會下降的比較厲害,主要原因是:所有的連接都在對同一個節點進行監聽,當服務器檢測到刪除事件時,要通知所有的連接,所有的連接同時收到事件,再次并發競爭,這就是羊群效應。
知識小百科:
(1)羊群效應
???????? 在羊群中,總有那么一些“出類拔萃”的,對肥美的小嫩草天生敏感的“領頭羊”,只要它們開始行動,其他羊就會不假思索地群起響應,即便附近可能有狼,或者有更好的草地,它們也會全然不顧。這就是典型的“羊群效應”。而在這一點上,人也沒比羊聰明到哪里去。我們習慣于通過觀察其他人的行為來提取信息,并做出快速而省心的決定,而信息的不斷趨同,彼此強化,最終就會產生跟風從眾的“羊群效應”。
???????? 投資中的“羊群效應”: 我們在投資過程中,“羊群效應”在炒股與投資基金方面體現得尤為明顯。對于投資經驗相對欠缺的個人投資者而言,在自己舉棋不定,猶豫不決時,總是會去看看其他同類投資者是怎么買的,什么時候買的,又是什么時候賣的。他們總認為在同一群體中的其他人更具有信息優勢。而這樣一來,個人投資者的投資資金將迅速匯聚,很容易形成趨同性的“羊群效應”,上漲時蜂擁而至,下跌時恐慌逃散,也就是我們常說的“追漲殺跌”現象。
???????? 那怎么去理解zk中的“羊群效應”?
???????? zk的客戶端可以在znode上添加一個watch,用來監聽znode相關事件并被通知
羊群效應就是 一個特定的znode 改變的時候ZooKeper 觸發了所有watches 的事件。舉個例子,如果有1000個客戶端watch 一個znode的exists調用,當這個節點被創建的時候,將會有1000個通知被發送。這種由于一個被watch的znode變化,導致大量的通知需要被發送,將會導致在這個通知期間的其他操作提交的延遲。因此,只要可能,我們都強烈建議不要這么使用watch。僅僅有很少的客戶端同時去watch一個znode比較好,理想的情況是只有1個。
???????? 舉個例子,有n 個clients 需要去拿到一個全局的lock.
一種簡單的實現就是所有的client 去create 一個/lock znode.如果znode 已經存在,只是簡單的watch 該znode 被刪除。當該znode 被刪除的時候,client收到通知并試圖create /lock。這種策略下,就會存在上文所說的問題,每次變化都會通知所有的客戶端。(羊群效應)
(2)公平鎖和非公平鎖
???????? 公平鎖:
就是很公平,在并發環境中,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果為空,或者當前線程是等待隊列的第一個,就占有鎖,否則就會加入到等待隊列中,以后會按照FIFO的規則從隊列中取到自己。
公平鎖的優點是等待鎖的線程不會餓死。缺點是整體吞吐效率相對非公平鎖要低,等待隊列中除第一個線程以外的所有線程都會阻塞,CPU喚醒阻塞線程的開銷比非公平鎖大。
非公平鎖:
上來就直接嘗試占有鎖,如果嘗試失敗,就再采用類似公平鎖那種方式。
非公平鎖的優點是可以減少喚起線程的開銷,整體的吞吐效率高,因為線程有幾率不阻塞直接獲得鎖,CPU不必喚醒所有線程。缺點是處于等待隊列中的線程可能會餓死,或者等很久才會獲得鎖。
3.1 方式二:公平鎖的實現
???????? 看如下的設計圖:
?
(1)請求進來,直接在/lock節點下創建一個臨時順序節點。
(2)判斷自己是不是lock節點下最小的節點。
???????? ① 是最小的,獲得鎖。
???????? ② 不是。對前面的節點進行監聽(watch)。
(3)獲得鎖的請求,處理鎖,即delete節點,然后后繼第一個節點收到通知,重復第2步驟判斷。
???????? 如上借助于臨時順序節點,可以避免同時多個節點的并發競爭鎖,緩解了服務端壓力。這種實現方式所有加鎖請求都進行排隊加鎖,是公平鎖的具體實現。
???????? 這里我們思考一種情況:對于臨時節點,我們之前說過當前session斷開之后就會被刪除了,那么他的下個節點監聽的是它,這樣子會不會有問題吶?
???????? 答案是不會的。當session斷開,節點被刪除就會通知監聽它的下個節點,這樣子下個節點就會執行它的回調方法,找到比它還小的節點,也就是被刪除的節點的上一個節點。
四、Curator 分布式鎖源碼分析
???????? 接下來我們看下Curator加鎖的原理,我們來看看是不是按照我們上面說的思路來進行設計的。
(1)首先我們看下new InterProcessMutex:
?
???????? 這里我們看到LockInternalsDriver的實現類是StandardLockInternalsDriver。
(2)接下來我們看一下獲取鎖的代碼:interProcessMutex.acquire();
???????? 進入到acquire()代碼中:
?
???????? 通過上面能知道真正去獲取鎖的代碼是attemptLock
(3)attemptLock():
???????? 在attemptLock()方法中核心要關注的地方就是這個while循環:
(4)createsTheLock ():
???????? 這個就是創建鎖,也就是創建節點,點擊進去看下:
?
???????? 可以看出這里就是創建節點了,父類節點是容器節點,子節點是臨時順序節點,和上面我們介紹的思路很像。
???????? 如果成功就會返回創建成功的路徑,到這里還沒有獲得鎖的哦,往下看。
(5)internalLockLoop ():
???????? 這個就是獲取鎖的方法了:
???????? 這里我說一下這里面代碼執行的事情:
(1)getSortedChildren():獲取某個節點下的所有排序的子節點。
(2)driver.getsTheLock(): 獲取鎖。
???????? 這里判斷很簡單,就是獲取到當前這個節點在已經排序的節點的位置,然后和maxLeases(這個在構造方法的時候賦值為1)進行比較,如果是在第一個位置也是就是最小的那個位置,那么就是獲取到了鎖,否則不是。并且緊接著不是的情況下,會返回它要watch的下個節點的路徑。
(3)獲取鎖:一種就是獲取成功了,一種情況就是獲取失敗了,那么就會wait(),這個方法是線程的Thread的等待方法,那么什么時候被喚醒吶?
???????? 等待能喚醒的情況那就是watch被調用了,我們看下這里的watch的實現:
???????? 進入到client.postSafeNotify():
?
?
???????? 這里就是去喚醒所有在等待的線程了。到這里我們就清晰了。
???????? 整個流程符合我們之前說的方案的思路。
五、小結
???????? 這一節內容可能有點小多,大家可以收藏起來慢慢消化一下,最后我挑幾個重點給大家總結下:
5.1 分布式鎖的使用
核心類是InterProcessmutex,核心代碼如下:
5.2 Curator實現分布式鎖的原理:
?
(1)請求進來,直接在/lock節點下創建一個臨時順序節點(父節點是容器節點)。
(2)判斷自己是不是lock節點下最小的節點。
???????? ① 是最小的,獲得鎖。
???????? ② 不是。對前面的節點進行監聽(watch)。
(3)獲得鎖的請求,處理鎖,即delete節點,然后后繼第一個節點收到通知,重復第2步驟判斷。
5.3 羊群效應:
???????? “羊群效應”可以理解為一種從眾心理,跟風、隨大流,別人干什么,我也干什么。
???????? Zookeeper中的羊群效應就是 一個特定的znode 改變的時候ZooKeper 觸發了所有watches 的事件。被監聽的節點就是這只領頭羊,其它監聽這個節點watches事件就是其它羊,如果被監聽的節點改變了,那么就會通知所有監聽的事件,引起羊群效應。
我就是我,是顏色不一樣的煙火。 我就是我,是與眾不同的小蘋果。à悟空學院:悟空學院
學院中有Spring?Boot相關的課程!!
SpringBoot視頻:從零開始學Spring Boot Plus - 網易云課堂
SpringBoot交流平臺:https://t.cn/R3QDhU0
SpringSecurity5.0視頻:權限管理spring security - 網易云課堂
ShardingJDBC分庫分表:分庫分表Sharding-JDBC實戰 - 網易云課堂
分布式事務解決方案:分布式事務解決方案「手寫代碼」 - 網易云課堂
JVM內存模型調優實戰:深入理解JVM內存模型/調優實戰 - 網易云課堂
Spring入門到精通:Spring零基礎從入門到精通 - 網易云課堂
大話設計模式之愛你:大話設計模式之愛你一萬年 - 網易云課堂
總結
以上是生活随笔為你收集整理的2. Spring Boot使用Apache Curator实现分布式锁(可重入排它锁)「第四章 ZooKeeper Curator应用场景实战」「架构之路ZooKeeper理论和实战」的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 抖音为什么这么火?抖音用户暴涨的秘密在哪
- 下一篇: CTB全球创新大挑战组队中 全新课题火热