Redisson源码跟踪和时间轮算法

177

Redisson

使用redis实现了分布式锁

分布式锁特性

互斥性

多个线程并发时,互相排斥,需要互斥则使用相同的key即可

1bb8e2333eda0034c5f0150d85adcfb7.png

cfed4b97717cfed7300cee65f1e1eab1.png
88b97a2b3ecf5badc78dbeb23573e095.png

getName为key名称,getLockName记录了线程

数据结构类似如下:

// redis key
{
  "线程id": 1
}

加锁成功脚本中都会返回nil,否则会返回剩余过期时间毫秒数

防死锁

防止加了锁,程序奔溃后没有释放锁的情况,导致其他线程永远无法成功加锁

通过给key添加过期时间来解决,但是如果过期时间到了,业务代码还没执行结束,这种情况redisson使用watch dog来解决,watch dog见下文

可重入

例如递归调用,如果当前线程加锁成功,当前线程递归调用该方法,应该可以成功加锁,而不是已经加了锁则报错加锁失败

15f4af420fe2f7a41e9021ba456d12e6.png

lua脚本的第二个if中,判断了ARGV[2]即线程id存在情况下,对value进行次数增加1,所以可重入通过value记录了重入的次数

锁误删

即只能是加锁的人来解锁或者防死锁情况自动解锁

锁误删举例:

  1. 线程A进行加锁,并加锁成功,继续执行业务代码
  2. redis服务器宕机,并且没有持久化
  3. redis服务器恢复
  4. 线程B进行加锁,并加锁成功(此时Redis服务器中的锁事线程B加的)
  5. 线程A业务执行完成,执行锁释放,这事如果没有锁误删限制,那么线程A就会释放线程B加的锁

实现:

d6212bac78354c5d415d94cae3169c9d.png
b909bf15b02691b5dd57303806aa93c6.png

e7520620c46909cb948640b375fec9e8.png

依旧通过线程id,作为hash的key的方式来防止误删

Watch Dog 看门狗

a418b451f9920ac70d18aa2af9cea911.png

9bf14814972c2684fe63e3b83b226d7b.png

c572f031eb212dc9a0c6608990e7dea3.png

f3689a839e535b1aa24733d712adbd2e.png

启动看门狗的代码如上图

使用commandExecutor.getConnectionManager().newTimeout的方式添加了一个延时任务

287a3577020196449ec44010b3bd4e35.png

使用timer添加刚才定义的Task延时任务,看看timer是什么

652191b40ac9060c8bf89e83eaa1ca6a.png

HashedWheelTimer是netty提供的一个基于时间轮算法的延迟队列工具,用于延迟执行任务

时间轮算法

参考原文https://www.cnblogs.com/lihao007/p/10588072.html

03e4c43042972587a18147565d655ca8.png

HashedWheelTimer(
                    ThreadFactory threadFactory,
            long tickDuration, TimeUnit unit, int ticksPerWheel, boolean leakDetection,
            long maxPendingTimeouts)

threadFactory:线程工厂,用于创建工作线程,并且只会创建一个
tickDuration + unit:在每一个格子上的停留时间
ticksPerWheel:时间轮的格子数,每一个格子里都是一个Timeout任务队列bucket,双向链表结构
leakDetection:默认为true,开启泄漏检测
maxPendingTimeouts:最大挂起Timeouts任务数量

案例:

如图,一个轮分成8个bucket,每一个tick需要1秒,现在有一个延迟任务为延迟20秒后执行,则需要tick20次,1轮需要tick8次,则20/8 = 2轮,这个任务在添加到这个轮(wheel)中时,通过hash算法计算出应该放到哪一个目标bucket中并将计算出的2轮设置为该任务的剩余轮数,当指针每tick一次,落到这个bucket中时,会将bucket中的timeout(我们自己的延迟任务)的剩余轮数-1,当剩余轮数小于等于0的时候就执行该任务并从bucket中移除