查看原文
其他

说说:用过分布式锁吗?你们是怎么做选型的?

为什么需要分布式锁?

微服务的架构下,多个应用服务要同时对同一条数据做修改,那么要确保数据的一致性,就只能有一个应用修改成功。

下图中,server1、server2、server3 这三个服务都要修改amount这个数据,每个服务更新的值不同,

为了保证数据的正确性,三个服务都向lock server服务申请分布式排他权限,最终server2拿到了修改权限,即server2将amount更新为2,其他服务由于没有获取到修改权限则返回更新失败。

分布式锁的实现方案

  • 基于数据库的悲观锁或者乐观锁
  • 基于redis实现分布式锁
  • 基于zookeeper实现分布式锁
  • 基于其他的中间件实现分布式锁

基于数据库实现分布式锁

性能比较低,数据库的性能,摆在那儿

和业务相关度高,无论是 悲观锁或者乐观锁, 都是和业务高度耦合的

通用的分布式锁方案,一般是基于 redis、zookeeper等其他的中间件实现。

请参见文末:Redis分布式锁 (图解-秒懂-史上最全)

基于redis实现分布式锁

因为redis是一个单独的非业务服务,不会受到其他业务服务的限制,所有的业务服务都可以向redis发送写入命令,

且只有一个业务服务可以写入命令成功,那么这个写入命令成功的服务即获得了锁,可以进行后续对资源的操作,其他未写入成功的服务,则进行其他处理。

简单加锁:使用set的命令时,同时设置过期时间

早期版本使用 setnx(set if not exists) 指令简单加锁,expire 设置锁过期时间。由于 setnx 和 expire 是两条指令而不是原子指令。如果中间出现问题,有可能造成永远不过期而发生死锁。如果这两条指令可以一起执行就不会出现问题。

新的版本使用set的命令时,同时设置过期时间,示例如下:

127.0.0.1:6379> set unlock "234" EX 100 NX
(nil)
127.0.0.1:6379>
127.0.0.1:6379> set test "111" EX 100 NX
OK

这样就完美的解决了分布式锁的原子性;set 命令的完整格式:

set key value [EX seconds] [PX milliseconds] [NX|XX]

EX seconds:设置失效时长,单位秒
PX milliseconds:设置失效时长,单位毫秒
NX:key不存在时设置value,成功返回OK,失败返回(nil)
XX:key存在时设置value,成功返回OK,失败返回(nil)

使用set命令实现加锁操作,先展示加锁的简单代码实习,再带大家慢慢解释为什么这样实现。

锁的释放

当客户端1操作完后,释放锁资源,即删除key 。

如果没有删除,则在过期时间之后,锁会自动释放。

之后,其他客户端尝试获取锁时,则会获取锁成功。

锁过期处理

假如服务A加锁成功,锁会在10s后自动释放,但由于业务复杂,执行时间过长,10s内还没执行完,此时锁已经被redis自动释放掉了。

此时服务B就重新获取到了该锁,服务B开始执行他的业务,服务A在执行到第12s的时候执行完了,那么服务A会去释放锁,则此时释放的却是服务B刚获取到的锁。

这会有锁过期和释放其他服务锁这种严重的问题。

那么锁过期这种问题该如何处理的?

虽然可以通过增加删除key时间来处理这个问题,但是并没有从根本上解决。

假设设个100s,绝大多数都是1s后就会释放锁,但是由于服务宕机,则会导致100s内其他服务都无法获取到锁,这也是灾难性的。

我们可以这样做,在锁将要过期的时候,如果服务还没有处理完业务,那么将这个锁再续一段时间。

比如设置key在10s后过期,那么再开启一个守护线程,在第8s的时候检测服务是否处理完,如果没有,则将这个key再续10s后过期。

在Redisson(Redis SDK客户端)中,就已经帮我们实现了这个功能,这个自动续时的我们称其为”看门狗”。

如果进行线程标识,避免错误释放,并且保证释放的原子性?

每个服务在设置value的时候,带上自己服务的唯一标识,如UUID,或者一些业务上的独特标识。

这样在删除key的时候,只删除自己服务之前添加的key就可以了。

如果需要先查看锁是否是自己服务添加的,需要先get取出来判断,然后再进行del。

这样的话就无法保证原子性了。

我们可以通过Lua脚本,将这两个操作合并成一个操作,就可以保证其原子性了。

如果是在单redis实例的情况下,上面的已经完全实现了分布式锁的功能了。

关于 Lua脚本的内容,请阅读 《Java高并发核心编程 卷3》

那么Redis宕机,Key丢失怎么办?

在生产环境上,都会使用redis集群,但是,且主从数据并不是强一致性。

