使用ZooKeeper實現Java跨JVM的分佈式鎖

1、使用ZooKeeper實現Java跨JVM的分佈式鎖java

2、使用ZooKeeper實現Java跨JVM的分佈式鎖(優化構思)node

3、使用ZooKeeper實現Java跨JVM的分佈式鎖(讀寫鎖)apache

 

說明:本文是使用Curator框架進行講解及演示,Curator是對Zookeeper客戶端的一個封裝,由於Zookeeper的客戶端實現偏底層,若是想要實現鎖或其餘功能都須要本身封裝,實現一些簡單的功能還能夠,若是想要實現鎖這種高併發下的東西,不建議本身封裝,除非你自信你寫的東西比國外大神寫的還好~ 若是是研究學習到是能夠本身寫一下,同時也能夠看看開源的代碼,那裏面仍是有不少值得學習的東西。安全

Zookeeper版本爲 Release 3.4.8(stable)服務器

Curator版本爲2.9.1session

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. <dependency>  
  2.     <groupId>org.apache.zookeeper</groupId>  
  3.     <artifactId>zookeeper</artifactId>  
  4.     <version>3.4.8</version>  
  5. </dependency>  
  6.   
  7. <dependency>  
  8.     <groupId>org.apache.curator</groupId>  
  9.     <artifactId>curator-recipes</artifactId>  
  10.     <version>2.9.1</version>  
  11. </dependency>  
  12.   
  13. <dependency>  
  14.     <groupId>org.apache.curator</groupId>  
  15.     <artifactId>curator-client</artifactId>  
  16.     <version>2.9.1</version>  
  17. </dependency>  

 

 

鎖原理:併發

一、首先要建立一個鎖的根節點,好比/mylock。app

 

二、想要獲取鎖的客戶端在鎖的根節點下面建立znode,做爲/mylock的子節點,節點的類型要選擇CreateMode.PERSISTENT_SEQUENTIAL,節點的名字最好用uuid(至於爲何用uuid我後面會講,先說一下~若是不這麼作在某種狀況下會發生死鎖,這一點我看了不少國內朋友本身的實現,都沒有考慮到這一層,這也是我爲何不建議你們本身去封裝這種鎖,由於它確實很複雜),假設目前同時有3個客戶端想要得到鎖,那麼/mylock下的目錄應該是這個樣子的。框架

xxx-lock-0000000001,xxx-lock-0000000002,xxx-lock-0000000003dom

xxx爲uuid , 0000000001,0000000002,0000000003 是zook服務端自動生成的自增數字。

 

三、當前客戶端經過getChildren(/mylock)獲取全部子節點列表並根據自增數字排序,而後判斷一下本身建立的節點的順序是否是在列表當中最小的,若是是 那麼獲取到鎖,若是不是,那麼獲取本身的前一個節點,並設置監聽這個節點的變化,當節點變化時從新執行步驟3 直到本身是編號最小的一個爲止。

舉例:假設當前客戶端建立的節點是0000000002,由於它的編號不是最小的,因此獲取不到鎖,那麼它就找到它前面的一個節點0000000001 並對它設置監聽。

 

四、釋放鎖,當前得到鎖的客戶端在操做完成後刪除本身建立的節點,這樣會激發zook的事件給其它客戶端知道,這樣其它客戶端會從新執行(步驟3)。

舉例:加入客戶端0000000001獲取到鎖,而後客戶端0000000002加入進來獲取鎖,發現本身不是編號最小的,那麼它會監聽它前面節點的事件(0000000001的事件)而後執行步驟(3),當客戶端0000000001操做完成後刪除本身的節點,這時zook服務端會發送事件,這時客戶端0000000002會接收到該事件,而後重複步驟3直到獲取到鎖)

 

上面的步驟實現了一個有序鎖,也就是先進入等待鎖的客戶端在鎖可用時先得到鎖。

若是想要實現一個隨機鎖,那麼只須要把PERSISTENT_SEQUENTIAL換成一個隨機數便可。

 

