Redis的PUBSUB不支持SUBSCRIBE模糊匹配,仅PSUBSCRIBE支持*和?模式匹配;同一连接不可混用SUBSCRIBE与PSUBSCRIBE;大量模式会降低PUBLISH性能;SCAN无法替代PSUBSCRIBE因消息不落盘;应优先用Stream实现持久化模糊查询。

Redis 的 PUBSUB 命令不支持模糊匹配订阅
Redis 原生的发布订阅机制只支持精确频道名匹配,SUBSCRIBE 和 PSUBSCRIBE 是两套完全独立的路径:SUBSCRIBE 订阅具体频道(如 "user:1001"),PSUBSCRIBE 才支持模式匹配(如 "user:*")。很多人误以为 SUBSCRIBE user:* 能工作,其实它只是订了一个叫 "user:*" 的字面量频道,不是通配符。
常见错误现象:PSUBSCRIBE user:* 后发 PUBLISH user:1001 "msg" 收不到消息——因为 PSUBSCRIBE 匹配的是**模式字符串**,而 PUBLISH 发送的是**具体频道名**,只要频道名符合模式就能触发,这点没问题;真正踩坑的是:用 SUBSCRIBE 传了带 * 的字符串,或混淆了两种订阅命令的语义。
-
PSUBSCRIBE模式只支持*(任意字符)和?(单字符),不支持[a-z]或正则 - 每个客户端最多只能有一个
PSUBSCRIBE连接,不能混用SUBSCRIBE和PSUBSCRIBE在同一连接上(会报错ERR only (P)SUBSCRIBE and (P)UNSUBSCRIBE may be used in this context) - 模式匹配是服务端逐个比对的,大量
PSUBSCRIBE模式(比如上万)会影响PUBLISH性能
用 PSUBSCRIBE 实现多前缀模糊订阅的实际写法
想同时监听 "order:created"、"order:updated"、"payment:success" 这类频道,不能靠一个模式覆盖,得组合多个 PSUBSCRIBE 指令。Redis 允许一次发多个模式:
PSPUBSUBSCRIBE order:* payment:*
但注意:这不是“OR”逻辑的模糊匹配,而是“同时订阅两个独立模式”。如果发 PUBLISH order:created "ok",只有匹配 order:* 的客户端收到;PUBLISH payment:success "ok" 则只触发 payment:* 的监听者。
- 模式之间用空格分隔,不要加逗号
- 同一个连接里重复
PSUBSCRIBE相同模式不会报错,但也不会新增订阅(幂等) - Python 的
redis-py库中,pubsub.psubscribe("order:*", "payment:*")是合法的,底层就是拼成一条命令 - Node.js 的
ioredis中,psubscribe(["order:*", "payment:*"])也等价
为什么不用 SCAN + 客户端轮询模拟“模糊订阅”
有人想绕过 PSUBSCRIBE 限制,改用定时 SCAN 查频道名、再 GET 消息存 Redis List 里。这条路走不通——Redis 的 PUBSUB 是纯内存、无状态、不落盘的,PUBLISH 之后消息立刻丢弃,没被订阅者接收就永远丢失。你根本 scan 不到“待收消息”,PUBSUB CHANNELS 只返回当前有订阅者的频道名,PUBSUB NUMPAT 返回模式数,都不含消息内容。
-
SCAN对PUBSUB无意义,它查的是 key 空间,不是消息队列 - 真要持久化+模糊查询,该换
Stream:用XADD stream:order * event "created" user_id 1001,再用XREAD BLOCK 0 STREAMS stream:order $+ 客户端过滤字段 - Stream 支持消费者组、消息重放、ACK,但代价是磁盘 IO 和更大内存占用,别为了“看起来像模糊订阅”硬套
生产环境容易忽略的三个细节
模式订阅看着简单,上线后常因这些点出问题:
- 客户端断连重连时,必须重新
PSUBSCRIBE,Redis 不保留模式订阅上下文——很多 SDK 默认不自动重订模式,只重订SUBSCRIBE -
PSUBSCRIBE模式数量受pubsub-patterns配置限制(默认 10000),超限后新PSUBSCRIBE会失败,报错ERR max number of psubscribe patterns reached - 模式匹配区分大小写:
"USER:*"和"user:*"是两个模式,发往"user:123"不会触发前者
最麻烦的是模式膨胀——比如按用户 ID 订阅 "user:1001:notify"、"user:1002:notify"……这种应该用单频道 + 消息体字段过滤,而不是为每个用户建一个模式。










