键空间触发器
基于键空间通知执行 JavaScript 函数
Redis 开源 | Redis Enterprise Software | Redis Cloud | Redis 开源 | Redis Enterprise for Kubernetes | 客户端 |
---|
键空间触发器允许您注册一个函数,该函数将在数据库中发生事件时执行。 大多数事件由命令调用触发,但有两个特殊事件可以独立于命令发生
- 已过期:当密钥从数据库中过期时会触发此事件。
- 已驱逐:当密钥从数据库中被驱逐时会触发此事件。
有关支持事件的完整列表,请参阅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
递增了一次,键 name_jerry
递增了一次。 如果我们用 multi
/exec
包装 hset
调用,我们会得到相同的结果吗?
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
递增了两次,而 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"