快盘下载:好资源、好软件、快快下载吧!

快盘排行|快盘最新

当前位置:首页软件教程电脑软件教程 → Redis分布式锁的手动实现

Redis分布式锁的手动实现

时间:2022-10-26 15:01:30人气:作者:快盘下载我要评论

手动实现redis分布式锁存在的问题

  • 锁被别人释放了怎么办?
  • 定义的过期时间太长或太短;怎么定义?
  • 锁的释放不是原子操作怎么处理?

小试牛刀

首先我们要定义锁的实现;也就是下面的getLock方法;使用的是RedisTemplate;并没有去定义工具类。

getLock方法获取锁

getLock方法使用的是redis的SETNX命令;但是由于SETNX命令无法进行原子性操作过期时间;所以我们在这里使用SET命令;从redis2.6.12版本开始;SET命令的行为可以通过一系列参数来修改;

  • EX seconds ; 将键的过期时间设置为 seconds 秒。 执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value。
  • PX milliseconds ; 将键的过期时间设置为 milliseconds 毫秒。 执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value。
  • NX ; 只在键不存在时; 才对键进行设置操作。 执行SET key value NX 的效果等同于执行 SETNX key value 。
  • XX ; 只在键已经存在时; 才对键进行设置操作。

同时使用SET LOCK ID EX 30 NX命令的好处就是可以加上ID这个唯一标识。后续会讲到。

release方法释放锁

在获取锁的时候我们设置了key;也就是锁的名称;ID就是键值对的value。好处就是为了释放锁的时候出问题;举个例子;当线程A获取到锁进行业务逻辑的时候;线程B的误操作;直接释放了锁。为了解决类似的异常情况;我们将当前的线程ID和锁进行绑定。
设计这个方法的步骤和思想如下;

  • 首先释放锁的时候需要传入当前线程信息;因为锁的value值是和线程id绑定的;同时需要传入锁的名称。
  • 获取到当前锁的value值;与当前需要释放锁线程ID是否一致。
  • 一致则可以释放;不一致不能释放。

问题它又来了;这里的get;del命令又是两条命令了;又得回到原子性的问题

  • 客户端1执行get命令;判断锁是不是自己的
  • 客户端2执行set命令;强制的获取到了锁
  • 客户端1执行了del命令;删掉了客户端2的锁

所以我们使用redis可以执行lua脚本的特性;写了一个脚本来原子性执行;解决这个问题。具体的实现如下代码所示;

;Component
public class RedisInfo {

    ;Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    /**
     * Title;getLock <br>
     * Description;获取锁的实现 <br>
     * author;于琦海 <br>
     * date;2022/10/25 10:58 <br>
     * ;param key 锁的名称
     * ;param time 预估的过期时间
     * ;param timeUnit 时间单位
     * ;param thread 当前操作获取锁的线程
     * ;return Boolean
     */
    public Boolean getLock(String key, Long time, Thread thread, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().setIfAbsent(key, thread.getId(), time, timeUnit);
    }

    /**
     * Title;release <br>
     * Description;释放锁的原子实现 <br>
     * author;于琦海 <br>
     * date;2022/10/25 11:12 <br>
     * ;param key 锁的名称
     * ;param thread 当前操作释放锁的线程
     * ;return Long
     */
    public Long release(String key, Thread thread) {
        String lua = ; if redis.call(;GET;,KEYS[1]) == ARGV[1] then return redis.call(;DEL;,KEYS[1]) else return 0 end;;
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setResultType(Long.class);
        script.setScriptText(lua);
        return redisTemplate.execute(script, Arrays.asList(key), thread.getId());
    }

    /**
     * Title;ttl <br>
     * Description;查询key的剩余过期时间 <br>
     * author;于琦海 <br>
     * date;2022/10/25 14:13 <br>
     * ;param key 锁的名称
     * ;param timeUnit 时间单位
     * ;return Long
     */
    public Long ttl(String key, TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }

    /**
     * Title;expire <br>
     * Description;更新key的过期时间 <br>
     * author;于琦海 <br>
     * date;2022/10/25 14:33 <br>
     * ;param key 锁的名称
     * ;param time 预估的过期时间
     * ;param timeUnit 时间单位
     * ;return Boolean
     */
    public Boolean expire(String key, long time, TimeUnit timeUnit) {
        return redisTemplate.expire(key, time, timeUnit);
    }
}

续期问题

假设我们设置锁的持有时间为30s;但是由于网络问题或者机器问题导致执行我的业务逻辑超过了30s;而此时锁已经失效了;这种情况就会有问题。那如果我设置为一个超大的时间;比如100s一定能执行完;此时锁的效率又会变得非常低。

根据这个情况;我们可以写一个守护线程来根据剩余时间来对锁进行续期;类似于Redisson的看门狗机制。

思路如下;

  • 首先开启一个守护线程 。
  • 获取当前锁的剩余过期时间。
  • 如果锁的剩余时间小于等于我设置的锁过期时间的三分之一;我设置30s;除以3;也就是10s;。
  • 对当前的锁续期。
;Service
public class RedisLockDemo {

    ;Autowired
    private RedisInfo redisInfo;

    /**
     * Title;businessInfo <br>
     * Description;这里面是业务逻辑伪代码 <br>
     * author;于琦海 <br>
     * date;2022/10/25 11:34 <br>
     */
    public void businessInfo() {
        // 获取锁
        Boolean lock = redisInfo.getLock(;qhyu;, 30L, Thread.currentThread(), TimeUnit.SECONDS);
        if (lock) {
            try {
                // 开启一个守护线程;去刷新过期时间,实现看门狗的机制
                Runnable thread = () -> {
                    while (true) {
                        // 会不会
                        // 获取key的剩余过期时间
                        Long timeLess = redisInfo.ttl(;qhyu;, TimeUnit.SECONDS);
                        // 当时间小于等于10s的时候;续命
                        if (timeLess <= 10L) {
                            System.out.println(;续命;);
                            redisInfo.expire(;qhyu;, 30L, TimeUnit.SECONDS);
                        }
                    }
                };
                Thread thread1 = new Thread(thread);
                thread1.setDaemon(true);
                thread1.start();
                int i = 0;
                while (true) {
                    if (i < 50) {
                        i;;;
                        // 执行我们的业务逻辑
                        System.out.println(;这里是我的业务逻辑; ; i);
                        Thread.sleep(10000L);
                    } else {
                        break;
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                redisInfo.release(;qhyu;, Thread.currentThread());
            }
        }
    }
}

测试

根据上面的代码;写了个controller掉用了一下;能达到自动续期的目的。

;RestController
;RequestMapping((;/qhyu/redis;))
public class RedisLockController {

    ;Autowired
    private RedisLockDemo redisLockDemo;

    ;GetMapping(;/test;)
    public void test(){
        redisLockDemo.businessInfo();
    }
}

在时钟正常的情况下;我们预计没执行两次业务逻辑;就会续命一次。以下的结果能证明这一点。
Redis分布式锁的手动实现
那么如果不是守护线程;直接开一个线程不行吗?肯定是不行的;因为当线程执行完成之后;新开的线程不会被销毁;如下所示;会一直去对锁进行续命;就会出现问题。

网友评论

快盘下载暂未开通留言功能。

关于我们| 广告联络| 联系我们| 网站帮助| 免责声明| 软件发布

Copyright 2019-2029 【快快下载吧】 版权所有 快快下载吧 | 豫ICP备10006759号公安备案:41010502004165

声明: 快快下载吧上的所有软件和资料来源于互联网,仅供学习和研究使用,请测试后自行销毁,如有侵犯你版权的,请来信指出,本站将立即改正。