能做,但必须用PatternTopic+RedisMessageListenerContainer,且需Redis Sentinel或单节点——因Redis Cluster不支持跨节点pub/sub广播,ChannelTopic在集群中会失效。

直接说结论:能做,但必须用 PatternTopic + RedisMessageListenerContainer,且不能依赖单机 Redis 实例——集群环境下得走 Redis Sentinel 或 Redis Cluster 的 pub/sub 通道,否则订阅会断连或收不到跨节点消息。
为什么 ChannelTopic 在 Spring Cloud 集群里基本失效
Spring Boot 默认用 ChannelTopic 订阅固定频道,比如 new ChannelTopic("notify")。问题在于:Redis Cluster 模式下,PUBLISH 命令只在当前 slot 所在节点执行,而 SUBSCRIBE 连接可能落在其他节点;Cluster 不转发 pub/sub 消息,导致只有同一分片上的订阅者才能收到。
- 现象:服务 A(在 node1)发消息,服务 B(在 node2)完全收不到,控制台无报错,
redis-cli --cluster call查各节点PUBSUB NUMSUB notify发现只有 node1 显示有订阅者 - 根本原因:Redis Cluster 的设计明确不支持跨节点 pub/sub 广播,这是官方文档写的限制(
Pub/Sub is not supported in Redis Cluster) - 绕过方式:要么退回到单节点或 Sentinel 模式,要么改用
PatternTopic并确保所有实例连接的是同一个 Redis 实例(非集群)
PatternTopic 怎么配才真正生效
用 PatternTopic 是为了兼容未来可能的频道扩展(比如 notify:* ),但更关键的是它和 ChannelTopic 在底层都走 SUBSCRIBE,区别只在匹配逻辑——真正起作用的是连接复用与监听容器配置。
- 必须显式关闭自动重连补偿:
container.setRecoveryInterval(0),否则默认 5 秒重试会反复创建新连接,压垮 Redis 的 client-output-buffer-limit - 每个微服务实例只能有一个
RedisMessageListenerContainerbean,重复注册会导致多个线程争抢同一连接,出现READONLY You can't write against a read only replica错误 - 序列化器要统一:如果发布端用
RedisSerializer.json(),订阅端也得设成一样,否则message.getBody()解出来是乱码或空字节数组 - 示例片段:
@Bean public RedisMessageListenerContainer redisContainer(RedisConnectionFactory factory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(factory); container.setRecoveryInterval(0); container.addMessageListener(listenerAdapter(), new PatternTopic("notify.*")); return container; }
发布端别用 convertAndSend 直接发字符串
redisTemplate.convertAndSend() 看起来方便,但它会先序列化再发,而默认的 JdkSerializationRedisSerializer 生成的字节流包含类名和 serialVersionUID,订阅端反序列化时若 classpath 没对应类或版本不一致,直接抛 ClassNotFoundException 或 InvalidClassException。
- 推荐做法:用
redisTemplate.getConnectionFactory().getConnection().publish()手动发原始字节数组 - 或者强制指定序列化器:
redisTemplate.setKeySerializer(new StringRedisSerializer()),再调convertAndSend,且 payload 必须是String或byte[] - 测试时加日志:
LOG.debug("Publish to {}, payload: {}", channel, payload),避免发了空字符串却以为成功 - 注意:Spring Data Redis 3.x 开始,
convertAndSend对PatternTopic无效,只认ChannelTopic,这点文档没写清楚
最常被忽略的一点:Redis 的 client-output-buffer-limit pubsub 默认只有 32MB/8MB/60,一旦某个订阅者消费慢(比如日志落盘阻塞),缓冲区满后连接会被强制断开,且不会重连——这导致广播“看似正常”实则部分实例静默失联。上线前务必用 CONFIG SET client-output-buffer-limit "pubsub 256mb 32mb 600" 调大,并监控 redis-cli info clients | grep output 中的 output_buffer 值。


















