键空间通知允许客户端订阅发布/订阅(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 集群的每个节点都会按照上述描述生成关于其自身键空间子集的事件。然而,与集群中常规的发布/订阅通信不同,事件通知不会广播到所有节点。换句话说,键空间事件是节点特定的。这意味着要接收集群中的所有键空间事件,客户端需要订阅每个节点。