首頁 > 軟體

Spring Boot整合Zookeeper實現分散式鎖的場景分析

2022-06-27 10:01:09

溫馨提示本篇文章要求掌握zk的資料結構,以及臨時序號節點!

zk實現分散式鎖完全是依靠zk節點型別當中的臨時序號節點來實現的

一、Java當中關於鎖的概念

1.1.什麼是鎖

鎖是用來控制多個執行緒存取共用資源的方式,一般來說,一個鎖能夠防止多個執行緒同時存取共用資源。

1.2.鎖的使用場景

以減庫存為例,庫存這時候就剩1個,那麼我們得保證只會有1個請求真正的完成減1操作,假如程式碼邏輯是,先從庫裡查庫存,通過if條件判斷,如果有就減,沒有就返回購買失敗。

這時候並行存取減庫存介面,可能這時候我們程式碼當中的if判斷已經失效了,多個請求同時查到還有一個,並且已經進入了if判斷,沒有加鎖的話,這時候事情就比較嚴重了,庫存一下子就成了負數。

對於這塊程式碼我們不希望同時有人減庫存,這時候就需要加鎖來控制,加完鎖之後,就是將並行請求改成了序列,也就是不管並行了多少個請求,我通過加鎖,只讓1個請求進行減庫存,你們去爭搶鎖資源吧,誰先搶到了就是誰的。

1.3.什麼是分散式鎖

分散式鎖是控制 微服務叢集 之間同步存取共用資源的一種方式。

1.4.分散式鎖的使用場景

使用分散式鎖前提:微服務一定是叢集,這裡的微服務不是指的zk,而是指的我們的業務模組,在專案當中一般的,由於並行量較高,往往會將業務拆分為一個模組一個模組,例如訂單模組,庫存模組,拆模組其中一個目的就是為了針對於模組的並行性進行叢集部署,比如訂單模組用的比較多,我可以搭建多個訂單模組,但是儘管他們的模組是多個,圍繞的資料還是共通的(一個資料庫)。

叢集情況下,我們在程式碼加普通鎖已經解決不了問題,假如現在有三個庫存微服務,設定了負載均衡的方式存取,普通鎖只能控制自己的服務減庫存不會出現負數,但是他控制不了其他兩個服務,資料是共通的,所以這時候只能使用分散式鎖,通過分散式鎖來控制三個服務當庫存只有一個商品的時候,只能有一個服務存取的請求可以減庫存成功。

二、zk實現分散式鎖

2.1.zk中鎖的種類:

  • 讀鎖:建立⼀個臨時序號節點,節點的名稱會包含READ的字母,表示是讀鎖,⼤家都可以讀,要想上讀鎖的前提:之前的鎖沒有寫鎖
  • 寫鎖:建立⼀個臨時序號節點,節點的名稱會包含WRIT的字母,表示是寫鎖,只有得到寫鎖的才能寫。要想上寫鎖的前提是,之前沒有任何鎖

讀鎖和寫鎖完全是按照建立的臨時序號節點的名稱來區分的!

  • 序號節點建立出的節點,根據先後順序,會在節點之後帶上⼀個數值,越往後執⾏數值越⼤,類似於mysql的主鍵自增
  • 臨時節點 :臨時節點是在對談結束後,⾃動被刪除的,通過這個特性,zk可以實現服務註冊與發現的效果。假如對談關掉後大概10s左右,建立的臨時節點就會消失。這個對談就是指的連線zk的使用者端。
  • 臨時序號節點:就是上面兩個的結合體

當需要上鎖的時候,就進行建立臨時序號節點,釋放鎖的時候就刪除節點。

2.2.zk如何上讀鎖

  • 建立一個臨時序號節點
  • 獲取當前zk中序號比自己小的所有節點
  • 判斷最小節點是否是讀鎖:
  • 如果不是讀鎖的話,則上鎖失敗,為最小節點設定監聽。阻塞等待,zk的watch機制會當最小節點發生變化時通知當前節點,於是再執行第二步的流程
  • 如果是讀鎖的話,則上鎖成功

想要上讀鎖,主要就是需要看比他小的節點當中是否有寫鎖。如果有寫鎖,就需要等他用完之後刪除節點,通過watch機制來通知他,寫鎖已經釋放,然後他再進行第二步判斷。

2.3.zk如何上寫鎖

  • 建立一個臨時序號節點
  • 獲取zk中所有的子節點
  • 判斷自己是否是最小的節點:
  • 如果是,則上寫鎖成功
  • 如果不是,說明前面還有鎖,則上鎖失敗,監聽最小的節點,如果最小節點有變化,則回到第二步。

2.4.⽺群效應

如果⽤上述的上鎖⽅式,只要有節點發⽣變化,就會觸發其他節點的監聽事件,這樣的話對zk的壓⼒⾮常⼤,——⽺群效應。可以調整成鏈式監聽。解決這個問題。

假如並行了100個請求都需要獲取寫鎖,這時候建立了100個節點來監聽最小節點,當最小節點發生變化的時候,意味著他一下子要進行通知100個節點,zk瞬間會壓力非常大。