簡單示例:

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. package com.framework.code.demo.zook;  
  2.   
  3. import org.apache.curator.RetryPolicy;  
  4. import org.apache.curator.framework.CuratorFramework;  
  5. import org.apache.curator.framework.CuratorFrameworkFactory;  
  6. import org.apache.curator.framework.recipes.locks.InterProcessMutex;  
  7. import org.apache.curator.retry.ExponentialBackoffRetry;  
  8.   
  9. public class CuratorDemo {  
  10.   
  11.     public static void main(String[] args) throws Exception {  
  12.           
  13.         //操做失敗重試機制 1000毫秒間隔 重試3次  
  14.         RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);  
  15.         //建立Curator客戶端  
  16.         CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.18:2181", retryPolicy);  
  17.         //開始  
  18.         client.start();  
  19.           
  20.         /** 
  21.          * 這個類是線程安全的,一個JVM建立一個就好 
  22.          * mylock 爲鎖的根目錄,咱們能夠針對不一樣的業務建立不一樣的根目錄 
  23.          */  
  24.         final InterProcessMutex lock = new InterProcessMutex(client, "/mylock");  
  25.         try {  
  26.             //阻塞方法,獲取不到鎖線程會掛起。  
  27.             lock.acquire();  
  28.             System.out.println("已經獲取到鎖");  
  29.              Thread.sleep(10000);  
  30.         } catch (Exception e) {  
  31.             e.printStackTrace();  
  32.         }  
  33.         finally{  
  34.             //釋放鎖,必需要放到finally裏面,已確保上面方法出現異常時也可以釋放鎖。  
  35.             lock.release();  
  36.         }  
  37.           
  38.         Thread.sleep(10000);  
  39.           
  40.         client.close();  
  41.     }  
  42.   
  43. }  


上面代碼再獲取鎖的地方暫停了10秒鐘,咱們使用zook的客戶端去查看目錄的建立狀況,因爲我前面已經作了幾回測試,因此序號是從12開始的。

 

 

 

模擬多個客戶端(也能夠認爲是多個JVM):

如今把上面的代碼改造一下放入到線程中去執行,模擬多個客戶端測試

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public class CuratorDemo {  
  2.   
  3.     public static void main(String[] args) throws Exception {  
  4.         for (int i = 0; i < 10; i++) {  
  5.             //啓動10個線程模擬多個客戶端  
  6.             Jvmlock jl = new Jvmlock(i);  
  7.             new Thread(jl).start();  
  8.             //這裏加上300毫秒是爲了讓線程按順序啓動,否則有可能4號線程比3號線程先啓動了,這樣測試就不許了。  
  9.             Thread.sleep(300);  
  10.         }  
  11.     }  
  12.       
  13.     public static class Jvmlock implements Runnable{  
  14.           
  15.         private int num;  
  16.         public Jvmlock(int num) {  
  17.             this.num = num;  
  18.         }  
  19.           
  20.         @Override  
  21.         public void run() {  
  22.             RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);  
  23.             CuratorFramework client = CuratorFrameworkFactory  
  24.                     .newClient("192.168.142.128:2181", retryPolicy);  
  25.             client.start();  
  26.               
  27.             InterProcessMutex lock = new InterProcessMutex(client,  
  28.                     "/mylock");  
  29.             try {  
  30.                 System.out.println("我是第" + num + "號線程,我開始獲取鎖");  
  31.                 lock.acquire();  
  32.                 System.out.println("我是第" + num + "號線程,我已經獲取鎖");  
  33.                 Thread.sleep(10000);  
  34.             } catch (Exception e) {  
  35.                 e.printStackTrace();  
  36.             } finally {  
  37.                 try {  
  38.                     lock.release();  
  39.                 } catch (Exception e) {  
  40.                     e.printStackTrace();  
  41.                 }  
  42.             }  
  43.             client.close();  
  44.         }  
  45.     }  
  46.   
  47. }  


經過客戶端軟件咱們能夠看到10個申請鎖的節點已經被建立出來了。

 

看一下打印結果,先申請獲取鎖的線程在鎖可用時最早獲取到鎖,由於他們申請鎖時建立節點的順序號是遞增的,先申請鎖的客戶端建立的節點編號最小,因此先獲取到鎖

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. 我是第0號線程,我開始獲取鎖  
  2. 我是第0號線程,我已經獲取鎖  
  3. 我是第1號線程,我開始獲取鎖  
  4. 我是第2號線程,我開始獲取鎖  
  5. 我是第3號線程,我開始獲取鎖  
  6. 我是第4號線程,我開始獲取鎖  
  7. 我是第5號線程,我開始獲取鎖  
  8. 我是第6號線程,我開始獲取鎖  
  9. 我是第7號線程,我開始獲取鎖  
  10. 我是第8號線程,我開始獲取鎖  
  11. 我是第9號線程,我開始獲取鎖  
  12. 我是第1號線程,我已經獲取鎖  
  13. 我是第2號線程,我已經獲取鎖  
  14. 我是第3號線程,我已經獲取鎖  
  15. 我是第4號線程,我已經獲取鎖  
  16. 我是第5號線程,我已經獲取鎖  
  17. 我是第6號線程,我已經獲取鎖  
  18. 我是第7號線程,我已經獲取鎖  
  19. 我是第8號線程,我已經獲取鎖  
  20. 我是第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對比,若是找到了,說明這個節點是它以前建立的,那麼久直接使用它,否則這個節點就會成爲一個死節點,致使死鎖。

 

