
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 更合理。