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究竟做了什么呢?
加锁背后
可以看到 当我们执行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 thenreturn 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分布式锁是如何实现的呢?