1.zookeeper作为配置中心
在springboot中,我们可以创建配置类。把配置类的配置信息放入到zookeeper,使用watch监控,当zookeeper的node的数据发生变化时,事件触发,获取最新的数据,修改配置类配置信息
2.zookeeper生成分布式唯一id
在过去的单库单表型系统中,通常可以使用数据库字段自带的auto_increment属性来自动为每条记录生成一个唯一的ID。但是分库分表后,就无法在依靠数据库的auto_increment属性来唯一标识一条记录了。此时我们就可以用zookeeper在分布式环境下生成全局唯一ID。
设计思路:
1.连接zookeeper服务器
2.指定路径生成有序节点
3.取有序节点名称上的序列号就可以作为分布式环境下的唯一ID
利用的是zookeeper的有序node的特性,它会给有序节点一个有序的序号,这个序号作为唯一id
3.zookeeper生成分布式锁
在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了很多并发处理相关的API,但是这些API在分布式场景中就无能为力了。也就是说单 纯的Java Api并不能提供分布式锁的能力。
分布式锁有多种实现方式,比如通过数据库、redis都可实现。作为分布式协同工具ZooKeeper,当然也有着标准的实现方式。下面介绍在zookeeper中如何实现排他锁。
设计思路:
利用zookeeper有序节点的生成会自动生成有序序号。当我要执行逻辑前,可以在一个node下去生成有序子节点,然后获取node下的子节点的列表,判断自己刚才生成的子节点是否是在第一位,如果是在第一位,代表获取了锁,执行逻辑,完成后删除刚才创建的子节点。否则就等待,并且监控排在前面的子节点被删除的事件,前一个节点被删除,释放锁了,取消等待,再判断自己是否排在第一位了,是,执行逻辑,释放锁。这样子就可以保证一个一个有序的执行了。执行顺序就是子节点的创建顺序。
1.客户端往/Locks下创建临时有序节点/Locks/Lock
2.客户端取得/Locks下子节点,并进行排序,判断排在最前面的是否为自己刚才创建的临时有序节点,如果自己刚才创建的锁节点在第一位,代表获取锁成功,执行逻辑完成,删除刚才创建的节点,完成锁的释放
3.如果自己的锁节点不在第一位,判断前一位node是否还存在,不存在,直接重新从第二步开始执行。若存在,则等待前一位node的删除监听事件触发
4.当前一位锁节点的逻辑执行完毕,释放锁了,再次从第二步开始执行,本节点再次判断是否获取锁
import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; public class MyLock { String IP = "192.168.60.130:2181"; // 计数器对象 CountDownLatch countDownLatch = new CountDownLatch(1); //ZooKeeper配置信息 ZooKeeper zooKeeper; private static final String LOCK_ROOT_PATH = "/Locks"; private static final String LOCK_NODE_NAME = "Lock_"; private String lockPath;
// 构造方法中打开zookeeper连接 public MyLock() { try { zooKeeper = new ZooKeeper(IP, 5000, new Watcher() { @Override public void process(WatchedEvent event) { if (event.getType() == Event.EventType.None) { if (event.getState() == Event.KeeperState.SyncConnected) { System.out.println("连接成功!"); countDownLatch.countDown(); } } } }); countDownLatch.await(); } catch (Exception ex) { ex.printStackTrace(); } }
//获取锁的方法 public void acquireLock() throws Exception { //首先自己创建node对象对应设计思路中的第一步
createLock();
//尝试获取锁 对应设计思路的2 3 4 5
attemptLock();
}
//创建锁节点 private void createLock() throws Exception { //判断Locks是否存在,不存在创建 Stat stat = zooKeeper.exists(LOCK_ROOT_PATH, false); if (stat == null) { zooKeeper.create(LOCK_ROOT_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } // 创建临时有序节点-返回的是新创建的临时节点的路径 lockPath = zooKeeper.create(LOCK_ROOT_PATH + "/" + LOCK_NODE_NAME, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL); System.out.println("节点创建成功:" + lockPath); }
//监视器对象,监视上一个节点是否被删除,这个监视器在设计思路的第三步中用到 Watcher watcher = new Watcher() { @Override public void process(WatchedEvent event) { if (event.getType() == Event.EventType.NodeDeleted) { synchronized (this) { notifyAll(); } } } };
//尝试获取锁 private void attemptLock() throws Exception { // 获取Locks节点下的所有子节点 - 对应涉及思路第二步获取子节点 List<String> list = zooKeeper.getChildren(LOCK_ROOT_PATH, false); // 对子节点进行排序 对应涉及思路第二步 对子节点进行排序 Collections.sort(list); // /Locks/Lock_000000001 int index = list.indexOf(lockPath.substring(LOCK_ROOT_PATH.length() + 1)); //对应涉及思路第二步 lockPath.substring(LOCK_ROOT_PATH.length() + 1)是获取刚才创建的子节点的名称 list.indexOf是获取刚才创建的子节点在子节点列表中的排序 if (index == 0) { //对应涉及思路第二步 排在第一位,标识获取锁成功,可以执行 System.out.println("获取锁成功!"); return; } else { //对应涉及思路第三步 不在第一位,获取锁失败,监听子节点列表中前一位的节点 // 上一个节点的路径 String path = list.get(index ‐ 1); Stat stat = zooKeeper.exists(LOCK_ROOT_PATH + "/" + path, watcher); if (stat == null) { //stat==null 表示前一位子节点没有了,再次调用本方法 对应设计思路第三步,判断前一个节点是否存在 attemptLock(); } else { //若是前一位节点还存在, synchronized (watcher) { watcher.wait(); //等待前一位节点被删除,前一位节点被删除,会调用watcher 的porcess方法,process方法中调用noticeAll方法,wait结束,只需往下执行 } attemptLock(); //对应设计思路第四 再次调用本方法 } } } //释放锁 public void releaseLock() throws Exception { //删除临时有序节点 zooKeeper.delete(this.lockPath,‐1); zooKeeper.close(); System.out.println("锁已经释放:"+this.lockPath); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?