redis发布订阅不支持内容路由,仅按频道或模式分发消息;实现字段级路由需应用层处理,推荐统一入口分发、语义化频道命名或改用stream。

Redis 发布订阅本身不支持基于内容的路由,所有消息都按频道(channel)或模式(pattern)分发,服务端不会解析消息体内容。想实现“根据消息字段决定投递给谁”,必须由应用层自己处理。
为什么 SUBSCRIBE 无法做内容路由
Redis 的 SUBSCRIBE 和 PSUBSCRIBE 只匹配频道名,不检查 PUBLISH 发送的 message 字符串内容。哪怕你发的是 JSON:PUBLISH orders {"type":"refund","order_id":"123"},Redis 也只把它当黑盒字符串原样推给所有 orders 频道的订阅者,不做任何解析或条件过滤。
常见错误现象包括:
- 多个业务模块共用一个频道,但各自只关心某类事件,却被迫接收全部消息
- 用 if/else 在回调里硬过滤,逻辑分散、易漏判、难维护
- 误以为
PSUBSCRIBE orders.*能匹配消息内容里的"type": "refund"—— 实际它只匹配频道名,比如orders.refund
应用层实现内容路由的三种可行方式
既然 Redis 不做,就得在消费端补上这一环。关键不是“能不能”,而是“怎么组织更可控”:
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 统一入口 + 消息分发器:所有订阅者监听同一个频道(如
events),收到后交给ContentRouter类,用JSON.parse()提取type、target等字段,再调用对应 handler(如handleRefund()) - 频道命名带语义:把业务维度前置到频道名,例如
event:order:created、event:order:refunded,靠PSUBSCRIBE event:order:*实现粗粒度路由,再在 handler 内做细粒度过滤 - 结合 Stream 做二次分发:用
XADD events * type refund order_id 123写入 Stream,再起独立消费者组(XGROUP)按type字段分流 —— 这种方式有持久化、可重放,但比 Pub/Sub 多一层抽象
Spring Data Redis 中容易踩的坑
用 RedisMessageListenerContainer + MessageListenerAdapter 时,很多人直接把业务逻辑塞进 onMessage(),结果出现:
- 消息体是字节数组(
byte[]),没显式转成String就JSON.parse()→ 报MalformedInputException - 多个
ChannelTopic共享同一个MessageListenerAdapter,但 handler 方法签名不同(有的接String,有的接Map)→ 反射失败静默丢消息 - 没设
setSubscriptionExecutor(),高并发下所有消息串行执行,吞吐卡死在单线程 - 用
convertAndSend()发送对象,默认序列化成 JDK 二进制格式,订阅端收不到可读 JSON → 必须配GenericJackson2JsonRedisSerializer
什么时候该放弃 Pub/Sub 改用 Stream
如果你发现应用层路由逻辑越来越重,比如要支持:
- 消息重试(某个 handler 失败后,稍后重投)
- 消费者离线期间的消息回溯
- 按时间范围查询历史事件(如“查昨天所有支付成功事件”)
- 多个下游系统对同一事件流有不同消费进度
那就别硬撑了。Stream 的 XREADGROUP + XPENDING 天然支持这些,而 Pub/Sub 的“发完即焚”模型会在这些点上反复暴露设计缺陷。强行在应用层模拟 Stream 行为,只会让代码越来越像轮子厂。










