如何实现靠谱的分布式锁

 

分布式锁,是用来控制分布式系统中互斥访问共享资源的一种手段,从而避免并行导致的结果不可控。基本的实现原理和单进程锁是一致的,通过一个共享标识来确定唯一性,对共享标识进行修改时能够保证原子性和和对锁服务调用方的可见性。由于分布式环境需要考虑各种异常因素,为实现一个靠谱的分布式锁服务引入了一定的复杂度。

分布式锁服务一般需要能够保证:

  • 同一时刻只能有一个线程持有锁;
  • 锁能够可重入;
  • 不会发生死锁;
  • 具备阻塞锁特性,且能够及时从阻塞状态被唤醒;
  • 锁服务保证高性能和高可用。

当前使用较多的分布式锁方案主要基于 redis、zookeeper 提供的功能特性加以封装来实现的,下面我
们会简要分析下这两种锁方案的处理流程以及它们各自的问题。

1. 基于 Redis 实现的锁服务

加锁流程

SET resource_name my_random_value NX PX max-lock-time

注:资源不存在时才能够成功执行 set 操作,用于保证锁持有者的唯一性;同时设置过期时间用于防
止死锁;记录锁的持有者,用于防止解锁时解掉了不符合预期的锁。

解锁流程

if redis.get(“resource_name”) == “ my_random_value”
return redis.del(“resource_name”)
else
return 0

注:使用 lua 脚本保证获取锁的所有者、对比解锁者是否所有者、解锁是一个原子操作。

该方案的问题在于

  1. 通过过期时间来避免死锁,过期时间设置多长对业务来说往往比较头疼,时间短了可能会造成:持有锁的线程 A 任务还未处理完成,锁过期了,线程 B 获得了锁,导致同一个资源被 A、B 两个线程并发访问;时间长了会造成:持有锁的进程宕机,造成其他等待获取锁的进程长时间的无效等待。
  2. Redis 的主从异步复制机制可能丢失数据,会出现如下场景:A 线程获得了锁,但锁数据还未同步到 slave 上,master 挂了,slave 顶成主,线程 B 尝试加锁,仍然能够成功,造成 A、B 两个线程并发访问同一个资源。

2. 基于 ZooKeeper 实现的锁服务

加锁流程

  1. 在 /resource_name 节点下创建临时有序节点。
  2. 获取当前线程创建的节点及 /resource_name 目录下的所有子节点,确定当前节点序号是否最小,是则加锁成功。否则监听序号较小的前一个节点。
    注:zab 一致性协议保证了锁数据的安全性,不会因为数据丢失造成多个锁持有者;心跳保活机制解决死锁问题,防止由于进程挂掉或者僵死导致的锁长时间被无效占用。具备阻塞锁特性,并通过watch 机制能够及时从阻塞状态被唤醒。

解锁流程

删除当前线程创建的临时接点。

该方案的问题在于

通过心跳保活机制解决死锁会造成锁的不安全性,可能会出现如下场景:持有锁的线程 A 僵死或网络故障,导致服务端长时间收不到来自客户端的保活心跳,服务端认为客户端进程不存活主动释放锁,线程 B 抢到锁,线程 A 恢复,同时有两个线程访问共享资源。

基于上诉对现有锁方案的讨论,我们能看到,一个理想的锁设计目标主要应该解决如下问题:

  1. 锁数据本身的安全性;
  2. 不发生死锁;
  3. 不会有多个线程同时持有相同的锁。

而为了实现不发生死锁的目标,又需要引入一种机制,当持有锁的进程因为宕机、GC 活者网络故障等各种原因无法主动过释放锁时,能够有其他手段释放掉锁,主流的做法有两种:

  1. 锁设置过期时间,过期之后 Server 端自动释放锁;
  2. 对锁的持有进程进行探活,发现持锁进程不存活时 Server 端自动释放。

实际上不管采用哪种方式,都可能造成锁的安全性被破坏,导致多个线程同时持有同一把锁的情况出现。因此我们认为锁设计方案应在预防死锁和锁的安全性上取得平衡,没有一种方案能够绝对意义上保证不发生死锁并且是安全的。而锁一般的用途又可以分为两种,实际应用场景下,需要根据具体需求出发,权衡各种因素,选择合适的锁服务实现模型。无论选择哪一种模型,需要我们清楚地知道它在安全性上有哪些不足,以及它会带来什么后果。

  • 为了效率,主要是避免一件事被重复的做多次,用于节省 IT 成本,即使锁偶然失效,也不会造成数据错误,该种情况首要考虑的是如何防止死锁。
  • 为了正确性,在任何情况下都要保证共享资源的互斥访问,一旦发生就意味着数据可能不一致,造成严重的后果,该种情况首要考虑的是如何保证锁的安全。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值