使用redisson做分布式锁源码分析

分布式锁加锁原则:

1、互斥性(同一时刻只能有一个线程持有锁)

2、防死锁(锁必须有过期时间,避免服务崩溃后出现死锁)

3、防误删(线程只能释放自己持有的锁)

为什么要使用redisson做分布式锁,而不是使用redisTemplate的原始命令:

1、原子性:使用 redisTemplate 的原始命令时,加锁和设置过期时间是两个操作,这就可能出现以下情况:

  a、加锁成功,此时服务宕机,那么就会出现死锁(redisson内部使用lua脚本,实现加锁和设置过期时间为一个原子性操作,避免出现死锁的风险)

2、锁自动续期:线程t1使用redisTemplate 手动设置锁过期时间时,业务逻辑执行时间超过锁过期时间,锁就会释放,此时其他线程t2抢占到锁,当前线程t1执行完业务释放锁,就会出现释放线程t2持有的锁(redisson内部当线程加锁成功后,就会执行锁续命逻辑(看门狗机制)定期检查当前线程加的锁是否还存在,存在则把锁过期时间重置(默认30秒),确保锁能持续持有直到业务逻辑执行完毕)

redisson加锁原理如下:

image

 

redisson依赖版本

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.6.5</version>
</dependency>

测试代码

@RestController
public class TestRedissonLock {
    @Autowired
    private RedissonClient redissonClient;
    private int stock = 10 ;

