Redis Pub/Sub仅用于唤醒等待线程,不能作状态同步,因其“发即忘”、无确认、不保证送达、消息不可回溯,收到通知后必须立即GET锁key二次校验。

Redis Pub/Sub 无法实现“非轮询的锁释放通知”——它只能在锁被主动删除或自然过期后,向已在线的订阅者广播一次信号,且不保证送达、不可回溯、无上下文。真要靠它做通知,必须接受这些硬限制。
为什么 SUBSCRIBE redisson_lock__channel:{lockName} 只能“唤醒等待线程”,不能当状态同步用
Redisson 确实用 redisson_lock__channel:{lockName} 频道发空消息通知锁释放,但这个机制只服务于一个目的:让阻塞在 tryLock(long, TimeUnit) 的客户端线程及时醒来重试,而不是传递“锁已归谁”的事实。
- 收到消息后必须立刻执行
GET lockName+ 解析 value,确认锁当前是否真的空闲(因为消息可能延迟、重复、或来自旧主库) - 若此时锁已被其他客户端抢走,你收到的通知就只是“过去式”,不是“当前态”
- 如果订阅连接断开超过 1 秒(比如 GC 停顿、网络抖动),这条消息就永久丢失,没有任何补救机制
- 该频道名中的
{lockName}是占位符,实际订阅时得写死,如SUBSCRIBE redisson_lock__channel:order_123
监听 __keyevent@0__:expired 能否捕获自然过期?有但极不可靠
开启键空间通知后,__keyevent@0__:expired 确实会在 key 因 TTL 到期被 Redis 自动删掉时触发一次事件,但它和分布式锁场景严重错配:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 只对
SET key val EX 30这类纯 TTL 过期有效;而生产环境的锁几乎都靠客户端主动DEL或 Lua 脚本释放,这类操作**完全不会触发 expired 事件** - 主从架构下,事件只在从库上发一次,且可能滞后数秒,多个订阅者收到时间不一致
- 消息体只有 key 名,没有 value、没有客户端 ID、没有时间戳,无法判断是不是你要关注的那个锁实例
- 要启用它还得额外执行
CONFIG SET notify-keyspace-events Ex,且该配置不持久化,重启 Redis 后失效
想“快速通知锁释放”,唯一可控的做法是客户端自己 PUBLISH
别等 Redis 推,让锁持有者在释放前主动喊一嗓子。这需要你在业务侧控制两个动作:释放锁 + 发通知,用 Lua 保证原子性:
if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("DEL", KEYS[1])
return redis.call("PUBLISH", KEYS[2], ARGV[2])
else
return 0
end调用方式:EVAL <script> 2 order_123:lock order_123:channel "abc123" "released"
-
KEYS[1]是锁 key,KEYS[2]是通知频道,ARGV[1]是持有者 token,ARGV[2]是自定义消息体 - 这样发出去的消息带上下文,订阅方能明确知道“谁放了哪把锁”
- 但注意:PUBLISH 仍是“发即忘”,若此时无人在线监听,消息就没了——所以它适合“唤醒+重试”,不适合“状态最终一致”
- 如果你的系统已用 Redis Stream,直接
XADD lock_events * event "released" lock_key "order_123" owner "clientA"更稳妥
真正容易被忽略的点:Pub/Sub 不是消息队列,它没有消费确认
很多团队踩坑是因为把 SUBSCRIBE 当成 RocketMQ 的消费者组来用——以为只要连上就能稳收消息。实际上:
- 连接断开期间所有消息全丢,Redis 不存、不重发、不记录 offset
- 没有 ACK 机制,你收到消息后崩溃了,没人知道这条通知是否被正确处理
- 频道名拼错、大小写不一致、没加命名空间前缀,都会导致 SUBSCRIBE 成功但收不到任何东西
- 单个 Redis 实例 pub/sub 频道数上限默认是 10000,高频锁场景下容易打满(
pubsub numpat可查)

















