跳转到主要内容

键空间通知

功能概览#

键空间通知允许客户端订阅 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 命令:

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

很容易看出,一个频道允许我们监听所有针对键 mykey 的事件,而另一个频道则允许我们获取所有成为 del 操作目标的键的信息。

第一种事件,频道带有 keyspace 前缀,称为**键空间通知 (Key-space notification)**;而第二种事件,带有 keyevent 前缀,称为**键事件通知 (Key-event notification)**。

在上面的示例中,为键 mykey 生成了一个 del 事件。具体情况是:

  • 键空间频道收到的消息是事件的名称。
  • 键事件频道收到的消息是键的名称。

可以只启用一种通知,以便仅传递我们感兴趣的事件子集。

配置#

默认情况下,键空间事件通知是禁用的,因为尽管影响不大,但该功能会消耗一些 CPU 资源。通知可以通过 KeyDB.conf 中的 notify-keyspace-events 或通过 **CONFIG SET** 来启用。

将参数设置为空字符串会禁用通知。要启用该功能,需要使用一个非空字符串,该字符串由多个字符组成,每个字符根据下表具有特殊含义:

K 键空间事件,以前缀 __keyspace@<db>__ 发布。
E 键事件事件,以前缀 __keyevent@<db>__ 发布。
g 通用命令(非特定类型),如 DEL、EXPIRE、RENAME 等。
$ 字符串命令
l 列表命令
s 集合命令
h 哈希命令
z 有序集合命令
t 流命令
d 模块键类型事件
x 过期事件(每次有键过期时生成的事件)
e 驱逐事件(当键因 maxmemory 而被驱逐时生成的事件)
m 键未命中事件(当访问不存在的键时生成的事件)
A "g$lshztxed" 的别名,因此 "AKE" 字符串表示除 "m" 之外的所有事件。

字符串中至少应包含 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 事件(取决于 wherefrom 参数)和一个 lpush/rpush 事件(取决于 whereto 参数)。在这两种情况下,顺序是有保证的(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 分别生成 sinterstoresunionostoresdiffstore 事件。在特殊情况下,如果结果集合为空,并且存储结果的键已存在,由于该键被移除,会生成一个 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 事件。

重要提示:所有命令仅在目标键确实被修改时才生成事件。例如,一个 SREM 命令从一个集合中删除一个不存在的元素,实际上不会改变键的值,因此不会生成任何事件。

如果不确定某个命令如何生成事件,最简单的方法是自己观察:

$ KeyDB-cli config set notify-keyspace-events KEA
$ KeyDB-cli --csv psubscribe '__key*__:*'
正在读取消息... (按 Ctrl-C 退出)
"psubscribe","__key*__:*",1

此时,在另一个终端中使用 KeyDB-cli向 KeyDB 服务器发送命令,并观察生成的事件:

"pmessage","__key*__:*","__keyspace@0__:foo","set"
"pmessage","__key*__:*","__keyevent@0__:set","foo"
...

过期事件的触发时机#

KeyDB 通过两种方式使有关联生存时间的键过期:

  • 当键被命令访问并发现已过期时。
  • 通过一个后台系统,该系统在后台以增量方式查找过期的键,以便能够收集到那些从未被访问的键。

当一个键被上述任一系统访问并发现已过期时,会生成 expired 事件。因此,无法保证 KeyDB 服务器一定能在键的生存时间达到零的那一刻生成 expired 事件。

如果没有命令持续地访问该键,并且有许多设置了 TTL 的键,那么从键的生存时间降至零到 expired 事件生成之间可能会有显著的延迟。

基本上,expired 事件是在 **KeyDB 服务器删除该键时生成的**,而不是在生存时间理论上达到零时生成的。

集群中的事件#

KeyDB 集群中的每个节点都会如上所述,为其自己的键空间子集生成事件。然而,与集群中常规的 Pub/Sub 通信不同,事件通知**不会**广播到所有节点。换句话说,键空间事件是节点特定的。这意味着要接收集群的所有键空间事件,客户端需要订阅每个节点。