📖 手撸 Redis 互斥锁那些坑
最近搞业务遇到高并发下同一个 key 的互斥操作,想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁,也顺便跟 Redisson 的 RLock 机制对比了下,记录一波,别踩我踩过的坑。
文章目录
- 📖 手撸 Redis 互斥锁那些坑
- 📌 场景复现
- 📌 手撸版 Redis 互斥锁实现
- 📌 这个实现存在哪些问题?
- 📌 Redisson RLock 的优势
- 📌 PubSub 机制到底为啥重要?
- 📌 多线程等待超时怎么办?
- 📌 总结
📌 场景复现
假设有个场景:
- 某张表 color 字段是口头唯一(没有唯一索引)
- 多线程并发去修改/插入同一个 color
- 希望同一时间只允许一个线程操作,保证互斥,其他线程排队或者放弃
常规想法:SELECT FOR UPDATE
但问题是,FOR UPDATE 锁不到不存在的行
于是就上了 Redis 分布式互斥锁。
📌 手撸版 Redis 互斥锁实现
核心思路:
- 利用 Redis 的
SET
命令配合NX
和EX
参数实现互斥锁的抢占 - 抢不到锁时用轮询+睡眠等待,超过等待时间放弃
伪代码实现:(注意是伪代码)
// expireSeconds 锁过期时间
// waitTimeoutMillis 下次抢锁等待时间
boolean lock(String key, long expireSeconds, long waitTimeoutMillis) {
long start = currentTimeMillis();
while (currentTimeMillis() - start < waitTimeoutMillis) {
String result = redis.set(key, "1", "NX", "EX", expireSeconds);
if ("OK".equals(result)) {
return true;
}
sleep(150);
}
return false;
}
📌 这个实现存在哪些问题?
- 轮询+sleep 实时性差,锁释放了也得等一小段时间才能感知
没有自动续期机制,业务执行时间超过锁过期时间,锁会被 Redis 自动释放,存在风险 (本文不解释这个点)
📌 Redisson RLock 的优势
虽然 Redisson RLock 本质上也是基于 Redis 的互斥锁实现,但它在以下方面做了大量优化:
功能 | 手撸实现 | Redisson RLock |
---|---|---|
锁释放通知(PubSub) | ❌ | ✅ |
❌ | ✅ | |
阻塞式等待与高效排队 | 低效(定时) | 高效(实时) |
多实例安全 | 需自行保障 | 内置支持 |
📌 PubSub 机制到底为啥重要?
- 手撸实现靠轮询(sleep + 重试)抢锁,锁释放时等待下一轮轮询,响应延迟明显
- Redisson 利用 Redis PubSub 订阅锁释放事件,锁一释放,等待线程立即收到通知,立刻尝试抢锁,响应更及时
📌 多线程等待超时怎么办?
- Redisson 的 tryLock(waitTime, leaseTime) 设置最大等待时间,超过即返回失败
- 不保证严格 FIFO 顺序,谁先抢到谁先执行,优先保证吞吐和实时性
- 需要阻塞直到抢到锁的场景,可以调用阻塞式的 lock() 方法
📌 总结
- 手撸的 Redis 互斥锁简单直观,适合低并发场景
- 高并发、分布式环境下,建议用成熟的 Redisson RLock,功能完善且稳定
- 理解分布式锁的核心机制,有助于排查业务并发问题,防止踩坑
📌本文为纯手撸实现思路,非官方科普,欢迎交流和拍砖。