赞
踩
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
初始化zookeeper客户端类,负责建立与zkServer的会话
new ZooKeeper(connectString, 30000, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("获取链接成功!!");
}
});
创建一个节点,1-节点路径 2-节点内容 3-访问控制控制 4-节点类型
String fullPath = zooKeeper.create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
判断一个节点是否存在
Stat stat = zooKeeper.exists(rootPath, false);
if (stat != null) {...}
查询一个节点的内容
Stat stat = new Stat();
byte[] data = zooKeeper.getData(path, false, stat);
更新一个节点
zooKeeper.setData(rootPath, new byte[]{}, stat.getVersion() + 1);
删除一个节点
zooKeeper.delete(path, stat.getVersion());
查询一个节点的子节点列表
List<String> children = zooKeeper.getChildren(rootPath, false);
关闭链接
if (zooKeeper != null) {
zooKeeper.close();
}
分布式锁的步骤:
1. 可自动释放锁(临时节点) :获得锁之后客户端所在机器宕机了,客户端没有主动删除子节点;如果创建的是永久的节点,那么这个锁永远不会释放,导致死锁;由于创建的是临时节点,客户端宕机后,过了一定时间zookeeper没有收到客户端的心跳包判断会话失效,将临时节点删除从而释放锁。
2. 可重入锁:借助于ThreadLocal
实现思路:
由于zookeeper获取链接是一个耗时过程,这里可以在项目启动时,初始化链接,并且只初始化一次。借助于spring特性,代码实现如下:
@Component public class zkClient { private static final String connectString = "192.168.107.135"; private static final String ROOT_PATH = "/distributed"; private ZooKeeper zooKeeper; @PostConstruct public void init() throws IOException { this.zooKeeper = new ZooKeeper(connectString, 30000, new Watcher() { @Override public void process(WatchedEvent watchedEvent) { System.out.println("zookeeper 获取链接成功"); } }); //创建分布式锁根节点 try { if (this.zooKeeper.exists(ROOT_PATH, false) == null) { this.zooKeeper.create(ROOT_PATH, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } @PreDestroy public void destroy() { if (zooKeeper != null) { try { zooKeeper.close(); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 初始化分布式对象方法 */ public ZkDistributedLock getZkDistributedLock(String lockname){ return new ZkDistributedLock(zooKeeper,lockname); } }
public class ZkDistributedLock { public static final String ROOT_PATH = "/distribute"; private String path; private ZooKeeper zooKeeper; public ZkDistributedLock(ZooKeeper zooKeeper, String lockname) { this.zooKeeper = zooKeeper; this.path = ROOT_PATH + "/" + lockname; } public void lock() { try { zooKeeper.create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(200); lock(); } catch (InterruptedException e) { e.printStackTrace(); } } public void unlock(){ try { this.zooKeeper.delete(path,0); } catch (InterruptedException e) { e.printStackTrace(); } catch (KeeperException e) { e.printStackTrace(); } } }
改造StockService的checkAndLock方法:
@Autowired private zkClient client; public void checkAndLock() { // 加锁,获取锁失败重试 ZkDistributedLock lock = this.client.getZkDistributedLock("lock"); lock.lock(); // 先查询库存是否充足 Stock stock = this.stockMapper.selectById(1L); // 再减库存 if (stock != null && stock.getCount() > 0) { stock.setCount(stock.getCount() - 1); this.stockMapper.updateById(stock); } lock.unlock(); }
Jmeter压力测试:
性能一般,mysql数据库的库存余量为0(注意:所有测试之前都要先修改库存量为5000)
基本实现存在的问题:
1. 性能一般(比mysql略好)
2. 不可重入
接下来首先来提高性能
基本实现中由于无限自旋影响性能:
试想:每个请求要想正常的执行完成,最终都是要创建节点,如果能够避免争抢必然可以提高性能。这里借助于zk的临时序列化节点,实现分布式锁:
代码实现:
public class ZkDistributedLock { public static final String ROOT_PATH = "/distribute"; private String path; private ZooKeeper zooKeeper; public ZkDistributedLock(ZooKeeper zooKeeper, String lockname) { this.zooKeeper = zooKeeper; try { this.path = zooKeeper.create(ROOT_PATH + "/" + lockname + "_", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } public void lock() { String preNode = getpreNode(path); //如果该节点没有前一个节点,说明该节点是最小的节点 if (StringUtils.isEmpty(preNode)) { return; } //重新检查是否获取到锁 try { Thread.sleep(20); lock(); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 获取指定节点的前节点 * * @param path * @return */ private String getpreNode(String path) { //获取当前节点的序列化序号 Long curSerial = Long.valueOf(StringUtil.substringAfter(path, '_')); //获取根路径下的所有序列化子节点 try { List<String> nodes = this.zooKeeper.getChildren(ROOT_PATH, false); //判空处理 if (CollectionUtils.isEmpty(nodes)) { return null; } //获取前一个节点 Long flag = 0L; String preNode = null; for (String node : nodes) { //获取每个节点的序列化号 Long serial = Long.valueOf(StringUtil.substringAfter(path, '_')); if (serial < curSerial && serial > flag) { flag = serial; preNode = node; } } return preNode; } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return null; } public void unlock() { try { this.zooKeeper.delete(path, 0); } catch (InterruptedException e) { e.printStackTrace(); } catch (KeeperException e) { e.printStackTrace(); } } }
主要修改了构造方法和lock方法:
并添加了getPreNode获取前置节点的方法。
测试结果如下:
性能反而更弱了。
原因:虽然不用反复争抢创建节点了,但是会自选判断自己是最小的节点,这个判断逻辑反而更复杂更 耗时。
解决方案:监听实现阻塞锁
对于这个算法有个极大的优化点:假如当前有1000个节点在等待锁,如果获得锁的客户端释放锁时,这1000个客户端都会被唤醒,这种情况称为“羊群效应”;在这种羊群效应中,zookeeper需要通知1000个 客户端,这会阻塞其他的操作,最好的情况应该只唤醒新的最小节点对应的客户端。应该怎么做呢?在 设置事件监听时,每个客户端应该对刚好在它之前的子节点设置事件监听,例如子节点列表 为/lock/lock-0000000000、/lock/lock-0000000001、/lock/lock-0000000002,序号为1的客户端监听 序号为0的子节点删除消息,序号为2的监听序号为1的子节点删除消息。
所以调整后的分布式锁算法流程如下:
改造ZkDistributedLock的lock方法:
![img](https://img-blog.csdnimg.cn/img_convert/ac04d300fa503a2fbfc13c839eb556b8.png) ![img](https://img-blog.csdnimg.cn/img_convert/e73941f88213026388bf3ccd3538909a.png) **网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。** **[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)** **一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!** 点删除消息,获得子节点变更通 知后重复此步骤直至获得锁; * 执行业务代码; * 完成业务流程后,删除对应的子节点释放锁。 改造ZkDistributedLock的lock方法:
[外链图片转存中…(img-FM4aqK6T-1715803092572)]
[外链图片转存中…(img-xELlZmLX-1715803092573)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。