Redis的键空间通知详解

重要: 键空间通知(Keyspace Notification)是一个从Redis 2.8.0版本开始可用的功能。

一、功能概述

键空间通知使得客户端能够订阅Pub/Sub(发布/订阅)频道,这样客户端便能接收到以某种方式影响Redis数据集的事件。

可能接收到的事件示例,如下所示:

  • 所有影响到一个给定键的命令。
  • 所有接收到一个LPUSH命令的键。
  • 所有数据库-0中的键全都过期。

Redis会使用标准的Pub/Sub层来传递事件,因此,实现了Pub/Sub功能的客户端能够直接使用这个功能,而不用进行任何修改。

因为Redis的Pub/Sub功能当前是“触发后不管(Fire and Forget)”的,所以如果你的应用程序对事件有着可靠通知的要求,那么它就不能使用Redis的键空间通知功能。也就是说,如果你的Pub/Sub客户端断开连接,然后再重新连接,那么在客户端断开连接的期间内传递的所有事件都会丢失。

Redis将来会改善事件传递的可靠性,但是很有可能会以一种更加常规的方式来解决这个问题,有可能会提高Pub/Sub功能自身的可靠性,也有可能会通过Lua脚本拦截Pub/Sub消息,然后再执行某些操作(例如,将事件存入一个列表之中)。

二、事件类型

每次执行会影响到Redis数据空间的操作时,键空间通知就会发送两个不同类型的事件。以DEL操作为例,当删除数据库-0中的一个名为mykey的键时,将会触发Redis传递两条消息,完全等价于下面两条PUBLISH命令:

  1. PUBLISH __keyspace@0__:mykey del
  2. PUBLISH __keyevent@0__:del mykey

很容易看出,如何让一个频道能够监听mykey键相关的所有事件,以及如何让另一个频道能够获取del操作所影响的所有的键的有关信息。

第一种类型的事件,频道名称的前缀为keyspace,这种事件被称为键空间通知;而第二种类型的事件,频道名称的前缀为keyevent,这种事件被称为键事件通知

在上面的示例中,Redis会针对mykey键产生一个del事件。期间发生的事情,如下所示:

  • 键空间频道会接收到一条消息,它的内容是事件名称。
  • 键事件频道会接收到一条消息,它的内容是键的名称。

为了使Redis只传递我们感兴趣的事件子集,因此只可以使用一种类型的通知。

三、配置方法

在默认情况下,键空间的事件通知功能是禁用的,因为这个功能会消耗一些CPU性能,虽然几乎感觉不到性能消耗。有两种方法可以启用通知功能:修改redis.conf文件的notify-keyspace-events参数,或者使用CONFIG SET命令。

将上述参数设置为空字符串,就能禁用通知功能。如果想要启用这个功能,那么就要将上述参数设置为一个空字符串,这个字符串由多个字符组成,其中的每个字符都具有特殊含义,如下表所示:

字符 含义
K 键空间(Keyspace)事件,通过__keyspace@<db>__前缀的频道发布。
E 键事件(Keyevent)事件,通过__keyevent@<db>__前缀的频道发布。
g 通用的命令(不特定类型),例如:DEL、EXPIRE、RENAME,等等。
$ 字符串(String)相关的命令。
l 列表(List)相关的命令。
s 集合(Set)相关的命令。
h 哈希(Hash)相关的命令。
z 有序集合相关的命令。
x 过期事件(每当一个键过期时,便会产生这种事件)。
e 内存回收事件(当达到最大内存,然后回收某个键的内存时,便会产生这种事件)。
A g$lshzxe的别名。因此,“AKE”字符串可以表示所有的事件。

配置字符串至少应当包含KE字符。否则,即使这个字符串包含其他任何字符,Redis也不会传递任何事件。

例如,若只想针对列表(List)启用键空间事件,则配置参数必须设置为Kl,以此类推。

KEA字符串可用于启用每种可能的事件。

四、不同命令产生的事件

