前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分布式锁实现

分布式锁实现

作者头像
春哥大魔王
发布2019-10-09 15:53:54
5910
发布2019-10-09 15:53:54
举报

写在前面

我们知道在同一个JVM中,可以通过Volatile、Synchronized、ReentrantLock 三个关键字来实现线程的安全。那么在分布式系统中这些是无法保证的,所以要通过分布式锁来实现。

基于分布式锁的实现有多种方案,现针对Redis 和Zookeeper 这两种方式聊一聊功能具体实现方式、优缺点以及各自适用的业务场景。

基于Redis Cluster模式下的分布式锁的实现

基于setnx 锁实现

加锁

正常基于Redis 自带的setnx 命令就可以实现简单的加锁功能:

setnx key value

Key 作为锁的唯一标识,当线程setnx返回1时,说明原本key不存在,该线程成功得到了锁;如果返回的结果为0,则说明key已经存在,线程获取锁失败。

解锁

当线程获取锁,执行完任务后,需要释放锁,以便后续线程使用。可以通过del 这个key来实现:

del key

但是由于加锁和解锁是分为两步实现,不是原子操作,所以可能会出现中间状态:

  • 即加锁完成后,没有解锁(或解锁失败);导致资源锁住。

基于此有这种方案:

  1. 通过制定key的过期时间,让锁到期后自动释放:

expire key

显而易见,这边也有一个上述的风险点:就是非原子操作,可能存在中间状态。

所以我们引入第二种方案。

  1. 基于set 原子操作实现:

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

  • EX seconds:设置键key的过期时间,单位是秒;
  • PX milliseconds:设置键key的过期时间,单位是毫秒;
  • NX:只有键key不存在的时候才会设置key的值;
  • XX:只有键key存在的时候才会设置key的值。

通过 set (key, value, EX a,NX) 取代setnx 来实现原子加锁(自动释放锁)操作。(redis-client中的API setnx 即是基于此实现,所以在使用中直接调用API的setnx)

基于Redis 实现分布式锁的一些问题

  • 在高并发下的分布式锁实现中,key的过期肯定不能设置的太长,否则会影响后续线程持有该锁;
  • 但是如果设置过期时间很短,直到key过期,持有该锁的线程还未执行完任务;接着下一个线程获取到该锁,这时候前一个线程执行完成后触发del释放该锁,而这把锁这个时候其实是另外一个线程持有;
  • 获取锁是非阻塞的,无论成功还是失败就直接返回;
  • 锁公平问题,所有等待线程同时发起获取锁命令操作。

针对上述问题需要另外服务来保证实现:

  1. 守护线程:如果某个线程在expire时间内,还未执行完成,守护线程自动expire一个新过期时间,直到该线程执行完成或释放;
  2. 释放验证:线程加锁前通过线程ID和Key Value匹配,释放前通过两者判断是否一致,一致再释放该锁,避免错误释放其他线程执行时持有相同的锁(原子性可以通过lua脚本来实现);
  3. 阻塞锁: 通过while true之类的机制去阻塞代码实现;
  4. 公平锁:可以通过将所有等待线程放入同一个队列来实现。

可重入性

Java中的Lock对象以及Synchronized关键字语块都可具有可重入性,可以实现同一个线程共用同一把锁;避免死锁发生的可能。

而在Redis上述实现中则没有相应的功能,如果业务上需要,则需在业务代码中实现其逻辑。

安全性

Redis Cluster 在master异常情况下,会发生主从切换,而主从是异步复制,极大可能导致数据丢失,从而导致锁的失效。这块安全性方面Redis Cluster 无法保证。

但是Redis 作者实现了基于多节点的高可用分布式锁的算法 RedLock。有兴趣的可以了解一下。

基于Zookeeper 集群模式下的分布锁的实现

Zookeeper 通过临时有序(顺序)节点实现分布式锁。

所谓临时顺序节点就是Zookeeper根据创建的时间给该节点名称进行编号,当创建节点的客户端与Zookeeper 断开连接后,临时节点就被删除。

Zookeeper 通过创建临时有序节点实现上锁,只有序号最小(或顺序最靠前)的可以成功获取到锁;

如果该序号不是最小(或不是顺序最靠前)则向它前一个节点注册Watcher,通过watch来监听前一个节点是否存在,等待watch事件(即监听节点的状态变化),如果监听到watch事件发生,则再次判断该节点是否为序号最小节点,如果是则成功获取锁,否则,继续监听等待。

Zookeeper 实现分布式锁的功能

Zookeeper 实现锁的方式上较为简单。

  • 由于Zookeeper 使用集群模式,可以避免单点故障;
  • 临时有序节点,临时节点可以避免锁的中间状态(即永久持有该锁);有序可以保障锁的公平问题;
  • 通过watch实现阻塞。

Zookeeper 实现分布式锁的一些问题

每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上。

所以性能开销这块比Redis 大,即并发性能不如Redis。

Redis 和 Zookeeper 分布锁实现比较

Redis Cluster

优点:实现指令性能较高

缺点:

  • 实现较为复杂;
  • 需要额外多个服务来保障;
  • 安全性较低。

适用场景:

高并发的分布式锁实现

Zookeeper

优点:

  • 现有的框架,实现简单;
  • 等待锁队列,提升抢占锁效率。

缺点:

添加和删除节点性能较低

适用场景:

并发量小,安全性要求较高的业务场景

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 春哥talk 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基于Redis Cluster模式下的分布式锁的实现
    • 加锁
      • 解锁
        • 基于Redis 实现分布式锁的一些问题
          • 可重入性
            • 安全性
            • 基于Zookeeper 集群模式下的分布锁的实现
              • Zookeeper 实现分布式锁的功能
                • Zookeeper 实现分布式锁的一些问题
                • Redis 和 Zookeeper 分布锁实现比较
                  • Redis Cluster
                    • Zookeeper
                    相关产品与服务
                    云数据库 Redis
                    腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档