Valkey Modules Rust SDK updates

Valkey 模块 Rust SDK 更新

2025-05-20 · Dmitry Polyakovsky

在之前的一篇文章中,我们探讨了构建 Valkey 模块的过程,以使开发者无需修改 Valkey 核心即可为其添加新命令和数据类型等功能。我们还介绍了Valkey 模块 Rust SDK,并演示了如何使用它来创建基本模块。在这篇后续文章中,我们将深入探讨该 SDK,并重点介绍过去一年中引入的几项新功能和改进。本文假设读者已非常熟悉 Rust 和 Valkey 模块。

Valkey 模块 Rust SDK 是什么?

该 SDK 基于 Redis 模块 Rust SDK,并在 Valkey 自身的模块 API 之上提供了抽象 API。对于熟悉 Rust 开发的人来说,该 SDK 是一个 Rust crate,可以像其他 Rust 依赖项一样添加到 Cargo.toml 文件中。它要求底层 Valkey 服务器具有适当的模块 API,但允许使用 Rust 编写 Valkey 模块,而无需使用原始指针或不安全代码。最近发布的 布隆过滤器模块 就是使用该 SDK 构建的,并且参与该模块开发的几位开发者也为 SDK 做出了贡献。让我们深入了解一下新功能。

客户端

我们首先介绍一些增强功能,这些功能使开发者能够更深入地了解连接到 Valkey 的客户端。Context 结构已扩展了几个新函数,允许检索客户端特定数据,例如客户端名称、用户名或 IP 地址。它为底层 Valkey 模块 API 中的 Module_GetClient* 函数提供了 Rust 封装。这些新函数大多返回 ValkeyResult,以便模块开发者可以适当处理错误。这些函数可以针对当前客户端调用,也可以通过指定 client_id 调用。

fn my_client_cmd(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
    let client_id = ctx.get_client_id();
    let username = ctx.get_client_username_by_id(client_id);
    Ok(ValkeyValue::from(username.to_string()))
}
valkey_module! {
    ...
    commands: [
        ["my_client_cmd", my_client_cmd, "", 0, 0, 0],
    ]
}

认证回调

另一个关键改进是支持 Valkey 7.2 中引入的认证回调。得益于 SDK 中的新功能,Rust 模块现在可以直接与 Valkey 的认证流程集成,从而可以实现自定义认证逻辑或按客户端增强安全策略。一个潜在用例是将其与上面描述的 ctx.get_client_ip 函数结合使用,以允许某些用户仅从特定 IP 地址访问。

fn auth_callback(
    ctx: &Context,
    username: ValkeyString,
    _password: ValkeyString,
) -> Result<c_int, ValkeyError> {
    if ctx.authenticate_client_with_acl_user(&username) == Status::Ok {
        let _client_ip = ctx.get_client_ip()?;
        ...
        return Ok(AUTH_HANDLED);
    }
    Ok(AUTH_NOT_HANDLED)
}
valkey_module! {
    ...
    auth: [auth_callback],
}

预加载验证

尽管 valkey_module! 宏已经提供了 init 回调以在模块加载期间执行自定义代码,但它在创建新命令和数据类型之后、在模块加载的最后阶段执行。这可能很有用,但如果我们想在这些事情发生之前停止模块加载怎么办?例如,我们可能需要限制模块仅在特定版本的 Valkey 上加载。这就是可选的 preload 的作用。

fn preload_fn(ctx: &Context, _args: &[ValkeyString]) -> Status {
    let _version = ctx.get_server_version().unwrap();
    // respond with Status::Ok or Status::Err to prevent loading
    Status::Ok
}
valkey_module! {
    ...
    preload: preload_fn,
    commands: [],
}

过滤器

要在特定 Valkey 命令之前执行自定义代码,我们可以使用命令过滤器,这现在在 SDK 中得到了支持。过滤器可用于将默认命令替换为自定义命令或修改参数。得益于 SDK 提供的抽象,我们只需创建一个 Rust 函数并在 valkey_module! 宏中注册即可。注意事项——由于过滤器在每个命令执行之前运行,因此此代码需要进行性能优化。

fn my_filter_fn(_ctx: *mut RedisModuleCommandFilterCtx) {
    let cf_ctx = CommandFilterCtx::new(ctx);
    // check the number of arguments
    if cf_ctx.args_count() != 3 {
        return;
    }
    // get which command is being executed
    let _cmd = cf_ctx.cmd_get_try_as_str().unwrap();
    // grab various args passed to the command
    let _all_args = cf_ctx.get_all_args_wo_cmd();
    // replace command
    cf_ctx.arg_replace(0, "custom_cmd");
}
valkey_module! {
    ...
    filters: [
        [my_filter_fn, VALKEYMODULE_CMDFILTER_NOSELF],
    ]
}

新事件处理器

对服务器事件的响应对于模块行为可能非常重要。SDK 已经扩展了对注册事件处理器的支持,允许开发者介入更多的服务器端事件。我们可以利用此功能在客户端连接/断开连接、服务器关闭或特定键事件时执行我们自己的代码。

