Redis 分布式锁

多台机器上的多个进程要互斥地操作同一份资源(比如扣库存、抢优惠券),进程内的 synchronized 不管用,需要一个“大家都看得见”的锁——把 Redis 当锁服务器就是最常见的方案。分布式锁要满足三个性质:互斥(同一时刻只有一人持有)、防死锁(持有者宕机锁也能释放)、防误删(只能释放自己的锁)。

SET NX EX 原子占锁

分布式锁加锁流程 Redis 2.6.12 起 SET 命令可一次带上 NX 与过期时间,原子完成“不存在才写入 + 设置 TTL”,这是占锁的标准姿势:

127.0.0.1:6379> SET lock:order:1001 550e8400-e29b-41d4 NX PX 30000
# 输出:OK            # 加锁成功,30 秒后自动过期
127.0.0.1:6379> SET lock:order:1001 other-value NX PX 30000
# 输出:(nil)         # 已被占用,抢锁失败

value 必须是每次请求唯一的随机串(如 UUID),否则可能出现:线程 A 持锁超时被自动释放,线程 B 拿到锁后,A 的“释放”操作把 B 的锁误删了。

用 Lua 原子释放

“先比较 value 再删除”必须保证原子,用 Lua 脚本一次执行,杜绝“比完还没删、锁已过期被别人抢走”的竞态:

127.0.0.1:6379> EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock:order:1001 550e8400-e29b-41d4
# 输出:(integer) 1    # value 匹配,锁已删除

客户端视角的完整流程(省略类外壳,需放入 main 方法或 Service 中运行):

// 需提前定义好 jedis 连接与 lockKey,UNLOCK_LUA 如下
String UNLOCK_LUA = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
String token = UUID.randomUUID().toString();      // 唯一标识
boolean ok = jedis.set(lockKey, token, SetParams.setParams().nx().px(30000)) != null;
if (ok) {
    try {
        // 执行业务逻辑...
    } finally {
        // Lua:只有 value 仍是自己的才删除
        jedis.eval(UNLOCK_LUA, java.util.List.of(lockKey), java.util.List.of(token));
    }
}

过期时间与看门狗续期

过期时间设短了,业务没跑完锁就没了;设长了,持有者宕机后锁要很久才释放。行业做法是“看门狗续期”:客户端起一个后台线程,锁快过期时自动把 TTL 续回去,业务结束再显式释放——Redisson 的 lock 默认就是这种机制,无需业务自己续期。自己实现续期要格外小心线程安全,建议优先使用成熟客户端而非手写。

Redisson 与 Redlock

  • Redisson:Java 的 Redis 客户端,内置分布式锁,开箱支持看门狗续期,还提供可重入锁、公平锁等变体,日常项目首选。
  • Redlock:Redis 作者提出的多实例锁算法——在 N 个互相独立的 Redis 主节点上依次加锁,拿到过半(多数派)才算成功,意在降低“单点故障丢锁”风险:
客户端 → 依次对 5 台独立 Redis 执行 SET NX PX → 成功数 >= 3 才算加锁成功

Redlock 一直有争议:分布式系统研究者指出它在时钟跳跃、GC 停顿等场景下仍可能失效,业界并未形成一致推荐。多数业务用“单主 + 从库 + 看门狗”已足够;追求极致互斥才考虑 Redlock,且要清楚它也不是绝对可靠。

边界与注意事项

  • 主从切换丢锁:锁写入主库后、还没复制到从库,主库就宕机了,从库晋升后不知道这把锁,另一客户端可能同时加锁成功。Redlock 想缓解这个问题,但如前述仍有争议。
  • 可重入:SET NX 锁本身不可重入,同一线程再次加锁会失败;需要重入时用 Redisson 这类基于 hash + 计数实现的锁。
  • 公平性:普通分布式锁“谁抢到算谁”,非公平;业务要求先来后到时才用公平锁,其性能较低,尽量少用。

小结:分布式锁的落地要点是“SET key 唯一值 NX PX 占锁 + Lua 比较释放 + 看门狗续期防业务未跑完锁过期”,优先用 Redisson 等成熟库;务必清楚主从切换、时钟跳变会让“绝对互斥”打折扣,关键场景要评估 Redlock 并接受其局限。

笔记加载中…