Redis 如何实现发布、订阅?
2026年01月01日
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
Redis 如何实现发布、订阅?
面试考察点
面试官提出这个问题,通常意在考察以下几个方面:
- 对 Redis 核心功能的掌握:你是否了解 Redis 除了缓存之外,还提供了哪些高级特性。
- 对发布/订阅模式的理解:你能否清晰地解释这种经典的消息通信模式,并说明它与消息队列(如 RabbitMQ, Kafka)的异同。
- 对机制与局限性的认识:你是否理解其非持久化、无消息堆积能力、消息“即发即弃” 等关键特性,这决定了你是否能为其选择正确的应用场景。
核心答案
Redis 通过内置的 PUBLISH、SUBSCRIBE、PSUBSCRIBE 等命令实现了一个轻量级、高性能但非持久化的发布/订阅(Pub/Sub)消息系统。
其核心机制是:服务器维护一个 pubsub_channels 字典来记录频道与订阅者(客户端)的映射关系。当客户端订阅(SUBSCRIBE)某个频道时,它就会被添加到对应频道的订阅者列表中。当有消息发布(PUBLISH)到该频道时,Redis 服务器会遍历这个列表,将消息推送给所有在线的订阅者。此外,还支持通过 PSUBSCRIBE 命令进行模式订阅,使用 pubsub_patterns 链表来实现全局模式匹配。
技术深度解析
原理/机制
- 数据结构:
pubsub_channels:一个字典,键是频道名,值是一个链表,链表中保存了所有订阅了该频道的客户端引用。pubsub_patterns:一个链表,其中每个节点保存了一个模式字符串和订阅了该模式的客户端。
- 工作流程:
- 订阅:执行
SUBSCRIBE news.java,客户端client-1会被添加到pubsub_channels[“news.java”]链表中。 - 发布:执行
PUBLISH news.java “Hello”,Redis 会: a. 从pubsub_channels中找到键为“news.java”的链表,将消息“Hello”依次发送给链表中的所有客户端。 b. 遍历pubsub_patterns链表,检查“news.java”是否匹配任何模式(如news.*),如果匹配,则同样将消息发送给对应的客户端。 - 退订:执行
UNSUBSCRIBE会将客户端从相应的链表或字典中移除。
- 订阅:执行
代码示例
以下是一段示例代码:
// 1. 发布者
@Component
public class RedisMessagePublisher {
@Autowired
private StringRedisTemplate redisTemplate;
public void publishMessage(String channel, String message) {
// 核心发布方法
redisTemplate.convertAndSend(channel, message);
}
}
// 2. 订阅者(消息监听器)
@Component
public class RedisMessageSubscriber implements MessageListener {
// 当有消息到达订阅的频道时,此方法被自动回调
@Override
public void onMessage(Message message, byte[] pattern) {
String channel = new String(message.getChannel());
String body = new String(message.getBody());
System.out.println(String.format("收到来自频道 [%s] 的消息:%s", channel, body));
}
}
// 3. 配置类(关键):将监听器绑定到指定的频道
@Configuration
public class RedisPubSubConfig {
@Autowired
private RedisConnectionFactory connectionFactory;
@Autowired
private RedisMessageSubscriber messageSubscriber;
@Bean
public ChannelTopic topic() {
// 定义监听的频道名
return new ChannelTopic("news.java");
}
@Bean
public RedisMessageListenerContainer messageListenerContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 将 messageSubscriber 与 “news.java” 频道绑定
container.addMessageListener(messageSubscriber, topic());
return container;
}
}
对比分析与最佳实践
- Pub/Sub vs 消息队列:这是最重要的区分。
- Pub/Sub:是广播模式。一条消息会被所有当前在线的订阅者收到。如果订阅者中途下线,将错过下线期间发布的消息(无持久化)。
- 消息队列(如 List 结构实现):是负载均衡/Worker 模式。一条消息只会被一个消费者取出并处理。消息可以持久化存储在 Redis 的 List 中,直到被消费。
- 最佳实践与适用场景:
- 适用:实时通知、聊天室、配置热更新、微服务间的轻量级事件广播(对消息可靠性要求不高)。
- 不适用:需要保证消息必达、顺序性、有大量消息积压、或需要复杂路由(如延迟队列、死信队列)的业务场景。这类场景应选用专业的消息中间件。
- 常见误区:
- 误用作持久化消息队列:这是最常见的错误。Redis Pub/Sub 的设计目标就是实时推送,不是可靠存储。
- 忽略客户端断连问题:网络不稳定或客户端重启会导致消息丢失,生产环境必须设计重连和状态恢复机制。
- 混淆
PSUBSCRIBE和SUBSCRIBE:模式订阅更灵活,但性能开销略高于精确频道订阅,在频道数极多时需注意。
总结
Redis 发布订阅是一个基于内存、高性能的轻量级消息通信方案,通过内部字典和链表高效管理频道与订阅者关系,非常适合对实时性要求高、允许少量消息丢失的广播场景,但在需要高可靠、可堆积的消息通信时,应选择专业的消息中间件。