您好,登錄后才能下訂單哦!
這篇文章主要介紹“如何使用ZooKeeper實現Java跨JVM的分布式鎖 ”,在日常操作中,相信很多人在如何使用ZooKeeper實現Java跨JVM的分布式鎖 問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何使用ZooKeeper實現Java跨JVM的分布式鎖 ”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
一、使用ZooKeeper實現Java跨JVM的分布式鎖
Zookeeper版本為 Release 3.4.8(stable)
Curator版本為2.9.1
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.8</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-client</artifactId> <version>2.9.1</version> </dependency>
鎖原理:
1、首先要創建一個鎖的根節點,比如/mylock。
2、想要獲取鎖的客戶端在鎖的根節點下面創建znode,作為/mylock的子節點,節點的類型要選擇CreateMode.PERSISTENT_SEQUENTIAL,節點的名字最好用uuid(至于為什么用uuid我后面會講,先說一下~如果不這么做在某種情況下會發生死鎖,這一點我看了很多國內朋友自己的實現,都沒有考慮到這一層,這也是我為什么不建議大家自己去封裝這種鎖,因為它確實很復雜),假設目前同時有3個客戶端想要獲得鎖,那么/mylock下的目錄應該是這個樣子的。
xxx-lock-0000000001,xxx-lock-0000000002,xxx-lock-0000000003
xxx為uuid , 0000000001,0000000002,0000000003 是zook服務端自動生成的自增數字。
3、當前客戶端通過getChildren(/mylock)獲取所有子節點列表并根據自增數字排序,然后判斷一下自己創建的節點的順序是不是在列表當中最小的,如果是 那么獲取到鎖,如果不是,那么獲取自己的前一個節點,并設置監聽這個節點的變化,當節點變化時重新執行步驟3 直到自己是編號最小的一個為止。
舉例:假設當前客戶端創建的節點是0000000002,因為它的編號不是最小的,所以獲取不到鎖,那么它就找到它前面的一個節點0000000001 并對它設置監聽。
4、釋放鎖,當前獲得鎖的客戶端在操作完成后刪除自己創建的節點,這樣會激發zook的事件給其它客戶端知道,這樣其它客戶端會重新執行(步驟3)。
舉例:加入客戶端0000000001獲取到鎖,然后客戶端0000000002加入進來獲取鎖,發現自己不是編號最小的,那么它會監聽它前面節點的事件(0000000001的事件)然后執行步驟(3),當客戶端0000000001操作完成后刪除自己的節點,這時zook服務端會發送事件,這時客戶端0000000002會接收到該事件,然后重復步驟3直到獲取到鎖)
上面的步驟實現了一個有序鎖,也就是先進入等待鎖的客戶端在鎖可用時先獲得鎖。
如果想要實現一個隨機鎖,那么只需要把PERSISTENT_SEQUENTIAL換成一個隨機數即可。
簡單示例:
package com.framework.code.demo.zook; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.recipes.locks.InterProcessMutex; import org.apache.curator.retry.ExponentialBackoffRetry; public class CuratorDemo { public static void main(String[] args) throws Exception { //操作失敗重試機制 1000毫秒間隔 重試3次 RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); //創建Curator客戶端 CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.18:2181", retryPolicy); //開始 client.start(); /** * 這個類是線程安全的,一個JVM創建一個就好 * mylock 為鎖的根目錄,我們可以針對不同的業務創建不同的根目錄 */ final InterProcessMutex lock = new InterProcessMutex(client, "/mylock"); try { //阻塞方法,獲取不到鎖線程會掛起。 lock.acquire(); System.out.println("已經獲取到鎖"); Thread.sleep(10000); } catch (Exception e) { e.printStackTrace(); } finally{ //釋放鎖,必須要放到finally里面,已確保上面方法出現異常時也能夠釋放鎖。 lock.release(); } Thread.sleep(10000); client.close(); } }
上面代碼再獲取鎖的地方暫停了10秒鐘,我們使用zook的客戶端去查看目錄的創建情況,由于我前面已經做了幾次測試,所以序號是從12開始的。
模擬多個客戶端(也可以認為是多個JVM):
現在把上面的代碼改造一下放入到線程中去執行,模擬多個客戶端測試。
public class CuratorDemo { public static void main(String[] args) throws Exception { for (int i = 0; i < 10; i++) { //啟動10個線程模擬多個客戶端 Jvmlock jl = new Jvmlock(i); new Thread(jl).start(); //這里加上300毫秒是為了讓線程按順序啟動,不然有可能4號線程比3號線程先啟動了,這樣測試就不準了。 Thread.sleep(300); } } public static class Jvmlock implements Runnable{ private int num; public Jvmlock(int num) { this.num = num; } @Override public void run() { RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3); CuratorFramework client = CuratorFrameworkFactory .newClient("192.168.142.128:2181", retryPolicy); client.start(); InterProcessMutex lock = new InterProcessMutex(client, "/mylock"); try { System.out.println("我是第" + num + "號線程,我開始獲取鎖"); lock.acquire(); System.out.println("我是第" + num + "號線程,我已經獲取鎖"); Thread.sleep(10000); } catch (Exception e) { e.printStackTrace(); } finally { try { lock.release(); } catch (Exception e) { e.printStackTrace(); } } client.close(); } } }
通過客戶端軟件我們可以看到10個申請鎖的節點已經被創建出來了。
看一下打印結果,先申請獲取鎖的線程在鎖可用時最先獲取到鎖,因為他們申請鎖時創建節點的順序號是遞增的,先申請鎖的客戶端創建的節點編號最小,所以先獲取到鎖
我是第0號線程,我開始獲取鎖 我是第0號線程,我已經獲取鎖 我是第1號線程,我開始獲取鎖 我是第2號線程,我開始獲取鎖 我是第3號線程,我開始獲取鎖 我是第4號線程,我開始獲取鎖 我是第5號線程,我開始獲取鎖 我是第6號線程,我開始獲取鎖 我是第7號線程,我開始獲取鎖 我是第8號線程,我開始獲取鎖 我是第9號線程,我開始獲取鎖 我是第1號線程,我已經獲取鎖 我是第2號線程,我已經獲取鎖 我是第3號線程,我已經獲取鎖 我是第4號線程,我已經獲取鎖 我是第5號線程,我已經獲取鎖 我是第6號線程,我已經獲取鎖 我是第7號線程,我已經獲取鎖 我是第8號線程,我已經獲取鎖 我是第9號線程,我已經獲取鎖
為什么節點的名稱要加上uuid,這是框架的英文解釋。
It turns out there is an edge case that exists when creating sequential-ephemeral nodes. The creation can succeed on the server, but the server can crash before the created node name is returned to the client. However, the ZK session is still valid so the ephemeral node is not deleted. Thus, there is no way for the client to determine what node was created for them.
Even without sequential-ephemeral, however, the create can succeed on the sever but the client (for various reasons) will not know it.
Putting the create builder into protection mode works around this. The name of the node that is created is prefixed with a GUID. If node creation fails the normal retry mechanism will occur. On the retry, the parent path is first searched for a node that has the GUID in it. If that node is found, it is assumed to be the lost node that was successfully created on the first try and is returned to the caller.
就是說 當客戶端創建了一個節點,這個創建的過程在zook的服務器端已經成功了,但是在將節點的路徑返回給客戶端之前服務器端掛了, 因為客戶端的session還是有效的,所以這個節點不會刪除, 這樣客戶端就不知道哪個節點是它創建的。
當客戶端發生創建失敗的時候,會進行重試,如果這個時候zook已經恢復可用,那么客戶端會查詢服務器端所有子節點,然后通過和自己創建的uuid對比,如果找到了,說明這個節點是它之前創建的,那么久直接使用它,不然這個節點就會成為一個死節點,導致死鎖。
實現非公平鎖:
重寫創建節點的方法,
package com.framework.code.demo.zook.lock; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.locks.StandardLockInternalsDriver; import org.apache.zookeeper.CreateMode; public class NoFairLockDriver extends StandardLockInternalsDriver { /** * 隨機數的長度 */ private int numLength; private static int DEFAULT_LENGTH = 5; public NoFairLockDriver() { this(DEFAULT_LENGTH); } public NoFairLockDriver(int numLength) { this.numLength = numLength; } @Override public String createsTheLock(CuratorFramework client, String path, byte[] lockNodeBytes) throws Exception { String newPath = path + getRandomSuffix(); String ourPath; if ( lockNodeBytes != null ) { //原來使用的是CreateMode.EPHEMERAL_SEQUENTIAL類型的節點 //節點名稱最終是這樣的_c_c8e86826-d3dd-46cc-8432-d91aed763c2e-lock-0000000025 //其中0000000025是zook服務器端資自動生成的自增序列 從0000000000開始 //所以每個客戶端創建節點的順序都是按照0,1,2,3這樣遞增的順序排列的,所以他們獲取鎖的順序與他們進入的順序是一致的,這也就是所謂的公平鎖 //現在我們將有序的編號換成隨機的數字,這樣在獲取鎖的時候變成非公平鎖了 ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL).forPath(newPath, lockNodeBytes); //ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, lockNodeBytes); } else { ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL).forPath(newPath); //ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path); } return ourPath; } /** * 獲得隨機數字符串 */ public String getRandomSuffix() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < numLength; i++) { sb.append((int) (Math.random() * 10)); } return sb.toString(); } }
把我們寫的類注冊進去:
InterProcessMutex lock = new InterProcessMutex(client,"/mylock", new NoFairLockDriver());
還是上面的例子,在跑一邊看結果,可以看到,獲取鎖的順序已經是無序的了,從而實現了非公平鎖。
我是第1號線程,我開始獲取鎖 我是第0號線程,我開始獲取鎖 我是第0號線程,我已經獲取鎖 我是第2號線程,我開始獲取鎖 我是第3號線程,我開始獲取鎖 我是第4號線程,我開始獲取鎖 我是第5號線程,我開始獲取鎖 我是第6號線程,我開始獲取鎖 我是第7號線程,我開始獲取鎖 我是第8號線程,我開始獲取鎖 我是第9號線程,我開始獲取鎖 我是第9號線程,我已經獲取鎖 我是第8號線程,我已經獲取鎖 我是第4號線程,我已經獲取鎖 我是第7號線程,我已經獲取鎖 我是第3號線程,我已經獲取鎖 我是第1號線程,我已經獲取鎖 我是第2號線程,我已經獲取鎖 我是第5號線程,我已經獲取鎖 我是第6號線程,我已經獲取鎖
到此,關于“如何使用ZooKeeper實現Java跨JVM的分布式鎖 ”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。