Redis从2.X版本开始,就支持一种基于非持久化消息的、使用发布/订阅模式实现的事件通知机制。所谓基于非连接保持,是因为一旦消息订阅者由于各种异常情况而被迫断开连接,在其重新连接后,其离线期间的事件是无法被重新通知的(一些redis资料中也称为即发即弃)。而其使用的发布/订阅模式,意味着其机制并不是由订阅者周期性的从Redis服务拉取事件通知,而是由Redis服务主动推送事件通知到符合条件的若干订阅者。
Redis中的事件功能可以提供两种不同的功能:
一类是基于Channel的消息事件,这一类消息和Redis中存储的Keys没有太多关联,也就是说即使不在Redis中存储任何Keys信息,这类消息事件也可以独立使用。
另一类消息事件可以对(也可以不对)Redis中存储的Keys信息的变化事件进行通知,可以用来向订阅者通知Redis中符合订阅条件的Keys的各种事件。Redis服务的事件功能在实际场景中虽然使用得不多,不过还是可以找到案例,例如服务治理框架DUBBO默认情况下使用Zookeeper作为各节点的服务协调装置,但可以通过更改DUBBO的配置,将Zookeeper更换为Redis。
我们先从比较简单的publish命令和subscribe命令开始介绍,因为这组命令所涉及到的Channel(通道)和Redis中存储的数据相对独立。publish命令由发送者使用,负责向指定的Channel发送消息;subscribe命令由订阅者使用,负责从指定的一个或者多个Channel中获取消息。
以下是 publish 命令和 subscribe 命令的使用示例:
// 该命令向指定的channel名字发送一条消息(字符串)
PUBLISH channel message
// 例如:向名叫FM955的频道发送一条消息,消息信息为“hello!”
PUBLISH FM955 "hello!"
// 再例如:向名叫FM900的频道发送一条消息,消息信息为“ doit!”
PUBLISH FM900 "doit!"
// 该命令可以开始向指定的一个或者多个channel订阅消息
SUBSCRIBE channel [channel ...]
// 例如:向名叫FM955的频道订阅消息
SUBSCRIBE FM955
// 再例如:向名叫FM955、FM900的两个频道订阅消息
SUBSCRIBE FM955 FM900
如果您使用需要使用publish命令和subscribe命令,您并不需要对Redis服务的配置信息做任何更改。以下示例将向读者展示两个命令的简单使用方式——前提是您的Redis服务已经启动好了:
-- 我们使用的Redis服务地址为192.168.61.140,端口为默认值
[root@kp2 ~]# redis-cli -h 192.168.61.140
192.168.61.140:6379> SUBSCRIBE ChannelA ChannelB
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ChannelA"
3) (integer) 1
1) "subscribe"
2) "ChannelB"
3) (integer) 2
-- 连接到Redis服务器后,直接运行PUBLISH命令,发送信息
[root@kp1 ~]# redis-cli -h 192.168.61.140
192.168.61.140:6379> PUBLISH ChannelB "hello"
(integer) 1
......
-- 这时订阅者收到消息如下:
1) "message"
2) "ChannelB"
3) "hello"
从以上示例中可以看到,客户端A确实收到了客户端B所发送的消息信息,并且收到三行信息。这三行信息分别表示消息类型、消息通道和消息内容。注意,以上介绍的这组publish命令和subscribe命令的操作过程并没有对Redis服务中已存储的任何Keys信息产生影响。
Redis中还支持一种模式订阅,它主要依靠psubscribe命令向技术人员提供订阅功能。模式订阅psubscribe最大的特点是,它除了可以通过Channel订阅消息以外,还可以配合配置命令来进行Keys信息变化的事件通知。
模式订阅psubscribe的Channel订阅和subscribe命令类似,这里给出一个命令格式,就不再多做介绍了(可参考上文对subscribe命令的介绍):
// 该命令可以开始向指定的一个或者多个channel订阅消息
// 具体使用示例可参见SUBSCRIBE命令
PSUBSCRIBE channel [channel ...]
模式订阅psubscribe对Keys变化事件的支持分为两种类型:keyspace(键空间通知)和keyevent(键事件通知),这两类事件都是依靠Key的变化触发的,而关键的区别在于事件描述的焦点,举例说明:
当Redis服务中0号数据库的MyKey键被删除时,键空间和键事件向模式订阅者分别发送的消息格式如下:
// 以下命令可订阅键空间通知
// 订阅0号数据库任何Key信息的变化
192.168.61.140:6379> psubscribe __keyspace@0__:*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyspace@0__:*"
3) (integer) 1
// 出现以上信息,说明订阅成功
// 当其他客户端执行 set mykey 123456 时,该订阅可收到以下信息
1) "pmessage"
2) "__keyspace@0__:*"
3) "__keyspace@0__:mykey"
4) "set"
以上收到的订阅信息,其描述可以概括为:“mykey的键空间发生了事件,事件为set”。这样的事件描述着重于key的名称,并且告诉客户端key的事件为set。我们再来看看订阅键事件通知时,发生同样事件所得到的订阅信息:
// 以下命令可订阅键事件通知
// 订阅0号数据库任何事件的变化
192.168.61.140:6379> psubscribe __keyevent@0__:*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyevent@0__:*"
3) (integer) 1
// 出现以上信息,说明订阅成功
// 当其他客户端执行 set mykey 123456 时,该订阅可收到以下信息
1) "pmessage"
2) "__keyevent@0__:*"
3) "__keyevent@0__:set"
4) "mykey"
以上收到的订阅信息中事件是主体,其信息可以概括为:“0号数据库发生了set事件,发生这个事件的key信息为mykey”。
要使用psubscribe命令进行键事件的订阅,就首先需要在Redis的主配置文件中对模式订阅进行设定。注意,如果您只是使用psubscribe命令通过Channel发送消息到订阅者,或者更单纯的使用publish命令和subscribe命令组合通过Channel发送和接收消息,就不需要进行这样的配置。
默认情况下Redis服务下的键空间通知和键事件通知都是关闭的。在redis.conf文件下,有专门的“EVENT NOTIFICATION”区域进行设定,设置的格式为:
......
notify-keyspace-events [通配符]
......
通配符的定义描述如下:
以下的几个实例说明了配置格式中通配符的用法:
// 监控任何数据格式的所有事件,包括键空间通知和键事件通知
notify-keyspace-events "AKE"
// 只监控字符串结构的所有事件,包括键空间通知和键事件通知
notify-keyspace-events "g$KExe"
// 只监控所有键事件通知
notify-keyspace-events "AE"
// 只监控Hash数据解构的键空间通知
notify-keyspace-events "ghKxe"
// 只监控Set数据结构的键事件通知
notify-keyspace-events "gsExe"
注意,在Redis主配置文件中进行事件通知的配置,其配置效果是全局化的。也就是说所有连接到Redis服务的客户端都会使用这样的Key事件通知逻辑。但如果单独需要为某一个客户端会话设置独立的Key事件通知逻辑,则可以在客户端成功连接Redis服务后,使用类似如下的命令进行设置:
......
192.168.61.140:6379> config set notify-keyspace-events KEA
OK
完成键事件的配置后,就可以使用psubscribe命令在客户端订阅消息通知了。这个过程还是需要使用通配符参数,才能完成订阅指定。通配符格式如下所示:
psubscribe __[keyspace|keyevent]@<db>__:[prefix]
// 例如:
// 订阅0号数据库中,所有的键变化事件,进行键空间通知
psubscribe __keyspace@0__:*
// 订阅0号数据库,所有的键变化事件,进行键空间通知和键事件通知
psubscribe __key*@0__:*
注意,就如上文所提到的那样,客户端能够进行键信息变化事件订阅的前提是Redis服务端或者这个客户端会话本身开启了相应配置。以下举例说明psubscribe命令中参数的使用方式:
// 注意,Redis服务上的配置信息如下
// notify-keyspace-events "gsExe"
// 即是说只允许监控Set结构的所有事件,并且之启用了键事件通知,没有启用键空间通知。
// 客户端使用以下命令开始订阅Key的变化事件
192.168.61.140:6379> psubscribe __key*@0__:*
// 以上命令订阅了0号数据库所有键信息的变化通知,包括键事件通知和键空间通知
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__key*@0__:*"
3) (integer) 1
// 接着,已连接到Redis服务上的另一个客户端执行了如下命令
// > sadd mysetkey rt
// 那么收到的消息通知为
1) "pmessage"
2) "__key*@0__:*"
3) "__keyevent@0__:sadd"
4) "mysetkey"
以上实例操作中有两个问题需要单独进行说明:
当客户端使用psubscribe命令进行订阅时(psubscribe key*@0:*),实际上是连同keyspace(键空间通知)和keyevent(键事件通知)一起订阅了。那么按照上文介绍的内容来说,这个订阅者本该收到两条事件消息。一条消息的描述重点在key上,另一条消息的描述重点在sadd事件上。但实际情况是,这个订阅者只收到了以描述重点在事件上的键事件通知。这是因为在以上实例中特别说明的一点:Redis服务端只开启键事件通知的配置。所以无论客户端如何订阅键空间通知,也收不到任何消息。
另外,包括Redis官方资料在内的资料都在阐述这样一个事实,既是通过sadd命令对一个Set结构中的元素进行变更和直接通过“PUBLISH keyevent@0:sadd mysetkey”这样的命令向订阅者发送消息,在消息订阅者看来效果都是一样。但是这两种不同的操作过程对于Redis存储的Key数据,则是完全不一样的。前者的操作方式会改变Redis中存储的数据状况,但后者则不会。
Redis提供的订阅/发布功能并不完美,更不能和ActiveMQ/RabbitMQ提供的订阅/发布功能相提并论。
首先这些消息并没有持久化机制,属于即发即弃模式。也就是说它们不能像ActiveMQ中的消息那样保证持久化消息订阅者不会错过任何消息,无论这些消息订阅者是否随时在线。
由于本来就是即发即弃的消息模式,所以Redis也不需要专门制定消息的备份和恢复机制。
也是由于即发即弃的消息模式,所以Redis也没有必要专门对使用订阅/发布功能的客户端连接进行识别,用来明确该客户端连接的ID是否在之前已经连接过Redis服务了。ActiveMQ中保持持续通知的功能的前提,就是能够识别客户端连接ID的历史连接情况,以便确定哪些订阅消息这个客户端还没有处理。
Redis当前版本有一个简单的事务机制,这个事务机制可以用于PUBLISH命令。但是完全没有ActiveMQ中对事务机制和ACK机制那么强的支持。而在我写作的“系统间通讯”专题中,专门讲到了ActiveMQ的ACK机制和事务机制。
Redis也没有为发布者和订阅者准备保证消息性能的任何方案,例如在大量消息同时到达Redis服务是,如果消息订阅者来不及完成消费,就可能导致消息堆积。而ActiveMQ中有专门针对这种情况的慢消息机制。