Redis设计与实现之订阅与发布

发布时间:2023年12月18日

目录

一、 订阅与发布

1、 频道的订阅与信息发送

2、订阅频道

3、发送信息到频道

4、 退订频道

5、模式的订阅与信息发送

?编辑?6、 订阅模式

7、 发送信息到模式

8、 退订模式

三、订阅消息断连

1、如果订阅者断开连接了,再次连接会不会丢失之前发布的消息?

2、订阅者是否需要一直保持连接状态才能接收到发布者的消息?

3、订阅与发布的消息会被持久化吗?

四、订阅频道来源

1、如果一个订阅者订阅了多个频道,如何判断收到的消息是来自哪个频道的?

2、是否可以在Redis中实现多个订阅者同时订阅同一个频道?

3、是否可以在Redis中实现多个发布者同时发布消息到同一个频道?

4、如何停止订阅某个频道的消息?

5、是否可以取消订阅所有频道的消息?

五、订阅与发布的功能在Redis集群中是否可用?

六、、小结


一、 订阅与发布

Redis 通过 PUBLISH 、SUBSCRIBE 等命令实现了订阅与发布模式,这个功能提供两种信息 机制,分别是订阅/发布到频道和订阅/发布到模式,下文先讨论订阅/发布到频道的实现,再讨 论订阅/发布到模式的实现。

1、 频道的订阅与信息发送

Redis 的 SUBSCRIBE 命令可以让客户端订阅任意数量的频道,每当有新信息发送到被订阅的频道时,信息就会被发送给所有订阅指定频道的客户端。
作为例子,下图展示了频道 channel1 ,以及订阅这个频道的三个客户端——client2 、client5 和 client1 之间的关系:

当有新消息通过 PUBLISH 命令发送给频道 channel1 时,这个消息就会被发送给订阅它的三个客户端:

?在后面的内容中,我们将探讨 SUBSCRIBE 和 PUBLISH 命令的实现,以及这套订阅与发布 机制的运作原理。

2、订阅频道

每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构,结构的pubsub_channels 属性是一个字典,这个字典就用于保存订阅频道的信息:

struct redisServer { // ...
    dict *pubsub_channels;
// ...
};

其中,字典的键为正在被订阅的频道,而字典的值则是一个链表,链表中保存了所有订阅这个 频道的客户端。

比如说,在下图展示的这个 pubsub_channels 示例中,client2 、client5 和 client1 就订 阅了 channel1 ,而其他频道也分别被别的客户端所订阅:

当客户端调用 SUBSCRIBE 命令时,程序就将客户端和要订阅的频道在 pubsub_channels 字 典中关联起来。

举个例子,如果客户端 client10086 执行命令 SUBSCRIBE channel1 channel2 channel3 , 那么前面展示的 pubsub_channels 将变成下面这个样子:

?SUBSCRIBE 命令的行为可以用伪代码表示如下:

def SUBSCRIBE(client, channels): 
    # 遍历所有输入频道
    for channel in channels:
    # 将客户端添加到链表的末尾 
    redisServer.pubsub_channels[channel].append(client)

通过 pubsub_channels 字典,程序只要检查某个频道是否字典的键,就可以知道该频道是否正 在被客户端订阅;只要取出某个键的值,就可以得到所有订阅该频道的客户端的信息。

3、发送信息到频道

了解了 pubsub_channels 字典的结构之后,解释 PUBLISH 命令的实现就非常简单了:当调 用 PUBLISH channel message 命令,程序首先根据 channel 定位到字典的键,然后将信息发 送给字典值链表中的所有客户端。

比如说,对于以下这个 pubsub_channels 实例,如果某个客户端执行命令 PUBLISH channel1 "hello moto" ,那么 client2 、client5 和 client1 三个客户端都将接收到 "hello moto" 信息:

PUBLISH 命令的实现可以用以下伪代码来描述:

def PUBLISH(channel, message):
    # 遍历所有订阅频道 channel 的客户端
    for client in server.pubsub_channels[channel]:
    # 将信息发送给它们 
    send_message(client, message)

4、 退订频道

使用 UNSUBSCRIBE 命令可以退订指定的频道,这个命令执行的是订阅的反操作:它从 pubsub_channels 字典的给定频道(键)中,删除关于当前客户端的信息,这样被退订频道的 信息就不会再发送给这个客户端。

5、模式的订阅与信息发送

当使用 PUBLISH 命令发送信息到某个频道时,不仅所有订阅该频道的客户端会收到信息,如 果有某个/某些模式和这个频道匹配的话,那么所有订阅这个/这些频道的客户端也同样会收到 信息。

下图展示了一个带有频道和模式的例子,其中 tweet.shop.* 模式匹配了 tweet.shop.kindle 频道和 tweet.shop.ipad 频道,并且有不同的客户端分别订阅它们三个:

当有信息发送到 tweet.shop.kindle 频道时,信息除了发送给 clientX 和 clientY 之外,还会发送给订阅 tweet.shop.* 模式的 client123 和 client256 :

