曹旭辉 发表于 2024-9-11 18:40:01

简约而不简单!分布式锁入门级实现主动续期-自省

一、背景

一个分布式锁应具备的功能特点中有制止死锁这一条:
   如果某个客户端获得锁之后处置惩罚时间超过最约莫定时间,大概持锁期间内发生了故障导致无法主动释放锁,其持有的锁也能够被其他机制精确释放,并保证后续其它客户端也能加锁,整个处置惩罚流程继续正常实验。
简单解释一下:

[*] 客户端抢到分布式锁之后开始实验任务,实验完毕后再释放分布式锁。
[*] 持锁后因客户端异常未能把锁释放,会导致锁成为永恒锁。
[*] 为了制止这种情况,在创建锁的时候给锁指定一个过期时间。
[*] 到期之后锁会被自动删撤除,这个角度看是对锁资源的一种保护。
二、理还乱?

逻辑看很简单,也很清晰,但任何事情都有两面性,自动删除自然有理,但肯定也有毛病。如果要把锁的功能做的健壮,总要从不断地自我质疑、自我反思中,理顺思路,探求答案,我认为这属于自省式学习,以后也想实验这种模式,一起来试试吧:


[*] 问题:锁过期了会被删掉,但是任务没结束怎么办?
        如果锁被释放的时候,任务尚未实验完毕,那就可能导致其它客户端又抢到锁,任务被重复实验。


[*] 问题:把锁的过期时间定的长一点?
        逻辑听起来没错,如果你能确定任务的最大耗时,那没问题;大部分情况都很难确定任务的最大耗时该是多少。


[*] 问题:锁的过期时间定多长符合?
       反正会被释放,过期时间定的富足长吧;如果锁使用的频率很高,加了锁步伐有bug释放不掉,服务端岂不是要出现大量的垃圾数据?思来想去,对一个健壮的分布式锁来说,过期时间设置太长了不符合,设置太短了也不符合。


[*] 问题:怎么平衡?
       不长不短,主动延期!持锁期间,酌情推后锁的过期时间,以基于Redis的分布式锁来说,就需要调用 API 重置锁 key 的过期时间。当前线程持锁后在实验任务期间不能再调用 API 重试锁 key 的过期时间。


[*] 问题:谁来调用API呢?
[*] 需要使用其他的线程来实验续期。
[*] 问题:给每个锁配一个线程?
        可以,如果使用分布式锁的场景中没有什么并发,一个客户端也就那么三两个锁同时存在,那就没问题。每个锁抢锁成功后,开启一个线程,在线程中通过循环给锁续期。
public void run() {
    while (true) {
      // 续租
      action.run();
    }
}

[*] 问题:多久实验一次续期?
       有一些常规处置惩罚是续租隔断默认采用过期时间的1/3。若把锁的过期时间设定为与现实耗时相差不大,如许通过一两次续租基本就满足了大部分的情况。


[*] 问题:为什么要触发一次续期操纵呢,这不浪费资源吗?
        采用过期时间1/3隔断,若用户定义锁3秒过期,那每秒钟都有一个续期指令,有没有觉得也不太符合。


[*] 问题:要不要制止续期指令太频繁?
       制止续期指令太频繁调用是有必要的,也可以增加一个续期的最小隔断时间,比如最少是5秒。可由用户自己控制续期周期,没必要肯定要发起续期调用。比如任务实验大多在5秒钟,那么就把锁定为7秒,续期时间定在6秒,那么6秒内任务结束了就不消续期,即不必把过期时间定的太长,也不必实验一两次续期操纵。


[*] 问题:续租的隔断怎么实现?
       线程内隔断控制通常是通过 sleep() 方法,轻微精准一点的话,单位使用毫秒。
public void run() {
    while (true) {
      // 1、间隔
      TimeUnit.MILLISECONDS.sleep(sleepTime);
      // 2、续租
      action.run();
    }
}

[*] 问题:线程要关闭吧?
        释放锁的时候要主动关闭负责续期的线程,以是线程的循环里要有一个变量来控制退出 while          循环
public void run() {
    while (isRunning) {
      // 1、间隔
      TimeUnit.MILLISECONDS.sleep(sleepTime);
      // 2、续租
      action.run();
    }
}

[*] 问题:变量是跨线程访问,如何保证跨线程的可见性呢?
        在变量上增加 volatile 关键字。
private volatile boolean isRunning = true;

void cancel(){
    //控制线程退出
    this.isRunning = true;
}

[*] 问题:如果续期线程里在 sleep(),那就一直等 sleep() 结束?
       如果等到 sleep() 结束,就挺浪费资源的


[*] 问题:能不能快速结束 sleep() 状态?
       可以,通过 interrupt(),需留意,被打断的时候会抛异常 InterruptedException
void cancel(){
    //控制线程退出
    this.isRunning = true;
    //中断线程
    this.interrupt();
}
到这里,似乎都理顺了。
https://i-blog.csdnimg.cn/blog_migrate/52128e0235a940262f5f62b1fd240fe8.jpeg

三、新的思索



[*] 问题:如果同时有成百上千个锁呢?
同时有成百上千个线程在工作,你若认为没问题,不存在,不消继续了。


[*] 那怎么办呢?
可以用 Executors.newScheduledThreadPool ,里边有 scheduleAtFixedRate


[*] 阿里 Java 代码规范不答应用Execurots嘛?
https://i-blog.csdnimg.cn/blog_migrate/8b11c9145abf7ac9515fe29562f5412b.png



[*] 不能用?风险是什么?你没看累嘛?
https://i-blog.csdnimg.cn/blog_migrate/432996e418f2bab19d4a5e5cb8a65e7f.jpeg


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 简约而不简单!分布式锁入门级实现主动续期-自省