不同的命令会产生不同类型的事件,如以下列表所示:

  • DEL
    这个命令会为每个被删除的键产生一个del事件。

  • RENAME
    这个命令会产生两个事件,一个为原始键产生的rename_from事件,以及一个为目标键产生的rename_to事件。

  • EXPIRE
    当为某个键设置过期时间时,这个命令便会产生一个expire事件;或者,每当为某个待删除的键设置一个过期结果时,这个命令便会产生一个expired事件(请参考EXPIRE命令的相关文档)。

  • SORT
    当使用STORE选项来设置一个新键时,这个命令便会产生一个sortstore事件。如果结果列表为空,并且使用了STORE选项,并且已经存在一个同名的键,那么Redis便会删除这个已有的键,在这种情况下,这个命令还会产生一个del事件。

  • SET
    这个命令,以及它的所有变种(SETEXSETNXGETSET)命令,都会产生set事件。除此之外,SETEX命令还会产生一个expire事件。

  • MSET
    这个命令会为每个键单独产生一个set事件。

  • SETRANGE
    这个命令会产生一个setrange事件。

  • INCRDECRINCRBYDECRBY
    这些命令都会产生incrby事件。

  • INCRBYFLOAT
    这个命令会产生一个incrbyfloat事件。

  • APPEND
    这个命令会产生一个append事件。

  • LPUSHLPUSHX
    这些命令都会产生一个lpush事件,即使有多个输入元素时,也是如此。

  • RPUSHRPUSHX
    这些命令都会产生一个rpush事件,即使有多个输入元素时,也是如此。

  • RPOP
    这个命令会产生一个rpop事件。另外,如果从列表中弹出最后一个元素,那么这个列表对应的键就会被删除,此时还会产生一个del事件。

  • LPOP
    这个命令会产生一个lpop事件。另外,如果从列表中弹出最后一个元素,那么这个列表对应的键就会被删除,此时还会产生一个del事件。

  • LINSERT
    这个命令会产生一个linsert事件。

  • LSET
    这个命令会产生一个lset事件。

  • LREM
    这个命令会产生一个lrem事件。另外,如果运行这个命令之后,列表变为空表,那么便会删除这个列表对应的键,此时还会产生一个del事件。

  • LTRIM
    这个命令会产生一个ltrim事件。另外,如果运行这个命令之后,列表变为空表,那么便会删除这个列表对应的键,此时还会产生一个del事件。

  • RPOPLPUSHBRPOPLPUSH
    这两个命令都会产生一个rpop事件和lpush事件。这两个事件的产生顺序都是固定不变的,先产生rpop事件,然后再产生lpush事件。另外,如果运行这两个命令之后,列表变为空表,那么便会删除这个列表对应的键,此时还会产生一个del事件。

  • HSETHSETNXHMSET
    这些命令都会产生一个hset事件。

  • HINCRBY
    这个命令会产生一个hincrby事件。

  • HINCRBYFLOAT
    这个命令会产生一个hincrbyfloat事件。

  • HDEL
    这个命令会产生一个hdel事件。如果运行这个命令之后,哈希变为空,那么便会删除这个哈希对应的键,此时还会产生一个del事件。

  • SADD
    这个命令会产生一个sadd事件,即使有多个输入元素时,也是如此。

  • SREM
    这个命令会产生一个srem事件。如果运行这个命令之后,集合变为空,那么便会删除这个集合对应的键,此时还会产生一个del事件。

  • SMOVE
    这个命令会为原始键产生一个srem事件,然后为目标键产生一个sadd事件。

  • SPOP
    这个命令会产生一个spop事件。如果运行这个命令之后,集合变为空,那么便会删除这个集合对应的键,此时还会产生一个del事件。

  • SINTERSTORESUNIONSTORESDIFFSTORE
    这些命令会分别产生sinterstoresunionostoresdiffstore事件。在特殊情况下,如果运行这些命令得到的集合为空,并且用于存储结果的键已经存在,那么这个键将会被删除,然后还会产生一个del事件。

  • ZINCRBY
    这个命令会产生一个zincr事件。

  • ZADD
    这个命令会产生一个zadd事件,即使有多个输入元素时,也是如此。

  • ZREM
    这个命令会产生一个zrem事件,即使需要删除多个元素时,也是如此。如果运行这个命令之后,有序集合变为空,那么便会删除这个有序集合对应的键,此时还会产生一个del事件。

  • ZREMRANGEBYSCORE
    这个命令会产生一个zrembyscore事件。如果运行这个命令之后,有序集合变为空,那么便会删除这个有序集合对应的键,此时还会产生一个del事件。

  • ZREMRANGEBYRANK
    这个命令会产生一个zrembyrank事件。如果运行这个命令之后,有序集合变为空,那么便会删除这个有序集合对应的键,此时还会产生一个del事件。

  • ZINTERSTOREZUNIONSTORE
    这两个命令会分别产生zinterstorezunionstore事件。在特殊情况下,如果运行这些命令得到的有序集合为空,并且用于存储结果的键已经存在,那么这个键将会被删除,然后还会产生一个del事件。

  • 每当一个键因为过期而被删除时,便会产生一个expired事件。

  • 每当一个键因为maxmemory策略而被删除,以便于回收内存时,便会产生一个evicted事件。

