敞开生长之旅!这是我参加「日新计划 12 月更文应战」的第 16 天,点击检查活动详情

我是石页兄,本篇不带情感,只聊干货

欢迎重视微信大众号「架构染色」沟通和学习

一、布景

在《# 分布式锁上-初探》中有提到一个分布式锁应具备的功用特色中有防止死锁这一条:

假如某个客户端取得锁之后处理时刻超越最大约定时刻,或许持锁期间内发生了故障导致无法主动开释锁,其持有的锁也能够被其他机制正确开释,并确保后续其它客户端也能加锁,整个处理流程持续正常履行。

简单解释一下:

  1. 客户端抢到分布式锁之后开始履行使命,履行完毕后再开释分布式锁。
  2. 持锁后因客户端反常未能把锁开释,会导致锁成为永久锁。
  3. 为了防止这种状况,在创建锁的时分给锁指定一个过期时刻。
  4. 到期之后锁会被主动删除掉,这个角度看是对锁资源的一种维护。

二、理还乱?

逻辑看很简单,也很明晰,但任何事情都有两面性,主动删除自然有理,但肯定也有坏处。假如要把锁的功用做的强健,总要从不断地自我质疑、自我反思中,理顺思路,寻觅答案,我以为这归于自省式学习,今后也想测验这种形式,一起来试试吧:

  • 问题:锁过期了会被删掉,可是使命没完毕怎么办?

    假如锁被开释的时分,使命没有履行完毕,那就可能导致其它客户端又抢到锁,使命被重复履行。

  • 问题:把锁的过期时刻定的长一点?

    逻辑听起来没错,假如你能确认使命的最大耗时,那没问题;大部分状况都很难确认使命的最大耗时该是多少。

  • 问题:锁的过期时刻定多长适宜?

    反正会被开释,过期时刻定的足够长吧;假如锁运用的频率很高,加了锁程序有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();
    }
    

到这里,似乎都理顺了。

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


三、新的思考

  • 问题:假如一起有成百上千个锁呢?

    一起有成百上千个线程在工作,你若以为没问题,不存在,那ok,不用持续看下一篇。

  • 那怎么办呢?

    能够用 Executors.newScheduledThreadPool ,里边有 scheduleAtFixedRate

  • 阿里 Java 代码标准不允许用Execurots嘛?

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

  • 不能用?危险是什么?你没看累嘛?

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


累了吧,下一篇再聊,歇息歇息。

四、最后说一句

我是石页兄,假如这篇文章对您有帮助,或许有所启示的话,欢迎重视笔者的微信大众号【 架构染色 】进行沟通和学习。您的支撑是我坚持写作最大的动力。

点击链接即可一键三连:重视、点赞、转发。