使用Zookeeper实现分布式锁

在单体项目中JVM中的锁就可以完成大部分需求,但是在分布式项目中,项目被拆分成各个模块,分别部署在不同的服务器中,比如一个完成一个订单可以需要多个操作,订单模块生成订单信息,支付模块完成支付,库存模块进行相关商品信息增减等等,往往一个业务需要多个模块共同完成操作,这个时候需要分布式锁对我们要操作的资源进行锁定,方便我们进行业务操作。分布式锁实现方式有很多种,有数据库实现,有缓存实现,也可以使用Zookeeper,在Zookeeper中实现分布式锁,最重要的特点是利用临时序号节点来实现。

初始代码实现:

复制
/** * 原生Zookeeper实现分布式锁,原理利用zookeeper临时序号节点的特点 */ public class DistributedLock1 { private static final String connectionLocalhost = "*.*.*.*:2181"; private static final int timeout = 60000; private ZooKeeper zooKeeper; private CountDownLatch connectionLatch = new CountDownLatch(1); private CountDownLatch isLock = new CountDownLatch(1); private String preNode; private String currentNode; public DistributedLock1(){ try{ zooKeeper = new ZooKeeper(connectionLocalhost,timeout,(watcher) -> { //只有与zookeeper集群建立连接之后才能进行后续操作 if(watcher.getState() == Watcher.Event.KeeperState.SyncConnected){ if(watcher.getType() == Watcher.Event.EventType.None){ System.out.println("成功与Zookeeper集群连接"); connectionLatch.countDown(); } } //当前节点的前一个临时序号节点删除的时候会触发当前节点获取分布式锁 if(watcher.getType() == Watcher.Event.EventType.NodeDeleted && watcher.getPath().equals(preNode)){ isLock.countDown(); } }); //成功建立连接之后才能进行后续操作 connectionLatch.await(); Stat stat = zooKeeper.exists("/locks", false); if(stat == null){ //根节点不存在的时候先建立根节点 zooKeeper.create("/locks","locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } }catch (Exception e){ e.printStackTrace(); } } /** * 上锁 */ public void lock(){ try { //创建临时序号节点 currentNode = zooKeeper.create("/locks/sub-","sub".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL); List<String> children = zooKeeper.getChildren("/locks", false); if(children.size() == 1){ //只有一个子节点,肯定是当前创建的临时序号节点,直接获取锁 return; } else { //获取当前节点在集合中的次序 Collections.sort(children); String thisPath = currentNode.substring("/locks/".length()); int index = children.indexOf(thisPath); if(index == 0){ //当前节点就是最小的节点,直接获取锁 return; } else if(index == -1){ System.out.println("获取节点索引错误"); }else{ this.preNode = "/locks/" + children.get(index-1); zooKeeper.getData(preNode,true,new Stat()); isLock.await(); return; } } } catch (KeeperException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } } /** * 释放锁 */ public void unlock(){ try{ zooKeeper.delete(currentNode,-1); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (KeeperException e) { throw new RuntimeException(e); } } }

创建两个线模拟:

上面的实现方式虽然实现分布式锁相关内容,但是整个架构设计不太好,不利于扩展,假如我现在要切换到Redis的实现方式,那么整个项目代码又得重复开发,不能复用,所以,我们对上面代码使用模板设计模式进行重新优化:

先看看顶层接口基类:

复制
/** * 分布式基类接口 */ public interface Lock { /** * 获取锁 */ void requireLock(); /** * 释放锁 */ void releaseLock(); }

只定义了两个最基本的方法,获取锁和释放锁

再看看抽象类:

复制
/** *模板抽象类 */ public abstract class AbstractTemplateLock implements Lock{ protected abstract boolean tryLock(); protected abstract void waitLock(); protected abstract void unLock(); @Override public void releaseLock() { unLock(); System.out.println(Thread.currentThread().getName() + "成功释放分布式锁"); } @Override public void requireLock() { if(tryLock()){ System.out.println(Thread.currentThread().getName() + "成功获取到分布式锁"); }else{ //等待并监听 waitLock(); //尝试重新获取锁 requireLock(); } } }

在获取锁的时候,先去判断是否成功获取到锁,如果成功获取到锁就继续,没有获取到锁就等待并监听,并再次尝试获取锁。

再看看基于Zookeeper中临时顺序节点原理实现方式:

复制
/** * 基于Zookeeper实现分布式锁 */ public class ZookeeperTemplateLock extends AbstractTemplateLock{ private static final String connectionLocalhost = "*.*.*.*:2181"; private static final int connectionTimeout = 60000; private ZooKeeper zooKeeper; private String currentNode; private String preNode; private CountDownLatch connectionLatch = new CountDownLatch(1); private CountDownLatch isLock = new CountDownLatch(1); public ZookeeperTemplateLock(){ try { zooKeeper = new ZooKeeper(connectionLocalhost,connectionTimeout,watcher -> { //连接成功建立的时候触发监听 if(watcher.getState() == Watcher.Event.KeeperState.SyncConnected){ if(watcher.getType() == Watcher.Event.EventType.None){ System.out.println("与Zookeeper连接建立成功"); connectionLatch.countDown(); } } //当前节点的前一个节点被删除的时候触发监听 if(watcher.getType() == Watcher.Event.EventType.NodeDeleted && watcher.getPath().equals(preNode)){ isLock.countDown(); } }); //只有当连接成功建立的时候才能继续后续操作,否则一直阻塞等待 connectionLatch.await(); Stat stat = zooKeeper.exists("/zkLocks", false); if(stat == null){ zooKeeper.create("/zkLocks","zkLocks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } catch (IOException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (KeeperException e) { throw new RuntimeException(e); } } @Override protected boolean tryLock() { try { if(currentNode == null){ currentNode = zooKeeper.create("/zkLocks/sub-", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); } List<String> children = zooKeeper.getChildren("/zkLocks", false); if(children.size() == 1){ //只有一个子节点,肯定是刚刚创建的子节点,获取锁 return true; }else{ Collections.sort(children); String thisPath = currentNode.substring("/zkLocks/".length()); int index = children.indexOf(thisPath); if(index == 0){ //当前节点是第一个节点获取锁 return true; }else{ preNode = "/zkLocks/" + children.get(index-1); return false; } } } catch (KeeperException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override protected void waitLock() { try { zooKeeper.getData(preNode,true,new Stat()); //阻塞并等待前一个节点删除 isLock.await(); } catch (KeeperException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override protected void unLock() { try { zooKeeper.delete(currentNode,-1); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (KeeperException e) { throw new RuntimeException(e); } } }

测试:创建6个线程并发执行

上面的设计架构有很好的扩展型,当我们想要切换到Redis或者其它实现方式的时候,只需要根据抽象类重写相应方式即可,整体架构不会变。

posted @   无涯子wyz  阅读(283)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示