重要: 所有的命令只有当目标键真的被修改时,才会产生事件。例如,当使用SREM命令删除一个集合中并不存在的元素时,实际上没有改变这个键的对应值,所以也就不会产生任何事件。

如果还在怀疑一个给定命令的事件是如何产生的,那么最简单的方法便是自己验证一下。在Shell终端中运行以下命令:

  1. redis-cli config set notify-keyspace-events KEA
  2. redis-cli --csv psubscribe '__key*__:*'

此时,Redis客户端便进入频道监听状态,如下图所示:

进入频道监听状态

此时,在另一个Shell终端中使用redis-cli命令向Redis服务器发送命令:

  1. redis-cli set foo hello

然后,便能在前一个Shell终端中观察到以下输出信息:

输出信息

五、过期事件的产生时机

Redis会通过以下两种方式使得具有生存时间的键过期:

  • 当使用某个命令访问这个键,然后发现这个键已经过期。
  • 通过一个后台系统在后台渐进地查找已经过期的键,这样还能够收集从未被访问过的键。

当通过上述两种方式之一访问某个键,并且发现这个键已经过期时,Redis就会产生expired事件。结论便是,Redis服务器并不能保证每当键的生存时间降低至0的时候就能立刻产生expired事件。

如果总是没有任何命令访问这个已经过期的键,并且带有生存时间(TTL:Time To Live)的键非常多的话,那么就很有可能感觉到键的生存时间降低至0和产生expired事件之间具有明显的延时。

基本上,只有当Redis服务器删除已经过期的键时才会产生expired事件,而不是当键的生存时间在理论上降低至0的时候。

六、键空间通知示例

接下来,会通过实际操作,简单讲解Redis键空间通知的使用方法。

1. 安装Redis

按照《在CentOS上安装Redis缓存系统》安装Redis服务器。

2. 启用键空间通知功能

在Shell终端(此处取名为终端-1)中运行以下命令,进入Redis客户端的命令行:

  1. redis-cli

然后在终端-1的Redis客户端的命令行中运行以下命令,启用键空间通知功能的所有通知:

  1. config set notify-keyspace-events KEA

3. 订阅键空间通知和键事件通知

打开一个新的Shell终端(此处取名为终端-2),在其中运行以下命令,进入telnet命令行:

  1. telnet localhost 6379

在终端-2的telnet命令行中,运行以下命令:

  1. psubscribe __key*__:*

上述命令会订阅键空间通知键事件通知,当对某个键执行修改命令时,终端-2便会同时收到上述两种通知。

4. 设置键的值

在终端-1的Redis客户端的命令行中运行以下命令,设置一个键的值:

  1. set mykey hello

5. 观察结果

此时,便能在终端-2观察到两条通知消息,如下图所示:

两条通知消息

上图的上半部分便是键空间通知,下半部分便是键事件通知。