Valkey 模块 101
什么是 Valkey 模块?
模块的理念是在不修改核心代码的情况下,允许为 Valkey 添加额外功能(例如新命令和数据类型)。模块是一种特殊的代码分发形式,称为共享库,可以在运行时被其他程序加载并执行。模块可以用 C 语言或支持 C 语言绑定的其他语言编写。在本文中,我们将介绍如何使用 C 语言和 Rust(使用 Valkey Module Rust SDK)构建简单的模块。本文假设读者对 git、C、Rust 和 Valkey 至少有所了解。
C 语言中的 Hello World 模块
如果我们通过运行 git clone git@github.com:valkey-io/valkey.git 克隆 Valkey 仓库,我们会在 src/modules 中找到大量示例。让我们在同一个文件夹中创建一个新文件 module1.c。
#include "../valkeymodule.h"
int hello(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
VALKEYMODULE_NOT_USED(argv);
VALKEYMODULE_NOT_USED(argc);
return ValkeyModule_ReplyWithSimpleString(ctx, "world1");
}
int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
VALKEYMODULE_NOT_USED(argv);
VALKEYMODULE_NOT_USED(argc);
if (ValkeyModule_Init(ctx,"module1",1,VALKEYMODULE_APIVER_1)
== VALKEYMODULE_ERR) return VALKEYMODULE_ERR;
if (ValkeyModule_CreateCommand(ctx,"module1.hello", hello,"",0,0,0)
== VALKEYMODULE_ERR) return VALKEYMODULE_ERR;
return VALKEYMODULE_OK;
}
在这里,我们调用 Valkey 所需的 C 函数 ValkeyModule_OnLoad,使用 ValkeyModule_Init 初始化 module1。然后,我们使用 ValkeyModule_CreateCommand 创建一个 Valkey 命令 hello,它使用 C 函数 hello 并返回 world1 字符串。在未来的博文中,我们将更深入地探讨这些领域。
现在我们需要更新 src/modules/Makefile
all: ... module1.so
module1.xo: ../valkeymodule.h
module1.so: module1.xo
$(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lc
在 src/modules 文件夹内运行 make module1.so。这将在 src/modules 文件夹中编译我们的模块。
Rust 语言中的 Hello World 模块
我们将在 bash 中运行 cargo new --lib module2 来创建一个新的 Rust 包。在 module2 文件夹中,我们将拥有 Cargo.toml 和 src/lib.rs 文件。要在 module2 文件夹中安装 valkey-module SDK,请运行 cargo add valkey-module。或者,我们可以在 Cargo.toml 的 [dependencies] 下添加 valkey-module = "0.1.0。运行 cargo build,它将创建或更新 Cargo.lock 文件。
修改 Cargo.toml 以将 crate-type 指定为 "cdylib",这将告诉 cargo 将目标构建为共享库。阅读 Rust 文档以了解有关 crate-type 的更多信息。
[lib]
crate-type = ["cdylib"]
现在在 src/lib.rs 中用以下代码替换现有代码
#[macro_use]
extern crate valkey_module;
use valkey_module::{Context, ValkeyResult, ValkeyString, ValkeyValue};
fn hello(_ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
Ok(ValkeyValue::SimpleStringStatic("world2"))
}
valkey_module! {
name: "module2",
version: 1,
allocator: (valkey_module::alloc::ValkeyAlloc, valkey_module::alloc::ValkeyAlloc),
data_types: [],
commands: [
["module2.hello", hello, "", 0, 0, 0],
]
}
Rust 语法与 C 有点不同,但我们正在创建 module2,其中包含一个返回 world2 字符串的 hello 命令。我们使用外部 crate valkey_module 和 Rust 宏,并向其传递 name 和 version 等变量。一些变量如 data_types 和 commands 是数组,我们可以传递零个、一个或多个值。由于我们不使用 ctx 或 args,因此我们像在 C 中那样用 _(Rust 约定)而不是 VALKEYMODULE_NOT_USED 作为它们的前缀。
在根文件夹中运行 cargo build。现在我们将看到 target/debug/libmodule2.dylib(在 macOS 上)。构建将在 Linux 上生成 *.so 文件,在 Windows 上生成 *.dll 文件。
使用两个模块运行 Valkey 服务器
返回 Valkey 仓库文件夹并运行 make 编译 Valkey 代码。然后将以下行添加到 valkey.conf 文件的底部。
loadmodule UPDATE_PATH_TO_VALKEY/src/modules/module1.so
loadmodule UPDATE_PATH_TO_MODULE2/target/debug/libmodule2.dylib
并运行 src/valkey-server valkey.conf。您将在日志输出中看到这些消息。
Module 'module1' loaded from UPDATE_PATH_TO_VALKEY/src/modules/module1.so
...
Module 'module2' loaded from UPDATE_PATH_TO_MODULE2/target/debug/libmodule2.dylib
然后使用 src/valkey-cli 进行连接。
src/valkey-cli -3
127.0.0.1:6379> module list
1) 1# "name" => "module2"
2# "ver" => (integer) 1
3# "path" => "UPDATE_PATH_TO_MODULE2/target/debug/libmodule2.dylib"
4# "args" => (empty array)
2) 1# "name" => "module1"
2# "ver" => (integer) 1
3# "path" => "UPDATE_PATH_TO_VALKEY/src/modules/module1.so"
4# "args" => (empty array)
127.0.0.1:6379> module1.hello
world1
127.0.0.1:6379> module2.hello
world2
我们现在可以同时运行这两个模块,如果我们修改 C 或 RS 文件,重新编译代码并重新启动 valkey-server,我们将获得新功能。
作为在 valkey.conf 文件中指定模块的替代方案,我们可以使用 valkey-cli 中的 MODULE LOAD 和 UNLOAD 来更新服务器。首先在 valkey.conf 中指定 enable-module-command yes 并重新启动 valkey-server。这使我们能够在运行时更新模块代码、重新编译并重新加载它。
127.0.0.1:6379> module load UPDATE_PATH_TO_VALKEY/src/modules/module1.so
OK
127.0.0.1:6379> module list
1) 1# "name" => "module1"
2# "ver" => (integer) 1
3# "path" => "UPDATE_PATH_TO_VALKEY/src/modules/module1.so"
4# "args" => (empty array)
127.0.0.1:6379> module unload module1
OK
127.0.0.1:6379> module list
(empty array)
127.0.0.1:6379>
请继续关注未来的更多文章,我们将探索 Valkey 模块的可能性以及何时使用 C 或 Rust 更合理。