Bootstrap

Redission实现分布式锁

大纲

1、代码中常见的redis分布式锁

2、redission lock实现,有什么问题,注意事项

3、红锁(redLock),联锁(multiLock)用来解决什么问题,比较

4、看门狗是什么

一、代码中常见的redis分布式锁

代码中常见的写法

boolean redisKeyBoolean = redisTemplate.opsForValue().setIfAbsent("redisKey","666");

如果key不存在设置成功返回true,如果key存在设置失败返回false

每个redisKey单线程访问,多线程访问时只有一个线程成功,其它线程立马返回失败,用户体验不友好,redis主从\集群模式下宕机问题(后面讲解),上面代码不设超时间(如果代码执行中服务宕机rediskey不过期问题),设置超时时间(超时时间内程序没跑完,key超时代码重复执行问题),这里吧问题先抛出来,后面总结

其它情况:这里只是举了这一个例子,还有很多,比如网上搜了一段代码,while循环判断获取锁时间,等等,或者自己造轮子等,不是说不好,只是或多或少存在写问题

二、redission lock实现,有什么问题,注意事项

既然有人把轮子造好了,直接拿过来岂不更好,redission也是我们现在reids最常用的分布式锁轮子,比如

RLock lock = redisson.getLock("myLock")
// 1. 最常见的使用方法
//lock.lock();
// 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
//lock.lock(10, TimeUnit.SECONDS);
//3、
boolean locked = lock.tryLock(waitTime, leaseTime, unit);
//执行业务
lock.unlock();

就是普通的加锁,执行业务,释放锁操作,这应该是我们代码中最常见的解决方案

底层是用lua脚本实现的,确保了redis的原子操作

这种方法大部分情况下是能满足业务的,但也有极端情况,如果redis是集群模式,当主节点加锁成功后尚未同步数据到从节点,主节点挂掉了,这时候主从切换,从节点是没有锁数据的,导致锁丢失

三、红锁(redLock),联锁(multiLock)用来解决什么问题,比较

连锁


/**
     * 连锁-只有所有的RedissonClient都锁成功才算成功
     *
     * @param waitTime 获取锁的等待时间
     * @param leaseTime 锁的持续时间
     * @param unit 时间的单位
     *
     * @return 获取锁的结果
     */
    public Boolean tryMultiLock(RedissonClient redisson1,RedissonClient redisson2,RedissonClient redisson3, long waitTime, long leaseTime, TimeUnit unit){
        RLock lock1 = redisson1.getLock("test:lock1");
        RLock lock2 = redisson2.getLock("test:lock2");
        RLock lock3 = redisson3.getLock("test:lock23");
        RLock lock = redissonClient.getMultiLock(lock1, lock2, lock3);
        try {
            // 同时加锁:lock1 lock2,lock3 所有的锁都上锁成功才算成功。
            lock.lock();
            // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
            boolean res = lock.tryLock(waitTime, unit);
            return res;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return false;

上面代码,假设redis有三个节点,不分主从,只有这三个节点都拿到锁后才算拿到锁

解决了第二种宕机的情况,当然也带来了新问题,就是全部拿到锁,时间会拉长,效率问题

红锁


/**
     * 红锁-只有过半的RedissonClient都锁成功才算成功
     *
     * @param waitTime 获取锁的等待时间
     * @param leaseTime 锁的持续时间
     * @param unit 时间的单位
     *
     * @return 获取锁的结果
     */
    public Boolean tryRedLock(RedissonClient redisson1,RedissonClient redisson2,RedissonClient redisson3, long waitTime, long leaseTime, TimeUnit unit){
        RLock lock1 = redisson1.getLock("test:lock1");
        RLock lock2 = redisson2.getLock("test:lock2");
        RLock lock3 = redisson3.getLock("test:lock23");
        RLock lock = redissonClient.getRedLock(lock1, lock2, lock3);
        try {
            // 同时加锁:lock1 lock2,lock3 所有的锁都上锁成功才算成功。
            lock.lock();
            // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
            boolean res = lock.tryLock(waitTime, unit);
            return res;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return false;

跟连锁的区别:连锁需要全部节点都拿到锁才算获取锁成功,红锁是过半拿到锁成功就算成功,丢掉了一部分精度,提高了一些效率

四、看门狗是什么

到目前为止上面提到的问题只有一个还没解决:设置了持有锁的时间,如果程序执行超过持有锁的时间,第二个线程进来导致业务重复执行的问题

看门狗就是干这个的

加锁成功后会启动一个线程,定时(锁持有时间/3),系统默认的锁持有时间30s,也就是默认每10秒去续约,将锁时间重置为30s,直至调用unlock或者服务挂掉超时释放锁

但,看门狗生效是有条件的,上面我们调用lock.trylock()或lock()时不能设置leaseTime或者leaseTime=-1时看门狗才会生效,如果我们设置了leaseTime(持有锁的时间)那便不会生效

;