最近一个半月一直比较忙,业务中使用了分布式锁来控制操作特定数据,考虑到重用性,使用curator框架封装zookeeper目录获取和释放分布式锁的模块。记录下来,以后就可以封装类似的组件了。
首先还是要把zookeeper安装好,具体步骤就不介绍了。网上很多。
1,先建立连接配置
@Data @ConfigurationProperties(prefix = ZOOKEEPER_CONFIG_PRE) public class CuratorConfiguration { private int retryCount; private int elapsedTimeMs; private String connectString; private int sessionTimeoutMs; private int connectionTimeoutMs; }
使用到的配置文件前缀:这个是要在使用到该组件的项目里配置,毕竟每个项目的zk环境可能不同,不能都用一个
public interface ConfigDef { /*配置文件的处理前缀*/ String ZOOKEEPER_CONFIG_PRE="zookeeper"; }
2.使用连接配置建立客户端连接
package com.kamowl.kamo.cloud.base.zookeeper.config; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.RetryNTimes; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import static com.kamowl.kamo.cloud.base.zookeeper.config.ConfigDef.ZOOKEEPER_CONFIG_PRE; /** * @author dushan * 使用连接工厂 * 通过配置文件参数创建连接 * @Description * @Date 2019-12-11 20:54 */ @Configuration @ConditionalOnClass({CuratorFrameworkFactory.class,CuratorFramework.class}) @EnableConfigurationProperties(CuratorConfiguration.class) @ConditionalOnProperty(prefix = ZOOKEEPER_CONFIG_PRE, value = "enable", havingValue = "true") public class ZookeeperAutoConfigure { @Autowired private CuratorConfiguration curatorConfiguration; @Bean public CuratorFramework curatorFramework() { return CuratorFrameworkFactory.newClient( curatorConfiguration.getConnectString(), curatorConfiguration.getSessionTimeoutMs(), curatorConfiguration.getConnectionTimeoutMs(), new RetryNTimes(curatorConfiguration.getRetryCount(), curatorConfiguration.getElapsedTimeMs())); } }
3、建立连接后直接使用工具,使用工具可以大大简化开发工作,不然我们自己写就会费劲
package com.kamowl.kamo.cloud.base.zookeeper.config; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.PathChildrenCache; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.ZooDefs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.CountDownLatch; /** * @author dushan * @date 2019-12-1211:04 */ @Service public class DistributedLockByCurator implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(DistributedLockByCurator.class); private final static String ROOT_PATH_LOCK = "rootlock"; private CountDownLatch countDownLatch = new CountDownLatch(1); @Autowired private CuratorFramework curatorFramework; /** * 获取分布式锁 * * @param lockName 锁实体目录,譬如用户钱包锁wallet-1 **/ public void acquireDistributeLock(String lockName) { String keyPath = "/" + ROOT_PATH_LOCK + "/" + lockName; while (true) { try { curatorFramework .create() .creatingParentsIfNeeded() .withMode(CreateMode.EPHEMERAL) .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) .forPath(keyPath); logger.info("success to acquire lock for path:{}", keyPath); break; } catch (Exception e) { logger.info("failed to acquire lock for path:{}", keyPath); logger.info("while try again ------------"); try { if (countDownLatch.getCount() <= 0) { countDownLatch = new CountDownLatch(1); } countDownLatch.await(); } catch (InterruptedException e1) { e1.printStackTrace(); } } } } /** * 释放分布式锁 * 只删除叶子节点就可以 */ public boolean releaseDistributeLock(String path) { try { String keyPath = "/" + ROOT_PATH_LOCK + "/" + path; if (curatorFramework.checkExists().forPath(keyPath) != null) { curatorFramework.delete().guaranteed() .forPath(keyPath); } } catch (Exception e) { logger.error("failed to release lock :{}"); return false; } return true; } /** * 创建 watcher 事件 */ private void addWatcher(String path) throws Exception { String keyPath; if (path.equals(ROOT_PATH_LOCK)) { keyPath = "/" + path; } else { keyPath = "/" + ROOT_PATH_LOCK + "/" + path; } final PathChildrenCache cache = new PathChildrenCache(curatorFramework, keyPath, false); cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT); cache.getListenable().addListener((client, event) -> { if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)) { String oldPath = event.getData().getPath(); logger.info("success to release lock for path:{}", oldPath); if (oldPath.contains(path)) { //释放计数器,让当前的请求获取锁 countDownLatch.countDown(); } } }); } /** * 创建父节点,并创建永久节点 * 命名空间 */ @Override public void afterPropertiesSet() { curatorFramework.start(); curatorFramework = curatorFramework.usingNamespace("lock-namespace"); String path = "/" + ROOT_PATH_LOCK; try { if (curatorFramework.checkExists().forPath(path) == null) { curatorFramework.create() .creatingParentsIfNeeded() .withMode(CreateMode.PERSISTENT) .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) .forPath(path); } addWatcher(ROOT_PATH_LOCK); logger.info("root path 的 watcher 事件创建成功"); } catch (Exception e) { logger.error("connect zookeeper fail,please check the log >> {}", e.getMessage(), e); } } }
4、使用到的依赖以及打包方式加进来
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>kamo-cloud-base</artifactId> <groupId>com.kamowl</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>kamo-cloud-base-distribute</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.10</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> <exclusion> <artifactId>log4j</artifactId> <groupId>log4j</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.12.0</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
整理完毕,该工具可以被项目组其他使用到分布式锁的成员引用,引用方式也很简单
只需要添加改模快组件就好了,下面举个栗子
<dependency> <groupId>com.kamowl</groupId> <artifactId>kamo-cloud-base-distribute</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
用的时候把依赖加进来
@Autowired private DistributedLockByCurator distributedLockByCurator;
distributedLockByCurator.acquireDistributeLock(lock12); distributedLockByCurator.acquireDistributeLock(lock34);
务必在finally中释放,否则异常不释放锁,下次再获取就失败了
finally { //释放操作货主钱包的分布式锁 distributedLockByCurator.releaseDistributeLock(); //释放操作司机钱包的分布式锁 distributedLockByCurator.releaseDistributeLock(); }