?

?另一方面,如果接收到信息的是频道 tweet.shop.ipad ,那么 client123 和 client256 同样 会收到信息:

?6、 订阅模式

redisServer.pubsub_patterns 属性是一个链表,链表中保存着所有和模式相关的信息:

struct redisServer { // ...
    list *pubsub_patterns;
// ...
};

链表中的每个节点都包含一个 redis.h/pubsubPattern 结构:

typedef struct pubsubPattern { 
    redisClient *client;
    robj *pattern;
} pubsubPattern;

client 属性保存着订阅模式的客户端,而 pattern 属性则保存着被订阅的模式。
每当调用 PSUBSCRIBE 命令订阅一个模式时,程序就创建一个包含客户端信息和被订阅模式的

pubsubPattern 结构,并将该结构添加到 redisServer.pubsub_patterns 链表中。 作为例子,下图展示了一个包含两个模式的 pubsub_patterns 链表,其中 client123 和

client256 都正在订阅 tweet.shop.* 模式:

如果这时客户端 client10086 执行 PSUBSCRIBE broadcast.list.* ,那么 pubsub_patterns 链表将被更新成这样:?

通过遍历整个 pubsub_patterns 链表,程序可以检查所有正在被订阅的模式,以及订阅这些模 式的客户端。

7、 发送信息到模式

发送信息到模式的工作也是由 PUBLISH 命令进行的,在前面讲解频道的时候,我们给出了这样一段伪代码,说它定义了 PUBLISH 命令的行为:

def PUBLISH(channel, message):
    # 遍历所有订阅频道 channel 的客户端
    for client in server.pubsub_channels[channel]:
        # 将信息发送给它们 
        send_message(client, message)

?但是,这段伪代码并没有完整描述 PUBLISH 命令的行为,因为 PUBLISH 除了将 message 发 送到所有订阅 channel 的客户端之外,它还会将 channel 和 pubsub_patterns 中的模式进行 对比,如果 channel 和某个模式匹配的话,那么也将 message 发送到订阅那个模式的客户端。

完整描述 PUBLISH 功能的伪代码定于如下:

def PUBLISH(channel, message):
    # 遍历所有订阅频道 channel 的客户端
    for client in server.pubsub_channels[channel]: 
        # 将信息发送给它们
        send_message(client, message)
    # 取出所有模式,以及订阅模式的客户端
    for pattern, client in server.pubsub_patterns:
        # 如果 channel 和模式匹配
        if match(channel, pattern):
            # 那么也将信息发给订阅这个模式的客户端 
            send_message(client, message)

举个例子,如果 Redis 服务器的 pubsub_patterns 状态如下:?

那么当某个客户端发送信息 "Amazon Kindle, $69." 到 tweet.shop.kindle 频道时,除了所 有订阅了 tweet.shop.kindle 频道的客户端会收到信息之外,客户端 client123 和 client256 也同样会收到信息,因为这两个客户端订阅的 tweet.shop.* 模式和 tweet.shop.kindle 频道 匹配。

8、 退订模式

使用 PUNSUBSCRIBE 命令可以退订指定的模式,这个命令执行的是订阅模式的反操作:程序 会删除 redisServer.pubsub_patterns 链表中,所有和被退订模式相关联的 pubsubPattern 结构,这样客户端就不会再收到和模式相匹配的频道发来的信息。

三、订阅消息断连

1、如果订阅者断开连接了,再次连接会不会丢失之前发布的消息?

Redis的消息订阅-发布模式中,如果订阅者断开连接,再次连接时会丢失之前发布的消息。 Redis的发布-订阅机制是一种消息通信模式,新的订阅者只能接收到自连接之后发布的新消息,而不能获取到之前发布的消息。

2、订阅者是否需要一直保持连接状态才能接收到发布者的消息?

是的,订阅者需要一直保持连接状态才能接收到发布者的消息。当订阅者与Redis服务器建立连接后,它会发送SUBSCRIBE命令来订阅一个或多个频道。然后,Redis服务器会将该订阅者添加到被订阅频道的订阅列表中。当有新的消息发布到被订阅的频道时,Redis服务器会将消息发送给所有已订阅的订阅者。因此,如果订阅者断开连接或关闭,它将无法接收到后续的消息。为了保持订阅者的连接状态,可以使用长连接或心跳机制来定期发送PING命令给Redis服务器,以保持连接活跃。

3、订阅与发布的消息会被持久化吗?

Redis订阅与发布的消息默认是不会被持久化的。当一个客户端订阅了一个频道后,一旦该客户端与服务端断开连接,它之前订阅的消息就会丢失。同样地,当一个客户端发布一条消息后,如果没有其他客户端订阅该频道,那么该消息也会丢失。

不过,你可以使用Redis的持久化功能,如RDB快照和AOF日志,来实现将订阅与发布的消息持久化。通过配置Redis的持久化选项,你可以定期保存数据库中的数据到磁盘(RDB快照),或者将每个写操作追加到一个日志文件(AOF日志)。这样,即使Redis服务器重新启动,之前发布的消息也可以被恢复。

