使用zookeeper實現分布式鎖是分布式鎖的實現方式的一種,相對於redis的實現,zookeeper的顯現能夠實現鎖的獲得順序,不出現死鎖等特點,關於zookeeper分布式鎖的實現原理大致總結如下:
- 客戶端向zookeeper的某一個持久節點下注冊臨時有序節點
- 獲取該父節點下的所有臨時有序節點,將自己剛才注冊的臨時節點與節點集合的第一個做比較,如果相同則表示自己是第一個注冊也就是可以獲得鎖的,否則,注冊比前置節點的監聽事件,如果監聽到前置節點被刪除(即節點狀態發生變化),則重新獲取節點集合並進行比較
原理性的東西網上很多很全面,現在我貼下代碼,做一個簡陋的分布式鎖的實現
1.下載zookeeper服務器到本地,啟動
https://zookeeper.apache.org/releases.html#download
解壓縮后,修改conf目錄下的zoo_sample.cfg復制為zoo.cfg,修改里面的配置,沒有/data和/log目錄就新建一個吧
在bin目錄下啟動zk_server.cmd,然后啟動zk_client.cmd客戶端來驗證下服務器是否已經啟動成功
2.pom.xml引入zkclient的客戶端jar包
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.11</version>
</dependency>
3.代碼實現
package com.cw.zklock; import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.ZkClient; import org.apache.zookeeper.ZooKeeper; import java.util.Collections; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; public class ZookeeperLock{ private ZkClient zkClient; private static final String ROOT_PATH="/lock"; private String lockName; private String eNodeName; private String eNodeNo; public ZookeeperLock(String lockName){ zkClient = new ZkClient("localhost:2181",2000); //連接zk this.lockName =ROOT_PATH.concat("/").concat(lockName); //創建持久節點,該節點下后面創建臨時有序節點 if (!zkClient.exists(this.lockName)) { zkClient.createPersistent(this.lockName); } } public void lock(){ for (;;) { if (eNodeName == null) { //創建臨時有序節點 eNodeName = zkClient.createEphemeralSequential(lockName.concat("/"), System.currentTimeMillis()); eNodeNo=eNodeName.substring(eNodeName.lastIndexOf("/")+1); } List<String> children = zkClient.getChildren(lockName); //節點排序 Collections.sort(children); if (children.size() > 0) { if (children.get(0).equals(eNodeNo)) { //自己創建的就是第一個臨時順序節點,意味着拿到了鎖 System.out.println(eNodeNo+"------------》獲得了鎖"); break; }else { CountDownLatch latch = new CountDownLatch(1); //獲取改節點的前一個節點,注冊監聽事件,並等待 String prevPath= children.get(children.indexOf(eNodeNo)-1); System.out.println(eNodeNo+"注冊監聽到------》"+prevPath); zkClient.subscribeDataChanges(lockName.concat("/").concat(prevPath), new IZkDataListener() { @Override public void handleDataChange(String s, Object o) throws Exception { latch.countDown(); } @Override public void handleDataDeleted(String s) throws Exception { System.out.println(eNodeNo+"!!!!!!!!!!!被喚醒"); latch.countDown(); } }); try { System.out.println(eNodeNo+"??????????????開始等待"); latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }else { //異常情況 System.out.println("子節點數據為空,拋出異常"); throw new RuntimeException("獲取鎖異常"); } } } public void unLock(){ zkClient.delete(eNodeName); zkClient.close(); System.out.println(eNodeNo+"********************釋放了鎖"); } public static void main(String[] args) { //測試5個線程的爭搶 int threadCount=5; CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { Thread thread = new Thread(()->{ try { latch.await(); ZookeeperLock lock = new ZookeeperLock("lockTest"); lock.lock(); //模擬處理業務邏輯 Thread.sleep(1000+ new Random().nextInt(100)*10); lock.unLock(); } catch (InterruptedException e) { e.printStackTrace(); } }); thread.start(); latch.countDown(); } } }