键空间通知允许客户端订阅发布/订阅(Pub/Sub)频道,以接收以某种方式影响 Valkey 数据集的事件。
可以接收的事件示例包括:
- 影响给定键的所有命令。
- 所有接收 LPUSH 操作的键。
- 数据库 0 中所有过期的键。
注意:Valkey 的发布/订阅是即发即弃的,即如果您的发布/订阅客户端断开连接,稍后重新连接,那么在客户端断开连接期间发送的所有事件都将丢失。
事件类型
键空间通知的实现方式是,对于每个影响 Valkey 数据空间的操作,发送两种不同类型的事件。例如,针对数据库 0
中名为 mykey
的键的 DEL
操作将触发两个消息的发送,这与以下两个 PUBLISH
命令完全等效:
PUBLISH __keyspace@0__:mykey del
PUBLISH __keyevent@0__:del mykey
第一个频道监听所有针对键 mykey
的事件,而另一个频道只监听针对键 mykey
的 del
操作事件。
第一种事件,频道中带有 keyspace
前缀的,称为键空间通知;第二种,带有 keyevent
前缀的,称为键事件通知。
在前面的示例中,为键 mykey
生成了一个 del
事件,导致产生了两个消息:
- 键空间频道接收的消息是事件的名称。
- 键事件频道接收的消息是键的名称。
可以只启用一种通知,以便只接收我们感兴趣的事件子集。
配置
默认情况下,键空间事件通知是禁用的,因为虽然不太敏感,但此功能会消耗一些 CPU 资源。通知可以通过 valkey.conf 中的 notify-keyspace-events
参数或通过 CONFIG SET 命令启用。
将参数设置为空字符串会禁用通知。要启用此功能,需要使用一个非空字符串,该字符串由多个字符组成,其中每个字符根据下表具有特殊含义:
K Keyspace events, published with __keyspace@<db>__ prefix.
E Keyevent events, published with __keyevent@<db>__ prefix.
g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
$ String commands
l List commands
s Set commands
h Hash commands
z Sorted set commands
t Stream commands
d Module key type events
x Expired events (events generated every time a key expires)
e Evicted events (events generated when a key is evicted for maxmemory)
m Key miss events (events generated when a key that doesn't exist is accessed)
n New key events (Note: not included in the 'A' class)
A Alias for "g$lshztxed", so that the "AKE" string means all the events except "m" and "n".
字符串中至少应包含 K
或 E
,否则无论字符串的其余部分如何,都不会发送任何事件。
例如,要仅启用列表的键空间事件,配置参数必须设置为 Kl
,依此类推。
您可以使用字符串 KEA
来启用大多数类型的事件。
不同命令生成的事件
根据以下列表,不同的命令会生成不同类型的事件。
DEL
为每个被删除的键生成一个del
事件。RENAME
生成两个事件:一个用于源键的rename_from
事件,以及一个用于目标键的rename_to
事件。MOVE
生成两个事件:一个用于源键的move_from
事件,以及一个用于目标键的move_to
事件。COPY
生成一个copy_to
事件。- 如果源键被移除,
MIGRATE
会生成一个del
事件。 RESTORE
为该键生成一个restore
事件。EXPIRE
及其所有变体(PEXPIRE
、EXPIREAT
、PEXPIREAT
)在调用时带有正超时(或未来时间戳)时会生成一个expire
事件。请注意,当这些命令调用时带有负超时值或过去的时间戳时,键会被删除,并且只会生成一个del
事件。- 当使用
STORE
选项设置新键时,SORT
会生成一个sortstore
事件。如果生成的列表为空,并且使用了STORE
选项,并且已经存在同名的键,则结果是该键被删除,因此在这种情况下会生成一个del
事件。 SET
及其所有变体(SETEX
、SETNX
、GETSET
)都会生成set
事件。然而,SETEX
也会生成一个expire
事件。MSET
为每个键生成一个单独的set
事件。SETRANGE
生成一个setrange
事件。INCR
、DECR
、INCRBY
、DECRBY
命令都生成incrby
事件。INCRBYFLOAT
生成一个incrbyfloat
事件。APPEND
生成一个append
事件。LPUSH
和LPUSHX
生成一个单独的lpush
事件,即使在可变参数情况下也是如此。RPUSH
和RPUSHX
生成一个单独的rpush
事件,即使在可变参数情况下也是如此。RPOP
生成一个rpop
事件。此外,如果键因列表中的最后一个元素被弹出而被移除,则会生成一个del
事件。LPOP
生成一个lpop
事件。此外,如果键因列表中的最后一个元素被弹出而被移除,则会生成一个del
事件。LINSERT
生成一个linsert
事件。LSET
生成一个lset
事件。LREM
生成一个lrem
事件,如果生成的列表为空且键被移除,还会生成一个del
事件。LTRIM
生成一个ltrim
事件,如果生成的列表为空且键被移除,还会生成一个del
事件。RPOPLPUSH
和BRPOPLPUSH
生成一个rpop
事件和一个lpush
事件。在这两种情况下,顺序是有保证的(lpush
事件总是在rpop
事件之后发送)。此外,如果生成的列表长度为零且键被移除,则会生成一个del
事件。LMOVE
和BLMOVE
会生成一个lpop
/rpop
事件(取决于 from 参数)和一个lpush
/rpush
事件(取决于 to 参数)。在这两种情况下,顺序是保证的(lpush
/rpush
事件总是在lpop
/rpop
事件之后发送)。此外,如果生成的列表长度为零并且键被移除,则会生成一个del
事件。HSET
、HSETNX
和HMSET
都生成一个单独的hset
事件。HINCRBY
生成一个hincrby
事件。HINCRBYFLOAT
生成一个hincrbyfloat
事件。HDEL
生成一个单独的hdel
事件,如果生成的哈希表为空且键被移除,则还会生成一个del
事件。SADD
生成一个单独的sadd
事件,即使在可变参数情况下也是如此。SREM
生成一个单独的srem
事件,如果生成的集合为空且键被移除,则还会生成一个del
事件。SMOVE
为源键生成一个srem
事件,为目标键生成一个sadd
事件。SPOP
生成一个spop
事件,如果生成的集合为空且键被移除,则还会生成一个del
事件。SINTERSTORE
、SUNIONSTORE
、SDIFFSTORE
分别生成sinterstore
、sunionstore
、sdiffstore
事件。在特殊情况下,如果生成的集合为空,并且存储结果的键已存在,则由于该键被移除,因此会生成一个del
事件。ZINCR
生成一个zincr
事件。ZADD
生成一个单独的zadd
事件,即使添加了多个元素也是如此。ZREM
生成一个单独的zrem
事件,即使删除了多个元素。当生成的有序集合为空且键被删除时,会额外生成一个del
事件。ZREMBYSCORE
生成一个单独的zrembyscore
事件。当生成的有序集合为空且键被删除时,会额外生成一个del
事件。ZREMBYRANK
生成一个单独的zrembyrank
事件。当生成的有序集合为空且键被删除时,会额外生成一个del
事件。ZDIFFSTORE
、ZINTERSTORE
和ZUNIONSTORE
分别生成zdiffstore
、zinterstore
和zunionstore
事件。在特殊情况下,如果生成的有序集合为空,并且存储结果的键已存在,则由于该键被移除,因此会生成一个del
事件。XADD
生成一个xadd
事件,当与MAXLEN
子命令一起使用时,可能会随后生成一个xtrim
事件。XDEL
生成一个单独的xdel
事件,即使删除了多个条目。XGROUP CREATE
生成一个xgroup-create
事件。XGROUP CREATECONSUMER
生成一个xgroup-createconsumer
事件。XGROUP DELCONSUMER
生成一个xgroup-delconsumer
事件。XGROUP DESTROY
生成一个xgroup-destroy
事件。XGROUP SETID
生成一个xgroup-setid
事件。XSETID
生成一个xsetid
事件。XTRIM
生成一个xtrim
事件。- 如果与键关联的过期时间已成功删除,
PERSIST
会生成一个persist
事件。 - 每当一个关联了生存时间的键因过期而从数据集中移除时,就会生成一个
expired
事件。 - 每当一个键为了释放内存而根据
maxmemory
策略从数据集中逐出时,就会生成一个evicted
事件。 - 每当一个新键被添加到数据集中时,就会生成一个
new
事件。
重要提示:所有命令仅在目标键确实被修改时才生成事件。例如,SREM
删除集合中不存在的元素时,实际上不会改变键的值,因此不会生成任何事件。
如果对给定命令如何生成事件有疑问,最简单的方法是自行观察:
$ valkey-cli config set notify-keyspace-events KEA
$ valkey-cli --csv psubscribe '__key*__:*'
Reading messages... (press Ctrl-C to quit)
"psubscribe","__key*__:*",1
此时,在另一个终端中使用 valkey-cli
向 Valkey 服务器发送命令并观察生成的事件:
"pmessage","__key*__:*","__keyspace@0__:foo","set"
"pmessage","__key*__:*","__keyevent@0__:set","foo"
...
过期事件的时间
Valkey 以两种方式使带有生存时间的键过期:
- 当命令访问键并发现其已过期时。
- 通过一个后台系统,该系统在后台逐步查找已过期的键,以便也能收集从未被访问过的键。
expired
事件是在键被访问并被上述任一系统发现已过期时生成的,因此无法保证 Valkey 服务器能够在键的生存时间达到零时立即生成 expired
事件。
如果没有命令持续访问该键,并且有许多键关联了 TTL,那么从键的生存时间降至零到生成 expired
事件之间可能会有显著的延迟。
基本上,expired
事件是在 Valkey 服务器删除键时生成的,而不是在生存时间理论上达到零时生成的。
集群中的事件
Valkey 集群的每个节点都会按照上述描述生成关于其自身键空间子集的事件。然而,与集群中常规的发布/订阅通信不同,事件通知不会广播到所有节点。换句话说,键空间事件是节点特定的。这意味着要接收集群中的所有键空间事件,客户端需要订阅每个节点。