redis发布订阅
前言
Redis的发布与订阅模型在许多编程语言中都有实现,也就是我们常说的设计模式中的一种——观察者模式。在一些应用场合,例如发送方不是以固定频率发送消息,如果接收方频繁去资讯发布方,这种操作无疑是很麻烦并且不友好的。
而订阅发布模型,订阅者只需要订阅注册某个频道就好了,当有消息发送过来的时候,会通过订阅的频道接收。优势在于把耦合点独立分离处理,作为发布方和接收方的中介,实现了发布方和接收方的分离。
订阅与发布系统是Redis的一个高级属性,多个客户端可以同时订阅同一个频道,类似广播的机制。所不同的是,一个客户端可以同时订阅多个频道。也就是Redis客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
实现原理
服务器中维护着一个pubsub_channels字典,所有的频道和订阅关系都存在这里。字典的键为频道的名称,而值为订阅频道的客户端链表。
- 当有新的客户端订阅某个频道时,会发生一下两种情况中的一种:
-
- 如果频道已经存在,则新的客户端会添加到pubsub_channels对应频道的链表末尾;
- 如果频道原本不存在,则会为频道创建一个键,该客户端成为链表的第一个元素。
2. 当客户端退订一个频道的时候,pubsub_channels对应键的链表会删除该客户端;
3. 发送消息时,服务器会遍历pubsub_channels中对应键的链表,向每个客户端发送消息。
服务器还维护着一个pubsub_patterns链表,链表的pattern属性记录了被订阅的模式,而Client属性记录了订阅模式的客户端。
- 当有新的客户端订阅某个模式时,进行如下步骤:
- 创建一个链表节点,pattern属性记录订阅的模式,Client记录订阅模式的客户端;
- 将这个链表节点添加到pubsub_patterns链表中。
- 当一个客户端退订某个模式时,服务器遍历pubsub_patterns链表,找到对应的pattern同时也是对应的Client客户端节点,将该节点删除;
- 发送信息时,服务器遍历pubsub_channels,查找与channels频道相匹配的模式,将消息发送给订阅了这些模式的客户端。
Redis订阅系统的优势:
- 当一个客户端向频道发送一个信息,订阅了同一频道/模式的多个客户端可以同时接收到信息,类似广播的机制;
- 便于Sentinel哨兵与服务器之间的通信并进行监控。
Redis发布订阅命令
1. psubscribe:订阅一个或多个符合给定模式的频道。
每个模式以 * 作为匹配符,比如:it* 匹配所有以 it 开头的频道(如,it.news,it.blog,it.tweets等)。news.*匹配所有以news.开头的频道。
支持的模式(patterns)有:
h?llo subscribes to hello, hallo and hxllo h*llo subscribes to hllo and heeeello h[ae]llo subscribes to hello and hallo, but not hillo
如果想输入普通的字符,可以在前面添加\
语法:
PSUBSCRIBE pattern [pattern ...]
实例:
127.0.0.1:6379> psubscribe mychannel h?llo h*llo h[ae]ll0 Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "mychannel" 3) (integer) 1 1) "psubscribe" 2) "h?llo" 3) (integer) 2 1) "psubscribe" 2) "h*llo" 3) (integer) 3 1) "psubscribe" 2) "h[ae]ll0" 3) (integer) 4
2. publish:将信息发送到指定的频道
语法:
PUBLISH channel message
返回值:接收到信息的订阅者数量
实例:
127.0.0.1:6379> publish mychannel 'mychannel message' (integer) 1 127.0.0.1:6379> publish hello 'hello message' (integer) 2 127.0.0.1:6379> publish hell0 'hell0 message'
3. pubsub:查看订阅与发布系统的状态,它由数个不同格式的子命令组成
语法:
PUBSUB <subcommand> [argument [argument ...]]
pubsub channels [pattern]:列出当前活跃的频道列表,活跃是指信道含有一个或多个订阅者(不包括从模式接收订阅的客户端),如果pattern未提供,所有的信道都会被列出,否则值列出匹配上指定全局-类型模式的信道被列出。
返回值:活跃的频道列表,或者符合指定模式的频道。
实例:
127.0.0.1:6379> pubsub channels mychannel (empty array)
PUBSUB NUMSUB [channel-1 ... channel-N]: 列出指定频道的订阅者个数(不包括订阅模式的客户端订阅者)。
返回值:频道的列表和每个列表中订阅者的个数,格式为:频道,个数,频道,个数,...简单的列表。
注意,不指定任何频道而直接调用这个命令也是可以的,此时,命令只返回一个空列表
实例:
127.0.0.1:6379> pubsub numsub (empty array) 127.0.0.1:6379> pubsub numsub mychannel 1) "mychannel" 2) (integer) 0
pubsub numpat:返回订阅模式的数量(使用命令psubscribe命名实现)。注意,这个命令返回的不是订阅模式的客户端的数量,而是客户端订阅的所有模式的数量总和。
返回值:客户端订阅的所有模式的总和。
实例:
127.0.0.1:6379> pubsub numpat (integer) 4
4. punsubscribe [ pattern [pattern ...]]:退订所有给定模式的频道。
说明:
该命令指示客户端退订指定模式,如果没有指定模式则退出所有的模式。
如果没有模式被指定,即一个无参的punsubscribe调用被执行,那么客户端使用psubscribe命令订阅的所有模式都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的模式。
返回值:这个命令在不同的客户端中有不同的表现。
实例:
127.0.0.1:6379> punsubscribe mychannel 1) "punsubscribe" 2) "mychannel" 3) (integer) 0 127.0.0.1:6379> punsubscribe 1) "punsubscribe" 2) (nil) 3) (integer) 0 127.0.0.1:6379>
5. subscribe:订阅给定的一个或多个频道信息
一旦客户端进入订阅状态,客户端就只可以接受订阅相关的命令SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE和PUNSUBSCRIBE除了这些命令,其他命令一律失效。
语法:
SUBSCRIBE channel [channel ...]
返回值:接收到的信息
实例:
127.0.0.1:6379> subscribe mychannel Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "mychannel" 3) (integer) 1 1) "message" 2) "mychannel" 3) "mychannel message"
6. unsubscribe: 退订给定的频道。
指示客户端退订给定的频道,若没有指定频道,则退订所有频道.
如果没有频道被指定,即,一个无参数的 UNSUBSCRIBE 调用被执行,那么客户端使用 SUBSCRIBE 命令订阅的所有频道都会被退订。 在这种情况下,命令会返回一个信息,告知客户端所有被退订的频道。
语法:
UNSUBSCRIBE channel [channel ...]
返回值:这个命令在不同的客户端中有不同的表现。
实例:
127.0.0.1:6379> unsubscribe mychannel 1) "unsubscribe" 2) "mychannel" 3) (integer) 0 127.0.0.1:6379> unsubscribe 1) "unsubscribe" 2) (nil) 3) (integer) 0
Sentinel哨兵中的应用
Sentinel服务器与Master服务器/Slave服务器之间的订阅发布系统是Sentinel监控过程的一个重要环节,通过订阅发布系统达到监控服务器状态的作用。其运行原理与上面的客户端服务器之间的订阅机制无太大区别,都是基于网络连接的数据传输。Sentinel之间的通信也是通过Sentinel与服务器之间的这个订阅发布系统实现的,一个Sentinel通过服务器的频道发送信息,其他Sentinel就会接收到。