分布式锁:Redisson源码解析——RLock(二)

一、复习

场景模拟

初始化

在获取锁的时候,会初始化一些参数,例如commandExecutor leaseTime uuid
● commandExecutor 命令执行器
● leaseTime 持有锁的最大时间,租期
● uuid 客户端的唯一标识

第一次加锁

  1. 设置获取到锁的主体,使用线程id作为标识
  2. 当未设置leaseTime的时候,也就是代表的锁可以无限持有时间
  3. 判断redis中是否已经存在了这个锁
  4. 第一次加锁肯定不存在,递增一个lockName为key的hash数据结构里面的一个key为uuid:threadId的元素
{
  "lockName": {
    "uuid:threadId": 1
  }
}
  1. 给设置这个redis key设置一个过期时间 leaseTime,返回一个null
  2. 此时,就表明了客户端已经加锁成功了
  3. 收到监听加锁结果后,判断条件是ttl == null就表示这个客户端加锁成功
  4. 如果锁是有最大持有时长的,就会直接结束整个流程
  5. 如果是没有最大持有时长,会启动一个leaseTime/3 后执行的任务,任务会将锁的过期时间重新给设置为leaseTime,这个机制也被称之为watchdog
  6. 同时,本地还会维护一个watchdog需要监听的map,也就是每一个加锁成功且需要watchdog去设置过期时间的锁都会被记录在map中
{
  "uuid:lockName": {
    "timeout": timeout object,
    "threadIds": {
      "threadId": 1,
      "counter": number
    }
  }
}

第二个其他线程来加锁: 互斥锁,锁竞争

  1. 获取当前线程的标识 threadId
  2. 第二个线程来加锁的时候,会发现一些不同的地方
  3. 判断redis中是否已经存在了这个锁
  4. 判断redis中这个key是否存在一个key为 uuid:threadId的元素
  5. 因为是其他线程,所以也不存在
  6. 会获取这个key的有效时间再返回出去
  7. 此时i,内部会判断ttl不为空,就会走进一个死循环
  8. 在循环内部会去持续的去获取锁
  9. 为了提高分布式锁的效率,就是在这个循环的内部是引入了信号量的数据结构的
  10. 这个时候,线程就阻塞住了

第三个线程来加锁:可重入锁

  1. 获取当前线程的标识threadId
  2. 判断redis中是否已经存在了这个锁
  3. 判断redis中这个key是否存在一个key为 uuid:threadId的元素
  4. 发现第二个条件是满足的
  5. 此时,会递增一下redis key的里面uuid:threadId的元素
  6. 重置过期时间为leaseTime
  7. 返回null,表示加锁成功
  8. 维护本地的map的时候,发现已经存在了当前key的元素,就会将里面的conter递增

释放锁

  1. 释放锁,其实和加锁的核心流程是类似的
  2. 先获取当前线程的threadId
  3. 去本地维护的map里,找到这个uuid:locakName删除这个threadId对应的map,如果key中元素threadIds已经是空了,就删除key
  4. 判断redis中lockName的key中是否存在一个key为uuid:threadId的元素
  5. 正常释放锁的情况下,会将redis中key对应的hash中的元素的值-1
  6. 判断递减之后的这个值是否是大于0的
  7. 如果大于零,也就是存在重入锁,就会做一个重置过期时间
  8. 否则就会直接删除redis key,还发了一个广播消息,不知道干啥的
  9. 返回1
  10. 本地处理只要返回的不是null就相当于释放锁成功,否则释放锁失败

获取锁后客户端宕机

  1. 当获取锁以后,客户端宕机了
  2. 此时,在客户端辛勤劳作的watchdog就会死掉
  3. 然后,等待redis中key的超时,其他客户端就会获取到锁

二、互斥、可重入、释放锁、锁超时和自动释放

互斥

通过在redis中维护一个hash结构,控制加锁的线程和机器不会重复
● redis性能足够高,能支撑高并发
● hash结构足够灵活

阻塞

如果一个锁获取失败的话,会在本地无限循环来获取锁,锁竞争都处于一个循环执行的状态
● 同时引入了信号量避免并发过高的问题

可重入

可重入锁是为了避免出现死锁的情况,需要的一个特点
通过在redis和本地都维护了一个当前机器threadId的加锁次数

释放锁

本质上,释放锁其实就是加锁的反操作,会递减加锁次数,去删除redis中的key,本地map中的key

if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then 
  return nil;
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then
	redis.call('pexpire', KEYS[1], ARGV[2]);
	return 0;
else
  redis.call('del', KEYS[1]);
  redis.call('publish', KEYS[2], ARGV[1]);
  return 1;
end;
return nil;

锁超时

就是加锁超时:waitTime
● 获取当前时刻current
● 尝试加锁后,如果加锁成功则直接返回,如果加锁失败则会waitTime减掉这个加锁过程的时间
● 判断是否已经超时了,超时则失败
● 后续会进入一个循环中去持续加锁
● 只要尝试加一次锁,就要去计算一下这个时间

超时锁自动释放

就设置了leaseTime参数
● 不会再去触发watchdog,等待redis中的锁超时后就会自动释放

总结

大图

在这里插入图片描述
图比较乱奥,但是应该能大概看得明白,比较核心的组件都画出来了

隐患

本质还是在集群中挑选一个master来加锁,实现了高可用机制,如果master宕机,slave会自动切换为master
如果master刚被写入一个锁,然后就宕机了,还没有被异步写入到slave中,slave就切换成了新的master,这样的话,其他线程就也会就获取锁成功

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值