redis分布式锁的实际业务使用和底层基本原理 对比 lock trylock

文章目录

  • 前言
  • 业务代码
  • 加锁背后
  • lock(有参、无参) trylock(有参、无参)的区别
  • 什么是watchDog
  • 总结

前言

本篇文章将通过一个具体的业务代码 带你理解分布式锁在redis中的实际数据结构 以及为何能作为分布式锁的原因。同时本文会比较lock (有参、无参)trylock(有参、无参)这四种的区别。

业务代码

   private ResultDto Pay(String payPasselNo, UserDto userDTO) {
        ResultDto finalResult = Builder.buildResult();
        log.info("支付开始");
        
        RLock lock = null;  // ← Redisson分布式锁声明
        try {
            List<String> list = getPaymentList(payPasselNo)
            
            // 1.根据key加锁 ← 获取锁对象
            String lockKey = "lock:salary:submit:" + payPasselNo;
            lock = redissonClient.getLock(lockKey);
            
            // 2.尝试获取锁 ← 非阻塞获取
            boolean success = lock.tryLock();
            if (!success) {
                log.info("加锁失败[{}]", payPasselNo);
                finalResult.append("失败");
                finalResult.failIncrease();
                return finalResult;  // ← 获取锁失败直接返回
            }


            // 3.执行业务逻辑 ← 在锁保护下执行
            // ... 业务处理代码 ...
            
            finalResult.setSuccess(true);
            
        } catch (Exception e) {
            // ... 异常处理 ...
        }  finally {
            // 4.确保释放锁 ← 在finally块中释放
            try {
                if (ObjectUtil.isNotEmpty(lock)) {
                    lock.unlock();  // ← 释放锁
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        
        log.info("支付结束");
        return finalResult;
    }

这是一个模拟支付的业务代码 根据支付批次号payPasselNo生成key 去进行支付。(已经抹去不相关的业务代码)

数据流转分析:

1、获取锁 lock = redissonClient.getLock(lockKey);
2、加锁 lock.tryLock();
3、执行业务代码
4、解锁 lock.unlock();

那么在tryLock背后 redis究竟做了什么呢?

加锁背后

redis分布式锁的实际业务使用和底层基本原理 对比 lock trylock_第1张图片
可以看到 当我们执行tryLock 并且redis加锁成功后 redis创建了一个hash类型的数据 用于存储锁的元数据(线程 ID、计数)

"a4344e6f-132a-47aa-b077-b80a7b7c2dcb:1"就是默认创建的线程ID

“1” 代表的就是锁被重入了1次

这下你知道了背后的数据结构是什么了吧,就是一个hash结构每次加锁就将计数+1 释放锁就计数-1 到期后锁就会消失。
好的,接下来我们要去区分下lock(有参、无参) trylock(有参、无参)这四种的区别。

lock(有参、无参) trylock(有参、无参)的区别

方法 是否阻塞 是否自动释放 是否支持 Watchdog
lock() 一直阻塞 否(必须手动 unlock) 是(30 秒 + 自动续期)
lock(leaseTime) 阻塞直到拿到锁 是(过期自动释放) 否(不启动 Watchdog)
tryLock(waitTime, leaseTime) 最多阻塞 waitTime 是(过期自动释放) 否(不续期)
tryLock() 非阻塞(立即返回) 否(必须 unlock) 是(30 秒 + 自动续期)

怎么去记忆 ? 赋值了参数 就不启动watchDog机制 没有赋值 就会启动watchDog 自动延期锁

不同的业务场景需要使用不同的方式

业务场景 推荐方法
必须执行,能等多久都行(例如批量发工资 不能失败、不能跳过、不能并发执行) lock()
试一下能不能抢到,不行就算了(高并发的抢单) tryLock()
只等 N 秒就放弃,抢到后 N 秒内必须完成 tryLock(wait, lease, unit)

什么是watchDog

在成功加锁(lock/tryLock)后,Redisson 会启动一个后台线程,每隔 10 秒执行一次 Lua 脚本,为 Redis 中的锁续期

lua脚本如下

// 首先判断当前线程是否持有锁 如果是就延期
if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then
    return redis.call('pexpire', KEYS[1], ARGV[2])
end

源码如下

    protected CompletionStage<Boolean> renewExpirationAsync(long threadId) {
        return this.evalWriteAsync(this.getRawName(), 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.singletonList(this.getRawName()), this.internalLockLeaseTime, this.getLockName(threadId));
    }

总结

你是否已经了解了redis分布式锁是如何实现的呢?

你可能感兴趣的:(多线程,java,redis,分布式,数据库,java)