Redis实现分布式锁

一、使用分布式锁的背景是什么

1、如果你公司的业务,各个应用都只部署了一台机器,那么完全用不着分布式锁,直接使用Java的锁即可

2、可是当你们的业务量大,多台机器并发情况下争夺一个资源的时候,就必须要保证业务的原子性了。

举例:

例子1、超卖问题:一共100个商品待抢购,当还有最后一个库存的时候,实例1,2,3.....10都查询到还有库存,用户同时都下单成功;那么就出现了超卖问题:你一共100的库存,哪来的110个商品发货?

怎么解决呢?将100个商品写入redis中,并加上分布式锁,必须一个实例,拿到锁,并完成交易,扣除库存后,释放锁,才能让下一个实例拿到锁;这样就保证了不会超卖

例子2、定时任务问题:多台分布式应用,要执行对账,如果多台机器同时执行,就有可能产生脏数据;如果使用分布式锁,使同时只有一个应用实例去执行一个任务,这样就实现了互斥,不会导致脏数据产生

二、Redis分布式锁,是如何实现的?

Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则set)的简写

获取锁:

#添加锁,lock是锁的名称,value就是值(根据业务命名) NX代表互斥,EX是设置超时时间
SET lock value NX EX 10

释放锁:

# 释放锁,删除即可
DEL key

分布式锁必须加超时时间的原因是,如果应用获取到锁后,宕机了,那么就永远释放不了锁了

三、Redisson实现分布式锁,如何控制锁的有效时长?

如果应用使用了分布式锁,但是在锁的有效时间都超过了(锁失效了),那么应用的其他实例就会拿到锁,也去执行业务代码,就有可能会导致脏数据产生

那么如何合理控制锁的有效时长呢?

1、根据业务预估

根据业务代码评估后,觉得实例拿到锁之后,多久能跑完,就给锁设值多久过期

但是这样很不准确

2、动态地给锁续期

我只要拿到锁的线程还在跑,那我就一直给锁加有效时间(超时时间)即可,每次加10秒钟,诶,非常合理

 有以下两种方式:

1、自己代码里新开一个线程,每隔10秒或者多少秒,延长一下锁的有效时间(过期时间)

或者说,在数据库标记一个标识,新开一个线程记录当前线程id + 状态,根据状态判断是否需要延期

2、使用Redisson的看门狗机制

Redisson自带有一个watch dog(看门狗机制),

如图所示,

1、实例1在获取到锁后,在加锁后,Redisson会另外开一个线程(看门狗线程),监控持有锁的线程,会不断增加持有锁的线程的持有锁的时间(超时时间,默认10秒)

看门狗线程,每次续期(release / 3)的时间,其中release是锁的默认超时时间:30s,也就是说默认10秒钟续期一次,每次重新设置为30秒过期时间

当实例1释放锁的时候,需要通知一次看门狗,不需要再做监听了,因为key已经被删除了

2、在实例2,尝试加锁的过程中,如果加锁失败,会循环,尝试获取锁;

当然实例2也不会无限循环尝试获取,一段时间没有加锁成功就会报错,这就是Redisson新增的重试机制

public void redisLock() throws InterruptedException{
    // 获取锁(重入锁),执行锁的名称
    RLok lock = redissonClient.getLock("myLockName");

    // 尝试获取锁,参数分别是:1、获取锁的最大等待时间(就是未获取到锁循环尝试获取的时间)
    // 2、锁自动释放时间(过期时间),如果你设置了这个值,那么Watch dog(看门狗)就不会生效,因为他会认为你能控制锁的超时时间,不会定时给你续期
    // 3、第三个参数为 前两个时间参数的单位
    // boolean isLock = lock.tryLock(10, 30, TimeUnit.SECONDS);
    boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);
    
    // 判断是否获取锁成功
    if(isLock) {
        try {
            System.out.prientln("执行你的业务逻辑");
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}
    
    

所有的加锁解锁,设置过期时间,看门狗续期等,都是根据lua脚本来实现的,保证执行的原子性

四、Redisson实现的分布式锁,可以重入吗?

为什么同一个线程要多次拿到锁呢?

因为如果一个任务,根据不同的传参,可能有不同的判断,要调用不同的方法,调用不同的方法耗时可能不同,那么就可以使用重入机制,调一个方法就延长一下锁的有效时长;

所以问题的答案是:Redisson实现的分布式锁可以重入,redission中可以记录线程id,使同一线程的不同方法,都可以重复拿到锁

如图:add1方法获取锁后,Redis会使用hash结果记录锁的key,获取到锁的线程id、以及重入次数

当add1方法调用add2方法后,add2方法也可以获取到锁,这时Redis会把重入次数加一;

当add2释放锁之后,重入次数会减一

当add1释放锁之后,重入次数也会减一,当重入次数value=0时,Redis就会把这个锁删除

五、Redission可以解决Redis主从不一致的问题吗?

什么叫主从不一致的问题?

如果说,你的应用实例1在主节点写入了一个分布式锁,这时候其他实例2,3,4是拿不到分布式锁的;

而如果在你实例1还在执行的时候,Redis主节点挂了(宕机了)!这时候呢,Redis的一个从节点会自动升级为主节点,但是这个新的从节点,里面是没有这个分布式锁的,那么实例2,3,4就又可以获得一个锁,来进行代码的执行,这就有可能导致脏数据问题

所以:Redisson分布式锁,是不能解决主从一致性问题的。

但是,Redis中有另一种解决方案,叫做红锁(redlock)机制,就是把分布式锁的信息保存在X台(X = Redis节点数 / 2  + 1,即超过半数)Redis的节点上;每次加锁,都必须拿到这X台(超过半数)机器的锁,才算真正获得锁

红锁:

但是红锁,一般是不会使用的,因为这样性能太低了,如果硬是要保持数据强一致性,还可以使用zookeeper实现的分布式锁,或者其他机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值