那些年背过的题:Redis发布订阅设计与实现

357 阅读6分钟

Redis 的发布与订阅(pub/sub)功能是一种消息传递模型,使得消息可以在不同的客户端之间进行分发。它涉及到几个关键概念:频道(channel)、发布者和订阅者。

设计概念

  1. 频道(Channel)

    • 类似于一个主题,消息发布者将消息发送到某个频道上,而订阅者会接收到该频道的消息。
  2. 发布者(Publisher)

    • 发布者将消息发送到指定的频道,不需要知道订阅者的信息。
  3. 订阅者(Subscriber)

    • 订阅者订阅一个或多个频道,实时接收这些频道上的消息。

实现机制

  1. 订阅过程

    • 客户端向 Redis 服务器发送 SUBSCRIBE 命令来订阅一个或多个频道。
    • Redis 将客户端添加到对应频道的订阅者列表中。
  2. 发布过程

    • 当有消息通过 PUBLISH 命令发布到某个频道时,Redis 会查找这个频道的所有订阅者,并将消息推送给他们。
  3. 模式订阅(Pattern Subscription)

    • 可以使用 PSUBSCRIBE 命令订阅符合某种模式的频道,例如 news.*
    • Redis 会根据模式匹配,将消息发送至符合条件的订阅者。
  4. 高效事件驱动

    • Redis 使用 Reactor 模式,这意味着它是事件驱动的,可以高效地处理大量连接和消息传递。
  5. 无持久化

    • Pub/Sub 是实时的、瞬时的消息系统,Redis 不会将消息持久化。因此,断线后无法收到之前的消息。

存储结构

频道订阅

  1. 字典(哈希表)

    • Redis 使用一个全局的字典来存储频道和订阅该频道的客户端之间的映射关系。
    • 键是频道名称,值是一个链表或集合,其中包含所有订阅这个频道的客户端。

模式订阅

  1. 模式字典

    • 类似于普通频道,Redis 也维护一个模式字典。
    • 键是模式字符串(可以包含通配符),值是订阅该模式的客户端列表或集合。

使用场景

  • 即时通讯:适合用于聊天室或通知系统。
  • 实时更新:如股票行情、体育比分等需要实时更新的场景。
  • 日志收集:集中收集并处理日志信息。

需要注意的是,由于发布订阅不提供消息持久化和确认机制,它更适合用于对消息丢失不敏感的场景。如果需要可靠性保障,通常会结合其他技术(例如 Kafka 或 RabbitMQ)。

举例说明

假设我们有一个简单的聊天应用,用户可以加入特定的频道进行聊天。每当用户在某个频道发送消息时,所有订阅了该频道的用户都会收到这条消息。

实现步骤

  1. 用户 A 和用户 B 订阅频道 "chatroom"

    用户 A 和用户 B 需要向 Redis 发送 SUBSCRIBE 命令来订阅频道。

    SUBSCRIBE chatroom
    
  2. 用户 C 发布消息到 "chatroom"

    用户 C 通过 PUBLISH 命令将一条消息发送到 "chatroom"。

    PUBLISH chatroom "Hello, everyone!"
    
  3. Redis 将消息分发给所有订阅 "chatroom" 的用户

    • 用户 A 收到消息:

      Message received on channel chatroom: "Hello, everyone!"
      
    • 用户 B 也收到相同的消息:

      Message received on channel chatroom: "Hello, everyone!"
      

注意事项

  • 当用户 C 发布消息时,他不需要知道有多少人订阅了该频道。
  • 如果用户 A 或用户 B在发布之前没有订阅,他们会错过这条信息。
  • 消息是实时传输的,没有存储,也无法重播。

通过这种方式,Redis 的发布与订阅机制可以很方便地实现类似聊天室、通知系统等场景的需求。

消息分发详细流程

在 Redis 的发布与订阅机制中,当一条消息通过某个频道发布时,Redis 会将该消息分发给所有订阅该频道的客户端。下面是消息分发的详细流程:

消息发布流程

  1. 接收到 PUBLISH 命令

    • 客户端发送 PUBLISH channel message 命令到 Redis 服务器。
  2. 查找订阅者

    • Redis 在其内部维护的频道字典中查找“channel”这个键,以获取所有订阅这个频道的客户端列表。
  3. 消息分发

    • 对于每一个订阅了该频道的客户端,Redis 会执行以下操作:

      • 将消息放入客户端的输出缓冲区。
      • 如果客户端的连接处于活跃状态且没有被阻塞,则立即尝试通过网络将消息发送出去。
  4. 发送完成或等待

    • 如果网络状况良好且客户端能够及时处理消息,消息会被立即发送到客户端。
    • 如果客户端暂时无法接收(例如网络延迟、客户端处理速度较慢),消息可能会在输出缓冲区中等待一段时间。

涉及的数据结构

  • 频道字典:存储频道名称和对应的客户端订阅者列表。
  • 客户端输出缓冲区:Redis为每个连接的客户端分配一个缓冲区,用于存储待发送给该客户端的消息。

思考题:如何判断订阅客户端的网络或者阻塞状态

  1. 输出缓冲区溢出

    • 每个连接到 Redis 的客户端都分配一个输出缓冲区,用于存储待发送的数据。如果订阅客户端的处理速度跟不上 Redis 的发送速度,这个缓冲区会不断增大。
    • 如果输出缓冲区超过配置的最大限制(由 client-output-buffer-limit 设置),Redis 会认为客户端可能有问题并断开连接,以避免对服务器资源的过度消耗。
  2. 定期检查机制

    • Redis 通过事件循环机制处理客户端连接,通过设置合理的心跳检测和超时参数,可以间接了解客户端的活跃性。例如,通过 timeout 配置选项,Redis 可以在客户端一段时间内没有任何请求时自动断开连接。
  3. ACK 确认机制的缺乏

    • 在原生的 Pub/Sub 模型中,并没有内置的 ACK 确认机制来判断消息是否成功送达。但通过监控客户端的响应行为(如是否继续发送订阅请求、PING 请求等)可以推测其状态。

操作与配置

  • 配置文件中的设置

    • timeout:指定客户端最长空闲时间,超过该时间未活动则会被关闭。
    • client-output-buffer-limit:设置不同类型客户端的输出缓冲区限制,包括正常客户端、发布订阅客户端和 slave/replica。
  • 命令行查看

    • 使用 CLIENT LIST 命令可以查看所有连接客户端的信息,包括每个客户端的状态、已发送和等待确认的数据量等。

通过以上机制,Redis 可以在一定程度上判断客户端的网络状况或者是否处于阻塞状态,从而采取相应措施来保障服务器的稳定运行。