5.Redisson源码-公平锁源码剖析之释放锁

一、说明

  1. 其实公平锁释放的源码也在RedissonFairLock中,unlockInnerAsync,笔记是接着之前的笔记一起的,所以需要连续的看下来

二、源码中的参数

这里的参数和获取锁的时候大部分是一样的,但是有略微的不同,还需要注意

KEYS = Arrays.<Object>asList(getName(), threadsQueueName, timeoutSetName)
KEYS[1] = getName() = 锁的名字,“anyLock”
KEYS[2] = threadsQueueName = redisson_lock_queue:{anyLock},基于redis的数据结构实现的一个队列
KEYS[3] = timeoutSetName = redisson_lock_timeout:{anyLock},基于redis的数据结构实现的一个Set数据集合,有序集合,可以自动按照你给每个数据指定的一个分数(score)来进行排序
KEYS[4] = redisson_lock_channel:{anyLock}

ARGV =  internalLockLeaseTime, getLockName(threadId), currentTime + threadWaitTime, currentTime
ARGV[1] = 0L
ARGV[2] = 30 * 1000
ARGV[3] = UUID:threadId
ARGV[4] = 当前时间(10:00:00)

三、代码

  1. 代码片段一、
@Override
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            // remove stale threads
            // 1. 首先会进入到这个死循环里面
            "while true do “
            // 1. Lindex redisson_lock_queue:{anyLock} 0 取出队列的第一个元素
            + "local firstThreadId2 = redis.call('lindex', KEYS[2], 0);”
            // 1. 如果队列中不存在的话,直接跳出死循环
            + "if firstThreadId2 == false then "
                + "break;"
            + "end; “
            // 1. 如果队列的第一个元素存在的话,则zscore redisson_lock_timeout:{anyLock} 取出他的超时时间
            + "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));”
            // 1. 如果超时时间timeout <= 当前时间,说明已经超时了
            + "if timeout <= tonumber(ARGV[4]) then “
                // 1. zerm redisson_lock_timeout:{anyLock} UUID_01:threadId_01,删除这个客户端,其实这里的01代表的是当前客户端的UUID和线程ID
                + "redis.call('zrem', KEYS[3], firstThreadId2); “
                // 1. lpop redisson_lock_queue:{anyLock} 从队列中删除这个元素
                + "redis.call('lpop', KEYS[2]); "
            + "else "
                + "break;"
            + "end; "
          + "end;"
            
            // 1. exists anyLock==0 如果anyLock不存在的话
          + "if (redis.call('exists', KEYS[1]) == 0) then “ + 
                // 1. lindex redisson_lock_queue:{anyLock} 0 取出队列中的第一个元素
                "local nextThreadId = redis.call('lindex', KEYS[2], 0); “ + 
                // 1. 如果队列中的元素也不存在,说明这个队列现在是空的
                "if nextThreadId ~= false then “ +
                // 1. 发布一个订阅事件,具体的订阅事件是干嘛的,暂时还看不出来
                    "redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
                "end; " +
                "return 1; " +
            "end;” +
            // 1. hexists anyLock 当前时间(10:00:00) + 5000毫秒 = 10:00:05
            "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                "return nil;" +
            "end; “ +
            // 1.hincrby anyLock  当前时间(10:00:00) + 5000毫秒 = 10:00:05 减一,其实意思就是如果是可重入加锁的话,减去一
            "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); “ +
            // 1. 如果加锁次数还是大于0的话
            "if (counter > 0) then “ +
                //1.pexpire anyLock UUID_01:threadId_01 更新他的生存周期
                "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                "return 0; " +
            "end; " +
              
            // 1. 删除anyLock,如果走到这一步,说明是当前持有锁的客户端释放锁,那么这个时候,释放锁,然后从队列中取出第一个元素,发布一个订阅事件,让后面的排队的线程或者客户端尝试获取锁  
            "redis.call('del', KEYS[1]); " +
            "local nextThreadId = redis.call('lindex', KEYS[2], 0); " + 
            "if nextThreadId ~= false then " +
                "redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
            "end; " +
            "return 1; ",
            Arrays.<Object>asList(getName(), threadsQueueName, timeoutSetName, getChannelName()), 
            LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId), System.currentTimeMillis());
}

四、总结

  1. 在客户端A他释放锁的时候,也会走while true的脚本逻辑,看一下有序集合中的元素的timeout时间如果小于了当前时间,如果小于的话,就认为他的那个排队就过期了,就删除他,让他后面重新尝试获取锁的时候重排序
  2. while true的逻辑,比如说客户端B或者客户端C,他们用的是tryAcquire()方法,他们其实设置了一个获取锁超时的时间,比如说他们在队列里排队,但是尝试获取锁超过了20秒,人家就不再尝试获取锁了
  3. 此时他们还是在队列redisson_lock_queue{anyLock}和有序集合redisson_lock_timeout:{anyLock}里占了一个坑位,while true的逻辑就可以保证说剔除掉这种不再尝试获取锁的客户端,有序集合里的timeout分数就不会刷新了,随着时间的推移,肯定就会剔除掉他
  4. 如果客户端宕机了,也会导致他就不会重新尝试来获取锁,也就不会刷新有序集合中的timeout分数,不会延长timeout分数,while true的逻辑也可以剔除掉这种宕机的客户端在队列里的占用
  5. 因为网络延迟等各种因素在里面,可能会在等待锁时间过长的时候,触发各个客户端的排队的顺序的重排序,有的客户端如果在队列里等待时间过长了,那么其实是可以触发一次队列的重排序的
  6. 他在这里发布一个锁被释放的消息,肯定在他的源码中是有一些人是订阅了这个释放锁的消息的,此时他们就可以得到一个锁被释放掉的通知

五、寄语

每天都会有很多不一样有意思的想法和灵感,想什么很重要,用行动去实践自己的想法也很重要,不管对的错的,走过了,经历了,才知道

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266