键空间触发器
基于键空间通知执行 JavaScript 函数
键空间触发器允许您注册一个函数,该函数将在数据库中发生事件时执行。大多数事件是由命令调用触发的,但有两个特殊事件可以独立于命令发生
- Expired:当一个键从数据库中过期时,会触发此事件。
- Evicted:当一个键从数据库中被驱逐时,会触发此事件。
有关支持事件的完整列表,请参阅 Redis 键空间通知页面。
要注册键空间触发器,您需要在加载库时使用 redis.registerKeySpaceTrigger
API。以下示例演示了如何注册一个数据库触发器,该触发器在每次修改哈希键时添加一个“最后更新”字段
#!js api_version=1.0 name=myFirstLibrary
redis.registerKeySpaceTrigger("consumer", "", function(client, data){
if (client.call("type", data.key) != "hash") {
// key is not a has, do not touch it.
return;
}
// get the current time in ms
var curr_time = client.call("time")[0];
// set '__last_updated__' with the current time value
client.call('hset', data.key, '__last_updated__', curr_time);
});
参数描述
consumer
:消费者名称。prefix
:触发器应该在其上触发的键前缀。callback
:要调用的回调函数,遵循与 同步和异步调用 相同的规则。回调函数只会在主分片上被调用。
运行示例
127.0.0.1:6379> hset h x 1
(integer) 1
127.0.0.1:6379> hgetall h
1) "x"
2) "1"
3) "__last_updated__"
4) "1658390831"
127.0.0.1:6379> hincrby h x 1
(integer) 2
127.0.0.1:6379> hgetall h
1) "x"
2) "2"
3) "__last_updated__"
4) "1658390910"
传递给消费者回调函数的 data
参数采用以下格式
{
"event": "<the event name that fired the trigger>",
"key": "<key name that the event was fired on as String>",
"key_raw": "<key name that the event was fired on as ArrayBuffer>"
}
注意,只有当键可以被解码为 JS
String
时才会给出 key
字段,否则该值为 null
。
我们可以使用 TFUNCTION LIST
命令显示触发器信息
127.0.0.1:6379> TFUNCTION list vvv
1) 1) "engine"
2) "js"
3) "api_version"
4) "1.0"
5) "name"
6) "foo"
7) "pending_jobs"
8) (integer) 0
9) "user"
10) "default"
11) "functions"
12) (empty array)
13) "keyspace_triggers"
14) (empty array)
15) "stream_triggers"
16) 1) 1) "name"
2) "consumer"
3) "num_triggered"
4) (integer) 2
5) "num_finished"
6) (integer) 2
7) "num_success"
8) (integer) 1
9) "num_failed"
10) (integer) 1
11) "last_error"
12) "TypeError: redis.call is not a function"
13) "last_exection_time"
14) (integer) 0
15) "total_exection_time"
16) (integer) 0
17) "avg_exection_time"
18) "0"
触发器保证
如果传递给触发器的回调函数是一个 JS
函数(不是协程),则保证回调函数将与导致触发器的操作一起原子地调用;这意味着所有客户端只有在回调函数完成之后才能看到数据。此外,还保证回调函数的效果将与触发触发器的命令一起,在 multi/exec
块中复制到副本和 AOF。
如果回调函数是一个协程,它将在后台执行,并且没有关于它在哪里或是否执行的保证。这些保证与 同步和异步调用 中描述的相同。
升级
当使用 TFUNCTION LOAD
命令的 REPLACE
选项升级现有触发器代码时,所有触发器参数都可以被修改。
高级用法
对于大多数用例,registerKeySpaceTrigger
API 就足够了。但是,在某些情况下,您可能需要对触发器何时触发有更好的保证。让我们看看以下示例
#!js api_version=1.0 name=myFirstLibrary
redis.registerKeySpaceTrigger("consumer", "", function(client, data){
if (client.call("type", data.key) != "hash") {
// key is not a has, do not touch it.
return;
}
var name = client.call('hget', data.key, 'name');
client.call('incr', `name_${name}`);
});
每当哈希键发生变化时,上面的示例将从哈希中读取 name
字段,例如 tom
,并将增加 name_tom
键的值。(**注意:此函数在集群上无法正常工作。我们需要在键的名称上使用 {}
来确保我们写入的是位于当前分片上的键。为了简单起见,我们暂时忽略集群问题**)。运行该函数将得到以下结果
127.0.0.1:6379> hset x name tom
(integer) 1
127.0.0.1:6379> hset x name jerry
(integer) 0
127.0.0.1:6379> get name_tom
"1"
127.0.0.1:6379> get name_jerry
"1"
我们可以看到 name_tom
键被增加了 1 次,name_jerry
键被增加了 1 次。如果我们将 hset
调用包装在 multi
/exec
中,我们会得到相同的结果吗?
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> hset x name tom
QUEUED
127.0.0.1:6379(TX)> hset x name jerry
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 1
2) (integer) 0
127.0.0.1:6379> get name_tom
(nil)
127.0.0.1:6379> get name_jerry
"2"
到底发生了什么?name_jerry
被增加了 2 次,而 name_tom
根本没有增加。发生这种情况是因为,在 multi
/exec
或 Lua 函数的情况下,通知会在事务结束时发出,因此所有客户端都会收到最后一个写入值的通知,即 jerry
。
为了修复代码并在 multi
/exec
上仍然获得预期结果。触发器和函数允许您指定一个可选的回调函数,该函数将在通知发生时(而不是在事务结束时)运行。对这个回调函数的约束是,它只能读取数据而不能执行任何写入操作。新的代码如下
#!js api_version=1.0 name=lib
redis.registerKeySpaceTrigger("consumer", "", function(client, data){
if (data.name !== undefined) {
client.call('incr', `name_${data.name}`);
}
},{
onTriggerFired: (client, data) => {
if (client.call("type", data.key) != "hash") {
// key is not a has, do not touch it.
return;
}
data.name = client.call('hget', data.key, 'name');
}
});
上面的代码为我们的触发器提供了一个可选的函数参数 onTriggerFired
。该函数将在键发生变化后立即触发,并将允许我们读取键的内容。我们将内容添加到 data
参数中,该参数将传递给可以写入数据的实际触发器函数。上面的代码按预期工作
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> hset x name tom
QUEUED
127.0.0.1:6379(TX)> hset x name jerry
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 1
2) (integer) 0
127.0.0.1:6379> get name_tom
"1"
127.0.0.1:6379> get name_jerry
"1"