一句话总结
Redis 分布式锁核心:SET key value NX EX seconds(原子加锁+过期时间)。三个关键问题:1)死锁→ 加过期时间;2)误删→ value 用唯一标识(UUID),释放时校验;3)锁过期→ Redisson 看门狗自动续期。生产推荐用 Redisson(封装了看门狗+可重入+公平锁)。Redlock 算法有争议(Martin Kleppmann 批评),一般单实例 Redis 够用。
初级理解
基础实现 — SETNX + 过期时间
# ❌ 错误做法(两步非原子)
SETNX lock:order 1
EXPIRE lock:order 30
# 问题:SETNX 后宕机,锁永远不释放 → 死锁
# ✅ 正确做法(原子操作)
SET lock:order unique_value NX EX 30
# NX: key 不存在才设置(Not eXists)
# EX 30: 30 秒过期
# 释放锁(Lua 脚本保证原子性)
EVAL "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
" 1 lock:order unique_value
# 先判断 value 是否匹配,再删除(防止误删别人的锁)
# Java 实现
public boolean tryLock(String key, String value, long expireTime) {
String result = jedis.set(key, value, "NX", "EX", expireTime);
return "OK".equals(result);
}
public void unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) " +
"else return 0 end";
jedis.eval(script, Collections.singletonList(key),
Collections.singletonList(value));
}
一句话总结:SET NX EX 原子加锁,Lua 脚本校验 value 后释放,防止死锁和误删。
中级深入
Redisson — 看门狗自动续期
基础实现的问题:业务执行时间超过锁过期时间 → 锁自动释放 → 其他线程获取锁 → 并发问题。Redisson 的 Watchdog(看门狗)机制自动续期。
# Redisson 看门狗原理
# 1. 加锁成功 → 启动定时任务(默认每 10 秒续期一次)
# 2. 续期:将锁过期时间重置为 30 秒
# 3. 业务完成 → 释放锁 → 取消定时任务
# Redisson 使用示例
RLock lock = redisson.getLock("lock:order");
try {
# 尝试加锁,等待 10 秒,锁 30 秒过期
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
# 业务逻辑
}
} finally {
# 判断是否是当前线程持有
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
# Redisson 锁结构(Hash)
# key: lock:order
# field: UUID:threadId(可重入计数)
# value: 1(重入次数)
# 可重入实现
# 加锁:field 存在且匹配 → value + 1
# 释放:value - 1,减到 0 删除 key
Redisson 公平锁与非公平锁
# 非公平锁(默认):先来不一定先得
RLock lock = redisson.getLock("lock:order");
# 公平锁:按等待队列顺序获取
RLock fairLock = redisson.getFairLock("lock:order");
# 原理:Redis 队列 + 发布订阅通知
# 读写锁(ReadWriteLock)
RReadWriteLock rwLock = redisson.getReadWriteLock("lock:order");
RLock readLock = rwLock.readLock(); # 读锁(共享)
RLock writeLock = rwLock.writeLock(); # 写锁(排他)
中级要点:Redisson 看门狗自动续期解决锁过期问题;支持可重入、公平锁、读写锁。
高级拓展
Redlock 红锁算法 — 多节点分布式锁
单实例 Redis 有单点故障风险。Redlock 算法在 N 个独立 Redis 实例(通常 N=5)上加锁,超过半数(N/2+1)成功才算获取锁。
# Redlock 加锁流程
# 1. 获取当前时间 t1
# 2. 依次向 N 个 Redis 实例加锁(SET NX EX)
# - 每个实例设置相同的 key + random value + TTL
# - 加锁超时时间远小于 TTL(防止阻塞)
# 3. 计算总耗时 t2 - t1
# 4. 成功数 >= N/2+1 && 总耗时 < TTL → 加锁成功
# 5. 加锁失败 → 向所有实例释放锁
# Redlock 争议(Martin Kleppmann)
# 1. 时钟跳跃:依赖过期时间,系统时钟跳跃会导致锁失效
# 2. GC 停顿:客户端 GC 停顿可能导致锁过期
# 3. 网络延迟:网络分区可能导致脑裂
# Redis 作者 Antirez 回应
# 1. 时钟跳跃可通过监控避免
# 2. GC 停顿可通过 fencing token 解决
# 3. 大多数场景单实例够用
# 建议:
# 一般场景 → Redisson 单实例
# 高可靠性 → Redlock 或 ZooKeeper/etcd
分布式锁对比
| 方案 | 原理 | 优点 | 缺点 |
| Redis | SET NX EX | 性能高、简单 | 单点故障、时钟依赖 |
| ZooKeeper | 临时顺序节点 | 强一致性、自动释放 | 性能低、较重 |
| etcd | Lease + 事务 | 强一致性、自动续约 | 性能中等 |
| MySQL | 唯一索引 + 行锁 | 简单、无需额外组件 | 性能差、无自动释放 |
实战场景
场景:防止重复下单
# 用分布式锁防止用户重复提交订单
public Order createOrder(Long userId, OrderRequest req) {
String lockKey = "lock:order:" + userId + ":" + req.getOrderId();
RLock lock = redisson.getLock(lockKey);
try {
# 尝试加锁,等待 3 秒,锁 10 秒过期
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
# 检查是否已创建
Order exist = orderMapper.findByOrderId(req.getOrderId());
if (exist != null) return exist;
# 创建订单
Order order = new Order();
order.setOrderId(req.getOrderId());
orderMapper.insert(order);
return order;
} else {
throw new BizException("操作太频繁,请稍后再试");
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
面试模拟
面试官:Redis 分布式锁怎么实现?
你:核心是 SET key value NX EX seconds 原子命令。三个要点:1)加过期时间防止死锁;2)value 用 UUID+线程ID,释放时 Lua 脚本校验防止误删;3)Redisson 看门狗自动续期防止锁过期。生产直接用 Redisson,它封装了看门狗、可重入、公平锁、读写锁。
面试官:Redlock 算法了解吗?有什么问题?
你:Redlock 在 N 个独立 Redis 实例上加锁,超过半数成功才算获取。争议点:1)依赖系统时钟,时钟跳跃可能导致锁失效;2)客户端 GC 停顿可能超时;3)网络分区可能脑裂。Martin Kleppmann 认为 Redlock 不安全,Antirez 回应说可通过 fencing token 解决。一般场景 Redisson 单实例够用,高可靠性用 ZooKeeper/etcd。