Redis 发布/订阅

Redis 发布/订阅

Redis 的发布/订阅功能主要由 PUBLISH,SUBSCRIBE,PSUBSCRIBE 命令组成。

一个或者多个客户端订阅了某个或者多个频道,当其他客户端向频道发送消息,订阅了该频道的客户端会收到对应的消息。

这个功能提供两种信息机制,分别为 订阅/发布到频道订阅/发布到模式。

频道的订阅和信息发送

Redis 的 subscribe 命令可以订阅一个或多个频道,当有消息发送到被订阅的频道时,消息会发送给所有订阅频道的客户端。

如下图,client1,client2,client3订阅了频道 channel 。

 当有消息通过publish 命令发送给频道channel时,这个消息会发送给订阅该频道的客户端。

 下面说一下 subscribe 和 publish 的命令的实现,频道发布订阅原理。

订阅频道

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

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

其中字典的键为被订阅的频道,值为所有订阅该频道的客户端,是一个链表的结构。

如下图,客户端client1、client2、client3订阅了频道channel1,客户端client4、client5、client6订阅了频道channel2。

 当一个客户端使用 subscribe命令订阅频道时,程序会把客户端加在字典中频道对应的链表。

如图,客户端client7 执行命令 subscribe channel2 ,上图则变成下面的图。

 subscribe 命令伪代码表示如下:

def SUBSCRIBE(client, channels):

    # 遍历所有输入频道
    for channel in channels:

        # 将客户端添加到链表的末尾
        redisServer.pubsub_channels[channel].append(client)

发送消息到频道

调用  publish channel message  命令, 程序首先根据 channel 定位到字典的键, 然后将信息发送给字典值链表中的所有客户端。

如图,客户端执行命令 publish channel1 "hello world!" ,那么client1、client2、client3 三个客户端都将接收到 "hello world!" 信息:

 

 publish 命令伪代码表示如下:

def PUBLISH(channel, message):

    # 遍历所有订阅频道 channel 的客户端
    for client in server.pubsub_channels[channel]:

        # 将信息发送给它们
        send_message(client, message)

退订频道

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

模式的订阅和信息发送

使用publish 命令发送消息给频道时,不仅订阅该频道的客户端会接受到消息,如果某些模式和这个频道匹配,那么所有订阅该模式的客户端也将接受到消息。

下图表示一个频道和一个模式,模式channel*匹配频道channel。

 当有信息发送到 channel 频道时, 信息除了发送给 client1、client2、client3之外, 还会发送给订阅 channel*模式的client4和client5:

 订阅模式

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 链表,其中client1和client2都订阅着channle*模式。

 这时客户端 client3 执行命令 psubscribe channel_test*, 那么 pubsub_patterns 链表将被更新成这样:

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

发送信息到模式

发送信息到模模式也是 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 状态如下:

 那么当消息发送到 channel 的时候,除了订阅channel的客户端会接受到消息,上图的client1和client2也会接受到消息。因为这两个客户端订阅的模式匹配了频道channel。

退订模式

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

应用场景

1 构建实时消息系统,比如普通的即时聊天,群聊等功能

2 在我们的分布式架构中,常常会遇到读写分离的场景,在写入的过程中,就可以使用redis发布订阅,使得写入值及时发布到各个读的程序中,就保证数据的完整一致性

3 在一个博客网站中,有100个粉丝订阅了你,当你发布新文章,就可以推送消息给粉丝们

参考文献

Redis 设计与实现

https://www.cnblogs.com/liuqingzheng/p/10009541.html

https://www.cnblogs.com/yitudake/p/6747995.html

原文地址:https://www.cnblogs.com/hulunbao/p/13744180.html