    @RequestMapping("/reduceStock")
    public void reduceStock(){
        RLock lock = redissonClient.getLock("key");
        //加锁,默认加锁时间30秒,每过10秒后台线程锁过期时间更新为30秒
        //抢占锁失败的线程,订阅等待释放锁的消息,等待抢占锁
        lock.lock();
        try{
            System.out.println("减库存前1:"+stock);
            stock=stock-1;
            System.out.println("减库存后2:"+stock);
        }finally {
            //解锁,并通知唤醒等待获取锁的阻塞线程
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();  
            }
        }
    }

    @RequestMapping("/reduceStockNoWait")
    public String reduceStockNoWait() {
        RLock lock = redissonClient.getLock("key");
        boolean isLocked = false;
        
        // 核心:tryLock(0, 30, TimeUnit.SECONDS)
        // 第一个参数0 = 等待时间0秒(加锁失败直接返回,不阻塞)
        // 第二个参数30 = 锁的持有时间30秒(防止死锁)
        try {
            // 尝试加锁:等待0秒(不阻塞),持有锁30秒
            isLocked = lock.tryLock(0, 30, TimeUnit.SECONDS);
            
            if (!isLocked) {
                // 加锁失败,直接返回提示,不执行减库存
                return "当前抢购人数过多,请稍后再试!";
            }
            
            // 加锁成功,执行减库存逻辑
            System.out.println("减库存前:" + stock);
            stock = stock - 1;
            System.out.println("减库存后:" + stock);
            return "减库存成功,剩余库存:" + stock;
            
        } catch (InterruptedException e) {
            // 线程被中断时的异常处理
            Thread.currentThread().interrupt(); // 恢复中断状态
            return "操作被中断,请重试!";
        } finally {
            // 必须判断:只有当前线程持有锁时才解锁
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

 

1、加锁、循环等待抢占锁、释放锁源码逻辑如下:

//a、查看RedissonLock的#91#行,使用lockInterruptibly()加可中断锁
this.lockInterruptibly();
//b、查看RedissonLock的#109#行,过期时间为-1
this.lockInterruptibly(-1L, (TimeUnit)null);
//c、继续从RedissonLock的#113#行,查看具体加锁逻辑如下
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
        //获取当前线程ID        
        long threadId = Thread.currentThread().getId();
        //1、多线程竞争加锁     
        //1.1线程a使用lua脚本尝试加锁,加锁成功返回ttl为null
        //1.2线程b使用lua脚本尝试加锁,加锁失败返回ttl为a线程加锁的剩余过期时间    
       /**lua脚本执行逻辑
         * commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
         * //当key为getName()的锁不存在时,则执行加锁,并设置过期时间internalLockLeaseTime
         * "if (redis.call('exists', KEYS[1]) == 0) then " +
         * "redis.call('hset', KEYS[1], ARGV[2], 1); " +
         * "redis.call('pexpire', KEYS[1], ARGV[1]); " +
         * "return nil; " +
         * "end; " +
         * //锁冲入逻辑
         * //当k1为getName(),k2为getLockName(threadId)的hash锁存在时,
         * //则设置hash锁的value值+1,也就是可冲入锁+1
         * "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
         * "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
         * "redis.call('pexpire', KEYS[1], ARGV[1]); " +
         * "return nil; " +
         * "end; " +
         * //加锁失败,则返回key为getName()的锁的过期时间
         * "return redis.call('pttl', KEYS[1]);",
         * Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
         */
        Long ttl = tryAcquire(leaseTime, unit, threadId);
        //线程a加锁成功,则直接返回
        if (ttl == null) {
            return;
        }
        //2、线程b加锁失败
        //2.1、线程b订阅锁释放消息,也就是lock.unlock()后,执行RedissonLock的#460#行,发布锁释放的消息。    
        //2.2、接受到锁释放消息后,执行LockPubSub的#36#行代码,调用semaphore.release()唤醒等待的线程b进入阻塞状态,准备抢占锁        
        RFuture<RedissonLockEntry> future = subscribe(threadId);
        commandExecutor.syncSubscription(future);
        //3、线程b抢占锁失败,循环执行抢占锁逻辑    
        try {
            while (true) {
                //3.1、线程b再次尝试加锁,
                //返回ttl为null,则加锁成功
                //返回ttl不为null则加锁失败,返回线程a加锁的剩余有效时间
                ttl = tryAcquire(leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    break;
                }
                // waiting for message
                if (ttl >= 0) {
                    //3.2执行代码后,线程b进入有时限等待状态,此时分为两种情况(注:调用getEntry(threadId).getLatch()返回new Semaphore(0))
                    //1、等待ttl秒后,线程b进入阻塞状态,调用3.1逻辑准备再次抢占锁
                    //2、lock.unlock()手动释放锁后,执行semaphore.release()唤醒线程b进入阻塞状态,调用3.1逻辑准备再次抢占锁        
                    getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    //ttl小于0时,线程b直接进入阻塞状态,调用3.1逻辑准备再次抢占锁
                    getEntry(threadId).getLatch().acquire();
                }
            }
        } finally {
            //4、释放订阅消息    
            unsubscribe(future, threadId);
        }
}

2、锁续命逻辑如下:

//a、查看RedissonLock的#115#行
Long ttl = tryAcquire(leaseTime, unit, threadId);
//b、查看RedissonLock的#146#行
return get(tryAcquireAsync(leaseTime, unit, threadId));
//c、查看RedissonLock的#171#行开始
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {
        if (leaseTime != -1) {
            return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        }
        //1、加锁    
        RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        //2、监听加锁逻辑执行完成后,则执行更新过期时间逻辑
        ttlRemainingFuture.addListener(new FutureListener<Long>() {
            @Override
            public void operationComplete(Future<Long> future) throws Exception {
                if (!future.isSuccess()) {
                    return;
                }
                Long ttlRemaining = future.getNow();
                //线程a加锁成功,执行具体更新过期时间逻辑
                if (ttlRemaining == null) {
                    scheduleExpirationRenewal(threadId);
                }
            }
        });
        return ttlRemainingFuture;
}
//2.1、具体更新锁过期时间逻辑从RedissonLock的#198#行开始查看
private void scheduleExpirationRenewal(final long threadId) {
        if (expirationRenewalMap.containsKey(getEntryName())) {
            return;
        }
        //每隔 internalLockLeaseTime / 3秒,更新锁过期时间为  internalLockLeaseTime( internalLockLeaseTime默认为30秒)
        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                        //如果是自己加的锁,则执行锁过期时间更新
                        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                            "return 1; " +
                        "end; " +
                        "return 0;",
                          Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
                //监听future,如果返回1,那么继续调用scheduleExpirationRenewal
                future.addListener(new FutureListener<Boolean>() {
                    @Override
                    public void operationComplete(Future<Boolean> future) throws Exception {
                        expirationRenewalMap.remove(getEntryName());
                        if (!future.isSuccess()) {
                            log.error("Can't update lock " + getName() + " expiration", future.cause());
                            return;
                        }
                        
                        if (future.getNow()) {
                            //循环调用自己
                            scheduleExpirationRenewal(threadId);
                        }
                    }
                });
            }
            //每隔 internalLockLeaseTime/3秒执行一次过期时间更新
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        //
        if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {
            task.cancel();
        }
}

