eloqstore 是什么?这是一款基于 io_uring 的高性能混合层键值存储引擎。它将对象存储(兼容 S3)与本地 NVMe SSD 相结合,旨在提供卓越的写入吞吐量和亚毫秒级的读取延迟。作为 EloqData 数据库产品的基础存储层,它使得基于 SSD 的工作负载能够实现接近内存的延迟特性,同时兼顾了数据的持久性与成本效益。
那么,如何让这个用 C++ 编写的存储引擎更好地服务于 Rust 生态呢?这就是 eloqstore Rust SDK 的使命。它本质上是一个精心设计的 FFI(外部函数接口)层,核心目标是在 Rust 程序中安全、高效地调用 eloqstore 的原生功能。对于开发者而言,一套简洁高效的接口就是 SDK 最大的价值。你可以将其理解为一个“简单且高效的定制传话层”:将 Rust 中的操作指令原封不动地传递给 eloqstore 内核执行,再将结果准确无误地返回。
整个集成过程遵循清晰的设计原则,大致可以分为以下几个阶段:
前提与边界约定
在开始之前,必须确立几条铁律以确保跨语言调用的稳定与安全:
- 边界只走 C ABI:所有导出函数必须使用
extern “C”,避免导出复杂的 C++ 类、STL 容器或模板。
- 异常不跨边界:在 C++ 封装层内部使用
try/catch(...) 捕获所有异常,并将其转换为明确的错误码返回给 Rust 侧。
- 所有权清晰:跨越 FFI 边界的内存必须遵循“谁分配,谁释放”的原则,必要时需要提供对应的
free_xxx() 函数供 Rust 调用。
选择并导出核心 API
第一步是确定需要暴露给 Rust 的核心操作,例如读(read)、写(write)、扫描(scan)等。
- C++ 侧:编写一个 C API 头文件,使用
extern “C” 导出一组面向过程的、基于 C 语言的函数。这些函数内部再去调用真正的 C++ 类实现功能。
- Rust 侧:使用
extern “C” 声明这些函数,并通过 unsafe 块进行调用。最关键的一步是,围绕这些原始的 unsafe 接口,构建一层利用 RAII(资源获取即初始化)等 Rust 安全特性的高级封装,最终向用户提供安全的 API。
在这个过程中,数据结构的布局需要做出适配:
- 字符串/字节数组:统一使用
(const uint8_t* ptr, size_t len) 或 (const char* ptr, size_t len) 这样的指针加长度的组合来表示。
- 调用方分配内存:由 Rust 传入一个 buffer 指针及其容量,C++ 函数将数据填充进去并返回实际使用的长度。
- 被调用方分配内存:由 C++ 侧通过
malloc 或 new 分配内存并返回指针,Rust 侧在使用完毕后,必须调用对应的 free_xxx() 函数来释放(这是最常用的模式)。
编译动态链接库
完成接口定义后,需要将 C++ 部分的代码编译成动态库,例如 libeloqstore_combine.so。之后,Rust 程序在运行时需要能够找到这个动态库,可以通过设置 rpath 或环境变量(如 LD_LIBRARY_PATH)来实现。
创建 Rust 项目结构
接下来,创建一个组织良好的 Rust 项目(crate)来作为调用层。一个推荐的目录结构如下:
.
|-- Cargo.lock
|-- Cargo.toml
|-- README.md
|-- docs
| |-- API.md
| |-- LINKING.md
| `-- QUICK_START.md
|-- eloqstore # 上层安全封装 crate(推荐使用)
| |-- Cargo.toml
| |-- README.md
| |-- examples
| | |-- basic_usage.rs
| | `-- cloud_storage.rs
| |-- src
| | |-- error.rs
| | |-- lib.rs
| | |-- request.rs
| | |-- response.rs
| | |-- store.rs
| | `-- traits.rs
| `-- tests
| |-- integration_test.rs
| `-- simple_bench.rs
`-- eloqstore-sys # 底层 FFI crate(包含原始绑定)
|-- Cargo.toml
|-- build.rs
|-- src
| |-- embedded_lib.rs
| |-- error.rs
| |-- lib.rs
| `-- types.rs
`-- vendor
|-- CMakeLists.txt
|-- README.md
|-- external -> ../../../external
|-- ffi
| |-- include
| | `-- eloqstore_capi.h
| `-- src
| `-- eloqstore_capi.cpp
|-- include -> ../../../include
`-- src -> ../../../src
这里采用了常见的双 crate 设计:
eloqstore-sys:底层 FFI crate,直接与 C 动态库交互,包含 unsafe 绑定。
eloqstore:上层安全封装 crate,为开发者提供符合 Rust 习惯的、安全易用的 API。
在 Rust 侧链接动态库
链接工作主要在 eloqstore-sys 的 Cargo.toml 和 build.rs 中配置。首先,在 Cargo.toml 中声明构建脚本和依赖:
[package]
name = "eloqstore-sys"
version = "0.1.0"
edition = "2024"
build = "build.rs"
[dependencies]
libc = "0.2"
tempfile = "3"
[features]
default = []
metrics = []
[build-dependencies]
cmake = "0.1"
然后,编写 build.rs 脚本,它的核心作用是告诉 Cargo 构建系统:去哪里查找动态库以及具体链接哪一个库。
手动编写 Rust FFI 声明
最后,也是最细致的一步,就是在 eloqstore-sys 中手动编写与 C 头文件对应的 Rust FFI 函数和类型声明。这些声明通常放在 src/lib.rs 或类似的文件中,需要严格匹配 C 侧的签名。
总结
通过上述一套完整的流程,EloqStore Rust SDK 实现了清晰高效的 FFI 架构,并带来了多重优势:
- 零拷贝传输:在 FFI 边界上精心设计,确保数据最多只被复制一次,最小化内存开销。
- 类型安全:在 Rust 侧构建了完整的类型安全抽象,在编译期就保证了调用的正确性。
- 批量性能极佳:充分利用底层 COW B-tree 架构的特性,使得批量写入操作性能表现突出。
- 灵活的双 API 设计:为简单场景提供 RocksDB-style 的易用接口,为复杂场景提供基于 Trait 的、更富表现力的 Request 接口,兼顾了易用性与灵活性。
这种将高性能 C++ 存储引擎 与现代化 Rust 语言生态结合的模式,为开发兼具性能与安全性的存储系统提供了宝贵参考。对类似技术实践感兴趣的开发者,可以关注 云栈社区 以获取更多深度内容。
相关链接
(以下为 Rust 日报同期其他内容)
Rust 基金会 2025 年度报告及三年战略规划
-- From 日报小组 侯盛鑫
|