发布/订阅者模式——Redis
本文最后更新于:December 1, 2021 am
最近的项目需要用到Redis
的发布/订阅者模式,在此进行学习记录
本文主要参考了官方文档Pub/Sub – Redis
Pub/Sub in Redis
Subcribe
, Unsubscribe
和Publish
实现了发布/订阅模式范例。这种情况下,在编程中并不需要明确指定将某个消息Message
发送给某个特定的接受者Subscriber
。被发布的消息以频道Channel
来刻画,并且不需要知道这个频道有哪些订阅者Subscriber
。订阅者可以对多个频道感兴趣,也只会收到其订阅了的频道的消息,而并不知道消息的发布者是谁。这种对于发布者和订阅者的解耦合使得更大规模和更动态的网络拓扑结构成为可能。
如果要订阅频道foo
和bar
,可以执行
1 |
|
被其它客户端发布到某个频道的消息会被Redis
广播给该频道的所有订阅者。
值得一提的是,虽然一个已经订阅了一定数量频道的某个客户端任然可以继续订阅新的频道,或者取消已经订阅的某些频道,但它无法再执行其它的redis命令,比如最简单的set key value
。
订阅和取消订阅操作的结果被以特定格式的消息返回回来,如下图。客户端可以读取一段清晰的消息流(消息中第一个元素明确消息的类型)。
综上,在一个已经订阅了某个频道的客户端的语境下,该客户端能够执行的命令是有限的,有且仅有:subscribe
, psubscribe
, unsubscribe
, punsubscribe
, ping
和quit
。
注意,在redis-cli
中,一旦执行了订阅操作,就不会再接受任何新的命令了。通过Ctrl-C结束客户端。
发布的消息的格式
一条消息是一个带有3个元素的数组回复Aray Reply
。可以参考上图。
第一个元素标志消息的种类:
- subscribe:客户端成功订阅了一个频道,频道的名字为返回的数组的第二个元素,第三个元素标志了该客户端当前订阅的频道数量。
- unsubscribe:客户端成功取消订阅了一个频道,频道的名字为返回的数组的第二个元素,第三个元素标志了该客户端当前订阅的频道数量。如果是0,那代表客户端当前没有订阅任何一个频道,那么也就可以执行其它的
Redis
命令了。 - message:标着这是一条由另一个个客户端执行的publish命令引起的消息发布。第二个元素同样是消息所在频道的名字,第三个元素是消息的实际内容。
数据库和范围
发布/订阅模式和键值空间没有任何关系。它故意被设计为不和键值空间产生任何干扰,这还包括数据库的编号。
在db 10
上进行发布,db 1
上的订阅者仍然会收到消息。
如果需要进行范围限制,正确的做法应该是在频道名字之前加上前缀,比如test
,staging
等。
模式匹配订阅
Redis
的发布/订阅支持字符串的匹配。客户端可以订阅一个glob
风格的模式字符串,来订阅所有名称符合这个模式的频道。比如,psubscribe news.*
将会订阅所有频道名符合这个模式的频道的消息,包括news.art.figurative
,news.music.jazz
等。所有的glob
风格的模式都支持,因此可以使用多个通配符。
使用punsubcribe news.*
可以批量取消订阅。
值得一提的是,通过这种方式订阅的频道发送给客户端的消息的格式和上文的描述并不相同:
某条消息的种类是
pmessage
:这意味着这条消息同样是由另一个客户端的publish
命令所引发,并且满足该客户端订阅时给出的匹配式子。第二个元素是被匹配上glob
风格式子,第三个元素是源频道的名字,第四个元素是真正的消息内容。
psubscribe
、punsubscribe
的操作成功返回的消息同subscribe
和unsubscribe
类似,可以看图。
值得一提的是,如果某个客户端通过模式订阅频道时重新订阅了某个已经订阅过的频道,则会多次收到同一条消息。比如,客户端先后执行subscribe foo
和psubscribe f**
。
在foo
频道上发布的消息会被此客户端受到两次,并且两种消息的type
不同,分别为message
和pmessage
。
订阅计数的意义
当消息的类别为subscribe
、unsubscribe
、psubscribe
和unsubscribe
的时(也就是四种命令的执行成功返回的消息),最后一个元素表达的是该客户端当前仍然活跃的订阅的计数。通过对所有订阅的频道和订阅的模式求和得到。因此只有当这个订阅计数回落到0,客户端才会退出发布/订阅模式。
编程范例
Pieter Noordhuis provided a great example using EventMachine and Redis to create a multi user high performance web chat.
关于库在实现上的提示
考虑到收到的所有消息都包含着导致消息分发的源订阅(频道名字或者glob
风格的式子),库可以通过哈希表将源订阅名绑定到回调函数上(例如匿名函数,块,或者函数指针)。当收到一个消息时,可以在$O(1)$的时间复杂度内找到注册的回调函数并且调用之。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!