文档:键空间通知

键空间通知允许客户端订阅发布/订阅(Pub/Sub)频道,以接收以某种方式影响 Valkey 数据集的事件。

可以接收的事件示例包括:

  • 影响给定键的所有命令。
  • 所有接收 LPUSH 操作的键。
  • 数据库 0 中所有过期的键。

注意:Valkey 的发布/订阅是即发即弃的,即如果您的发布/订阅客户端断开连接,稍后重新连接,那么在客户端断开连接期间发送的所有事件都将丢失。

事件类型

键空间通知的实现方式是,对于每个影响 Valkey 数据空间的操作,发送两种不同类型的事件。例如,针对数据库 0 中名为 mykey 的键的 DEL 操作将触发两个消息的发送,这与以下两个 PUBLISH 命令完全等效:

PUBLISH __keyspace@0__:mykey del
PUBLISH __keyevent@0__:del mykey

第一个频道监听所有针对键 mykey 的事件,而另一个频道只监听针对键 mykeydel 操作事件。

第一种事件,频道中带有 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".

字符串中至少应包含 KE,否则无论字符串的其余部分如何,都不会发送任何事件。

例如,要仅启用列表的键空间事件,配置参数必须设置为 Kl,依此类推。

您可以使用字符串 KEA 来启用大多数类型的事件。

不同命令生成的事件

根据以下列表,不同的命令会生成不同类型的事件。

  • DEL 为每个被删除的键生成一个 del 事件。
  • RENAME 生成两个事件:一个用于源键的 rename_from 事件,以及一个用于目标键的 rename_to 事件。
  • MOVE 生成两个事件:一个用于源键的 move_from 事件,以及一个用于目标键的 move_to 事件。
  • COPY 生成一个 copy_to 事件。
  • 如果源键被移除,MIGRATE 会生成一个 del 事件。
  • RESTORE 为该键生成一个 restore 事件。
  • EXPIRE 及其所有变体(PEXPIREEXPIREATPEXPIREAT)在调用时带有正超时(或未来时间戳)时会生成一个 expire 事件。请注意,当这些命令调用时带有负超时值或过去的时间戳时,键会被删除,并且只会生成一个 del 事件。
  • 当使用 STORE 选项设置新键时,SORT 会生成一个 sortstore 事件。如果生成的列表为空,并且使用了 STORE 选项,并且已经存在同名的键,则结果是该键被删除,因此在这种情况下会生成一个 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 事件。在这两种情况下,顺序是有保证的(lpush 事件总是在 rpop 事件之后发送)。此外,如果生成的列表长度为零且键被移除,则会生成一个 del 事件。
  • LMOVEBLMOVE 会生成一个 lpop/rpop 事件(取决于 from 参数)和一个 lpush/rpush 事件(取决于 to 参数)。在这两种情况下,顺序是保证的(lpush/rpush 事件总是在 lpop/rpop 事件之后发送)。此外,如果生成的列表长度为零并且键被移除,则会生成一个 del 事件。
  • HSETHSETNXHMSET 都生成一个单独的 hset 事件。
  • HINCRBY 生成一个 hincrby 事件。
  • HINCRBYFLOAT 生成一个 hincrbyfloat 事件。
  • HDEL 生成一个单独的 hdel 事件,如果生成的哈希表为空且键被移除,则还会生成一个 del 事件。
  • SADD 生成一个单独的 sadd 事件,即使在可变参数情况下也是如此。
  • SREM 生成一个单独的 srem 事件,如果生成的集合为空且键被移除,则还会生成一个 del 事件。
  • SMOVE 为源键生成一个 srem 事件,为目标键生成一个 sadd 事件。
  • SPOP 生成一个 spop 事件,如果生成的集合为空且键被移除,则还会生成一个 del 事件。
  • SINTERSTORESUNIONSTORESDIFFSTORE 分别生成 sinterstoresunionstoresdiffstore 事件。在特殊情况下,如果生成的集合为空,并且存储结果的键已存在,则由于该键被移除,因此会生成一个 del 事件。
  • ZINCR 生成一个 zincr 事件。
  • ZADD 生成一个单独的 zadd 事件,即使添加了多个元素也是如此。
  • ZREM 生成一个单独的 zrem 事件,即使删除了多个元素。当生成的有序集合为空且键被删除时,会额外生成一个 del 事件。
  • ZREMBYSCORE 生成一个单独的 zrembyscore 事件。当生成的有序集合为空且键被删除时,会额外生成一个 del 事件。
  • ZREMBYRANK 生成一个单独的 zrembyrank 事件。当生成的有序集合为空且键被删除时,会额外生成一个 del 事件。
  • ZDIFFSTOREZINTERSTOREZUNIONSTORE 分别生成 zdiffstorezinterstorezunionstore 事件。在特殊情况下,如果生成的有序集合为空,并且存储结果的键已存在,则由于该键被移除,因此会生成一个 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 集群的每个节点都会按照上述描述生成关于其自身键空间子集的事件。然而,与集群中常规的发布/订阅通信不同,事件通知不会广播到所有节点。换句话说,键空间事件是节点特定的。这意味着要接收集群中的所有键空间事件,客户端需要订阅每个节点。