手写一个基于redis的分布式锁(watch dog看门狗 / redisson分布式锁的底层原理)


Redisson分布式锁的底层原理:https://www.cnblogs.com/windpoplar/p/11964088.html

1,设计思路

redis锁的核心注意点主要有:

  • 设置锁和过期时间是否是原子操作
  • 过期时间设置是否合理?太长如果当前实例crash掉影响其他实例获取锁的效率(例如设置一分钟,则其他线程需要等待一分钟之后才能重新获取锁,在这期间无法处理业务),太短的话可能会导致业务还没处理完key就过期,导致锁失效的雪崩效应。

在设计上主要参考了JUC锁的使用模式,实现其Lock接口,在使用上当作ReentranLock使用即可。

2,代码分析

首先看一下锁的主体,首先在过期时间上采取一个比较折中的策略:默认30s,目前直接在代码写死,后期优化成可配置的形式,这样程序宕掉也不至于长时间的不可用;其次,关于业务线程可能阻塞导致的执行时间过长的问题,这边可以看到在lock的时候会启动一个WatchDog线程,此线程的作用是用于监视key的剩余过期时间,发现过小时完成自动续约,以此来保证锁不会被提前释放。

@Component
@Slf4j
public class DefaultLock implements Lock {
    public static final String LOCK = "lock";
    //默认锁过期时间30秒,后期优化做成可配置
    private static long DEFAULT_EXPIRE_TIME = 30L;
    @Autowired
    private RedisTemplate redisTemplate;
 
    private WatchDog watchDog;
    @Override
    public void lock() {
        //1.这里应对和本地jvm锁一样竞争的场景,如果竞争失败,则自旋
        while(!tryLock()){
            log.info("线程{}获取分布式锁失败",Thread.currentThread().getName());
        }
        //2.这里应对有些需要没有获取到锁直接返回失败的场景,后期会做一个策略优化
//        Boolean re = redisTemplate.opsForValue().setIfAbsent(LOCK, UUID.randomUUID(), DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
//        if(Boolean.FALSE.equals(re)){
//            throw new LockCompititionFailException("获取分布式锁失败,当前线程ID:"+Thread.currentThread().getId());
//        }
        //走到这里说明获取分布式锁成功,开启线程监视key过期时间,防止业务流程还没结束就释放锁的情况
        startWatchDog();
    }
 
    private void startWatchDog(){
        watchDog = new WatchDog(true,redisTemplate);
        watchDog.start();
    }
 
    @Override
    public void lockInterruptibly() throws InterruptedException {
 
    }
 
    @Override
    public boolean tryLock() {
        return redisTemplate.opsForValue().setIfAbsent(LOCK, UUID.randomUUID(), DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
    }
 
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }
 
    @Override
    public void unlock() {
        //业务流程结束,释放锁
        redisTemplate.delete(LOCK);
        //将watchdog线程停止
        watchDog.setBusinessDone(false);
    }
 
    @Override
    public Condition newCondition() {
        return null;
    }
}

再来看一下WatchDog的实现,比较简单,主要完成续约的问题。

public class WatchDog extends Thread{
    //业务流程是否完成
    private boolean businessDone;
 
    private RedisTemplate redisTemplate;
 
    public WatchDog(boolean businessDone,RedisTemplate redisTemplate){
        this.businessDone = businessDone;
        this.redisTemplate = redisTemplate;
    }
 
    public boolean isBusinessDone() {
        return businessDone;
    }
 
    public void setBusinessDone(boolean businessDone) {
        this.businessDone = businessDone;
    }
 
    @Override
    public void run() {
        while(businessDone){
            //如果锁的剩余过期时间小于10s ,则将其重置
            if(redisTemplate.getExpire(DefaultLock.LOCK) < 10L){
                redisTemplate.expire(DefaultLock.LOCK,30L, TimeUnit.SECONDS);
            }
            //为避免空转太频繁,适当让线程sleep
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3,关于使用

使用上比较简单,用ioc注入DefaultLock实例即可。

public class ZmqTestController {
    @Autowired
    DefaultLock defaultLock;
 
    //模拟商品数量
    private Integer count = 50;
 
    @RequestMapping("/decrease")
    @ResponseBody
    public String testDistributeLock(){
        try {
            //加上锁
            defaultLock.lock();
            if(count < 1){
                log.info("库存不足");
                return "失败";
            }
            count--;
            log.info("当前线程:{},库存扣减成功,剩余库存:{}",Thread.currentThread().getId(),count);
        }catch (Exception e){
            log.info(e.getMessage());
        }finally {
            //释放锁
            defaultLock.unlock();
        }
        return "成功";

原文链接:https://blog.csdn.net/coderyjz/article/details/103560037

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值