电脑之家
柔彩主题三 · 更轻盈的阅读体验

Redisson 分布式锁源码拆解:高并发场景下怎么扛住抢购洪峰?

发布时间:2026-04-29 23:31:32 阅读:12 次

双十一大促前夜,你写的秒杀接口被压测到 CPU 98%,日志里全是 LockTimeoutException —— 这时候光看文档、调 API 已经不够用了,得翻进 Redisson 的源码里,看看它到底怎么用 Redis 实现可重入、自动续期、异常释放的分布式

为什么选 Redisson 而不是自己手撸?

有人试过用 SET key value NX PX 30000 加锁,结果遇到服务宕机没来得及 DEL,锁永远卡死;也有人加了守护线程续期,却在 GC STW 期间错过心跳,锁被误删。Redisson 把这些坑都踩过了,而且把逻辑封装得足够清晰。

核心入口:RLock lock = redisson.getLock("seckill:goods:1001")

这行代码背后不是简单返回一个对象,而是构建了一个 RedissonLock 实例,它持有一个 commandExecutor(负责发命令)、一个 id(客户端唯一 UUID)、以及最重要的 internalLockLeaseTime(默认 30 秒)。

加锁逻辑:tryAcquireAsync 怎么跑的?

真正干活的是 tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId)。它最终执行的 Lua 脚本长这样:

if (redis.call('exists', KEYS[1]) == 0) then
  redis.call('hincrby', KEYS[1], ARGV[2], 1);
  redis.call('pexpire', KEYS[1], ARGV[1]);
  return nil;
end;
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;
return redis.call('pttl', KEYS[1]);

注意两个关键点:一是用 HINCRBY 实现可重入(同一个线程多次加锁,hash 字段值累加);二是无论是否首次加锁,都重设过期时间——这就是自动续期的底层依据。

看门狗(Watchdog)怎么偷偷续命?

如果你没传 leaseTime,Redisson 就会启动看门狗,默认每 10 秒向 Redis 发一次 PTTL + HGETALL + PEXPIRE 组合操作。源码在 ExpirationEntry 类里,定时任务由 cleanMapping 触发,只对未手动释放、且还活着的锁续期。

所以别在加锁后 sleep(5000),再手动 unlock——这时候看门狗可能已经续了两次,而你 unlock 时执行的是 DEL,直接把别人刚续的锁干掉了。

解锁为啥必须用 Lua?

解锁方法 unlockInnerAsync 同样走 Lua:

if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
  return nil;
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then
  redis.call('pexpire', KEYS[1], ARGV[2]);
  return 1;
else
  redis.call('del', KEYS[1]);
  return 1;
end;

这段脚本确保:只有锁持有者才能减计数;减完是 0 才真删 key;非持有者调用直接返回 nil,不误删。没有原子性保障,分布式锁就形同虚设。

实战小提醒

在 Spring Boot 里用 @Transactional 包裹加锁逻辑?小心事务提交后锁提前释放。建议锁范围覆盖整个业务流程,包括 DB 写入和缓存更新;异步回调里加锁,记得用 RLock.lockInterruptibly() 防止线程假死。