01 前言
我们现在拥有这么一个集群,集群里面有个缓存服务,集群中每个程序都会用到这个缓存,如果此时缓存中有一项缓存过期了,在大并发环境下,同一时刻中许许多多的服务都过来访问缓存,获取缓存中的数据,发现缓存过期,就要再去数据库取,然后更新到缓存服务中去。但是其实我们仅仅只需要一个请求过来数据库去更新缓存即可,然后这个场景,我们该怎么去做?
我们参考多线程的场景下会使用到锁的这个方法,放到现在的并发场景下,我们也是需要通过一种锁来实现。
02 使用Zookeeper来进行开发
1.锁的特点与原生zookeeper
(1) 普通锁具备什么特点?
①排他(互斥)性:只有一个线程能获取到
文件系统(同一个文件不支持多个人去修改)
数据库:主键唯一约束 for update
缓存:redis setnx命令
zookeeper:类似文件系统
②阻塞性:其他未抢到的线程阻塞,直到锁被释放再进行抢这个行为
③可重入性:线程获取锁后,后续是否可重复获得该锁复制代码
(2)为什么zookeeper可以用来实现锁
①同一个父目录下面不能有相同的子节点,这就是zookeeper的排他性
②通过JDK的栅栏来实现阻塞性
③可重入性我们可以通过计数器来实现复制代码
(3)原生的zookeeper存在着什么问题
①接口难以使用
②连接zookeeper超时不支持自动重连
③watch注册一次会失效,需要反复注册
④不支持递归创建节点(递归创建的话,比方说我要创建一个文件,假如我在idea创建,那我可以连带着包一起创建,但是在window我就做不到,这种整一个路径一并创建下来的就可以视为递归创建)
⑤需要手动设置序列化的问题复制代码
(4) 创建客户端的核心类:Zookeeper
2.使用第三方客户端zkClient来简化操作
(1)实现序列化接口 ZkSerializer
MyZkSerializer.java
(2)zkclient的简单使用
ZkClientDemo.java
运行结果
调用ls /zk---可以发现app6已经被创建,
通过get /zk/app6---可获取到我们设置的123这个值
说明我们的程序没有问题,可以成功执行
这里测试监听事件
create /zk/app6/tellYourDream时---控制台打印/zk/app6子节点发生变化:[tellYourDream]
delete /zk/app6/tellYourDream---控制台打印/zk/app6子节点发生变化:[],此时已经不存在任何节点,所以为空
set /zk/app6 123456---/zk/app6发生变化:123456
delete /zk/app6---同时触发了两个监听事件,/zk/app6子节点发生变化:null 和 /zk/app6节点被删除
(3) CreateMode 的补充
①持久化节点:不删除节点永远存在。且可以创建子节点
②非持久节点,换言之就是临时节点,临时节点就是客户端连接的时候创建,客户端挂起的时候,临时节点自动删除。不能创建子节点
还有更多的一些监听方法,我们可以自己去尝试一下。
3.Zookeeper实现分布式锁
(1) zookeeper实现分布式锁方式一
我们之前有提到,zookeeper中同一个子节点下面的节点名称是不能相同的,我们可以利用这个互斥性,就可以实现分布式锁的工具
临时节点就是创建的时候存在,消失的时候,节点自动删除,当客户端失联,网络不稳定或者崩溃的时候,这个通过临时节点所创建的锁就会自行消除。这样就可以完美避免死锁的问题。所以我们利用这个特性,实现我们的需求。
原理其实就是节点不可重名+watch机制。
比如说我们的程序有多个服务实例,哪个服务实例都去创建一个lock节点,谁创建了,谁就获得了锁,剩下我们没有创建的应用,就去监听这个lock节点,如果这个lock节点被删除掉,这时可能出现两种情况,一就是客户端连不上了,另一种就是客户端释放锁,将lock节点给删除掉了。
ZkDistributeLock.java(注意,不需要重写的方法已经删除)
实现Lock接口要重写的方法(包括尝试创建临时节点tryLock(),解锁unlock(),上锁lock(),waitForLock()实现阻塞和唤醒的功能方法)
ZkDistributeLock,现在我们再总结一下流程
这个设计会有一个缺点,比如我的实例现在有无数个,此时我们的lock每次被创建,有人获取了锁之后,其他的人都要被通知阻塞,此时我们就浪费了很多的网络资源,也就是惊群效应。
此时我们必须进行优化
(2)zookeeper实现分布式锁方式二
我们的Lock作为一个znode,也可以创建属于它的子节点,我们使用lock创建临时顺序节点,zookeeper是有序的,临时顺序节点会自动进行由小到大的自动排序,此时我们把实例分配至这些顺序子节点上,然后编号最小的获取锁即可。这非常类似于我们的公平锁的概念,也是遵循FIFO原则的
原理:取号 + 最小号取lock + watch
同样是基于Lock接口的实现
ZkDistributeImproveLock.java(注意,不需要重写的方法已经删除)
ps:不用担心内存占满的问题,JVM会进行垃圾回收
4.更为简单的第三方客户端---Curator
这里对于curator就不做展开了,有兴趣可以自己去玩下
对于选举leader,锁locking,增删改查的framework等都有实现