6. pub/sub
Redis可以执行发布/订阅模式(publish/subscribe), 该模式可以解耦消息的发送者和接收者,使程序具有更好的扩展性。从宏观上来讲,Redis的发布/订阅模式具有如下特点:
客户端执行订阅以后,除了可以继续订阅(SUBSCRIBE或者PSUBSCRIBE),取消订阅(UNSUBSCRIBE或者PUNSUBSCRIBE), PING命令和结束连接(QUIT)外, 不能执行其他操作,客户端将阻塞直到订阅通道上发布消息的到来。
发布的消息在Redis系统中不存储。因此,必须先执行订阅,再等待消息发布。 但是,相反的顺序则不支持。
订阅的通道名称支持glob模式匹配。如果客户端同时订阅了glob模式的通道和非glob模式的通道,并且名称存在交集,则对于一个发布的消息,该执行订阅的客户端接收到两个消息。
Redis的发布/订阅设计模式相关的命令有六个:
- SUBSCRIBE命令执行订阅。客户端可以多次执行该命令, 也可以一次订阅多个通道。 多个客户端可以订阅相同的通道。该命令的响应包括三部分, 依次是:命令名称(字符串subscribe),订阅的通道名称,总共订阅的通道数(包含glob通道)。
- PSUBSCRIBE命令执行glob模式订阅。客户端可以多次执行该命令, 也可以一次订阅多个glob通道。 多个客户端可以订阅相同的glob通道。该命令的响应包括三部分, 依次是:命令名称(字符串psubscribe),订阅的glob通道名称,总共订阅的通道数(包含非glob通道)。
- UNSUBSCRIBE命令取消订阅指定的通道。可以指定一个或者多个取消的订阅通道名称,也可以不带任何参数,此时将取消所有的订阅的通道(不包括glob通道)。该命令的响应包括三部分, 依次是:命令名称(字符串unsubscribe),取消的订阅通道名称,总共订阅的通道数(包含glob通道)。
- PUNSUBSCRIBE命令取消订阅指定的glob模式通道。可以指定一个或者多个取消的glob模式的订阅通道名称,也可以不带任何参数,此时将取消所有的glob模式订阅的通道(不包括非glob通道)。该命令的响应包括三部分, 依次是:命令名称(字符串punsubscribe),取消的glob模式的订阅通道名称,总共订阅的通道数(包含非glob通道)。
- PUBLISH命令在指定的通道上发布消息。只能在一个通道上发布消息,不能在多个通道上同时发布消息。该命令的响应包括通知的接收者个数,需要注意的是,这里的接收者数目大于等于订阅该通道的客户端数目(因为一个客户端的glob通道和非glob通道同时匹配发布通道的话,则视为两个接收者)。而在接收端,收到的响应包括三部分,依次是 :message或者pmessage字符串(取决于是否为glob匹配),匹配的通道名称,发布的消息内容。
- PUBSUB命令执行状态查询。支持若干子命令。需要注意的是,该命令不能在客户端进入订阅后执行。
6.1 普通订阅
当一个客户端执行 SUBSCRIBE 命令, 订阅某个或某些频道的时候, 这个客户端与被订阅频道之间就建立起了一种订阅关系。
Redis 将所有频道的订阅关系都保存在服务器状态的 pubsub_channels 字典里面, 这个字典的键是某个被订阅的频道, 而键的值则是一个链表, 链表里面记录了所有订阅这个频道的客户端:
struct redisServer {
// ...
// 保存所有频道的订阅关系
dict *pubsub_channels;
// ...
};
每当客户端执行 SUBSCRIBE 命令, 订阅某个或某些频道的时候, 服务器都会将客户端与被订阅的频道在 pubsub_channels 字典中进行关联。
根据频道是否已经有其他订阅者, 关联操作分为两种情况执行:
- 如果频道已经有其他订阅者, 那么它在 pubsub_channels 字典中必然有相应的订阅者链表, 程序唯一要做的就是将客户端添加到订阅者链表的末尾。
- 如果频道还未有任何订阅者, 那么它必然不存在于 pubsub_channels 字典, 程序首先要在 pubsub_channels 字典中为频道创建一个键, 并将这个键的值设置为空链表, 然后再将客户端添加到链表, 成为链表的第一个元素。
订阅了之后还能取消订阅,使用UNSUBSCRIBE即可,他的操作就是SUBSCRIBE的反向操作。
6.2 模式订阅
前面说过,服务器将所有频道的订阅关系都保存在服务器状态的pubsub_channels属性里面,与此类似,服务器也将所有模式的订阅关系都保存在服务器状态的pubsub_patterns属性里面:
struct redisServer {
// ...
// 保存所有模式订阅关系
list *pubsub_patterns;
// ...
};
pubsub_patterns属性是一个链表,链表中的每个节点都包含着一个pubsub Pattern结构,这个结构的pattern属性记录了被订阅的模式,而client属性则记录了订阅模式的客户端:
typedef struct pubsubPattern {
// 订阅模式的客户端
redisClient *client;
// 被订阅的模式
robj *pattern;
} pubsubPattern;
每当客户端执行PSUBSCRIBE命令订阅某个或某些模式的时候,服务器会对每个被订阅的模式执行以下两个操作:
- 新建一个pubsubPattern结构,将结构的pattern属性设置为被订阅的模式,client属性设置为订阅模式的客户端。
- 将pubsubPattern结构添加到pubsub_patterns链表的表尾(即每个模式只有一个客户端,即使模式相同也是再重复添加一个)。