当主节点宕机后,主节点的数据还未来得及同步到从节点,进行主从切换后,新的主节点并没有老的主节点的全部数据,这就会导致刚写入到老的主节点的锁在新的主节点并没有,其他服务来获取锁时还是会加锁成功。

此时则会有2个服务都可以操作公共资源,此时的分布式锁则是不安全的。

redis的作者也想到这个问题,于是他发明了RedLock。

什么是高可用的RedLock?

要实现高可用的RedLock,需要至少5个实例(官方推荐),且每个实例都是master,不需要从库和哨兵。

RedLock算法思想:

不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,n / 2 + 1,必须在大多数redis节点上都成功创建锁,才能算这个整体的RedLock加锁成功,避免说仅仅在一个redis实例上加锁而带来的问题。

这个场景是假设有一个 redis cluster,有 5 个 redis master 实例。然后执行如下步骤获取一把红锁:

  1. 获取当前时间戳,单位是毫秒;

  2. 跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒;

  3. 尝试在大多数节点上建立一个锁,比如 5 个节点就要求是 3 个节点 n / 2 + 1;

  4. 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了;

  5. 要是锁建立失败了,那么就依次之前建立过的锁删除;

  6. 只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁。

即当客户端在大多数redis实例上申请加锁成功后,且加锁总耗时小于锁过期时间,则认为加锁成功。

释放锁需要向全部节点发送锁释放命令。

第3步为啥要计算申请锁前后的总耗时与锁释放时间进行对比呢?

因为如果申请锁的总耗时已经超过了锁释放时间,那么可能前面申请redis的锁已经被释放掉了,保证不了大于等于3个实例都有锁存在了,锁也就没有意义了

这样的话分布式锁就真的没问题了嘛?

1、得5个redis实例,成本大大增加

2、可以通过上面的流程感受到,这个RedLock锁太重了

3、主从切换这种场景绝大多数的时候不会碰到,偶尔碰到的话,保证最终的兜底操作我觉得也没啥问题。

4、分布式系统中的NPC问题

RedLock是基于redis实现的分布式锁,它能够保证以下特性:

  • 互斥性:在任何时候,只能有一个客户端能够持有锁;避免死锁:
  • 当客户端拿到锁后,即使发生了网络分区或者客户端宕机,也不会发生死锁;(利用key的存活时间)
  • 容错性:只要多数节点的redis实例正常运行,就能够对外提供服务,加锁或者释放锁;

高可用的红锁会导致性能降低

提前说明,使用redis分布式锁,是追求高性能, 在cap理论中,追求的是 ap 而不是cp。

所以,如果追求高可用,建议使用 zookeeper分布式锁。

redis分布式锁可能导致的数据不一致性,建议使用业务补偿的方式去弥补。所以,不太建议使用红锁,但是从学习的层面来说,大家还是一定要掌握的。

聊了这么多的redis实现分布式锁。也简单了解下zookeeper是如何实现分布式锁的吧。

基于zookeeper实现分布式锁

尼恩的这篇博客,非常细致(文末)

Zookeeper 分布式锁 (图解+秒懂+史上最全)

那么实际的工作中,该如何选择分布式锁呢?

AP  Redis 分布式锁

CP  Redis 红锁(>=5个redis实例)、 或者 Zookeeper分布式锁

参考文献

Redis分布式锁 (图解-秒懂-史上最全)

https://www.cnblogs.com/crazymakercircle/p/14731826.html

Zookeeper 分布式锁 (图解+秒懂+史上最全

https://www.cnblogs.com/crazymakercircle/p/14504520.html

https://www.cnblogs.com/crazymakercircle/p/14731826.html

https://blog.csdn.net/zs18753479279/article/details/115751593

http://blog.csdn.net/jackpk/article/details/30073097

http://www.jb51.net/article/65264.htm

https://juejin.cn/post/6844904116485898248

https://www.jianshu.com/p/4b929f54a8d1

https://blog.csdn.net/weixin_42452888/article/details/127557245


硬核面试题推荐


硬核文章推荐


硬核电子书

本文收录于:《尼恩Java 面试宝典》V18版 

长按二维码,点击“识别图中二维码”即可查看老架构师尼恩个人微信,发暗号 “领电子书” 给尼恩,获取最新PDF。


  • 最新的《尼恩Java面试宝典》

    极致经典,不断升级,目前最新为V18


  • 尼恩Java高并发三部曲

    《Java高并发核心编程-卷1(加强版)》,不断升级

    《Java高并发核心编程-卷2(加强版)》,不断升级

    《Java高并发核心编程-卷3(加强版)》,不断升级


  • 尼恩架构笔记100篇+,不断添加 


继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存