键空间通知
#
功能概览键空间通知允许客户端订阅 Pub/Sub(发布/订阅)频道,以接收以某种方式影响 KeyDB 数据集的事件。
可以接收的事件示例如下:
- 影响给定键的所有命令。
- 所有接收到 LPUSH 操作的键。
- 所有在数据库 0 中过期的键。
事件通过 KeyDB 的常规 Pub/Sub 层进行传递,因此实现了 Pub/Sub 的客户端无需修改即可使用此功能。
由于 KeyDB 的 Pub/Sub 目前是“即发即忘”模式,如果您的应用程序需要可靠的事件通知,则无法使用此功能。也就是说,如果您的 Pub/Sub 客户端断开连接后又重新连接,客户端断开期间传递的所有事件都将丢失。
未来计划允许更可靠的事件传递,但这可能会在更通用的层面上解决,要么为 Pub/Sub 本身带来可靠性,要么允许 Lua 脚本拦截 Pub/Sub 消息以执行诸如将事件推入列表之类的操作。
#
事件类型对于每个影响 KeyDB 数据空间的操作,键空间通知会发送两种不同类型的事件。例如,针对数据库 0
中名为 mykey
的键执行的 DEL
操作将触发两条消息的传递,这两条消息完全等同于以下两个 PUBLISH
命令:
很容易看出,一个频道允许我们监听所有针对键 mykey
的事件,而另一个频道则允许我们获取所有成为 del
操作目标的键的信息。
第一种事件,频道带有 keyspace
前缀,称为**键空间通知 (Key-space notification)**;而第二种事件,带有 keyevent
前缀,称为**键事件通知 (Key-event notification)**。
在上面的示例中,为键 mykey
生成了一个 del
事件。具体情况是:
- 键空间频道收到的消息是事件的名称。
- 键事件频道收到的消息是键的名称。
可以只启用一种通知,以便仅传递我们感兴趣的事件子集。
#
配置默认情况下,键空间事件通知是禁用的,因为尽管影响不大,但该功能会消耗一些 CPU 资源。通知可以通过 KeyDB.conf 中的 notify-keyspace-events
或通过 **CONFIG SET** 来启用。
将参数设置为空字符串会禁用通知。要启用该功能,需要使用一个非空字符串,该字符串由多个字符组成,每个字符根据下表具有特殊含义:
字符串中至少应包含 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
事件(取决于 wherefrom 参数)和一个lpush
/rpush
事件(取决于 whereto 参数)。在这两种情况下,顺序是有保证的(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
、sunionostore
、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
事件。
重要提示:所有命令仅在目标键确实被修改时才生成事件。例如,一个 SREM
命令从一个集合中删除一个不存在的元素,实际上不会改变键的值,因此不会生成任何事件。
如果不确定某个命令如何生成事件,最简单的方法是自己观察:
此时,在另一个终端中使用 KeyDB-cli
向 KeyDB 服务器发送命令,并观察生成的事件:
#
过期事件的触发时机KeyDB 通过两种方式使有关联生存时间的键过期:
- 当键被命令访问并发现已过期时。
- 通过一个后台系统,该系统在后台以增量方式查找过期的键,以便能够收集到那些从未被访问的键。
当一个键被上述任一系统访问并发现已过期时,会生成 expired
事件。因此,无法保证 KeyDB 服务器一定能在键的生存时间达到零的那一刻生成 expired
事件。
如果没有命令持续地访问该键,并且有许多设置了 TTL 的键,那么从键的生存时间降至零到 expired
事件生成之间可能会有显著的延迟。
基本上,expired
事件是在 **KeyDB 服务器删除该键时生成的**,而不是在生存时间理论上达到零时生成的。
#
集群中的事件KeyDB 集群中的每个节点都会如上所述,为其自己的键空间子集生成事件。然而,与集群中常规的 Pub/Sub 通信不同,事件通知**不会**广播到所有节点。换句话说,键空间事件是节点特定的。这意味着要接收集群的所有键空间事件,客户端需要订阅每个节点。