模块文档由以下页面组成
- Valkey 模块介绍(本文)。概述 Valkey 模块系统和 API。建议从此处开始阅读。
- 实现原生数据类型 涵盖了原生数据类型在模块中的实现。
- 阻塞操作 介绍了如何编写阻塞命令,这些命令不会立即回复,但会阻塞客户端,同时不会阻塞 Valkey 服务器,并在可能时提供回复。
- Valkey 模块 API 参考 是从 ValkeyModule 函数的 module.c 顶部注释生成的。这是理解每个函数工作原理的良好参考。
Valkey 模块使得使用外部模块扩展 Valkey 功能成为可能,能够快速实现新 Valkey 命令,其功能类似于核心内部可以实现的功能。
Valkey 模块是动态库,可以在 Valkey 启动时加载,或者使用 MODULE LOAD
命令加载。Valkey 导出了一个 C API,形式为一个名为 valkeymodule.h
的 C 头文件。模块旨在用 C 语言编写,但是也可以使用 C++ 或其他具有 C 绑定功能的语言。
模块设计旨在能够加载到不同版本的 Valkey 中,因此特定模块无需为特定 Valkey 版本而设计或重新编译。因此,模块将使用特定的 API 版本向 Valkey 核心注册。当前 API 版本为 "1"。
加载模块
为了测试您正在开发的模块,您可以使用以下 valkey.conf
配置指令加载模块
loadmodule /path/to/mymodule.so
也可以在运行时使用以下命令加载模块
MODULE LOAD /path/to/mymodule.so
要列出所有已加载的模块,请使用
MODULE LIST
最后,您可以使用以下命令卸载(如果需要,稍后可以重新加载)模块
MODULE UNLOAD mymodule
请注意,上面的 mymodule
不是不带 .so
后缀的文件名,而是模块用于向 Valkey 核心注册自身的名称。该名称可以使用 MODULE LIST
获取。然而,最佳实践是动态库的文件名与模块向 Valkey 核心注册时使用的名称相同。
您可以编写的最简单的模块
为了展示模块的不同部分,我们将展示一个非常简单的模块,它实现了一个输出随机数的命令。
#include "valkeymodule.h"
#include <stdlib.h>
int HelloworldRand_ValkeyCommand(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
ValkeyModule_ReplyWithLongLong(ctx,rand());
return VALKEYMODULE_OK;
}
int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
if (ValkeyModule_Init(ctx,"helloworld",1,VALKEYMODULE_APIVER_1)
== VALKEYMODULE_ERR) return VALKEYMODULE_ERR;
if (ValkeyModule_CreateCommand(ctx,"helloworld.rand",
HelloworldRand_ValkeyCommand, "fast random",
0, 0, 0) == VALKEYMODULE_ERR)
return VALKEYMODULE_ERR;
return VALKEYMODULE_OK;
}
示例模块有两个函数。一个实现了名为 HELLOWORLD.RAND 的命令。此函数是该模块特有的。然而,名为 ValkeyModule_OnLoad()
的另一个函数必须存在于每个 Valkey 模块中。它是模块初始化、注册其命令以及可能使用的其他私有数据结构的入口点。
请注意,模块以模块名称后跟一个点,最后是命令名称来调用命令是个好主意,就像 HELLOWORLD.RAND
的情况一样。这样可以减少冲突的可能性。
请注意,如果不同模块有冲突的命令,它们将无法在 Valkey 中同时工作,因为 ValkeyModule_CreateCommand
函数会在其中一个模块中失败,从而模块加载将中止并返回错误条件。
模块初始化
上述示例展示了 ValkeyModule_Init()
函数的用法。它应该是模块 OnLoad
函数调用的第一个函数。以下是函数原型
int ValkeyModule_Init(ValkeyModuleCtx *ctx, const char *modulename,
int module_version, int api_version);
Init
函数向 Valkey 核心声明模块具有给定名称、其版本(由 MODULE LIST
报告),以及它将使用特定版本的 API。
如果 API 版本错误、名称已被占用或存在其他类似错误,该函数将返回 VALKEYMODULE_ERR
,且模块的 OnLoad
函数应尽快返回并报告错误。
在调用 Init
函数之前,不能调用任何其他 API 函数,否则模块将发生段错误,Valkey 实例将崩溃。
调用的第二个函数 ValkeyModule_CreateCommand
用于向 Valkey 核心注册命令。以下是其原型
int ValkeyModule_CreateCommand(ValkeyModuleCtx *ctx, const char *name,
ValkeyModuleCmdFunc cmdfunc, const char *strflags,
int firstkey, int lastkey, int keystep);
如您所见,大多数 Valkey 模块 API 调用都将模块的 context
作为第一个参数,以便它们可以引用调用它的模块、执行给定命令的命令和客户端等。
要创建一个新命令,上述函数需要上下文、命令名称、指向实现命令的函数的指针、命令标志以及命令参数中键名的位置。
实现命令的函数必须具有以下原型
int mycommand(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc);
命令函数参数只是上下文(将传递给所有其他 API 调用)、命令参数向量以及用户传递的总参数数量。
如您所见,参数作为指向特定数据类型 ValkeyModuleString
的指针提供。这是一种不透明数据类型,您可以使用 API 函数访问和使用它,无需直接访问其字段。
深入查看示例命令实现,我们可以找到另一个调用
int ValkeyModule_ReplyWithLongLong(ValkeyModuleCtx *ctx, long long integer);
此函数向调用命令的客户端返回一个整数,就像其他 Valkey 命令一样,例如 INCR
或 SCARD
。
模块清理
在大多数情况下,无需进行特殊清理。当模块被卸载时,Valkey 会自动注销命令并取消订阅通知。然而,如果模块包含一些持久性内存或配置,模块可能会包含一个可选的 ValkeyModule_OnUnload
函数。如果模块提供了此函数,它将在模块卸载过程中被调用。以下是函数原型
int ValkeyModule_OnUnload(ValkeyModuleCtx *ctx);
OnUnload
函数可以通过返回 VALKEYMODULE_ERR
来阻止模块卸载。否则,应返回 VALKEYMODULE_OK
。
Valkey 模块的设置和依赖
Valkey 模块不依赖于 Valkey 或其他库,也不需要使用特定的 valkeymodule.h
文件编译。要创建新模块,只需将最新版本的 valkeymodule.h
复制到您的源代码树中,链接所有您想要的库,然后创建一个导出 ValkeyModule_OnLoad()
函数符号的动态库。
该模块将能够加载到不同版本的 Valkey 中。
模块可以设计为支持较新和较旧的 Redis OSS 版本,其中某些 API 函数并非在所有版本中都可用。如果某个 API 函数在当前运行的 Redis OSS 版本中未实现,则函数指针将设置为 NULL。这允许模块在使用函数之前检查函数是否存在。
if (ValkeyModule_SetCommandInfo != NULL) {
ValkeyModule_SetCommandInfo(cmd, &info);
}
在最新版本的 valkeymodule.h
中,定义了一个便利宏 RMAPI_FUNC_SUPPORTED(funcname)
。使用该宏或仅与 NULL 比较是个人偏好问题。
向 Valkey 模块传递配置参数
当模块使用 MODULE LOAD
命令加载,或使用 valkey.conf
文件中的 loadmodule
指令时,用户可以通过在模块文件名后添加参数来向模块传递配置参数。
loadmodule mymodule.so foo bar 1234
在上述示例中,字符串 foo
、bar
和 1234
将作为 ValkeyModuleString 指针数组,通过 argv
参数传递给模块的 OnLoad()
函数。传递的参数数量存储在 argc
中。
如何访问这些字符串将在本文档的其余部分进行解释。通常,模块会将模块配置参数存储在某些可在整个模块范围内访问的 static
全局变量中,以便配置可以更改不同命令的行为。
使用 ValkeyModuleString 对象
传递给模块命令的命令参数向量 argv
以及其他模块 API 函数的返回值都是 ValkeyModuleString
类型。
通常您会直接将模块字符串传递给其他 API 调用,但有时您可能需要直接访问字符串对象。
有一些用于处理字符串对象的函数
const char *ValkeyModule_StringPtrLen(ValkeyModuleString *string, size_t *len);
上述函数通过返回字符串指针并在 len
中设置其长度来访问字符串。您不应写入字符串对象指针,正如您从 const
指针修饰符中可以看到的那样。
但是,如果您愿意,可以使用以下 API 创建新的字符串对象
ValkeyModuleString *ValkeyModule_CreateString(ValkeyModuleCtx *ctx, const char *ptr, size_t len);
上述命令返回的字符串必须使用相应的 ValkeyModule_FreeString()
调用释放。
void ValkeyModule_FreeString(ValkeyModuleString *str);
但是,如果您想避免手动释放字符串,本文档后面介绍的自动内存管理可以是一个很好的替代方案,它会为您完成此操作。
请注意,通过参数向量 argv
提供的字符串永远不需要释放。您只需释放您创建的新字符串,或由其他 API 返回且明确指定需要释放的新字符串。
从数字创建字符串或将字符串解析为数字
从整数创建新字符串是一种非常常见的操作,因此有一个函数可以完成此操作
ValkeyModuleString *mystr = ValkeyModule_CreateStringFromLongLong(ctx,10);
同样,为了将字符串解析为数字
long long myval;
if (ValkeyModule_StringToLongLong(ctx,argv[1],&myval) == VALKEYMODULE_OK) {
/* Do something with 'myval' */
}
从模块访问 Valkey 键
大多数 Valkey 模块,为了有用,必须与 Valkey 数据空间交互(并非总是如此,例如 ID 生成器可能永远不会触及 Valkey 键)。Valkey 模块有两种不同的 API 用于访问 Valkey 数据空间,一种是提供非常快速访问和一组操作 Valkey 数据结构函数的低级 API。另一种是更高级别的 API,允许调用 Valkey 命令并获取结果,类似于 Lua 脚本访问 Valkey 的方式。
高级 API 也可用于访问不作为 API 提供的 Valkey 功能。
通常,模块开发人员应优先选择低级 API,因为使用低级 API 实现的命令运行速度与原生 Valkey 命令的速度相当。然而,高级 API 肯定有其用例。例如,瓶颈往往可能是数据处理而不是数据访问。
另请注意,有时使用低级 API 并不比高级 API 更难。
调用 Valkey 命令
访问 Valkey 的高级 API 是 ValkeyModule_Call()
函数与访问 Call()
返回的回复对象所需的函数的总和。
ValkeyModule_Call
使用一种特殊的调用约定,其中包含一个格式说明符,用于指定您作为参数传递给函数的是哪种类型的对象。
Valkey 命令仅使用命令名称和参数列表来调用。然而,在调用命令时,参数可能源自不同类型的字符串:以 null 结尾的 C 字符串、命令实现中从 argv
参数接收的 ValkeyModuleString 对象、带有指针和长度的二进制安全 C 缓冲区等等。
例如,如果我想调用 INCRBY
,其中第一个参数(键)是参数向量 argv
中接收到的字符串(一个 ValkeyModuleString 对象指针数组),第二个参数(增量)是一个表示数字 "10" 的 C 字符串,我将使用以下函数调用
ValkeyModuleCallReply *reply;
reply = ValkeyModule_Call(ctx,"INCRBY","sc",argv[1],"10");
第一个参数是上下文,第二个参数始终是带有命令名称的以 null 结尾的 C 字符串。第三个参数是格式说明符,其中每个字符对应于后续参数的类型。在上述情况下,"sc"
表示一个 ValkeyModuleString 对象和一个以 null 结尾的 C 字符串。其他参数就是指定的两个参数。实际上 argv[1]
是一个 ValkeyModuleString,而 "10"
是一个以 null 结尾的 C 字符串。
以下是格式说明符的完整列表
- c -- 以 null 结尾的 C 字符串指针。
- b -- C 缓冲区,需要两个参数:C 字符串指针和
size_t
长度。 - s -- 在
argv
中接收到或由其他返回 ValkeyModuleString 对象的 Valkey 模块 API 返回的 ValkeyModuleString。 - l -- 长长整型(long long integer)。
- v -- ValkeyModuleString 对象数组。
- ! -- 此修饰符仅告知函数将命令复制到副本和 AOF。从参数解析的角度来看,它被忽略。
- A -- 当给出
!
时,此修饰符表示抑制 AOF 传播:命令将仅传播到副本。 - R -- 当给出
!
时,此修饰符表示抑制副本传播:如果启用,命令将仅传播到 AOF。
成功时函数返回一个 ValkeyModuleCallReply
对象,出错时返回 NULL。
当命令名称无效、格式说明符使用无法识别的字符或命令调用参数数量错误时,返回 NULL。在上述情况下,errno
变量设置为 EINVAL
。在启用集群的实例中,当目标键涉及非本地哈希槽时,也返回 NULL。在这种情况下,errno
设置为 EPERM
。
使用 ValkeyModuleCallReply 对象。
ValkeyModule_Call
返回可以通过 ValkeyModule_CallReply*
系列函数访问的回复对象。
为了获取回复类型(对应于 Valkey 协议支持的数据类型之一),使用函数 ValkeyModule_CallReplyType()
reply = ValkeyModule_Call(ctx,"INCRBY","sc",argv[1],"10");
if (ValkeyModule_CallReplyType(reply) == VALKEYMODULE_REPLY_INTEGER) {
long long myval = ValkeyModule_CallReplyInteger(reply);
/* Do something with myval. */
}
有效的回复类型是
VALKEYMODULE_REPLY_STRING
批量字符串或状态回复。VALKEYMODULE_REPLY_ERROR
错误。VALKEYMODULE_REPLY_INTEGER
有符号 64 位整数。VALKEYMODULE_REPLY_ARRAY
回复数组。VALKEYMODULE_REPLY_NULL
NULL 回复。
字符串、错误和数组都有一个关联的长度。对于字符串和错误,长度对应于字符串的长度。对于数组,长度是元素的数量。要获取回复长度,使用以下函数
size_t reply_len = ValkeyModule_CallReplyLength(reply);
为了获取整数回复的值,使用以下函数,如上面的示例所示
long long reply_integer_val = ValkeyModule_CallReplyInteger(reply);
当使用错误类型的回复对象调用时,上述函数总是返回 LLONG_MIN
。
数组回复的子元素通过以下方式访问
ValkeyModuleCallReply *subreply;
subreply = ValkeyModule_CallReplyArrayElement(reply,idx);
如果您尝试访问超出范围的元素,上述函数将返回 NULL。
字符串和错误(它们类似于字符串但类型不同)可以通过以下方式访问,确保永远不要写入结果指针(它作为 const
指针返回,因此误用必须非常明确)。
size_t len;
char *ptr = ValkeyModule_CallReplyStringPtr(reply,&len);
如果回复类型不是字符串或错误,则返回 NULL。
ValkeyCallReply 对象与模块字符串对象(ValkeyModuleString 类型)不同。然而,有时您可能需要将字符串或整数类型的回复传递给期望模块字符串的 API 函数。
在这种情况下,您可能需要评估使用低级 API 是否能更简单地实现您的命令,或者您可以使用以下函数从字符串、错误或整数类型的调用回复中创建新的字符串对象
ValkeyModuleString *mystr = ValkeyModule_CreateStringFromCallReply(myreply);
如果回复类型不正确,则返回 NULL。返回的字符串对象应像往常一样使用 ValkeyModule_FreeString()
释放,或者通过启用自动内存管理(参见相关章节)来释放。
释放调用回复对象
回复对象必须使用 ValkeyModule_FreeCallReply
释放。对于数组,您只需释放顶层回复,而不是嵌套回复。目前,模块实现提供了一种保护机制,以避免在错误释放嵌套回复对象时崩溃,但此功能不保证永久存在,因此不应将其视为 API 的一部分。
如果您使用自动内存管理(本文档后面会解释),您不需要释放回复(但如果您希望尽快释放内存,仍然可以这样做)。
从 Valkey 命令返回值
像普通的 Valkey 命令一样,通过模块实现的新命令必须能够向调用者返回值。API 为此目标导出了一组函数,以便返回 Valkey 协议的常用类型以及此类类型的数组作为元素。也可以返回带有任何错误字符串和代码的错误(错误代码是错误消息中的首字母大写字母,例如“BUSY the sever is busy”错误消息中的“BUSY”字符串)。
所有向客户端发送回复的函数都命名为 ValkeyModule_ReplyWith<something>
。
要返回错误,请使用
ValkeyModule_ReplyWithError(ValkeyModuleCtx *ctx, const char *err);
对于键类型错误,有一个预定义的错误字符串
VALKEYMODULE_ERRORMSG_WRONGTYPE
示例用法
ValkeyModule_ReplyWithError(ctx,"ERR invalid arguments");
在上面的示例中,我们已经看到了如何回复一个 long long
类型的值
ValkeyModule_ReplyWithLongLong(ctx,12345);
要回复一个不能包含二进制值或换行符的简单字符串(因此适合发送“OK”等小词),我们使用
ValkeyModule_ReplyWithSimpleString(ctx,"OK");
可以使用两个不同的函数回复二进制安全的“批量字符串”
int ValkeyModule_ReplyWithStringBuffer(ValkeyModuleCtx *ctx, const char *buf, size_t len);
int ValkeyModule_ReplyWithString(ValkeyModuleCtx *ctx, ValkeyModuleString *str);
第一个函数接受一个 C 指针和长度。第二个接受一个 ValkeyModuleString 对象。根据您手头的源类型使用其中一个。
为了回复一个数组,您只需使用一个函数来发出数组长度,然后根据数组的元素数量,调用上述函数多次
ValkeyModule_ReplyWithArray(ctx,2);
ValkeyModule_ReplyWithStringBuffer(ctx,"age",3);
ValkeyModule_ReplyWithLongLong(ctx,22);
返回嵌套数组很简单,您的嵌套数组元素只需使用另一个对 ValkeyModule_ReplyWithArray()
的调用,然后是发出子数组元素的调用。
返回动态长度数组
有时无法预先知道数组中的项目数量。例如,设想一个 Valkey 模块实现一个 FACTOR 命令,给定一个数字输出其质因数。更好的解决方案是,不是将数字分解质因数、将质因数存储到数组中,然后生成命令回复,而是启动一个长度未知的数组回复,并在以后设置其长度。这是通过 ValkeyModule_ReplyWithArray()
的一个特殊参数实现的
ValkeyModule_ReplyWithArray(ctx, VALKEYMODULE_POSTPONED_LEN);
上述调用启动了一个数组回复,因此我们可以使用其他 ReplyWith
调用来生成数组项。最后,为了设置长度,使用以下调用
ValkeyModule_ReplySetArrayLength(ctx, number_of_items);
对于 FACTOR 命令,这相当于以下类似代码
ValkeyModule_ReplyWithArray(ctx, VALKEYMODULE_POSTPONED_LEN);
number_of_factors = 0;
while(still_factors) {
ValkeyModule_ReplyWithLongLong(ctx, some_factor);
number_of_factors++;
}
ValkeyModule_ReplySetArrayLength(ctx, number_of_factors);
此功能的另一个常见用例是遍历某个集合的数组,并仅返回通过某种过滤的元素。
可以有多个具有延迟回复的嵌套数组。每次调用 SetArray()
都将设置最新对应 ReplyWithArray()
调用的长度。
ValkeyModule_ReplyWithArray(ctx, VALKEYMODULE_POSTPONED_LEN);
// ... generate 100 elements ...
ValkeyModule_ReplyWithArray(ctx, VALKEYMODULE_POSTPONED_LEN);
// ... generate 10 elements ...
ValkeyModule_ReplySetArrayLength(ctx, 10);
ValkeyModule_ReplySetArrayLength(ctx, 100);
这会创建一个包含 100 个元素的数组,其中最后一个元素是一个包含 10 个元素的数组。
参数数量和类型检查
命令通常需要检查参数的数量和键的类型是否正确。为了报告错误的参数数量,有一个专门的函数 ValkeyModule_WrongArity()
。其用法很简单
if (argc != 2) return ValkeyModule_WrongArity(ctx);
检查错误类型涉及打开键并检查其类型
ValkeyModuleKey *key = ValkeyModule_OpenKey(ctx,argv[1],
VALKEYMODULE_READ|VALKEYMODULE_WRITE);
int keytype = ValkeyModule_KeyType(key);
if (keytype != VALKEYMODULE_KEYTYPE_STRING &&
keytype != VALKEYMODULE_KEYTYPE_EMPTY)
{
ValkeyModule_CloseKey(key);
return ValkeyModule_ReplyWithError(ctx,VALKEYMODULE_ERRORMSG_WRONGTYPE);
}
请注意,通常您希望在键是预期类型或为空时,都继续执行命令。
对键的低级访问
对键的低级访问允许直接对与键关联的值对象执行操作,其速度类似于 Valkey 内部用于实现内置命令的速度。
键一旦打开,就会返回一个键指针,该指针将与所有其他低级 API 调用一起使用,以对键或其关联值执行操作。
由于此 API 旨在非常快速,它无法进行过多的运行时检查,因此用户必须注意遵循某些规则
- 多次打开同一个键(其中至少有一个实例以写入模式打开)是未定义的行为,并可能导致崩溃。
- 当键打开时,只能通过低级键 API 访问它。例如,打开一个键,然后使用
ValkeyModule_Call()
API 对同一个键调用 DEL 将导致崩溃。然而,安全的操作是:打开一个键,使用低级 API 执行一些操作,关闭它,然后使用其他 API 管理同一个键,之后再重新打开它以执行更多工作。
为了打开一个键,使用 ValkeyModule_OpenKey
函数。它返回一个键指针,我们将使用该指针进行所有后续调用以访问和修改值。
ValkeyModuleKey *key;
key = ValkeyModule_OpenKey(ctx,argv[1],VALKEYMODULE_READ);
第二个参数是键名,它必须是一个 ValkeyModuleString
对象。第三个参数是模式:VALKEYMODULE_READ
或 VALKEYMODULE_WRITE
。可以使用 |
对两种模式进行按位或操作,以在两种模式下打开键。目前,以写入模式打开的键也可以进行读取,但这应被视为实现细节。在健全的模块中应使用正确的模式。
您可以打开不存在的键进行写入,因为当尝试写入键时,键将被创建。然而,当仅以读取模式打开键时,如果键不存在,ValkeyModule_OpenKey
将返回 NULL。
使用完键后,可以使用以下命令关闭它
ValkeyModule_CloseKey(key);
请注意,如果启用了自动内存管理,您无需强制关闭键。当模块函数返回时,Valkey 将负责关闭所有仍处于打开状态的键。
获取键类型
为了获取键的值,使用 ValkeyModule_KeyType()
函数
int keytype = ValkeyModule_KeyType(key);
它返回以下值之一
VALKEYMODULE_KEYTYPE_EMPTY
VALKEYMODULE_KEYTYPE_STRING
VALKEYMODULE_KEYTYPE_LIST
VALKEYMODULE_KEYTYPE_HASH
VALKEYMODULE_KEYTYPE_SET
VALKEYMODULE_KEYTYPE_ZSET
上述只是常见的 Valkey 键类型,额外增加了空类型,它表示键指针与尚未存在的空键相关联。
创建新键
要创建新键,请以写入模式打开它,然后使用其中一个键写入函数写入。示例
ValkeyModuleKey *key;
key = ValkeyModule_OpenKey(ctx,argv[1],VALKEYMODULE_WRITE);
if (ValkeyModule_KeyType(key) == VALKEYMODULE_KEYTYPE_EMPTY) {
ValkeyModule_StringSet(key,argv[2]);
}
删除键
只需使用
ValkeyModule_DeleteKey(key);
如果键未以写入模式打开,则函数返回 VALKEYMODULE_ERR
。请注意,键被删除后,它会被设置为可供新键命令操作。例如,ValkeyModule_KeyType()
将返回它是一个空键,写入它将创建一个新键,可能是另一种类型(取决于使用的 API)。
管理键过期(TTL)
为了控制键过期,提供了两个函数,它们能够设置、修改、获取和取消与键关联的生存时间(TTL)。
一个函数用于查询开放键的当前过期时间
mstime_t ValkeyModule_GetExpire(ValkeyModuleKey *key);
该函数返回键的生存时间(以毫秒为单位),或返回特殊值 VALKEYMODULE_NO_EXPIRE
,表示键没有关联的过期时间或根本不存在(您可以通过检查键类型是否为 VALKEYMODULE_KEYTYPE_EMPTY
来区分这两种情况)。
为了更改键的过期时间,使用以下函数
int ValkeyModule_SetExpire(ValkeyModuleKey *key, mstime_t expire);
当对不存在的键调用时,返回 VALKEYMODULE_ERR
,因为该函数只能将过期时间与现有开放键关联(不存在的开放键仅用于创建具有数据类型特定写入操作的新值)。
同样,expire
时间以毫秒为单位指定。如果键当前没有过期时间,则设置新的过期时间。如果键已经有过期时间,则会用新值替换。
如果键有过期时间,并且使用特殊值 VALKEYMODULE_NO_EXPIRE
作为新的过期时间,则过期时间将被移除,类似于 Valkey 的 PERSIST
命令。如果键已经持久化,则不执行任何操作。
获取值的长度
有一个函数可以检索与开放键关联值的长度。返回的长度是值特定的,对于字符串是字符串长度,对于聚合数据类型(列表中、集合中、有序集合中、哈希中元素的数量)是元素的数量。
size_t len = ValkeyModule_ValueLength(key);
如果键不存在,函数返回 0
字符串类型 API
设置新的字符串值,像 Valkey 的 SET
命令一样,使用以下方式执行
int ValkeyModule_StringSet(ValkeyModuleKey *key, ValkeyModuleString *str);
该函数的工作方式与 Valkey 的 SET
命令本身完全相同,也就是说,如果存在先前的值(任何类型),它将被删除。
为了提高速度,访问现有字符串值是使用 DMA(直接内存访问)进行的。API 将返回一个指针和长度,以便可以直接访问并(如果需要)修改字符串。
size_t len, j;
char *myptr = ValkeyModule_StringDMA(key,&len,VALKEYMODULE_WRITE);
for (j = 0; j < len; j++) myptr[j] = 'A';
在上述示例中,我们直接写入字符串。请注意,如果您要写入,则必须确保请求 WRITE
模式。
DMA 指针仅在 DMA 调用之后、使用指针之前没有对键执行其他操作时才有效。
有时当我们想直接操作字符串时,也需要改变它们的大小。为此,使用 ValkeyModule_StringTruncate
函数。示例
ValkeyModule_StringTruncate(mykey,1024);
该函数根据需要截断或扩大字符串,如果原长度小于新请求的长度,则用零字节填充。如果字符串不存在(因为 key
与一个开放的空键关联),则会创建并关联一个字符串值到该键。
请注意,每次调用 StringTruncate()
时,我们都需要重新获取 DMA 指针,因为旧的指针可能已失效。
有关字符串类型函数的完整列表,请参阅模块 API 参考中的字符串类型的键 API。
列表类型 API
可以从列表值中推入和弹出值
int ValkeyModule_ListPush(ValkeyModuleKey *key, int where, ValkeyModuleString *ele);
ValkeyModuleString *ValkeyModule_ListPop(ValkeyModuleKey *key, int where);
在这两个 API 中,where
参数使用以下宏指定是从尾部还是头部推入或弹出
VALKEYMODULE_LIST_HEAD
VALKEYMODULE_LIST_TAIL
ValkeyModule_ListPop()
返回的元素类似于使用 ValkeyModule_CreateString()
创建的字符串,它们必须使用 ValkeyModule_FreeString()
释放,或者通过启用自动内存管理来释放。
有关列表类型函数的完整列表,请参阅模块 API 参考中的列表类型的键 API。
集合类型 API
直接用于集合类型键的 API 尚未实现。请使用 ValkeyModule_Call
API 以及 SADD 等集合命令来访问集合类型的键。
有序集合类型 API
请参阅模块 API 参考中的有序集合类型的键 API 部分。
哈希类型 API
请参阅模块 API 参考中的哈希类型的键 API。
命令复制
如果您希望在 Valkey 副本实例的上下文中,或使用 AOF 文件进行持久化时,像普通 Valkey 命令一样使用模块命令,那么模块命令以一致的方式处理其复制非常重要。
当使用高级 API 调用命令时,如果您在 ValkeyModule_Call()
的格式字符串中使用 "!" 修饰符,复制将自动发生,如下例所示
reply = ValkeyModule_Call(ctx,"INCRBY","!sc",argv[1],"10");
如您所见,格式说明符是 "!sc"
。感叹号不会被解析为格式说明符,但它会在内部将命令标记为“必须复制”。
如果您使用上述编程风格,则不会有问题。然而,有时情况会更复杂,您会使用低级 API。在这种情况下,如果命令执行没有副作用,并且始终一致地执行相同的工作,那么可以按用户执行的方式逐字复制命令。为此,您只需调用以下函数
ValkeyModule_ReplicateVerbatim(ctx);
当您使用上述 API 时,不应使用任何其他复制函数,因为它们不保证能很好地混合使用。
然而,这不是唯一的选择。还可以使用类似于 ValkeyModule_Call()
的 API,精确地告诉 Valkey 命令执行后要复制哪些命令,但不是调用命令,而是将其发送到 AOF/副本流。示例
ValkeyModule_Replicate(ctx,"INCRBY","cl","foo",my_increment);
可以多次调用 ValkeyModule_Replicate
,每次调用都会发出一个命令。所有发出的序列都包装在 MULTI/EXEC
事务中,这样 AOF 和复制效果与执行单个命令相同。
请注意,如果您想混合使用 Call()
复制和 Replicate()
复制两种形式(如果有更简单的方法,这不一定是好主意),它们有一个规则。使用 Call()
复制的命令始终是最终 MULTI/EXEC
块中首先发出的命令,而所有使用 Replicate()
发出的命令将随后发出。
自动内存管理
通常,在用 C 语言编写程序时,程序员需要手动管理内存。这就是 Valkey 模块 API 具有释放字符串、关闭开放键、释放回复等函数的原因。
然而,鉴于命令在受限环境和一套严格的 API 下执行,Valkey 能够为模块提供自动内存管理,代价是牺牲一些性能(大多数情况下,代价非常低)。
启用自动内存管理时
- 您不需要关闭开放的键。
- 您不需要释放回复。
- 您不需要释放 ValkeyModuleString 对象。
但是,如果您愿意,仍然可以这样做。例如,自动内存管理可能处于活动状态,但在一个分配大量字符串的循环中,您可能仍然希望释放不再使用的字符串。
为了启用自动内存管理,只需在命令实现开始时调用以下函数
ValkeyModule_AutoMemory(ctx);
自动内存管理通常是首选方式,但经验丰富的 C 程序员可能为了获得一些速度和内存使用效益而不使用它。
在模块中分配内存
普通 C 程序使用 malloc()
和 free()
来动态分配和释放内存。尽管在 Valkey 模块中技术上不禁止使用 malloc,但最好使用 Valkey 模块特定的函数,它们是 malloc
、free
、realloc
和 strdup
的精确替代品。这些函数是
void *ValkeyModule_Alloc(size_t bytes);
void* ValkeyModule_Realloc(void *ptr, size_t bytes);
void ValkeyModule_Free(void *ptr);
void ValkeyModule_Calloc(size_t nmemb, size_t size);
char *ValkeyModule_Strdup(const char *str);
它们的工作方式与 libc
中的等效调用完全相同,但它们使用 Valkey 相同的分配器,并且使用这些函数分配的内存会在 INFO
命令的内存部分报告,在执行 maxmemory
策略时也会被计算在内,并且通常是 Valkey 可执行文件的一等公民。相反,在模块内部使用 libc malloc()
分配的内存对 Valkey 是透明的。
使用模块函数分配内存的另一个原因是,当在模块内部创建原生数据类型时,RDB 加载函数可以直接将反序列化的字符串(来自 RDB 文件)作为 ValkeyModule_Alloc()
分配返回,这样它们就可以在加载后直接用于填充数据结构,而无需将其复制到数据结构中。
内存池分配器
有时在命令实现中,需要执行许多小型分配,这些分配在命令执行结束时不会保留,而仅用于执行命令本身。
使用 Valkey 内存池分配器可以更轻松地完成此工作
void *ValkeyModule_PoolAlloc(ValkeyModuleCtx *ctx, size_t bytes);
它的工作方式类似于 malloc()
,返回对齐到大于或等于 bytes
的下一个 2 的幂的内存(最大对齐为 8 字节)。然而,它以块为单位分配内存,因此分配开销很小,更重要的是,分配的内存会在命令返回时自动释放。
因此,一般来说,短期存在的分配是内存池分配器的良好候选。
编写兼容 Valkey Cluster 的命令
请参阅模块 API 参考中以下命令的说明