分布式进阶(二三)——分布式框架之可扩展:Zookeeper分布式锁

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

我在分布式框架之高性能:Redis分布式锁一章中,介绍过Redis分布式锁。事实上,生产环境中,Zookeeper分布式锁更加成熟,在工业运用中也更多。

本章,我将基于比较常用的Curator开源框架,来聊一聊Curator对ZooKeeper(以下简称ZK)分布式锁的实现。

Curator可以看成是Zookeeper Client,类似于Jedis、Redisson之于Redis,读者可以从这里获取到Curator这个ZK客户端的更多资料:http://curator.apache.org 。

一、基本原理

首先大家看看下面的图,如果现在有两个客户端要一起争抢ZK上的一把分布式锁,会是个什么场景?

上图中,ZK里有一把锁“my_lock”,这个锁其实就是ZK的一个znode。然后两个客户端都要来获取这个锁,具体是怎么来获取呢?

1.1 创建临时顺序节点

我们假设客户端A抢先一步,对ZK发起了加分布式锁的请求,这个加锁请求其实就是在"my_lock"这个znode下,创建一个 临时顺序节点 ,这个顺序节点有ZK内部自行维护的一个节点序号。

比如说,第一个客户端来创建一个顺序节点,zk内部会给它起个名字叫做:xxx-000001。然后第二个客户端来搞创建一个同名节点,ZK会起另一个名字叫做:xxx-000002。大家注意一下, 最后一个数字都是依次递增的 ,从1开始逐次递增,ZK会维护这个顺序。

大家看下面的图,Curator框架大概会弄成如下的样子:

也就是说,客户端A发起一个加锁请求,则会在要加锁的node下搞一个临时顺序节点,这一大坨长长的名字都是Curator框架自己生成出来的。注意一下,因为客户端A是第一个发起请求的,所以顺序节点的序号是"1"。

客户端A创建完一个顺序节点后,他会查一下"my_lock"这个znode下的所有子节点,他大概会拿到这么一个集合:

    [
        "_c_0abad917-53a6-ab12-872a-bfac2d12a20a-lock-0000000001"
    ]

然后,客户端A判断自己创建的那个顺序节点是不是排在第一个的,如果是就加锁成功了:

1.2 创建监听器

接着,客户端B过来想要加锁,这个时候他也会干一样的事儿:在"my_lock"这个znode下创建一个临时顺序节点:

因为客户端B是第二个创建临时顺序节点的,所以ZK内部会维护序号为"2"。接着客户端B同样会走加锁判断逻辑,查询"my_lock"这个znode下的所有子节点,按序号顺序排列,此时他看到的类似于:

    [
       "_c_0abad917-53a6-ab12-872a-bfac2d12a20a-lock-0000000001",
       "_c_0abad917-18a6-ab12-872a-dac2d12a201a-lock-0000000002"
    ]

客户端B还想自己创建的顺序节点不是第一个,所以加锁失败!加锁失败以后,客户端B就会通过ZK的API, 对他的上一个顺序节点加一个监听器 ,监听这个节点是否被删除等变化。

说了那么多,老规矩,给大家来一张图,直观的感受一下:

1.3 删除临时顺序节点

接着,客户端A加锁成功之后,处理自身的业务逻辑,处理完后就会释放锁。释放锁其实就是把自己在ZK里创建的那个顺序节点给删除掉。删除了这个节点之后,ZK会负责通知监听该节点的监听器,也就是客户端B之前加的那个监听器:

此时,客户端B的监听器感知到了上一个顺序节点被删除,就会通知客户端B重新尝试去获取锁。客户端B一判断,发现自己居然是集合中的第一个顺序节点,然后就可以加锁了。加锁成功后,执行自身业务逻辑,然后释放锁:


如果有客户端C、客户端D等N个客户端争抢一个ZK分布式锁,原理都是类似的。大家都是上来直接在某个znode下的一个接一个得创建临时顺序节点:

  • 如果自己不是第一个节点,就对自己上一个节点加监听器;
  • 只要上一个节点释放锁,自己就排到前面去了,相当于是一个排队机制。

而且用临时顺序节点的另外一个用意是:如果某个客户端创建临时顺序节点之后,自己宕机了也没关系,因为ZK感知到那个客户端宕机后,会自动删除对应的临时顺序节点,相当于自动释放了锁。

了解了Zookeeper分布式锁的基本原理,咱们来看下用Curator框架进行加锁和释放锁的一个过程:

    // 定义锁节点名称
    InterProcessMutex lock = InterProcessMutex(client, "/locks/my_lock");
    
    // 加锁
    lock.acquire();
    
    // 业务逻辑代码...
    
    // 释放锁
    lock.release();

二、重复加锁问题

当使用Zookeeper来实现分布式锁时,如果网络发生分区,可能会出现“脑裂问题”。

比如,客户端A加锁成功后,如果客户端A和Zookeeper之间网络断开,Zookeeper未收到客户端A的心跳,可能认为客户端A挂了,就会释放锁。此时,之前等待的客户端B就会获取到锁,导致客户端B和客户端A都认为自己获取到了同一把锁,就会出现问题。

解决该问题的一个基本思路就是,Zookeeper在创建“锁”时保存对应客户端的标识,这样客户端A如果挂了,客户端B尝试来获取锁时,Zookeeper就会判断客户端A的锁还没释放,就会拒绝客户端B获取锁。

三、优缺点

我们来比较下Zookeeper分布式锁和Redis分布式锁。

3.1 优点

Zookeeper分布式锁,如果客户端获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小;而Redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能。

另外一点,Zookeeper创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁;而Redis获取锁的那个客户端如果挂了,那么只能等待超时时间之后才能释放锁。

最后,从分布式系统的协调语义来看,ZooKeeper做分布式锁更好一些,因为Redis本身其实是缓存。

3.2 缺点

Zookeeper本身不适合大规模集群部署,其适用场景就是部署三五台机器,不是承载高并发请求的,仅仅是用作分布式系统的协调工作;而Redis本身其实是缓存,能抗高并发,在高并发场景下性能更好一些。

四、总结

本章,我介绍了Zookeeper分布式锁的基本原理,并拿它和Redis分布式锁进行了比较。一般来讲,绝大多数的公司其实很少有超高并发的业务场景,那么ZooKeeper分布式锁基本都能满足需求。对于一些大型公司的核心业务,一般也会针对实际的业务场景,对ZK分布式锁或Redis分布式锁进行定制改造,以满足自身业务需求。

  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值