3、解锁逻辑如下:

//3.1、查看RedissonLock的#365#行
public void unlock() {
    //解锁
    /**解锁lua脚本具体逻辑 
     * commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
     *          //当key为getName()的锁不存在,则发布锁释放消息,通知调用semaphore.release()释放锁,唤醒等待线程进入阻塞状态开始抢占锁
     *         "if (redis.call('exists', KEYS[1]) == 0) then " +
     *         "redis.call('publish', KEYS[2], ARGV[1]); " +
     *         "return 1; " +
     *         "end;" +
     *         //如果hash锁getName(),getLockName(threadId)不存在,直接返回null
     *         "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
     *         "return nil;" +
     *         "end; " +
     *         "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
     *         //counter>0则可重入锁,重入次数未释放完成
     *         //继续更新过期时间为internalLockLeaseTime
     *         "if (counter > 0) then " +
     *         "redis.call('pexpire', KEYS[1], ARGV[2]); " +
     *         "return 0; " +
     *         "else " +
     *         //counter=0则为可重入锁,释放完成重入次数
     *         //counter<0则为非重入锁,
     *         //则删除getName()锁,发布锁释放消息,通知调用semaphore.release()释放锁,唤醒等待线程进入阻塞状态开始抢占锁
     *         "redis.call('del', KEYS[1]); " +
     *         "redis.call('publish', KEYS[2], ARGV[1]); " +
     *         "return 1; "+
     *         "end; " +
     *         "return nil;",
     * Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
     */
    Boolean opStatus = get(unlockInnerAsync(Thread.currentThread().getId()));
    if (opStatus == null) {
        throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                + id + " thread-id: " + Thread.currentThread().getId());
    }
    //如果解锁成功,则取消锁续命任务    
    if (opStatus) {
        cancelExpirationRenewal();
    }
}
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            //如果getName()锁已不存在,那么就发布消息通知其他竞争锁失败的线程,重新执行tryAcquire(leaseTime, unit, threadId)竞争分布式锁
            "if (redis.call('exists', KEYS[1]) == 0) then " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; " +
            "end;" +
            //检查当前线程是否持有锁,是则返回null
            "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                "return nil;" +
            "end; " +
            //减少锁的重入计数(可重入锁逻辑)
            "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
            //如果大于0,证明是重入锁,重置锁过期时间
            "if (counter > 0) then " +
                "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                "return 0; " +
            "else " +
            //不是重入锁,则删除锁,并发布消息通知其他竞争锁失败的线程,重新执行tryAcquire(leaseTime, unit, threadId)竞争分布式锁
                "redis.call('del', KEYS[1]); " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; "+
            "end; " +
            "return nil;",
            Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
}
3.2、执行redis.call('del', KEYS[1]); 删除分布式锁
3.3、执行redis.call('publish', KEYS[2], ARGV[1]);发布锁释放消息
3.4、触发分布式订阅,执行订阅逻辑,具体代码查看LockPubSub的#36#行onMessage方法
protected void onMessage(RedissonLockEntry value, Long message) {
    if (message.equals(unlockMessage)) {
        //具体通过new Semaphore(0).release()唤醒阻塞的加锁线程
        value.getLatch().release();
        //    
        while (true) {
            Runnable runnableToExecute = null;
            synchronized (value) {
                Runnable runnable = value.getListeners().poll();
                if (runnable != null) {
                    if (value.getLatch().tryAcquire()) {
                        runnableToExecute = runnable;
                    } else {
                        value.addListener(runnable);
                    }
                }
            }
            
            if (runnableToExecute != null) {
                runnableToExecute.run();
            } else {
                return;
            }
        }
    }
}
3.5、继续执行1步骤**加锁、循环等待抢占锁、释放锁源码**

 

posted @ 2023-08-27 12:24  西风51668  阅读(238)  评论(0)    收藏  举报