#[client_changed_event_handler]
fn client_changed_event_handler(_ctx: &Context, client_event: ClientChangeSubevent) {
    match client_event {
        ClientChangeSubevent::Connected => {}
        ClientChangeSubevent::Disconnected => {}
    }
}
#[shutdown_event_handler]
fn shutdown_event_handler(_ctx: &Context, _event: u64) {
    ...
}
#[key_event_handler]
fn key_event_handler(ctx: &Context, key_event: KeyChangeSubevent) {
    match key_event {
        KeyChangeSubevent::Deleted => {}
        KeyChangeSubevent::Evicted => {}
        KeyChangeSubevent::Overwritten => {}
        KeyChangeSubevent::Expired => {}
    }
}

自定义 ACL 类别支持

Valkey 8 引入了对自定义 ACL 类别的支持,这简化了模块中引入的自定义命令的访问控制。要实现这一点,我们需要在 Cargo.toml 中启用 required-features = ["min-valkey-compatibility-version-8-0"] 并在 valkey_module! 宏中注册新类别。然后,我们可以将自定义命令限制在这些自定义 ACL 类别中。

valkey_module! {
    ...
    acl_categories: [
        "acl_one",
        "acl_two"
    ]
    commands: [
        ["cmd1", cmd1, "write",  0, 0, 0, "acl_one"],
        ["cmd2", cmd2, "", 0, 0, 0, "acl_one acl_two"],
    ]

验证/拒绝 CONFIG SET

配置灵活性很重要,但验证也同样重要。SDK 现在支持指定可选的回调函数来验证或拒绝自定义配置。这适用于 Stringi64boolenum 类型的配置。
以下是 i64 自定义配置的此类验证示例。

lazy_static! {
    static ref CONFIG_I64: ValkeyGILGuard<i64> = ValkeyGILGuard::default();
}
fn on_i64_config_set<G, T: ConfigurationValue<i64>>(
    config_ctx: &ConfigurationContext,
    _name: &str,
    val: &'static T,
) -> Result<(), ValkeyError> {
    if val.get(config_ctx) == custom_logic_here {
        log_notice("log message here");
        Err(ValkeyError::Str("error message here "))
    } else {
        Ok(())
    }
}
valkey_module! {
    configurations: [
        i64: [
            ["my_i64", &*CONFIG_I64, 10, 0, 1000, ConfigurationFlags::DEFAULT, Some(Box::new(on_configuration_changed)), Some(Box::new(on_i64_config_set::<ValkeyString, ValkeyGILGuard<i64>>))],
        ],
        ...
    ]
}

碎片整理

对于内存敏感型应用,碎片整理至关重要。SDK 现在为自定义数据类型提供了安全且符合 Rust 习惯的碎片整理 API 抽象。新的 Defrag 结构抽象了原始的 C FFI 调用。

static MY_VALKEY_TYPE: ValkeyType = ValkeyType::new(
    "mytype123",
    0,
    raw::RedisModuleTypeMethods {
        ...
        defrag: Some(defrag),
    },
);
unsafe extern "C" fn defrag(
    defrag_ctx: *mut raw::RedisModuleDefragCtx,
    _from_key: *mut RedisModuleString,
    value: *mut *mut c_void,
) -> i32 {
    let defrag = Defrag::new(defrag_ctx);
    ...
    0
}
valkey_module! {
    ...
    data_types: [
        MY_VALKEY_TYPE,
    ],
    ...
}

Redis 支持

需要您的模块同时与 Valkey 和最新版本的 Redis 配合使用?SDK 包含一个兼容性功能标志,以确保您的模块在 Valkey 和 Redis 上都能运行。指定 use-redismodule-api 以便模块使用 RedisModule API 初始化来实现向后兼容。

[features]
use-redismodule-api = ["valkey-module/use-redismodule-api"]
default = []

cargo build --release --features use-redismodule-api

单元测试和内存分配

此功能允许编写在 Valkey 或 Redis 外部运行的单元测试。它不使用 Valkey 分配器,而是依赖系统分配器。
单元测试使我们能够进行更精细的测试并更快地执行。
核心逻辑位于 alloc.rs 中,但开发者只需在模块的 Cargo.toml 中指定此功能。

[features]
enable-system-alloc = ["valkey-module/system-alloc"]

cargo test --features enable-system-alloc

总结

过去一年中,Valkey 模块 Rust SDK 取得了令人兴奋的改进,使得扩展 Valkey 变得更加容易和强大。但我们不会止步于此。未来的一些开发想法包括对单元测试的模拟上下文支持增强过滤器内的上下文访问以及特定环境配置,以简化开发和测试。此外,引入crontab 调度将允许使用 cron_event_handler 在定义的计划上执行自定义逻辑。

我们希望本概述能帮助您了解如何利用该 SDK,并激发您探索 Valkey 模块的无限可能。敬请关注未来的更新。

我们还要感谢在过去一年中为 SDK 做出贡献的工程师们——KarthikSubbaraodmitrypolsachinvmurthyzackcamYueTang-VanessahahnandrewMkmkme