pub_sub

第五章 发布-订阅系统(Publish/Subscribe System)

1 概述

发布-订阅是一种常见的设计模式,它常常应用于消息分发系统。发布-订阅系统具有低耦合、可扩展性好等优点。发布-订阅功能并不是传统数据库中的一个功能部件,但是,因为发布-订阅模式广泛的被用于各大系统之中,而且Redis的内部设计非常适合于实现发布-订阅系统,因此,发布-订阅功能被当作Redis的一个热点功能被推出。

发布-订阅模式中有三个角色;它们是发布者、订阅者和消息代理人(Message Broker)。如下图所示,发布者向消息代理人发布主题(Topic)和消息;主题又常常被称为频道(Channel)。订阅者向消息代理人订阅主题和接收消息。消息代理人保存发布者、订阅者和主题之间的关系。当有新消息发布时,消息代理人将会把消息转发给相应的订阅者。

在Redis系统中,发布者和订阅者都是Redis的客户端或者Redis应用程序,而消息代理人为Redis服务器。

图一 发布-订阅模式

图一 发布-订阅模式。

2 Redis发布-订阅系统的基本操作

PUBLISH, SUBSCRIBE和UNSUBSCRIBE三条命令搭建了一个基本的发布-订阅消息分发系统。Redis使用频道(Channel)来抽象消息的主题(Topic)。当发布者向Redis发布一个新的主题之后,Redis会内部创建一个频道(Channel)。随后,订阅者可以通过订阅这个频道中的消息来“订阅主题”。Redis则扮演了消息代理人(Message Broker)的角色。发布者和订阅者直接和Redis通信。

在如下的例子中,我们使用了两个Redis客户端,分别扮演发布者(Publisher)和订阅者(Subscriber),它们分别使用S>和P>以示区别。首先,订阅者向Redis订阅频道littlewaterdrop_redis。当订阅者订阅了一个频道之后,订阅者就不能执行其他命令了;它只能等待消息或者执行与订阅相关的命令(SUBSCRIBE和UNSUBSCRIBE命令)、发送心跳命令PING和退出命令QUIT。

随后,我们在发布者客户端使用PUBLISH命令向littlewaterdrop_redis频道发送消息"Welcome to Little Waterdrop Redis channel"。在此命令执行完毕后,订阅者客户端立即收到了这则消息,并打印在屏幕上。

操作顺序订阅者(Subscriber)发布者(Publisher)
1S> SUBSCRIBE littlewaterdrop_redis
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "littlewaterdrop_redis"
3) (integer) 1
 
2 P> PUBLISH littlewaterdrop_news "Welcome to Little Waterdrop Redis channel."
(integer) 1
31) "message"
2) "littlewaterdrop_news"
3) "Welcome to Little Waterdrop Redis channel."
 

上述是一个典型的使用发布-订阅模式的例子。在实际应用中,一个客户端可以同时订阅多个频道。与此同时,一个发布者可以向多个频道发布消息。

3 Redis发布-订阅系统中的模式匹配

除了上述使用PUBLISH和SUBSCRIBE命令发布和订阅频道以外,Redis还支持使用模式匹配的方式订阅频道,这种方法是由PSUBSCRIBE和PUNSUBSCRIBE命令完成的。

我们假设有一位订阅者不仅喜欢Redis频道的信息,他还喜欢其他所有Little Waterdrop出品的内容。那么,他可以使用PSUBSCRIBE littlewaterdrop*来订阅所有名称以"littlewaterdrop"开始的频道。发布者只需往某一个频道发布消息即可,Redis会进行频道名称的匹配。这里的星号"*"作为通配符(wildcard)使用,可以匹配任意字符。

在下面的例子中,订阅者首先订阅了所有名字以"littlewaterdrop"开头的频道。然后,发布者向littlewaterdrop_news频道发布了一条消息"Welcome to Little Waterdrop Redis channel"。因为订阅者订阅频道的模式匹配于littlewaterdrop_news,所以,该订阅者能收到这则消息。

操作顺序订阅者(Subscriber)发布者(Publisher)
1S> PSUBSCRIBE littlewaterdrop*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "littlewaterdrop*"
3) (integer) 1
 
2 P> PUBLISH littlewaterdrop_news "Welcome to Little Waterdrop Redis channel."
(integer) 1
31) "pmessage"
2) "littlewaterdrop*"
3) "littlewaterdrop_redis"
4) "Welcome to Little Waterdrop Redis channel."
 

4 Redis发布-订阅系统的内部实现

Redis的发布-订阅系统的内部实现非常简单。当使用SUBSCRIBE命令订阅具体的频道时,Redis使用哈希表和链表来管理订阅频道和订阅者的。Redis为每个频道建立一个订阅者链表,然后,以频道名称为Key,订阅者链表为Value存放在哈希表中。这里,需要指出的是,发布-订阅系统使用的是独立的Key空间(Independent Key Space)。用户创建的Key/Value的记录不会与发布-订阅系统中的记录相冲突。换句话说,开发人员可以“littlewaterdrop_redis”为key创建一个Key/Value记录,与此同时,开发人员还可以发布或者订阅"littlewaterdrop_redis"频道。

图二 Redis发布-订阅系统内部实现

图二 Redis发布-订阅系统内部实现。

当使用PSUBSCRIBE命令订阅频道时,Redis是将所有的订阅者放在一个链表中的。这里不能使用哈希表的原因是Redis无法在频道名称(Key)哈希之后做模式匹配。所以,Redis将所有的订阅者发在一个链表中,当有消息发布时,Redis会逐一扫描订阅者,进行频道名称的模式匹配。

图三 Redis发布-订阅系统模式匹配内部实现

图三 Redis发布-订阅系统模式匹配内部实现。

发布-订阅系统的代码在src/server.h和src/pubsub.c文件中。如下的代码所示,在Redis服务器的结构中, pubsub_channels是一个哈希表类型的结构指针,它包含着所有发布-订阅系统的数据。而pubsub_patterns是一个链表,它包含着所有频道名称的模式匹配结构,每个模式匹配的结构指向其对应的订阅者。pubsubPattern结构体抽象的是模式匹配,它包含了一个订阅者client,和对应的模式匹配对象。

// Redis 5.0.8 版本
// Redis发布-订阅系统的定义与实现在 src/server.h 和 src/pubsub.c 文件中
struct redisServer {
    ...
    dict *pubsub_channels;  /* Map channels to list of subscribed clients */
    list *pubsub_patterns;  /* A list of pubsub_patterns */
    ...
}

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

5 小结

本章简单的介绍了Redis的发布-订阅系统的工作原理。虽然Redis声称是一个Key/Value数据库,但是,它并没有将自己局限在数据库的领域里。它还为开发人员准备了一个设计小巧的发布-订阅系统。从Redis的内部实现来看,使用模式匹配的订阅方式是比较耗时的,因为Redis需要逐一查询匹配频道的名称。

 

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.