1. 功能概览
键空间通知使得客户端可以通过订阅频道或模式, 来接收那些以某种方式改动了 Redis 数据集的事件。
以下是一些键空间通知发送的事件的例子:
-
所有修改键的命令。
-
所有接收到 LPUSH key value [value …] 命令的键。
-
0
号数据库中所有已过期的键。
事件通过 Redis 的订阅与发布功能(pub/sub)来进行分发, 因此所有支持订阅与发布功能的客户端都可以在无须做任何修改的情况下, 直接使用键空间通知功能。
因为 Redis 目前的订阅与发布功能采取的是发送即忘(fire and forget)策略, 所以如果你的程序需要可靠事件通知(reliable notification of events), 那么目前的键空间通知可能并不适合你: 当订阅事件的客户端断线时, 它会丢失所有在断线期间分发给它的事件。
2. 事件的类型
对于每个修改数据库的操作,键空间通知都会发送两种不同类型的事件。
比如说,对 0
号数据库的键 mykey
执行 DEL key [key …]命令时, 系统将分发两条消息, 相当于执行以下两个命令:
PUBLISH __keyspace@0__:mykey del
PUBLISH __keyevent@0__:del mykey
订阅第一个频道 __keyspace@0__:mykey
可以接收 0
号数据库中所有修改键 mykey
的事件, 而订阅第二个频道 __keyevent@0__:del
则可以接收 0
号数据库中所有执行 del
命令的键。
以 keyspace
为前缀的频道被称为键空间通知(key-space notification),
以 keyevent
为前缀的频道则被称为键事件通知(key-event notification)。
当 del mykey
命令执行时:
-
键空间频道的订阅者将接收到被执行的事件的名字,在这个例子中,就是
del
。 -
键事件频道的订阅者将接收到被执行事件的键的名字,在这个例子中,就是
mykey
。
3. 配置
因为开启键空间通知功能需要消耗一些 CPU , 所以在默认配置下, 该功能处于关闭状态。
可以通过修改 redis.conf
文件, 或者直接使用 CONFIG SET
命令来开启或关闭键空间通知功能:
-
当
notify-keyspace-events
选项的参数为空字符串时,功能关闭。 -
另一方面,当参数不是空字符串时,功能开启。
notify-keyspace-events
的参数可以是以下字符的任意组合, 它指定了服务器该发送哪些类型的通知:
字符 | 发送的通知 |
---|---|
| 键空间通知,所有通知以 |
| 键事件通知,所有通知以 |
|
|
| 字符串命令的通知 |
| 列表命令的通知 |
| 集合命令的通知 |
| 哈希命令的通知 |
| 有序集合命令的通知 |
| 过期事件:每当有过期键被删除时发送 |
| 驱逐(evict)事件:每当有键因为 |
| 参数 |
输入的参数中至少要有一个 K
或者 E
, 否则的话, 不管其余的参数是什么, 都不会有任何通知被分发。
举个例子, 如果只想订阅键空间中和列表相关的通知, 那么参数就应该设为 Kl
, 诸如此类。
将参数设为字符串 "KEA"
表示发送所有类型的通知。
4. 命令产生的通知
命令 | 说明 |
DEL key [key …] | 命令为每个被删除的键产生一个 del 通知 |
RENAME key newkey | 产生两个通知: 来源键(source key)产生一个 rename_from 通知, 目标键(destination key)产生一个 rename_to 通知 |
EXPIRE key seconds EXPIREAT key timestamp | 在键被正确设置过期时间时产生一个 expire 通知 当 EXPIREAT key timestamp 设置的时间已经过期,或者 EXPIRE key seconds 传入的时间为负数值时,键被删除,并产生一个 del 通知 |
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern …]] [ASC | DESC] [ALPHA] [STORE destination] | 在命令带有 STORE 参数时产生一个 sortstore 事件。如果 STORE 指示的用于保存排序结果的键已经存在,那么程序还会发送一个 del 事件 |
SET key value [EX seconds] [PX milliseconds] [NX|XX] 以及它的所有变种(SETEX SETNX 和 GETSET ) | 都产生 set 通知。其中 SETEX key seconds value 还会产生 expire 通知 |
MSET key value [key value …] | 为每个键产生一个 set 通知 |
SETRANGE key offset value | 产生一个 setrange 通知 |
INCR key 、 DECR key 、 INCRBY key increment 和 DECRBY key decrement | 产生 incrby 通知 |
INCRBYFLOAT key increment | 产生 incrbyfloat 通知 |
APPEND key value | 产生 append 通知 |
LPUSH key value [value …] LPUSHX key value | 都产生单个 lpush 通知,即使有多个输入元素时,也是如此 |
RPUSH key value [value …] RPUSHX key value | 都产生单个 rpush 通知,即使有多个输入元素时,也是如此 |
RPOP key | 产生 rpop 通知。如果被弹出的元素是列表的最后一个元素,那么还会产生一个 del 通知 |
LPOP key | 产生 lpop 通知。如果被弹出的元素是列表的最后一个元素,那么还会产生一个 del 通知 |
LINSERT key BEFORE|AFTER pivot value | 产生一个 linsert 通知 |
LSET key index value | 产生一个 lset 通知 |
LTRIM key start stop | 产生一个 ltrim 通知。如果 LTRIM key start stop 执行之后,列表键被清空,那么还会产生一个 del 通知 |
RPOPLPUSH source destination 和 BRPOPLPUSH source destination timeout | 产生一个 rpop 通知,以及一个 lpush 通知。两个命令都会保证 rpop 的通知在 lpush 的通知之前分发。如果从键弹出元素之后,被弹出的列表键被清空,那么还会产生一个 del 通知 |
HSET hash field value 、 HSETNX hash field value 和 HMSET | 都只产生一个 hset 通知 |
HINCRBY | 产生一个 hincrby 通知 |
HINCRBYFLOAT | 产生一个 hincrbyfloat 通知 |
HDEL | 产生一个 hdel 通知。如果执行 HDEL 之后,哈希键被清空,那么还会产生一个 del 通知 |
SADD key member [member …] | 产生一个 sadd 通知,即使有多个输入元素时,也是如此 |
SREM key member [member …] | 产生一个 srem 通知,如果执行 SREM key member [member …] 之后,集合键被清空,那么还会产生一个 del 通知 |
SMOVE source destination member | 为来源键(source key)产生一个 srem 通知,并为目标键(destination key)产生一个 sadd 事件 |
SPOP key | 产生一个 spop 事件。如果执行 SPOP key 之后,集合键被清空,那么还会产生一个 del 通知 |
SINTERSTORE destination key [key …] | 产生 sinterstore通知。如果用于保存结果的键已经存在,那么还会产生一个 del 通知 |
SUNIONSTORE destination key [key …] | 产生 sunionostore通知。如果用于保存结果的键已经存在,那么还会产生一个 del 通知 |
SDIFFSTORE destination key [key …] | 产生 sdiffstore通知。如果用于保存结果的键已经存在,那么还会产生一个 del 通知 |
ZINCRBY key increment member | 产生一个 zincr 通知 |
ZADD key score member [[score member] [score member] …] | 产生一个 zadd 通知,即使有多个输入元素时,也是如此 |
ZREM key member [member …] | 产生一个 zrem 通知,即使有多个输入元素时,也是如此。 如果执行 ZREM key member [member …] 之后,有序集合键被清空,那么还会产生一个 del 通知 |
ZREMRANGEBYSCORE key min max | 产生一个 zrembyscore 通知。 如果用于保存结果的键已经存在,那么还会产生一个 del 通知 |
ZREMRANGEBYRANK key start stop | 产生一个 zrembyrank 通知。 如果用于保存结果的键已经存在,那么还会产生一个 del 通知 |
一个键因为过期而被删除时 | 产生一个 expired 通知 |
一个键因为 maxmemory 策略而被删除时 | 产生一个 evicted 通知 |
🎈注意:
所有命令都只在键真的被改动了之后,才会产生通知。
比如说,当 SREM key member [member …] 试图删除不存在于集合的元素时,删除操作会执行失败,因为没有真正的改动键,所以这一操作不会发送通知。
🎈Redis 使用以下两种方式删除过期的键:
当一个键被访问时,程序会对这个键进行检查,如果键已经过期,那么该键将被删除。
底层系统会在后台渐进地查找并删除那些过期的键,从而处理那些已经过期、但是不会被访问到的键。
当过期键被以上两个程序的任意一个发现、 并且将键从数据库中删除时, Redis 会产生一个
expired
通知。Redis 并不保证生存时间(TTL)变为
0
的键会立即被删除: 如果程序没有访问这个过期键, 或者带有生存时间的键非常多的话, 那么在键的生存时间变为0
, 直到键真正被删除这中间, 可能会有一段比较显著的时间间隔。因此, Redis 产生
expired
通知的时间为过期键被删除的时候, 而不是键的生存时间变为0
的时候。
5. 实例
如果对命令所产生的通知有疑问, 最好还是使用以下命令, 自己来验证一下:
启动一个消费客户端, 设置config set notify-keyspace-events KEA开启键空间通知,发送所有类型的通知。
❯ .\redis-cli.exe -h 127.0.0.1
127.0.0.1:6379> config set notify-keyspace-events KEA
OK
127.0.0.1:6379> psubscribe '__key*__:*'
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__key*__:*"
3) (integer) 1
使用另一个连接出发redis 数据变更事件
❯ .\redis-cli.exe -h 127.0.0.1
127.0.0.1:6379> set dog woohoo
OK
127.0.0.1:6379> get dog
"woohoo"
127.0.0.1:6379> rename dog doge
OK
127.0.0.1:6379> set dog bage
OK
127.0.0.1:6379> rename dog doge
OK
127.0.0.1:6379> del doge
(integer) 1
查看消费者收到的通知
# set dog woohoo
1) "pmessage"
2) "__key*__:*"
3) "__keyspace@0__:dog"
4) "set"
1) "pmessage"
2) "__key*__:*"
3) "__keyevent@0__:set"
4) "dog"
# rename dog doge
1) "pmessage"
2) "__key*__:*"
3) "__keyspace@0__:dog"
4) "rename_from"
1) "pmessage"
2) "__key*__:*"
3) "__keyevent@0__:rename_from"
4) "dog"
1) "pmessage"
2) "__key*__:*"
3) "__keyspace@0__:doge"
4) "rename_to"
1) "pmessage"
2) "__key*__:*"
3) "__keyevent@0__:rename_to"
4) "doge"
# del doge
1) "pmessage"
2) "__key*__:*"
3) "__keyspace@0__:doge"
4) "del"
1) "pmessage"
2) "__key*__:*"
3) "__keyevent@0__:del"
4) "doge"