结论:subscribe 本身不能做服务发现,因其纯内存、无状态、不记录元数据、不感知断连;可靠服务发现必须基于 set + expire + keyevent 机制,通过带 ttl 的键维护节点状态,并用 psubscribe __keyevent@0__:expired 监听过期事件触发上下线广播。

直接说结论:SUBSCRIBE 本身不能做服务发现
Redis 的 SUBSCRIBE 是纯内存、无状态的广播通道,不记录节点元数据、不感知断连、不提供上下线语义。拿它当服务注册中心用,等于把心跳和存活判断交给“运气”——只要一次网络抖动、进程崩溃或消费延迟,就没人知道谁掉线了。
为什么 SET + EXPIRE + KEYEVENT 才是可靠基础
真正能支撑服务发现的,是 Redis 的键生命周期机制,不是发布订阅本身。发布订阅只负责广播变更事件,而状态维护必须靠可落地、可验证、带 TTL 的键操作:
-
SET service:node-{id} "{ip}:{port}" EX 15:每个节点每 5 秒续一次心跳,过期时间设为 15 秒,容忍连续 2 次丢包 - 键名必须带唯一标识(如
hostname:pid),否则多实例会相互覆盖 - 值建议存 JSON,至少含
ip、port、timestamp,方便后续做健康检查或权重路由 - 必须用
SET key value EX seconds(原子命令),别用SETEX(非原子,Redis 早期版本已废弃) - 若用 Redis 集群,所有心跳 key 要落在同一哈希槽,加
{service}前缀即可,例如{service}:node-001
如何监听下线事件并广播变更
靠 __keyevent@0__:expired 监听键过期事件,才是触发“节点下线”通知的正确入口:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 单独起一个长连接进程执行
PSUBSCRIBE __keyevent@0__:expired - 收到消息如
["pmessage","__keyevent@0__:expired","__keyevent@0__:expired","service:node-001"]后,先EXISTS service:node-001确认是否真过期(防误触发) - 确认后调用
PUBLISH service:discovery "offline:node-001"广播下线事件 - 上线事件则由节点启动时主动
PUBLISH service:discovery "online:node-001",不要依赖订阅者自己扫描
容易被忽略的三个实操坑
很多团队卡在调试阶段,问题往往出在配置和边界上:
- Redis 默认关闭键事件通知,必须显式开启:
notify-keyspace-events Ex(E表示键事件,x表示过期事件),仅靠redis.conf或CONFIG SET设置,重启后失效需持久化 -
PSUBSCRIBE连接不能复用——它会阻塞整个连接,无法再发其他命令;必须专用于监听,另起连接做PUBLISH和SET - 没有“上线即广播”的兜底逻辑:节点启动后应先
SET心跳,再PUBLISH online,否则监听进程可能漏掉首次上线事件
服务发现的本质是状态同步,不是消息广播。把 SUBSCRIBE 当主干,等于把地基建在流沙上;真正稳的,永远是带 TTL 的键 + 显式事件驱动 + 主动探测兜底。