實現非公平鎖:

重寫建立節點的方法,

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. package com.framework.code.demo.zook.lock;  
  2.   
  3. import org.apache.curator.framework.CuratorFramework;  
  4. import org.apache.curator.framework.recipes.locks.StandardLockInternalsDriver;  
  5. import org.apache.zookeeper.CreateMode;  
  6.   
  7. public class NoFairLockDriver extends StandardLockInternalsDriver {  
  8.   
  9.     /** 
  10.      * 隨機數的長度 
  11.      */  
  12.     private int numLength;   
  13.     private static int DEFAULT_LENGTH = 5;  
  14.       
  15.     public NoFairLockDriver() {  
  16.         this(DEFAULT_LENGTH);  
  17.     }  
  18.       
  19.     public NoFairLockDriver(int numLength) {  
  20.         this.numLength = numLength;  
  21.     }  
  22.       
  23.     @Override  
  24.     public String createsTheLock(CuratorFramework client, String path, byte[] lockNodeBytes) throws Exception  
  25.     {  
  26.         String newPath = path + getRandomSuffix();  
  27.         String ourPath;  
  28.         if ( lockNodeBytes != null )  
  29.         {  
  30.             //原來使用的是CreateMode.EPHEMERAL_SEQUENTIAL類型的節點  
  31.             //節點名稱最終是這樣的_c_c8e86826-d3dd-46cc-8432-d91aed763c2e-lock-0000000025  
  32.             //其中0000000025是zook服務器端資自動生成的自增序列 從0000000000開始  
  33.             //因此每一個客戶端建立節點的順序都是按照0,1,2,3這樣遞增的順序排列的,因此他們獲取鎖的順序與他們進入的順序是一致的,這也就是所謂的公平鎖  
  34.             //如今咱們將有序的編號換成隨機的數字,這樣在獲取鎖的時候變成非公平鎖了  
  35.             ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL).forPath(newPath, lockNodeBytes);  
  36.             //ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, lockNodeBytes);  
  37.         }  
  38.         else  
  39.         {  
  40.             ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL).forPath(newPath);  
  41.             //ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path);  
  42.         }  
  43.         return ourPath;  
  44.     }  
  45.       
  46.     /** 
  47.      * 得到隨機數字符串 
  48.      */  
  49.     public String getRandomSuffix() {  
  50.         StringBuilder sb = new StringBuilder();  
  51.         for (int i = 0; i < numLength; i++) {  
  52.             sb.append((int) (Math.random() * 10));  
  53.         }  
  54.         return sb.toString();  
  55.     }  
  56.       
  57. }  



 

把咱們寫的類註冊進去:

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. InterProcessMutex lock = new InterProcessMutex(client,"/mylock", new NoFairLockDriver());  


仍是上面的例子,在跑一邊看結果,能夠看到,獲取鎖的順序已是無序的了,從而實現了非公平鎖。

 

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
    1. 我是第1號線程,我開始獲取鎖  
    2. 我是第0號線程,我開始獲取鎖  
    3. 我是第0號線程,我已經獲取鎖  
    4. 我是第2號線程,我開始獲取鎖  
    5. 我是第3號線程,我開始獲取鎖  
    6. 我是第4號線程,我開始獲取鎖  
    7. 我是第5號線程,我開始獲取鎖  
    8. 我是第6號線程,我開始獲取鎖  
    9. 我是第7號線程,我開始獲取鎖  
    10. 我是第8號線程,我開始獲取鎖  
    11. 我是第9號線程,我開始獲取鎖  
    12. 我是第9號線程,我已經獲取鎖  
    13. 我是第8號線程,我已經獲取鎖  
    14. 我是第4號線程,我已經獲取鎖  
    15. 我是第7號線程,我已經獲取鎖  
    16. 我是第3號線程,我已經獲取鎖  
    17. 我是第1號線程,我已經獲取鎖  
    18. 我是第2號線程,我已經獲取鎖  
    19. 我是第5號線程,我已經獲取鎖  
    20. 我是第6號線程,我已經獲取鎖  
相關文章
相關標籤/搜索