当前位置: 首页 > article >正文

zookeeper集群与分布式锁二

1.分布式锁概述1.1 什么是分布式锁1要介绍分布式锁首先要提到与分布式锁相对应的是线程锁。线程锁主要用来给方法、代码块加锁。当某个方法或代码使用锁在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果因为线程锁的实现在根本上是依靠线程之间共享内存实现的比如synchronized是共享对象头显示锁Lock是共享某个变量state。分布式锁分布式锁即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题而分布式锁就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是分布式系统中竞争共享资源的最小粒度从线程升级成了进程。分布式锁是在分布式或者集群环境下 多进程可见并且互斥的锁。2分布式锁介绍传统单体应用单机部署的情况下可以使用并发处理相关的功能进行互斥控制但是原单体单机部署的系统被演化成分布式集群系统后由于分布式系统多线程、多进程并且分布在不同机器上这将使原单机部署情况下的并发控制锁策略失效。提出分布式锁的概念是为了解决跨机器的互斥机制来控制共享资源的访问。分布式场景下解决并发问题需要应用分布式锁技术。如上图所示分布式锁的目的是保证在分布式部署的应用集群中多个服务在请求同一个方法或者同一个业务操作的情况下对应业务逻辑只能被一台机器上的一个线程执行避免出现并发问题。1.2 分布式锁的设计原则Redis官网上对使用分布式锁提出至少需要满足如下三个要求互斥属于安全性: 在任何给定时刻只有一个客户端可以持有锁。无死锁属于有效性: 即如果一个线程已经持有了锁那么它可以多次获取该锁而不会发生死锁。容错性属于有效性: 如果一个线程获取了锁那么即使崩溃或者失去连接锁也必须被释放。除此之外分布式锁的设计中还可以需要考虑加锁解锁的同源性A加的锁不能被B解锁。获取锁非阻塞如果获取不到锁不能无限期等待在某个服务来获取锁时假设该锁已经被另一个服务获取我们要能直接返回失败不能一直等待。。锁失效机制假设某个应用获取到锁之后一直没有来释放锁可能服务本身已经挂掉了不能一直不释放导致其他服务一直获取不到锁。高性能加锁解锁是高性能的加锁时间一般是几毫秒。我们这个分布式锁可能会有很多的服务器来获取所以加锁解锁一定是需要高能的。高可用为了避免单点故障锁需要有一定的容错方式。例如锁服务本身就是一个集群的形式。1.3 分布式锁的实现方式分布式锁的使用流程 加锁 -----》 执行业务逻辑 ----》释放锁基于数据库实现分布式锁基于 redis 实现分布式锁基于 zookeeper实现分布式锁2.基于mysql实现分布式锁基于Mysql实现分布式锁适用于对性能要求不高并且不希望因为要使用分布式锁而引入新组件。可以利用唯一键索引不能重复插入的特点实现。2.1 基于唯一索引实现2.1.1 实现思路创建锁表内部存在字段表示资源名及资源描述同一资源名使用数据库唯一性限制。多个进程同时往数据库锁表中写入对某个资源的占有记录当某个进程成功写入时则表示其获取锁成功其他进程由于资源字段唯一性限制插入失败陷入自旋并且失败重试。当执行完业务后持有该锁的进程则删除该表内的记录此时回到步骤一。2.1.2 创建数据库以及表在mysql下创建数据库名为:distribute_lock(这里使用navicat创建)多个进程同时往表中插入记录锁资源为1描述为测试锁插入成功则执行流程执行完流程后删除其在数据库表中的记录。create table database_lock( id BIGINT NOT NULL AUTO_INCREMENT, resource INT NOT NULL COMMENT 锁资源, description varchar(1024) NOT NULL DEFAULT COMMENT 描述, PRIMARY KEY (id), UNIQUE KEY resource (resource) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT数据库分布式锁表;2.1.3 创建maven工程创建maven工程distribute-lock,引入依赖?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion ​ groupIdcom.nnn/groupId artifactIdzk-client1/artifactId version1.0-SNAPSHOT/version ​ dependencies dependency groupIdjunit/groupId artifactIdjunit/artifactId version4.10/version scopetest/scope /dependency ​ !--curator-- dependency groupIdorg.apache.curator/groupId artifactIdcurator-framework/artifactId version4.0.0/version /dependency ​ dependency groupIdorg.apache.curator/groupId artifactIdcurator-recipes/artifactId version4.0.0/version /dependency !--日志-- dependency groupIdorg.slf4j/groupId artifactIdslf4j-api/artifactId version1.7.21/version /dependency ​ dependency groupIdorg.slf4j/groupId artifactIdslf4j-log4j12/artifactId version1.7.21/version /dependency ​ !-- zookeeper -- dependency groupIdcom.101tec/groupId artifactIdzkclient/artifactId version0.10/version /dependency ​ !-- lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.6/version /dependency ​ !-- jdbc -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.48/version /dependency ​ /dependencies ​ build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.1/version configuration source1.8/source target1.8/target /configuration /plugin /plugins /build /project添加数据库配置文件db.propertiesdrivercom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/distribute_lock?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai userroot password123456创建包结构基础包结构 com.mashibing.lock 在lock包下创建mysql包导入工具类PropertiesReaderSlf4j public class PropertiesReader { ​ // Properties缓存文件 private static final MapString, Properties propertiesCache new HashMapString, Properties(); ​ public static Properties getProperties(String propertiesName) throws IOException { if (propertiesCache.containsKey(propertiesName)) { return propertiesCache.get(propertiesName); } loadProperties(propertiesName); return propertiesCache.get(propertiesName); } ​ private synchronized static void loadProperties(String propertiesName) throws IOException { FileReader fileReader null; ​ try { // 创建Properties集合类 Properties pro new Properties(); // 获取src路径下的文件---ClassLoader类加载器 ClassLoader classLoader PropertiesReader.class.getClassLoader(); URL resource classLoader.getResource(propertiesName); // 获取配置路径 String path resource.getPath(); // 读取文件 fileReader new FileReader(path); // 加载文件 pro.load(fileReader); // 初始化 propertiesCache.put(propertiesName, pro); } catch (IOException e) { log.error(读取Properties文件失败Properties名为: propertiesName); throw e; } finally { try { if (fileReader ! null) { fileReader.close(); } } catch (IOException e) { log.error(fileReader关闭失败, e); } } } } JDBCUtils Slf4j public class JDBCUtils { ​ private static String url; private static String user; private static String password; ​ static { //读取文件获取值 try { Properties properties PropertiesReader.getProperties(db.properties); url properties.getProperty(url); user properties.getProperty(user); password properties.getProperty(password); String driver properties.getProperty(driver); //4.注册驱动 Class.forName(driver); } catch (IOException | ClassNotFoundException e) { log.error(初始化jdbc连接失败, e); } } ​ /** * 获取连接 * * return 连接对象 */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } ​ /** * 释放资源 * * param rs * param st * param conn */ public static void close(ResultSet rs, Statement st, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st ! null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }2.1.4 数据库操作类/** * MySQL 锁操作类加锁释放锁 */ Slf4j public class MySQLDistributedLockService { ​ private static Connection connection; private static Statement statement; private static ResultSet resultSet; ​ static{ try { connection JDBCUtils.getConnection(); statement connection.createStatement(); resultSet null; } catch (SQLException e) { log.error(数据库连接失败); } } ​ /** * 锁表 - 获取锁 * param resource 资源 * param description 锁描述 * return 是否操作成功 */ public static boolean tryLock(int resource,String description){ ​ String sql insert into database_lock (resource,description) values ( resource , description );; ​ //获取数据库连接 try { int stat statement.executeUpdate(sql); return stat 1; } catch (SQLException e) { return false; } } ​ /** * 锁表-释放锁 * return */ public static boolean releaseLock(int resource) throws SQLException { String sql delete from database_lock where resource resource; //获取数据库连接 int stat statement.executeUpdate(sql); return stat 1; } ​ /** * 关闭连接 */ public static void close(){ log.info(当前线程 ManagementFactory.getRuntimeMXBean().getName().split()[0] ,关闭了数据库连接); JDBCUtils.close(resultSet,statement,connection); } }2.1.5 创建LockTable/** * mysql分布式锁 * 执行流程 多进程抢占数据库某个资源然后执行业务执行完释放资源 * 锁机制 单一进程获取锁时则其他进程提交失败 */ Slf4j public class LockTable extends Thread { ​ Override public void run() { super.run(); ​ //获取Java虚拟机的进程ID String pid ManagementFactory.getRuntimeMXBean().getName().split()[0]; try{ while(true){ log.info(当前进程PID pid ,尝试获取锁资源); if(MySQLDistributedLockService.tryLock(1,测试锁)){ log.info(当前进程PID pid ,获取锁资源成功); ​ //sleep模拟业务处理过程 log.info(开始处理业务); Thread.sleep(10*1000); log.info(业务处理完成); ​ MySQLDistributedLockService.releaseLock(1); log.info(当前进程PID pid ,释放了锁资源); break; }else{ log.info(当前进程PID pid 获取锁资源失败); Thread.sleep(2000); } } }catch (Exception e){ log.error(抢占锁发生错误,e); }finally { MySQLDistributedLockService.close(); } } ​ // 程序入口 public static void main(String[] args) { ​ new LockTable().start(); } }2.1.6 分布式锁测试运行时开启并行执行选项每次运行三个或三个以上进程. Allow parallel run 运行并行执行三个进程的执行情况注意事项该锁为非阻塞的当某进程持有锁并且挂死时候会造成资源一直不释放的情况造成死锁因此需要维护一个定时清理任务去清理持有过久的锁要注意数据库的单点问题最好设置备库进一步提高可靠性该锁为非可重入锁如果要设置成可重入锁需要添加数据库字段记录持有该锁的设备信息以及加锁次数2.2 基于乐观锁2.2.1 需求分析需求 数据库中设定某商品基本信息名为外科口罩数量为10多进程对该商品进行抢购当商品数量为0时结束抢购。创建表# 创建表 create table database_lock_2( id BIGINT NOT NULL AUTO_INCREMENT, good_name VARCHAR(256) NOT NULL DEFAULT COMMENT 商品名称, good_count INT NOT NULL COMMENT 商品数量, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT数据库分布式锁表2; # 插入原始数据 insert into database_lock_2 (good_name,good_count) values (医用口罩,10);2.2.2 实现思路每次执行业务前首先进行数据库查询查询当前的需要修改的资源值或版本号。进行资源的修改操作并且修改前进行资源或版本号的比对操作比较此时数据库中的值是否和上一步查询结果相同。查询结果相同则修改对应资源值不同则回到第一步。2.2.3 代码实现在MySQLDistributedLockService中添加对乐观锁的操作/** * 乐观锁-获取资源 * param id 资源ID * return result */ public static ResultSet getGoodCount(int id) throws SQLException { String sql select * from database_lock_2 where id id; //查询数据 resultSet statement.executeQuery(sql); return resultSet; } /** * 乐观锁-修改资源 * param id 资源ID * param goodCount 资源 * return 修改状态 */ public static boolean setGoodCount(int id, int goodCount) throws SQLException { String sql update database_lock_2 set good_count good_count - 1 where id id and good_count goodCount; int stat statement.executeUpdate(sql); return stat 1; } /** * 乐观锁-开启事务自动提交 */ public static void AutoCommit(){ try { connection.setAutoCommit(true); } catch (SQLException e) { log.error(开启自动提交,e); } }创建OptimisticLock模拟并发操作分布式锁/** * mysql分布式锁-乐观锁 * 执行流程 多进程抢购同一商品每次抢购成功商品数量-1商品数据量为0时退出 * 锁机制 单一进程获取锁时则其他进程提交失败 */ Slf4j public class OptimisticLock extends Thread{ Override public void run() { super.run(); String pid ManagementFactory.getRuntimeMXBean().getName().split()[0]; ResultSet resultSet null; String goodName null; int goodCount 0; try { while(true){ log.info(当前线程 pid 开始抢购商品); //获取当前商品信息 resultSet MySQLDistributedLockService.getGoodCount(1); while (resultSet.next()){ goodName resultSet.getString(good_name); goodCount resultSet.getInt(good_count); } log.info(获取库存成功当前商品名为 goodName 当前库存剩余量为 goodCount); //模拟执行业务操作 Thread.sleep(2*3000); if(0 goodCount){ log.info(抢购失败当前库存为0 ); break; } //修改库存信息库存量-1 if(MySQLDistributedLockService.setGoodCount(1,goodCount)){ log.info(当前线程 pid 抢购商品 goodName 成功剩余库存为 (goodCount -1)); //模拟延迟防止锁每次被同一进程获取 Thread.sleep(2 * 1000); }else{ log.error(抢购商品 goodName 失败商品数量已被修改); } } }catch (Exception e){ log.error(抢购商品发生错误,e); }finally { if(resultSet ! null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); log.error(关闭Result失败 , e); } } MySQLDistributedLockService.close(); } } public static void main(String[] args) { new OptimisticLock().start(); } }2.3.4 代码测试开启三个进程查看执行情况9 7 4 18 5 26 3注意事项该锁为非阻塞的该锁对于业务具有侵入式如果设置版本号校验则增加的额外的字段增加了数据库冗余当并发量过高时会有大量请求访问数据库的某行记录对数据库造成很大的写压力因此乐观锁适用于并发量不高并且写操作不频繁的场景2.3 基于悲观锁2.3.1 实现思路关闭jdbc连接自动commit属性每次执行业务前先使用查询语句后接for update表示锁定该行数据注意查询条件如果未命中主键或索引此时将会从行锁变为表锁执行业务流程修改表资源执行commit操作2.3.2 代码实现在MySQLDistributedLockService中添加对悲观锁的操作/** * 悲观锁-获取资源 * param id 资源ID * return result */ public static ResultSet getGoodCount2(int id) throws SQLException { String sql select * from database_lock_2 where id id for update; //查询数据 resultSet statement.executeQuery(sql); return resultSet; } /** * 悲观锁-修改资源 * param id 资源ID * return 修改状态 */ public static boolean setGoodCount2(int id) throws SQLException { String sql update database_lock_2 set good_count good_count - 1 where id id; int stat statement.executeUpdate(sql); return stat 1; } /** * 悲观锁-关闭事务自动提交 */ public static void closeAutoCommit(){ try { connection.setAutoCommit(false); } catch (SQLException e) { log.error(关闭自动提交失败,e); } } /** * 悲观锁-提交事务 */ public static void commit(String pid,String goodName,int goodCount) throws SQLException { connection.commit(); log.info(当前线程 pid 抢购商品 goodName 成功剩余库存为 (goodCount-1)); } /** * 悲观锁-回滚 */ public static void rollBack() throws SQLException { connection.rollback(); }创建PessimisticLock模拟并发操作分布式锁/** * mysql 分布式锁-悲观锁 * 执行流程多个进程抢占同一个商品执行业务完毕则通过connection.commit() 释放锁 * 锁机制单一进程获取锁时则其他进程将阻塞等待 */ Slf4j public class PessimisticLock extends Thread { Override public void run() { super.run(); ResultSet resultSet null; String goodName null; int goodCount 0; String pid ManagementFactory.getRuntimeMXBean().getName().split()[0]; //关闭自动提交 MySQLDistributedLockService.closeAutoCommit(); try{ while(true){ log.info(当前线程 pid ); //获取库存 resultSet MySQLDistributedLockService.getGoodCount2(1); while (resultSet.next()) { goodName resultSet.getString(good_name); goodCount resultSet.getInt(good_count); } log.info(获取库存成功当前商品名称为: goodName ,当前库存剩余量为: goodCount); // 模拟执行业务事件 Thread.sleep(2 * 1000); if (0 goodCount) { log.info(抢购失败当前库存为0); break; } // 抢购商品 if (MySQLDistributedLockService.setGoodCount2(1)) { // 模拟延时防止锁每次被同一进程获取 MySQLDistributedLockService.commit(pid, goodName, goodCount); Thread.sleep(2 * 1000); } else { log.error(抢购商品: goodName 失败!); } } }catch (Exception e){ //抢购失败 log.error(抢购商品发生错误,e); try { MySQLDistributedLockService.rollBack(); } catch (SQLException ex) { log.error(回滚失败 ,e); } }finally { if(resultSet ! null){ try { resultSet.close(); } catch (SQLException e) { log.error(Result关闭失败,e); } } MySQLDistributedLockService.close(); } } public static void main(String[] args) { new PessimisticLock().start(); } }2.3.3 代码测试开启三个进程查看执行情况9 6 3 08 5 27 4 1注意事项该锁为阻塞锁每次请求存在额外加锁的开销在并发量很高的情况下会造成系统中存在大量阻塞的请求影响系统的可用性因此悲观锁适用于并发量不高读操作不频繁的写场景总结在实际使用中由于受到性能以及稳定性约束对于关系型数据库实现的分布式锁一般很少被用到。但是对于一些并发量不高、系统仅提供给内部人员使用的单一业务场景可以考虑使用关系型数据库分布式锁因为其复杂度较低可靠性也能够得到保证。3.基于Zookeeper分布式锁3.1 Zookeeper分布式锁应用场景全部的订单服务在调用 createId 接口前都往 ZooKeeper 的注册中心的指定目录写入注册信息如 /lock/server 01和绑定值改变事件全部的订单服务判断自己往注册中心指定目录写入的注册信息是否是全部注册信息中的第一条如果是调用 createId 接口不是第一条就等着。调用结束后去注册中心移除自己的信息ZooKeeper 注册中心信息改变后通知所有的绑定了值改变事件的订单服务执行第 2 条3.2 Zookeeper分布式锁分析客户端对zookeeper集群而言向zookeeper集群进行了上线注册并在一个永久节点下创建有序的临时子节点后根据编号顺序最小顺序的子节点获取到锁其他子节点由小到大监听前一个节点。当拿到锁的节点处理完事务后释放锁后一个节点监听到前一个节点释放锁后立刻申请获得锁以此类推过程解析第一部分客户端在zookeeper集群创建带序号的、临时的节点第二部分判断节点是否是最小的节点如果是获取到锁如果不是监听前一个节点3.3 分布式锁实现1创建 Distributedlock类, 获取与zookeeper的连接构造方法中获取连接添加 CountDownLatch (闭锁)CountDownLatch是具有synchronized机制的一个工具目的是让一个或者多个线程等待直到其他线程的一系列操作完成。CountDownLatch初始化的时候需要提供一个整形数字数字代表着线程需要调用countDown()方法的次数当计数为0时线程才会继续执行await()方法后的其他内容。/** * 分布式锁 */ public class DistributedLock { private ZooKeeper client; // 连接信息 private String connectString 192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183; // 超时时间 private int sessionTimeOut 30000; private CountDownLatch countDownLatch new CountDownLatch(1); //1. 在构造方法中获取连接 public DistributedLock() throws Exception { client new ZooKeeper(connectString, sessionTimeOut, new Watcher() { Override public void process(WatchedEvent watchedEvent) { } }); //等待Zookeeper连接成功连接完成继续往下走 countDownLatch.await(); //2. 判断节点是否存在 Stat stat client.exists(/locks, false); if(stat null){ //创建一下根节点 client.create(/locks,locks.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } //3.对ZK加锁 public void zkLock(){ //创建 临时带序号节点 //判断 创建的节点是否是最小序号节点如果是 就获取到锁如果不是就监听前一个节点 } //4.解锁 public void unZkLock(){ //删除节点 } }2对zk加锁/** * 分布式锁 */ public class DistributedLock { private ZooKeeper client; // 连接信息 private String connectString 192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183; // 超时时间 private int sessionTimeOut 30000; // 等待zk连接成功 private CountDownLatch countDownLatch new CountDownLatch(1); // 等待节点变化 private CountDownLatch waitLatch new CountDownLatch(1); //当前节点 private String currentNode; //前一个节点路径 private String waitPath; //1. 在构造方法中获取连接 public DistributedLock() throws Exception { client new ZooKeeper(connectString, sessionTimeOut, new Watcher() { Override public void process(WatchedEvent watchedEvent) { //countDownLatch 连上ZK可以释放 if(watchedEvent.getState() Event.KeeperState.SyncConnected){ countDownLatch.countDown(); } //waitLatch 需要释放 (节点被删除并且删除的是前一个节点) if(watchedEvent.getType() Event.EventType.NodeDeleted watchedEvent.getPath().equals(waitPath)){ waitLatch.countDown(); } } }); //等待Zookeeper连接成功连接完成继续往下走 countDownLatch.await(); //2. 判断节点是否存在 Stat stat client.exists(/locks, false); if(stat null){ //创建一下根节点 client.create(/locks,locks.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } //3.对ZK加锁 public void zkLock(){ //创建 临时带序号节点 try { currentNode client.create(/locks/ seq-, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); //判断 创建的节点是否是最小序号节点如果是 就获取到锁如果不是就监听前一个节点 ListString children client.getChildren(/locks, false); //如果创建的节点只有一个值就直接获取到锁如果不是监听它前一个节点 if(children.size() 1){ return; }else{ //先排序 Collections.sort(children); //获取节点名称 String nodeName currentNode.substring(/locks/.length()); //通过名称获取该节点在集合的位置 int index children.indexOf(nodeName); //判断 if(index -1){ System.out.println(数据异常); }else if(index 0){ //就一个节点可以获取锁 return; }else{ //需要监听前一个节点变化 waitPath /locks/ children.get(index-1); client.getData(waitPath,true,null); //等待监听执行 waitLatch.await(); return; } } } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }3zk删除锁//4.解锁 public void unZkLock() throws KeeperException, InterruptedException { //删除节点 client.delete(currentNode,-1); }4测试public class DistributedLockTest { public static void main(String[] args) throws Exception { final DistributedLock lock1 new DistributedLock(); final DistributedLock lock2 new DistributedLock(); new Thread(new Runnable() { Override public void run() { try { lock1.zkLock(); System.out.println(线程1 启动 获取到锁); Thread.sleep(5 * 1000); lock1.unZkLock(); System.out.println(线程1 释放锁); } catch (InterruptedException | KeeperException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { Override public void run() { try { lock2.zkLock(); System.out.println(线程2 启动 获取到锁); Thread.sleep(5 * 1000); lock2.unZkLock(); System.out.println(线程2 释放锁); } catch (InterruptedException | KeeperException e) { e.printStackTrace(); } } }).start(); } }3.4 Curator框架实现分布式锁案例3.4.1 InterProcessMutex介绍Apache Curator 内置了分布式锁的实现InterProcessMutex。InterProcessMutex有两个构造方法public InterProcessMutex(CuratorFramework client, String path) { this(client, path, new StandardLockInternalsDriver()); } public InterProcessMutex(CuratorFramework client, String path, LockInternalsDriver driver) { this(client, path, LOCK_NAME, 1, driver); }参数说明如下参数说明clientcurator中zk客户端对象path抢锁路径同一个锁path需一致driver可自定义lock驱动实现分布式锁主要方法如下//获取锁若失败则阻塞等待直到成功支持重入 public void acquire() throws Exception //超时获取锁超时失败 public boolean acquire(long time, TimeUnit unit) throws Exception //释放锁 public void release() throws Exception注意点调用acquire()方法后需相应调用release()来释放锁3.4.2 实现思路3.4.3 分布式锁测试9 6 3 08 5 27 4 1

相关文章:

zookeeper集群与分布式锁二

1.分布式锁概述 1.1 什么是分布式锁 1)要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁。 线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。 线程锁只在同一J…...

Qwen-Image惊艳作品集:Qwen-VL生成的30组高质量图文推理链(含错误分析与修正)

Qwen-Image惊艳作品集:Qwen-VL生成的30组高质量图文推理链(含错误分析与修正) 1. 视觉语言模型的惊艳表现 Qwen-VL作为通义千问推出的视觉语言模型,在多模态理解与推理方面展现出令人印象深刻的能力。基于RTX 4090D 24GB显存环境…...

MCP与VS Code插件集成:5个关键配置项+4类高频报错,95%开发者踩过的坑你避开了吗?

第一章:MCP与VS Code插件集成教程 如何实现快速接入MCP(Model Control Protocol)是一种轻量级、面向大模型服务编排的通信协议,专为本地开发环境与AI服务端协同而设计。VS Code 作为主流开发者工具,通过官方扩展机制可…...

零代码部署LFM2.5-1.2B-Thinking:ollama图文指南

零代码部署LFM2.5-1.2B-Thinking:ollama图文指南 1. 为什么你需要一个“口袋里的思考伙伴”? 想象一下这个场景:你正在写一份项目方案,思路卡住了,需要一个能快速帮你梳理逻辑、提供灵感的助手。你不想把未成形的想法…...

别再混淆了!一文讲清NTLMv1、NTLMv2哈希的区别与各自的破解方法(附Hashcat/John命令)

深入解析NTLMv1与NTLMv2哈希:从原理到实战破解 在Windows网络认证体系中,NTLM协议作为经典的身份验证机制,至今仍广泛应用于企业内网环境。许多安全从业者在渗透测试或安全评估过程中,常会遇到需要破解NTLM哈希的情况。然而&#…...

Fish-Speech 1.5实战体验:无需配置音素,直接输入文字生成语音

Fish-Speech 1.5实战体验:无需配置音素,直接输入文字生成语音 1. 颠覆传统TTS的全新体验 过去使用语音合成工具时,最令人头疼的环节莫过于音素配置。无论是XTTS还是CosyVoice,都需要繁琐的音素转换步骤:安装g2p工具、…...

VideoAgentTrek-ScreenFilter一键部署教程:基于Node.js的环境配置与快速启动

VideoAgentTrek-ScreenFilter一键部署教程:基于Node.js的环境配置与快速启动 你是不是也遇到过这种情况:想快速体验一个酷炫的AI视频处理项目,结果被复杂的依赖安装和环境配置搞得头大?尤其是那些基于Node.js的项目,版…...

移动宽带也能玩转远程桌面?手把手教你用IPv6直连家里电脑(含防火墙设置避坑指南)

移动宽带用户如何通过IPv6实现高效远程桌面连接 1. IPv6远程桌面连接的基础原理与优势 IPv6作为下一代互联网协议,其128位地址长度彻底解决了IPv4地址枯竭问题。对于移动宽带用户而言,IPv6的最大价值在于每个联网设备都能获得独立的公网地址,…...

ComfyUI文生图新体验:Nunchaku FLUX.1-dev镜像,一键生成惊艳视觉作品

ComfyUI文生图新体验:Nunchaku FLUX.1-dev镜像,一键生成惊艳视觉作品 还在为配置复杂的ComfyUI环境而头疼吗?想体验最新的FLUX.1-dev模型,却被繁琐的插件安装和模型下载劝退?今天,我要分享一个堪称“懒人福…...

如何重构传统定位技术:下一代UWB室内定位系统实战指南

如何重构传统定位技术:下一代UWB室内定位系统实战指南 【免费下载链接】UWB-Indoor-Localization_Arduino Open source Indoor localization using Arduino and ESP32_UWB tags anchors 项目地址: https://gitcode.com/gh_mirrors/uw/UWB-Indoor-Localization_Ar…...

刷题笔记:力扣第17题-电话号码的字母组合

1.题目不难理解,本质上就是一类找全部组合的问题,需要用到递归算法,2-9每个数字都代表一层递归。可以定义一个字符串数组vis来记录2-9的字母映射,同时定义一个数组visLen记录2-9映射的字母数量:1. const char *vis[8] …...

深度解析:资深鸿蒙开发工程师的核心能力与实践路径

随着HarmonyOS的蓬勃发展,市场对具备深厚鸿蒙开发经验的工程师需求激增,尤其是能驾驭复杂应用、游戏、PC应用及智能设备互联场景的资深人才。本文将从职位要求出发,系统性地剖析成为一名合格的资深鸿蒙开发工程师所需掌握的核心技术栈、开发理…...

鸿蒙与Android跨平台开发深度实践与技术面试指南

第一章 鸿蒙系统架构解析 1.1 HarmonyOS分布式架构 鸿蒙系统采用分布式软总线技术实现跨设备协同,其核心架构包含四个关键层次: 应用层 框架层 系统服务层 内核层分布式数据管理通过分布式数据服务实现跨设备数据同步,其数据同步模型可表示为: $$ \frac{\partial \text{…...

Android音频处理实战:基于CosyVoice的高效语音流架构设计与避坑指南

在Android应用开发中,音频处理一直是个既基础又充满挑战的领域。无论是语音通话、实时翻译还是音频直播,我们开发者常常被几个“老朋友”困扰:音频延迟高导致体验割裂,内存占用大引发应用卡顿甚至崩溃,还有那令人头疼的…...

DAMOYOLO-S模型效果深度评测:多场景数据集对比展示

DAMOYOLO-S模型效果深度评测:多场景数据集对比展示 最近在目标检测领域,DAMOYOLO-S这个名字出现的频率越来越高。很多开发者都在讨论,这个号称“又快又准”的模型,实际效果到底怎么样?是不是真的能在各种复杂场景下都…...

DRV2605触觉驱动芯片嵌入式集成与LRA/ERM双模控制实战

1. DRV2605驱动库技术解析:面向嵌入式触觉反馈系统的高精度Haptic控制器集成指南 DRV2605是德州仪器(TI)推出的一款高度集成的触觉驱动芯片,专为智能手机、可穿戴设备、工业人机界面(HMI)及消费类电子产品的…...

RT-Thread事件集原理与工程实践指南

1. RT-Thread事件集机制深度解析:面向嵌入式工程师的同步原语实践指南 在实时嵌入式系统开发中,线程间同步是构建可靠、可预测多任务应用的核心基础。RT-Thread作为一款成熟稳定的国产实时操作系统,提供了信号量(Semaphore&#x…...

万象熔炉·丹青幻境环境配置避坑指南:Anaconda虚拟环境管理详解

万象熔炉丹青幻境环境配置避坑指南:Anaconda虚拟环境管理详解 刚接触“万象熔炉丹青幻境”这类AI绘画或图像生成项目时,很多朋友遇到的第一个拦路虎不是模型本身,而是环境配置。你可能兴致勃勃地下载了代码,结果一运行&#xff0…...

赢了所有争论,却输掉内心平静?

戒掉“永远正确”,治愈中年焦虑说句实在话,到了我们这个岁数,最怕的不是白天连轴转的会,而是半夜两三点钟,突然毫无征兆地醒来。前些年一段时间,我就是这样。凌晨两点半,窗外路灯的光顺着窗帘缝…...

DAMOYOLO-S一键部署教程:基于Anaconda的Python环境快速配置

DAMOYOLO-S一键部署教程:基于Anaconda的Python环境快速配置 你是不是刚拿到DAMOYOLO-S这个目标检测模型,看着一堆代码和依赖包有点无从下手?别担心,今天咱们就来手把手搞定它。我见过不少朋友卡在环境配置这一步,不是…...

嵌入式密码学加速引擎的软硬件协同驱动设计

1. 项目概述本项目聚焦于嵌入式系统中密码学加速引擎(Cryptographic Engine, CE)的软硬件协同设计与驱动实现,面向基于ArtinChip系列SoC的嵌入式平台。其核心目标是将片上集成的硬件加密模块——包括AES对称加密单元、SHA哈希计算单元及后续可…...

嵌入式密码加速器CE驱动测试指南

1. 测试指南嵌入式密码加速器(Cryptographic Engine, CE)的验证是硬件安全模块开发流程中不可省略的关键环节。CE驱动的正确性不仅关系到上层加密算法的执行效率,更直接影响密钥保护、数据完整性校验等安全机制的可靠性。本测试指南面向已集成…...

Qwen3-ASR-1.7B流式推理教程:实时语音转写实现方案

Qwen3-ASR-1.7B流式推理教程:实时语音转写实现方案 想要实现实时语音转写但不知道从何入手?本教程将手把手教你使用Qwen3-ASR-1.7B模型搭建流式语音识别系统,让音频实时转换为文字变得简单易行。 1. 引言:为什么需要流式语音识别&…...

YOLO12模型在计算机视觉竞赛中的实战技巧

YOLO12模型在计算机视觉竞赛中的实战技巧 1. 竞赛场景下的真实效果体验 参加计算机视觉竞赛时,模型效果往往决定了最终排名。去年我带队参加了Kaggle上的一个工业缺陷检测比赛,前几轮用YOLOv8和YOLOv11都卡在了mAP 0.72左右,直到尝试YOLO12…...

ChatTTS WebUI 异常处理实战:解决 ‘exception on /tts [post]‘ 的 AI 辅助方案

最近在折腾一个语音合成的项目,用到了 ChatTTS 这个挺有意思的文本转语音模型。为了更方便地使用,我部署了它的 WebUI 界面。本来想着通过网页点点按钮就能生成语音,美滋滋,结果在实际调用 /tts 接口时,频繁遇到了一个…...

UVW对位平台与Halcon联合C#编程学习参考

uvw对位平台,halcon联合c#编程,供学习的朋友参考最近在搞工业视觉对位平台,发现uvw平台这玩意儿是真有意思。三轴联动的机械结构配合视觉校正,比传统的XYθ平台灵活多了。今天就跟大伙儿唠唠怎么用HalconC#玩转这个组合&#xff0…...

springboot+nodejs+vue3汉服商城系统 汉服文化交流平台

目录技术栈选择与分工系统模块设计数据交互规范关键实现技术点部署与运维文化内容运营项目技术支持源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作技术栈选择与分工 后端框架:Spring Boot(Java)用于构建…...

Stable Diffusion Anything V5商业应用:自动生成商品主图实战

Stable Diffusion Anything V5商业应用:自动生成商品主图实战 1. 引言:电商视觉内容的生产痛点 在当今电商行业,商品主图的质量直接影响着点击率和转化率。传统商品摄影面临三大核心挑战: 成本高昂:专业摄影棚、器材…...

小白也能懂:AI手势识别核心功能与彩虹骨骼效果全解析

小白也能懂:AI手势识别核心功能与彩虹骨骼效果全解析 1. 引言:从“动手”到“懂手”的AI魔法 你有没有想过,电脑或者手机是怎么“看懂”你比划的“耶”或者“赞”的?这背后,就是AI手势识别技术在发挥作用。过去&…...

Qwen-Image效果实测:在40GB数据盘中高效缓存Qwen-VL权重与高频测试图像集

Qwen-Image效果实测:在40GB数据盘中高效缓存Qwen-VL权重与高频测试图像集 1. 开箱即用的多模态推理环境 当我们需要快速验证一个视觉语言模型的实际效果时,最头疼的往往是环境配置问题。不同版本的CUDA、PyTorch、以及各种依赖库的兼容性问题常常让人望…...