一、发布和订阅
除了任务队列以外,redis还有一种基于“发布/订阅”模式的消息传递,使得客户端可以订阅某个频道,当频道有消息产生时,会把消息传递到所有的订阅者。和列表不一样的是,发布和订阅可以是一对多的关系,即同一个消息可以同时传递到多个客户端(订阅者)。而列表只能允许一个客户端接收一个消息。
订阅和退订的命令是PUBLISH/SUBSCRIBE
,PUBLISH
是向频道发送消息,SUBSCRIBE
是订阅频道。
1.1 发布
往指定频道发布消息的操作:
1 |
PUBLISH channel message |
消息发送成功后,将会返回一个整数,表示收到这条消息的订阅者数量,如果没有任何客户端订阅频道,返回0。
一个要注意的问题是消息发布后不会被持久化,如果当前没有订阅者订阅频道,后续再订阅也不会收到订阅前的消息。
示例
往一个没有任何订阅者的频道chan
发送消息,返回结果0:
1 2 |
127.0.0.1:6379> PUBLISH chan helloworld (integer) 0 |
1.2 订阅
订阅频道的命令:
1 |
SUBSCRIBE channel [channel ...] |
订阅后会返回一个subcribe类型的消息,该消息包含三行,第一行为subcribe表示订阅成功,第二行是订阅的频道名,第三行是当前客户端订阅的频道数量。可以同时订阅多个频道,例如:SUBSCRIBE chan1 chan2
。
示例
订阅频道chan
:
1 2 3 4 5 |
127.0.0.1:6379> SUBSCRIBE chan Reading messages... (press Ctrl-C to quit) 1) "SUBSCRIBE" 2) "chan" 3) (integer) 1 |
客户端会阻塞,等待频道发布消息才展示数据。此时新开一个终端,在频道内发布一个消息:
1 2 |
127.0.0.1:6379> PUBLISH chan helloworld (integer) 1 |
客户端就可以收到了:
1 2 3 |
1) "message" 2) "chan" 3) "helloworld" |
一共三行,第一行说明收到了一个订阅消息,它主要是和订阅频道时候的输出subcribe
区分,第二行值表示消息的来源频道名,第三行值是消息的内容。
1.3 取消订阅
取消订阅使用命令UNSUBSCRIBE [channel ...]
,带上频道名取消特定频道,默认取消所有频道。
由于redis-cli客户端是交互式终端,所以在订阅后会阻塞,无法手动输入取消订阅命令,如果要取消订阅使用Ctrl-C
退出即可。在第三方客户端例如python
或者其他程序语言客户端中要取消订阅,需要进行SUBSCRIBE
操作。
二、模式的发布和订阅
除了SUBSCRIBE/UNSUBSCRIBE
以外,redis还有两个订阅的指令PSUBSCRIBE
和PUNSUBSCRIBE
。
命令和上面的两个命令类似,只是这两个命令允许频道名使用通配符参数,通过模式字符串来匹配频道。
例如PSUBSCRIBE chan?
将会订阅类似chan1 chan2 chan3...
的频道:
1 2 3 4 5 |
redis > PSUBSCRIBE chan? Reading messages... (press Ctrl-C to quit) 1) "PSUBSCRIBE" # 订阅 2) "chan?" # 订阅的频道 3) (integer) 1 # 当前订阅的频道 |
这里的返回值和SUBSCRIBE
命令一致,只是在收到消息后会多出一个值:
1 2 3 4 |
1) "pmessage" # 消息类型 2) "chan?" # 订阅的频道规则 3) "chan1" # 实际匹配上的频道 4) "abc" # 消息的内容 |
PSUBSCRIBE
可以重复订阅频道,订阅后将会显示实际订阅的频道信息:
1 2 3 4 5 6 7 8 |
redis > PSUBSCRIBE chan1 chan? Reading messages... (press Ctrl-C to quit) 1) "PSUBSCRIBE" 2) "chan1" 3) (integer) 1 1) "PSUBSCRIBE" 2) "chan?" 3) (integer) 2 |
这种情况下发布一则消息订阅者会接收多次,次数根据订阅规则数量的不同而不同。同时,发布者此时也会显示有多个订阅者接收到了这条消息:
1 2 3 4 5 6 7 8 9 10 11 12 |
# 发布者 127.0.0.1:6379> PUBLISH chan1 abc # 发送消息 (integer) 2 # 有两个订阅者收到了消息 # 订阅者 1) "pmessage" # 第一条消息 2) "chan1" 3) "chan1" 4) "abc" 1) "pmessage" # 第二条消息 2) "chan?" # 订阅规则 3) "chan1" # 实际接收的频道 4) "abc" |
使用PSUBSCRIBE
订阅的频道只能使用PUNSUBSCRIBE
命令取消订阅。
三、python实现订阅发布
3.1 发布者代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# -*- coding=utf8 -*- import redis def SUBSCRIBE(conn): pub_sub = conn.pubsub() pub_sub.SUBSCRIBE(["chan"]) i = 0 for item in pub_sub.listen(): # 循环监听订阅消息 print item i += 1 if i == 2: pub_sub.unSUBSCRIBE() # 取消订阅 elif i == 3: # 退出监听 print "exit" break if __name__ == "__main__": conn = redis.StrictRedis() SUBSCRIBE(conn) |
3.2 订阅者代码
1 2 3 4 5 6 7 8 9 10 |
# -*- coding:utf8 -*- import redis def PUBLISH(conn): for i in range(5): conn.PUBLISH("chan", i) # 发布消息 if __name__ == "__main__": conn = redis.StrictRedis() PUBLISH(conn) |
3.3 运行结果
先运行订阅者,将会先打印一条订阅的消息:
1 |
{"pattern": None, "type": "SUBSCRIBE", "channel": "chan", "data": 1L} |
然后运行发布者代码,订阅者输出:
1 2 3 4 |
{"pattern": None, "type": "SUBSCRIBE", "channel": "chan", "data": 1L} {"pattern": None, "type": "message", "channel": "chan", "data": "0"} {"pattern": None, "type": "message", "channel": "chan", "data": "1"} exit |
3.4 问题
根据书上的描述,在取消订阅后将会收到一条取消订阅的消息:
1 |
{"pattern": None, "type": "unSUBSCRIBE", "channel": "chan", "data": 0L} |
但是这里并没有收到,很奇怪,不知道是不是版本的原因,先不管了。
四、实现原理
频道订阅存放在redisServer结构题中,普通频道订阅和模式订阅是不同实现,普通频道订阅结构一个字典结构,而模式频道订阅是链表结构。
1 2 3 4 5 6 7 |
struct redisServer { // ... /* Pubsub */ dict *pubsub_channels; /* Map channels to list of subscribed clients */ list *pubsub_patterns; /* A list of pubsub_patterns */ // ... } |
两种频道订阅时候的表现形式为:
使用普通频道订阅的时候,redis先通过频道名找到所有的订阅者,然后给所有订阅者发送消息,命中了订阅的时间复杂度是O(N),N是订阅了该频道的订阅者个数。模式订阅的时候,redis要遍历整个模式订阅链表,每个客户端匹配模式串,不管是否命中订阅,时间复杂度都是O(N),N是模式字符串个数。
评论