朋友,如果喜歡,就去表白吧,不要因為害羞,更不要因為自卑,如果現在你都還不敢表白,那麼多年後,再回頭來看的時候,你可能會為曾經的膽小而後悔,也可能會為錯過一個人而心中久久不能釋懷,所以,大膽一點,即使失敗也無所謂,至少我們曾經做過,做過了就無怨無悔,在人生這條道路上,時光稍縱即逝,我們應該把握好眼前的一切,愛是一種力量,更是一種內心的慰藉,衝吧!不要因為錢不夠,不要因為容貌不出中國,更不要因為身世不顯赫,你只要足夠勇敢,這一切都是附加品!
今天來分享一下zookeeper分散式鎖,分散式鎖是為了解決在分散式環境下資料的一致性,在單體系統中,我們可以直接使用Java自帶的鎖來進行並行控制,比如synchronized
,Lock
等,但是在分散式系統中,因為服務部署在多機或者多容器裡面,所以不在一個JVM中,就不能使用Java自帶的鎖機制,所以就必須得使用分散式鎖,實現分散式鎖的方式有幾種,如Redis可以實現,今天我們主要說Zookeeper實現分散式的原理。
在說zookeeper分散式鎖之前,我們先來說一下zk的節點型別,Zookeeper資料結構就像樹,由節點構成,節點叫做Znode,Znode分為四種型別。
預設的節點型別,使用者端與zk斷開連線後,節點依然存在
在建立節點時zk根據建立的時間順序對節點進行編號
當建立節點的使用者端與zk斷開連線後,臨時節點會被刪除
斷開連線
臨時順序節點結合和臨時節點和順序節點的特點:在建立節點時,Zookeeper 根據建立的時間順序給該節點名稱進行編號,當建立節點的使用者端與 Zookeeper 斷開連線後,臨時節點會被刪除。
zookeeper分散式鎖運用了臨時順序節點的特點
1.在 Zookeeper 當中建立一個節點 ParentLock,這個節點可以設定為臨時節點,也可設定成持久節點,Curator內建的分散式鎖使用的臨時節點,當第一個使用者端想要獲得鎖時,需要在 ParentLock 這個節點下面建立一個臨時順序節點 LockA。
2.ClientA 查詢 ParentLock 下面所有的臨時順序節點並排序,判斷自己所建立的節點 LockA 是不是順序最靠前的一個。如果是第一個節點,則成功獲得鎖。
3.這時候,如果再有一個使用者端 ClientB 前來獲取鎖,則在 ParentLock 下載再建立一個臨時順序節點 LockB。
4.ClientB 查詢 ParentLock 下面所有的臨時順序節點並排序,判斷自己所建立的節點 LockB 是不是順序最靠前的一個,結果不是,ClientB 向排序僅比它靠前的節點 LockA 註冊 Watcher,用於監聽 Lock1A節點是否存在。這意味著 Client2B搶鎖失敗,進入了等待狀態。
5.這時候,如果又有一個使用者端 ClientC前來獲取鎖,則在 ParentLock 下載再建立一個臨時順序節點 LockC。
6.ClientC 查詢 ParentLock 下面所有的臨時順序節點並排序,判斷自己所建立的節點 LockC 是不是順序最靠前的一個,結果同樣發現節點 LockC並不是最小的。於是,ClientC向排序僅比它靠前的節點 LockB註冊 Watcher,用於監聽 LockB節點是否存在。這意味著 ClientC同樣搶鎖失敗,進入了等待狀態。
這樣一來,ClientA 得到了鎖,ClientB監聽了 LockA,ClientC監聽了 LockB,形成了一個等待佇列,
當任務完成時,ClientA 會顯示呼叫刪除節點 LockA 的指令。
獲得鎖的 ClientA 在任務執行過程中,如果崩潰,則會斷開與 Zookeeper 伺服器端的連結。根據臨時節點的特性,相關聯的節點 LockA 會隨之自動刪除。
由於 ClientB 一直監聽著 LockA的存在狀態,當 LockA節點被刪除,ClientB會立刻收到通知。這時候 ClientB 會再次查詢 ParentLock 下面的所有節點,確認自己建立的節點 LockB 是不是目前最小的節點。如果是最小,則 ClientB 順理成章獲得了鎖。
Curator是Zookeeper的Java使用者端庫,使用Curator能夠更加方便輕鬆的使用Zookeeper,Curator內建了分散式鎖供我們使用,下面我們使用程式碼演示一下Curator的分散式鎖。
CuratorFramework設定zookeeper連線,操作zookeeper服務需要使用CuratorFramework。
@Bean("curatorFramework")
public CuratorFramework curatorFramework() {
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.sessionTimeoutMs(10000)
.connectionTimeoutMs(10000)
.retryPolicy(new BoundedExponentialBackoffRetry(10000,30000, 3))
.build();
curatorFramework.start();
return curatorFramework;
}
@RestController
@AllArgsConstructor
public class LockController {
final CuratorFramework curatorFramework;
@GetMapping("/testLock")
public void testLock() throws Exception {
InterProcessLock lock = new InterProcessMutex(curatorFramework, "/lock/order");
try {
//加鎖
boolean acquire = lock.acquire(20, TimeUnit.SECONDS);
if (acquire){
System.out.println("---------------獲取鎖成功--------------- ");
}else {
System.out.println("---------------獲取鎖失敗--------------- ");
}
}catch (Exception e){
e.printStackTrace();
}finally {
//釋放鎖
lock.release();
}
}
}
從Zookeeper可是化工具中可以看出大量請求進行了排隊,由此可以看出分散式鎖使用成功。
如果直接使用程式碼的方式使用分散式鎖,那麼可能比較麻煩,那麼我們可以使用註解的方式來封裝一個starter,在SpringBoot專案中直接使用,之前基於Curator自定義了一個分散式鎖的SpringBoot starter , 直接引入即可,如下使用註解@U2Lock
便可。
@GetMapping("/get")
@U2Lock(lockName = "order-get",lockType = LockType.MUTEX_LOCK,requireTime = 5000)
public Map<String,Object> lock(){
Map<String,Object> map = new HashMap<>();
if (count > 0){
count--;
map.put("order_num",count);
return map;
}
map.put("msg","商品已售罄");
return map;
}
註解可以設定鎖名稱,所型別,預設為可重入排他鎖InterProcessMutex
,還有過期時間,代表在指定的時間內沒有獲取到鎖,那麼鎖就過期了,將會自動刪除鎖。
/**
* 鎖註解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Inherited
public @interface U2Lock {
/**
* 鎖名字
*/
String lockName() default "";
/**
* 鎖型別-預設為可重入排他鎖
* @return
*/
LockType lockType() default LockType.MUTEX_LOCK;
/**
* 過期時間
*/
long requireTime() default 30000;
/**
* 單位
*/
TimeUnit unit() default TimeUnit.MILLISECONDS;
}
U2Lock地址: https://gitee.com/steakliu/u2-lock/tree/master/zookeeper-distribute-lock-starter
關於zookeeper分散式鎖的介紹和實戰,就說到這裡,感謝你的觀看,我們下期見!