需要注意的是,Redis的持久化功能会对性能造成一定的影响,并且AOF日志相对于RDB快照来说更加耗费磁盘空间。因此,你需要根据自己的需求来选择合适的持久化方式。

四、订阅频道来源

1、如果一个订阅者订阅了多个频道,如何判断收到的消息是来自哪个频道的?

在Redis中,当一个订阅者订阅多个频道时,收到的消息会包含频道名和消息内容两部分。你可以通过解析收到的消息来判断消息来自哪个频道。

以下是一个示例订阅多个频道并解析消息的代码片段:

import redis

def process_message(channel, message):
    print("Received message from channel: %s, Message: %s" % (channel, message))

# 创建Redis连接
r = redis.Redis()

# 创建订阅对象
p = r.pubsub()

# 订阅多个频道
p.subscribe('channel1', 'channel2', 'channel3')

# 循环接收消息
for message in p.listen():
    # 解析消息
    if message['type'] == 'message':
        channel = message['channel']
        message_data = message['data']

        # 处理消息
        process_message(channel, message_data)

在上述示例中,首先创建了一个Redis连接,然后通过pubsub()方法创建了一个订阅对象p。接着使用subscribe()方法订阅了多个频道。

在循环中,通过listen()方法来接收消息。接收到的消息是一个字典,其中包含typechanneldata等字段。通过读取channel字段就可以判断消息来自哪个频道。

最后,你可以在process_message()函数中处理消息,例如打印消息内容或执行其他操作。

2、是否可以在Redis中实现多个订阅者同时订阅同一个频道?

是的,Redis可以实现多个订阅者同时订阅同一个频道。当有消息发布到这个频道时,所有订阅者都会接收到这条消息。

3、是否可以在Redis中实现多个发布者同时发布消息到同一个频道?

是的,Redis 支持多个发布者同时发布消息到同一个频道。当有多个客户端连接到 Redis 服务器,并且同时向同一个频道发布消息时,Redis 会将所有消息发送给订阅该频道的所有订阅者。无论发布者的数量有多少,Redis 都会将消息按照发布顺序逐个发送给所有订阅者。

4、如何停止订阅某个频道的消息?

在Redis中,要停止订阅某个频道的消息,需要使用UNSUBSCRIBE命令。

以下是停止订阅单个频道的示例:

UNSUBSCRIBE channel_name

如果你想停止订阅多个频道,可以使用以下命令:

UNSUBSCRIBE channel_name1 channel_name2 ...

请注意,当没有活动的订阅时,Redis连接将自动进入发布/订阅模式之外的模式。

5、是否可以取消订阅所有频道的消息?

是的,Redis可以取消订阅所有频道的消息。您可以使用UNSUBSCRIBE命令来取消订阅所有频道的消息。命令如下:

UNSUBSCRIBE

执行这个命令后,Redis将取消订阅当前连接已订阅的所有频道的消息。

五、订阅与发布的功能在Redis集群中是否可用?

订阅和发布功能在Redis集群中是可用的,但是有一些限制。在Redis集群中,只有一个节点可以执行写入操作,而其他节点只能执行读取操作。这意味着只有一个节点可以用于发布消息,而其他节点只能用于订阅消息。

在Redis集群中,订阅和发布的消息只能在同一个节点上进行,无法在多个节点之间传递。这是因为Redis集群中的不同节点具有不同的数据分片,并且消息的处理只能在具有相应数据分片的节点上进行。

要使用订阅和发布功能,在Redis集群中,您需要将订阅和发布的操作都发送到具有相同数据分片的节点上。您可以使用Redis的客户端库,在代码中明确指定发送到哪个节点。或者,您可以使用Redis的代理或消息中间件,将订阅和发布的操作转发到正确的节点。

总之,订阅和发布功能在Redis集群中是可用,但需要注意节点分片和消息处理的限制。

六、、小结

? 订阅信息由服务器进程维持的redisServer.pubsub_channels字典保存,字典的键为被 订阅的频道,字典的值为订阅频道的所有客户端。

? 当有新消息发送到频道时,程序遍历频道(键)所对应的(值)所有客户端,然后将消息 发送到所有订阅频道的客户端上。

? 订阅模式的信息由服务器进程维持的redisServer.pubsub_patterns链表保存,链表的 每个节点都保存着一个 pubsubPattern 结构,结构中保存着被订阅的模式,以及订阅该 模式的客户端。程序通过遍历链表来查找某个频道是否和某个模式匹配。

? 当有新消息发送到频道时,除了订阅频道的客户端会收到消息之外,所有订阅了匹配频 道的模式的客户端,也同样会收到消息。

? 退订频道和退订模式分别是订阅频道和订阅模式的反操作

文章来源:https://blog.csdn.net/LYX_WIN/article/details/135050624
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。