基于zookeep实现分布式锁的实际场景运用
首先我们先来看看使用zk实现分布式锁的原理,在zk中是使用文件目录的格式存放节点内容,其中节点类型分为:
- 持久节点(PERSISTENT ):节点创建后,一直存在,直到主动删除了该节点。
- 临时节点(EPHEMERAL):生命周期和客户端会话绑定,一旦客户端会话失效,这个节点就会自动删除。
- 序列节点(SEQUENTIAL ):多个线程创建同一个顺序节点时候,每个线程会得到一个带有编号的节点,节点编号是递增不重复的,如下图:

方式1:基于zookeep不能重复创建一个节点的特性来实现,如图:
存在问题:如果有100个线程同时竞争该锁,即这100线程中都需要基于zookeep中的watch机制观察该节点,当该节点释放后,需要同时通知这100个线程去公平获取锁,会存在“惊群”现象;
方式2 :基于zookeep的序列节点;
比如现在要对订单001加锁,线程A已经获取到锁,线程B、线程C、线程D等待;
则zookeep会有默认机制按照顺序在001下创建时序节点,在获取001锁时找该节点下的最小节点并获取锁,释放锁后删除该节点在寻找下一个最小节点获取锁,
这里基于Curator对第二种方式实现:
引入依赖:
<dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.5.0</version> <exclusions> <exclusion> <artifactId>zookeeper</artifactId> <groupId>org.apache.zookeeper</groupId> </exclusion> </exclusions> </dependency>
代码:
@Component public class CuratorSingleton { private static final Logger log = LoggerFactory.getLogger(CuratorSingleton.class); private static CuratorFramework client = null; private static final String ZK_HOST = YmlUtil.getYmlValue("zookeeper.zookeeperUrl"); private CuratorSingleton() throws InterruptedException { if (client == null) { synchronized (CuratorSingleton.class) { if (client == null) { start(); } } } } /** * 单例模式获取CuratorFramework对象 * @return * @throws IOException * @throws InterruptedException */ public static CuratorFramework getInstance() throws InterruptedException { if (client == null) { synchronized (CuratorSingleton.class) { if (client == null) { start(); } } } else { if ("STOPPED".equals(client.getState().toString())) { synchronized (CuratorSingleton.class) { if ("STOPPED".equals(client.getState().toString())) { start(); } } } } return client; } private static void start() throws InterruptedException { RetryPolicy retryPolicy = new RetryUntilElapsed(30000, 1000); client = CuratorFrameworkFactory.newClient(ZK_HOST, retryPolicy); client.start(); client.getZookeeperClient().blockUntilConnectedOrTimedOut(); } /** * @Description 删除一个文件夹下的空节点 一个文件夹就是一种业务对象 * @return void * @throws * @Author zhanglei * @Date 20:44 2020/4/21 * @Param [folder] **/ public static void clearOneFolder(String folder) throws Exception { final CuratorFramework curator = getInstance(); for (String nodePath : curator.getChildren().forPath(folder)) { //四级路径 String nodeFullPath = folder + "/" + nodePath; List<String> folders = curator.getChildren().forPath(nodeFullPath); if (CollectionUtils.isEmpty(folders)) { try { //没有子目录直接删除 deleteNode(nodeFullPath); } catch (Exception e) { log.info("fail to delete: {}", nodeFullPath, e); } continue; } for (String path : folders) { //删除第五级目录 String pathFive = nodeFullPath + "/" + path; try { deleteNode(pathFive); } catch (Exception e) { log.info("fail to delete: {}", pathFive, e); } } try {//删除第四级目录 deleteNode(nodeFullPath); } catch (Exception e) { log.info("fail to delete: {}", nodeFullPath, e); } } } /** * @Description 安全删除由于锁造成的空节点 确保节点存在、没有子节点、没有数据、最近修改时间在两小时前 * @return void * @throws * @Author zhanglei * @Date 20:44 2020/4/21 * @Param [path] **/ public static void deleteNode(String path) throws Exception { final CuratorFramework curator = getInstance(); Stat stat = curator.checkExists().forPath(path); if (stat == null) { return; } if (stat.getNumChildren() > 0) { return; } if (curator.getData().forPath(path).length > 0) { return; } if (System.currentTimeMillis() - stat.getMtime() < 3600 * 2000) { return; } curator.delete().inBackground().forPath(path); } }
public class DistributedLockBean { private String granularity; //主节点地址,以谁为粒度加锁(例如按客户加锁,则传入cusId) private boolean lockSuccess; //是否获取锁成功 private LockService lockService; //具体调用方法 private Object result; //方法返回结果 private int maxLease; //最大信号量(信号量锁) private int qty; //消耗信号量(信号量锁) public String getGranularity() { return granularity; } /** * 设置锁业务的粒度 * * @param granularity */ public void setGranularity(String granularity) { this.granularity = "/distributeLock/" + granularity; } protected boolean isLockSuccess() { return lockSuccess; } protected void setLockSuccess(boolean lockSuccess) { this.lockSuccess = lockSuccess; } /** * 在锁内执行的操作 * @return */ public LockService getLockService() { return lockService; } public void setLockService(LockService lockService) { this.lockService = lockService; } protected Object getResult() { return result; } protected void setResult(Object result) { this.result = result; } public int getMaxLease() { return maxLease; } public void setMaxLease(int maxLease) { this.maxLease = maxLease; } public int getQty() { return qty; } public void setQty(int qty) { this.qty = qty; } }
@Component public class DistributedLockService { private static final Logger log = LoggerFactory.getLogger(DistributedLockService.class); /** * 通过分布式锁执行相应的业务操作 * * @throws Exception */ public Object doServiceByLock(DistributedLockBean dlBean) throws Exception { return doServiceByLock(dlBean,false); } private void releaseLock(InterProcessMutex lock, CuratorFramework client) { try { lock.release(); log.debug("释放锁成功"); } catch (Exception e) { client.close(); log.error("释放锁失败", e); } } /** * 通过分布式锁执行相应的业务操作 * * @param dlBean * @param releaseLockAfterTransaction 在事务提交后才释放锁。<br>警告:如果是true,需要注意这个方法不能在一个循环中调用,如果各循环体中包含相同的granularity,有死锁的危险!!! * @return * @throws CuratorException */ public Object doServiceByLock(DistributedLockBean dlBean,boolean releaseLockAfterTransaction) throws Exception { try { final CuratorFramework client = CuratorSingleton.getInstance(); final InterProcessMutex lock = new InterProcessMutex(client, dlBean.getGranularity()); if (lock.acquire(60, TimeUnit.SECONDS)) {// 等待60s,如果60s未获得锁,则不再获取锁 try { log.debug("获取锁成功,开始执行业务操作"); Object result = dlBean.getLockService().doService(); dlBean.setResult(result); dlBean.setLockSuccess(true); } finally { if(releaseLockAfterTransaction && TransactionHookUtil.isInTransaction()){ // 如果参数指定在事务之后释放,并且当前在事务中 TransactionHookUtil.registAfterCompletionHook(new TransactionHookUtil.CompletionHook() { @Override public void execute(int status) { releaseLock(lock, client); } }); }else{ // 直接释放锁 releaseLock(lock, client); } } } if (!dlBean.isLockSuccess()) { throw new CuratorException("获取锁失败,lock path:"+dlBean.getGranularity()); } } catch (Exception e) { log.error(e.getMessage(),e); throw e; } return dlBean.getResult(); } /** * * @Description 分布式锁-信号量 * @Date 11:05 2019/3/28 * @Param * @return **/ public Object doServiceBySemaphoreLock(DistributedLockBean dlBean) throws Exception { try { final CuratorFramework client = CuratorSingleton.getInstance(); InterProcessSemaphoreV2 interProcessSemaphoreV2=new InterProcessSemaphoreV2(client, dlBean.getGranularity(), dlBean.getMaxLease()); Collection< Lease > leases = interProcessSemaphoreV2.acquire(dlBean.getQty(),60,TimeUnit.SECONDS); if(leases != null) {// 等待60s,如果60s未获得锁,则不再获取锁 try { log.debug("获取信号量锁成功,开始执行业务操作"); Object result = dlBean.getLockService().doService(); dlBean.setResult(result); dlBean.setLockSuccess(true); } finally { // 直接释放锁 releaseSemaphoreLock(interProcessSemaphoreV2,leases, client); } } if (!dlBean.isLockSuccess()) { throw new CuratorException("获取信号量锁失败,lock path:"+dlBean.getGranularity()); } } catch (Exception e) { log.error(e.getMessage(),e); throw e; } return dlBean.getResult(); } /** * * @Description 释放信号量锁 * @Date 11:05 2019/3/28 * @Param * @return **/ private void releaseSemaphoreLock(InterProcessSemaphoreV2 interProcessSemaphoreV2,Collection<Lease> leases, CuratorFramework client) { try { interProcessSemaphoreV2.returnAll(leases); log.debug("释放信号量锁成功"); } catch (Exception e) { client.close(); log.error("释放信号量锁失败", e); } } }
public interface LockService { public Object doService() throws Exception; }
使用:
DistributedLockBean distributedLockBean = new DistributedLockBean(); distributedLockBean.setGranularity(DistributedLockConstant.ONLINE_STATISTICS+onlineInfo.getSessionId()); distributedLockBean.setLockService(new LockService() { @Override public Object doService() throws Exception { // doServicereturn null; } }); try{ distributedLockService.doServiceByLock(distributedLockBean,true); }catch (Exception e){ log.info(e.getMessage()); }
注意,这里由Curator创建的节点在锁释放后并没有删除,建议执行定时任务手动删除,直接调用CuratorSingleton类中删除方法即可,可删除指定节点,也可删除文件夹i下空节点;
我们曾如此渴望生命的波澜,到后来才发现,人生最曼妙的风景是内心的淡定与从容