Valkey Modules 101

Valkey 模块 101

2024-05-01 · Dmitry Polyakovsky

什么是 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.tomlsrc/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_moduleRust 宏,并向其传递 nameversion 等变量。一些变量如 data_typescommands 是数组,我们可以传递零个、一个或多个值。由于我们不使用 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 LOADUNLOAD 来更新服务器。首先在 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 更合理。