Redis 分布式锁怎么实现?

2025年 阅读约 15 分钟 面试指南 · Redis

深入解析Redis分布式锁:SETNX+过期时间(原子操作)、Redisson看门狗(Watchdog自动续期)、Redlock红锁算法(多节点加锁)及其争议、Lua脚本保证原子性,附面试模拟问答。

一句话总结

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

分布式锁对比

方案原理优点缺点
RedisSET NX EX性能高、简单单点故障、时钟依赖
ZooKeeper临时顺序节点强一致性、自动释放性能低、较重
etcdLease + 事务强一致性、自动续约性能中等
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。