分布锁的要求:
a:互斥
b:宕机避免死锁
c:只能自己解锁
1.数据库版本
2.redis
3.zookeeper
4.pom文件
I...依赖
<!-- 原生zk支持--> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.3.6</version> </dependency> <!-- zkclient原生客户端支持--> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency>
II...必要参数采用yml配置注入
zklock: url: 127.0.0.1:2181 rootPath: /newlock17
III...创建锁工具类
要点介绍:
1.实现lock接口,开闭原则
2.构造函数中实例化ZkClient
3.在根节点下常见临时顺序节点记得加分隔符“/”:
package com.test.domi.common.utils.lock; import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.ZkClient; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; public class ZKlock implements Lock { private ZkClient zkClient; private String rootPath; private String currentPath; private String beforePath; public ZKlock(String url,String rootPath){ this.rootPath = rootPath; zkClient = new ZkClient(url); if (!zkClient.exists(rootPath)) { try { zkClient.createPersistent(rootPath); } catch (RuntimeException e) { } } } @Override public boolean tryLock() { if (currentPath == null) { currentPath = zkClient.createEphemeralSequential(rootPath + "/","aaa"); } List<String> childrens = zkClient.getChildren(rootPath); Collections.sort(childrens); if (currentPath.equals(rootPath + "/" + childrens.get(0))) { return true; }else{ int curIndex = childrens.indexOf(currentPath.substring(rootPath.length() + 1)); beforePath = rootPath + "/" + childrens.get(curIndex - 1); } return false; } @Override public void lock() { if (!tryLock()) { waiForLock(); lock(); } } @Override public void unlock() { zkClient.delete(currentPath); } private void waiForLock(){ CountDownLatch cdl = new CountDownLatch(1); IZkDataListener listener = new IZkDataListener() { @Override public void handleDataChange(String s, Object o) throws Exception { } @Override public void handleDataDeleted(String s) throws Exception { cdl.countDown(); } }; zkClient.subscribeDataChanges(beforePath,listener); if (zkClient.exists(beforePath)) { try { cdl.await(); } catch (InterruptedException e) { } } zkClient.unsubscribeDataChanges(beforePath,listener); } @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public Condition newCondition() { return null; } }
currentPath = zkClient.createEphemeralSequential(rootPath + "/","aaa");
IV...将锁工具类交给spring管理,这样就不需要每次new的时候传入客户端的连接ip,一次注入终生受用,
但是切记@Scope为多例,因为ZKLock里面的全局变量不能再多线程共享。
package com.test.domi.config; import com.test.domi.common.utils.lock.ZKlock; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @Configuration public class ZkLockConfig { @Value("${zklock.rootPath}") private String rootPath; @Value("${zklock.url}") private String url; @Bean @Scope("prototype") public ZKlock getZKlock(){ return new ZKlock(url,rootPath); } }
V...使用方式:
要点:
1.zklock.unlock():一般要放在finally中
2.重入之后一般需要判断业务是否执行过,已经执行过的应立即退出
package com.test.domi.controller; import com.test.domi.common.utils.SpringContextUtil; import com.test.domi.common.utils.lock.ZKlock; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/zk") public class ZKController { private int k = 1; @GetMapping("/lock") public Boolean getLock() throws Exception{ for (int i = 0; i < 50; i++) { new Thread(new Runnable() { @Override public void run() { ZKlock zklock = SpringContextUtil.getBean(ZKlock.class); zklock.lock(); try { System.out.println(Thread.currentThread().getName() + "获得锁,生成唯一的单据编号" + k++); } catch (Exception e) { e.printStackTrace(); }finally { zklock.unlock(); } } }).start(); } return true; } }