所以這時候可以採用鏈式監聽,鏈式監聽仍然是依靠的序號節點的特點。就好比mysql設定自增後,不管多少並行請求,他仍然能保證id的唯一性,zk的序號節點同樣也是。讓他們都不再監聽最小節點,而是監聽他的上一個節點。當上一個節點釋放鎖後,那當前節點就可以建立寫鎖了。

這裡還會遇到一個問題,假如他的上一個節點意外刪除了,但是並不是等著拿到鎖後釋放鎖,而是單純的不想等了,所以刪除了節點,而上面還有很多節點加著鎖呢,所以我們不能單純的靠上一個節點刪除後當前節點就進行加鎖。我們加寫鎖要保證的是,他上面沒有任何節點加鎖。這時候讓他進行監聽上上個節點即可。假如上上個節點仍然有問題了,那就監聽上上上個節點。總之一點就是儘量避免不讓多個節點同時去監聽一個節點

三、springboot整合分散式鎖

springboot整合curator使用者端:https://www.jb51.net/article/181082.htm

我直接是基於上一篇文章當中的專案進行 分散式鎖 練習的!

根據上面提到的zk分散式鎖實現思路,我們其實並不用去自己寫,在curator使用者端已經給我們提供了現成的方法,我們只需要簡單的呼叫使用者端提供的方法,就可以實現分散式鎖功能!

@Autowired
CuratorFramework curatorFramework;

/**
 * 獲取讀鎖的條件是前面沒有寫鎖
 * 當/lock1節點不存在的時候,我們不需要手動去建立,獲取鎖的時候會自動建立
 * 自動建立的是臨時節點,用完之後釋放鎖的時候會刪除掉的
 * 獲取到鎖之後會在/lock1節點下建立一個臨時序號節點
 * 然後沒有獲取到鎖的執行緒也會建立一個節點,這時候處於等待期間
 * 釋放鎖的時候,首先會刪除掉自己的序號節點,然後假如沒有人在排隊用鎖,這時候會把/lock1節點也刪除掉
 *
 * @throws Exception
 */
@Test
void testGetReadLock() throws Exception {
    // 讀寫鎖
    InterProcessReadWriteLock interProcessReadWriteLock = new
            InterProcessReadWriteLock(curatorFramework, "/lock1");
    // 獲取讀鎖物件(建立物件耗時也就18毫秒)
    InterProcessLock interProcessLock = interProcessReadWriteLock.readLock();
    System.out.println("等待獲取讀鎖物件!");

    // 獲取鎖(假如一直沒拿到鎖這個方法一直會是阻塞的,就算不阻塞的情況下,這個方法耗時也是特別長,高達17秒)
    interProcessLock.acquire();

    // 正常的我們程式碼假如走到了這一步,說明已經獲取到鎖了,這裡寫相關的業務程式碼即可,執行完記住釋放鎖
    System.out.println("獲取到了鎖!");
    for (int i = 1; i <= 100; i++) {
        Thread.sleep(3000);
        System.out.println(i);
    }
    // 釋放鎖(方法耗時13毫秒)
    interProcessLock.release();
    // 走到這一步的時候節點已經被刪除了
    System.out.println("等待釋放鎖!");
}

/**
 * 獲取寫鎖的條件是前面沒有任何的鎖
 *
 * @throws Exception
 */
@Test
void testGetWriteLock() throws Exception {
    // 讀寫鎖
    InterProcessReadWriteLock interProcessReadWriteLock = new
            InterProcessReadWriteLock(curatorFramework, "/lock1");
    // 獲取寫鎖物件
    InterProcessLock
            interProcessLock = interProcessReadWriteLock.writeLock();
    System.out.println("等待獲取寫鎖物件!");
    // 獲取鎖(假如一直沒拿到鎖這個方法一直會是阻塞的)
    interProcessLock.acquire();
    for (int i = 1; i <= 100; i++) {
        Thread.sleep(3000);
        System.out.println(i);
    }
    // 釋放鎖
    interProcessLock.release();
    System.out.println("等待釋放鎖!");
}

/**
 * 方便測試多個執行緒獲取讀鎖
 *
 * @throws Exception
 */
@Test
void testGetReadLock1() throws Exception {
    testGetReadLock();
}

這個是兩個獲取讀鎖的時候的節點場景:

這個是一個獲取讀鎖,一個獲取寫鎖的場景,然後讀鎖節點是0004,所以是後建立的,他只有等待0003釋放寫鎖,才能獲取到讀鎖。

zk分散式鎖會產生死鎖嗎?

這個肯定是不會的,因為假如有使用者端拿到了鎖,還沒釋放,服務掛了,這時候依據臨時節點的特性,當臨時節點和使用者端斷開連線幾秒後會自動刪除的,刪除節點也就意味著自動釋放鎖!

正常情況不建議使用zk作為分散式鎖,效率屬實太慢。

到此這篇關於Spring Boot整合Zookeeper實現分散式鎖的文章就介紹到這了,更多相關Spring Boot整合Zookeeper分散式鎖內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com