ZK_03Zookeeper分布式锁和注册中心使用
2023-05-17 10:14:42 6 举报
AI智能生成
Zookeeper分布式锁和注册中心使用
作者其他创作
大纲/内容
<span style="font-size: 24px;">分布式锁</span>
概念
来处理跨机器的进程之间的数据同步问题,这种跨机器的锁就是分布式锁
特点
<span style="color: rgb(33, 37, 41); font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, "Segoe UI", Roboto, Arial, "Noto Sans", "PingFang SC", "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft Yahei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px;">ZooKeeper保证了数据的强一致性</span>
每时每刻,zk集群中任意节点(一个zk server)上的相同znode的数据是一定是相同的
锁服务可以分为两类,一个是保持独占,另一个是控制时序
保持独占<br>
所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁
zk上的一个znode看作是一把锁,通过create znode的方式来实现
所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁
控制时序
所有来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了
通过创建临时有序节点
主流的方案
数据库的分布式锁
db操作性能较差,并且有锁表的风险,一般不考虑
Redis的分布式锁
适用于并发量很大、性能要求很高而可靠性问题可以通过其他方案去弥补的场
ZooKeeper的分布式锁
适用于高可靠(高可用),而并发量不是太高的场景
实现思路
Zookeeper分布式锁
思路1
创建一个临时节点lock
如果创建成功则获取锁
执行完业务释放锁(断开连接,临时节点失效、)<br>
如果创建失败则使用watch机制,检测临时节点失效等于释放锁<br>
再去竞争去创建临时节点获取锁
问题
<div yne-bulb-block="paragraph" style="white-space: pre-wrap; line-height: 1.75; font-size: 14px;"><span style="color: rgb(223, 64, 42);">羊群效应</span></div>
如果所有的锁请求者都 watch 锁持有者,当代表锁持有者的 znode 被删除以后,所有的锁请求者都会通知到,但是只有一个锁请求者能拿到锁
思路2<br>
创建一个临时顺序节点lock
去获取lock 下的子节点
将子节点按照序号大小从小到大排序<br>
将节点序号最小的获取锁<br>
执行完业务释放锁(断开连接,临时节点失效、)
其他节点继续阻塞,等待唤醒
阻塞的节点监听比自己小1的节点
当检测到临时节点失效时,则阻塞节点依次唤醒
思路3
Curator 可重入分布式锁
优点
ZooKeeper分布式锁(如InterProcessMutex),具备高可用、可重入、阻塞锁特性,可解决失效死锁问题
缺点
因为需要频繁的创建和删除节点,性能上不如Redis
在高性能、高并发的应用场景下,不建议使用ZooKeeper的分布式锁
并发量不是太高的应用场景中,还是推荐使用ZooKeeper的分布式锁
redis分布式锁
setnx
使用方法<br>
setnx key value
将 key 的值设为 value,若给定的 key 已经存在,则 SETNX 不做任何动作<br>
EXISTS job # job这个key不存在
返回(integer) 0<br>
SETNX job "programmer" # 设置job
(integer) 1
SETNX job "programmer2" # 重新设置job,失败
(integer) 0
GET job # job还是第一次设置的值
"programmer"
使用思路
根据setnx的结果来判断是否是获取到锁,是就执行减库存的操作,不是则返回网络错误
当在执行删除key的操作前,程序异常退出了,后面的请求永远不会执行到减库存的逻辑
try…finally来解决
除了程序异常退出,还可能机器宕机,这样异常就捕获不到了,这是就需要给key设置一个过期时间
等到过期时间到后key就会被redis销毁掉,不会影响其他的请求
设置过期时间的代码和设置锁值的代码应该使用原子性来执行
redisson<br>
使用方法<br>
获得redisson锁
//拿到锁对象<br>RLock redissonLock=redisson.getLock(couponKey);<br>
加锁(同时实现加锁,看门狗锁续命的功能)
//拿到锁对象<br>RLock redissonLock=redisson.getLock(couponKey);<br>
释放锁
//解锁<br>redissonLock.unlock();<br>
使用思路
考虑到业务时间比超时时间长,考虑可以给锁续命的问题
让后面的线程无法删除前面线程<br>
每个线程都设置一个唯一ID(UUID)。在删除锁的时候,判断一下是不是自己线程的ID,这样就无法删除其他线程的锁了
锁续命
如果业务代码在无异常,并且仍然在执行的情况下,我们加一个可以自动将锁时间延长的功能
释放锁如何保持原子性
释放锁的时候,如果出现异常续命任务还会继续执行
数据库分布式锁
插入一条数据lock字段作为唯一键<br>
如果插入成功则可以加锁成功,
执行完业务释放锁<br>
如果插入失败则等待,唯一数据删除后才能获取锁<br>
问题
当输入获取到锁的服务宕机了,则无法释放锁,其他服务将一直处于等待状态
注册中心
zookeeper可以充当一个服务注册表(Service Registry)
RPC框架中有3个重要的角色
注册中心 :保存所有服务的名字,服务提供者的ip列表,服务消费者的IP列表
服务提供者: 提供跨进程服务
服务消费者: 寻找到指定命名的服务并消费
让多个服务提供者形成一个集群
让服务消费者通过服务注册表获取具体的服务访问地址(ip+端口)去访问具体的服务提供者
实现思路
服务注册
在zookeeper中创建了一个znode节点
IP
端口
调用方式
协议
序列化方式
该节点承担着最重要的职责,它由服务提供者(发布服务时)创建<br>
以供服务消费者获取节点中的信息,从而定位到服务提供者真正网络拓扑位置以及得知如何调用
过程
服务提供者启动时,会将其服务名称,ip地址注册到配置中心
<br>
当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从IP列表中取一个服务提供者的服务器调用服务
<br>
<br>
当某个服务的所有服务器都下线了,那么这个服务也就下线了
<br>
服务提供方可以根据服务消费者的数量来作为服务下线的依据
感知服务
心跳检测功能
定时向各个服务提供者发送一个请求(长链接)
如果长期没有响应,服务中心就认为该服务提供者已经“挂了,并将其剔除
zookeeper实现方式
服务消费者会去监听相应服务路径
路径上的数据有任务变化(增加或减少)
zookeeper都会通知服务消费方服务提供者地址列表已经发生改变,从而进行更新
特点
zookeeper 与生俱来的容错容灾能力(比如leader选举),可以确保服务注册表的高可用性
zookeeper 作为注册中心时,客户端订阅服务时会向 zookeeper注册自身<br>
主要是方便对调用方进行统计、管理
订阅时是否注册 client 不是必要行为,和不同的注册中心实现有关,例如使用 consul 时便没有注册
0 条评论
下一页