Redis 如何实现发布、订阅?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 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 如何实现发布、订阅?

面试考察点

面试官提出这个问题,通常意在考察以下几个方面:

  1. 对 Redis 核心功能的掌握:你是否了解 Redis 除了缓存之外,还提供了哪些高级特性。
  2. 对发布/订阅模式的理解:你能否清晰地解释这种经典的消息通信模式,并说明它与消息队列(如 RabbitMQ, Kafka)的异同。
  3. 对机制与局限性的认识:你是否理解其非持久化、无消息堆积能力、消息“即发即弃” 等关键特性,这决定了你是否能为其选择正确的应用场景。

核心答案

Redis 通过内置的 PUBLISHSUBSCRIBEPSUBSCRIBE 等命令实现了一个轻量级、高性能但非持久化的发布/订阅(Pub/Sub)消息系统。

其核心机制是:服务器维护一个 pubsub_channels 字典来记录频道与订阅者(客户端)的映射关系。当客户端订阅(SUBSCRIBE)某个频道时,它就会被添加到对应频道的订阅者列表中。当有消息发布(PUBLISH)到该频道时,Redis 服务器会遍历这个列表,将消息推送给所有在线的订阅者。此外,还支持通过 PSUBSCRIBE 命令进行模式订阅,使用 pubsub_patterns 链表来实现全局模式匹配。

技术深度解析

原理/机制

  1. 数据结构
    • pubsub_channels:一个字典,键是频道名,值是一个链表,链表中保存了所有订阅了该频道的客户端引用。
    • pubsub_patterns:一个链表,其中每个节点保存了一个模式字符串订阅了该模式的客户端
  2. 工作流程
    • 订阅:执行 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 中,直到被消费。
  • 最佳实践与适用场景
    • 适用:实时通知、聊天室、配置热更新、微服务间的轻量级事件广播(对消息可靠性要求不高)。
    • 不适用:需要保证消息必达、顺序性、有大量消息积压、或需要复杂路由(如延迟队列、死信队列)的业务场景。这类场景应选用专业的消息中间件。
  • 常见误区
    1. 误用作持久化消息队列:这是最常见的错误。Redis Pub/Sub 的设计目标就是实时推送,不是可靠存储
    2. 忽略客户端断连问题:网络不稳定或客户端重启会导致消息丢失,生产环境必须设计重连和状态恢复机制。
    3. 混淆 PSUBSCRIBESUBSCRIBE:模式订阅更灵活,但性能开销略高于精确频道订阅,在频道数极多时需注意。

总结

Redis 发布订阅是一个基于内存、高性能的轻量级消息通信方案,通过内部字典和链表高效管理频道与订阅者关系,非常适合对实时性要求高、允许少量消息丢失的广播场景,但在需要高可靠、可堆积的消息通信时,应选择专业的消息中间件。