• 沒有找到結果。

订阅与发布

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

4.2.1 频道的订阅与信息发送

Redis 的 SUBSCRIBE 命令可以让客户端订阅任意数量的频道,每当有新信息发送到被订阅的 频道时,信息就会被发送给所有订阅指定频道的客户端。

作 为 例 子, 下 图 展 示 了 频 道 channel1 , 以 及 订 阅 这 个 频 道 的 三 个 客 户 端 ——client2 、 client5 和 client1 之间的关系:

channel1

client2

subscribe client5

subscribe

client1 subscribe

92 Chapter 4. 功能的实现

Redis 设计与实现, 第一版

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

PUBLISH channel1 message

channel1

client2

message client5

message

client1 message

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

4.2.2 订阅频道

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

struct redisServer { // ...

dict *pubsub_channels;

// ...

};

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

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

pubsub_channels

client1 NULL client5

NULL

client4 client6 NULL

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

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

pubsub_channels

client1 client10086

client5 NULL

client10086 NULL

client10086 NULL client4 client6

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

def SUBSCRIBE(client, channels):

# 遍历所有输入频道

for channel in channels:

# 将客户端添加到链表的末尾

redisServer.pubsub_channels[channel].append(client)

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

4.2.3 发送信息到频道

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

比如说,对于以下这个 pubsub_channels 实例,如果某个客户端执行命令 PUBLISH channel1

"hello moto" ,那么 client2 、client5 和 client1 三个客户端都将接收到 "hello moto"

信息:

94 Chapter 4. 功能的实现

Redis 设计与实现, 第一版

client1 NULL client5

NULL

client4 client6 NULL

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

def PUBLISH(channel, message):

# 遍历所有订阅频道 channel 的客户端

for client in server.pubsub_channels[channel]:

# 将信息发送给它们

send_message(client, message)

4.2.4 退订频道

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

4.2.5 模式的订阅与信息发送

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

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

tweet.shop.kindle tweet.shop.ipad

tweet.shop.*

match match

subscribe subscribe clientX

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

tweet.shop.ipad

PUBLISH tweet.shop.kindle message

tweet.shop.kindle

clientX message

clientY message

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

tweet.shop.kindle tweet.shop.ipad

PUBLISH tweet.shop.ipad message

tweet.shop.*

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

struct redisServer { // ...

list *pubsub_patterns;

// ...

};

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

96 Chapter 4. 功能的实现

Redis 设计与实现, 第一版

typedef struct pubsubPattern { redisClient *client;

robj *pattern;

} pubsubPattern;

client 属性保存着订阅模式的客户端,而 pattern 属性则保存着被订阅的模式。

每当调用 PSUBSCRIBE 命令订阅一个模式时,程序就创建一个包含客户端信息和被订阅模式的 pubsubPattern 结构,并将该结构添加到 redisServer.pubsub_patterns 链表中。

作为例子,下图展示了一个包含两个模式的 pubsub_patterns 链表,其中 client123 和 client256 都正在订阅 tweet.shop.* 模式:

redisServer ...

pubsub_patterns ...

pubsubPattern client client123

pattern tweet.shop.*

pubsubPattern client client256

pattern tweet.shop.*

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

pattern tweet.shop.*

pubsubPattern client client256

pattern tweet.shop.*

pubsubPattern client client10086

pattern broadcast.live.*

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

4.2.7 发送信息到模式

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

def PUBLISH(channel, message):

# 遍历所有订阅频道 channel 的客户端

但是,这段伪代码并没有完整描述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 状态如下:

redisServer ...

pubsub_patterns ...

pubsubPattern client client123

pattern tweet.shop.*

pubsubPattern client client256

pattern tweet.shop.*

pubsubPattern client client10086

pattern broadcast.live.*

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

4.2.8 退订模式

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

4.2.9 小结

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

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

98 Chapter 4. 功能的实现

Redis 设计与实现, 第一版

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

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

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