拓冰建站拓冰建站
首页 / 资讯中心 / 正文

TiKV 协处理器插件示例编写指南:从 dylib 构建到插件注册

TiKV 协处理器插件示例编写指南从 dylib 构建到插件注册【免费下载链接】tikvDistributed transactional key-value database, originally created to complement TiDB项目地址: https://gitcode.com/GitHub_Trending/ti/tikv导读TiKV 在 v2 协处理器框架coprocessor-v2中提供了可插拔的插件机制允许用户在 TiKV 节点上执行自定义的原始RawKV 操作逻辑。components/test_coprocessor_plugin组件及其example_plugin示例正是这一机制的最小可运行范例。本文以该示例插件为骨架结合coprocessor_plugin_api源码与PluginRegistry实现完整讲解插件工程的三要素——Cargo.toml的dylib配置、CoprocessorPlugintrait 与declare_plugin!宏、构建产物命名与白名单——并延伸到插件的加载、热加载与配置启用流程。读完本文你将能独立从零编写并构建一个可被 TiKV 加载的协处理器插件。一、示例插件组件概览components/test_coprocessor_plugin目录的核心用途正如其README所述存放用于测试的 TiKV 协处理器示例插件。目录结构如下README.md示例插件的编写规范说明example_plugin/src/lib.rs一个最小可编译的插件实现作为 TiKV 单测中加载插件的目标产物example_plugin/Cargo.toml演示插件必须满足的工程配置。从源码结构看该插件被 TiKV 自身的单元测试直接引用src/coprocessor_v2/plugin_registry.rs中的测试如load_plugin、registry_load_and_get_plugin、plugin_registry_hot_reloading会通过pkgname_to_libname(example-coprocessor-plugin)定位编译出的动态库并加载验证因此它既是教程示例也是 CI 中验证插件加载机制正确性的真实测试对象。二、插件工程的第一要素Cargo.toml 必须产出 dylib2.1crate-type [dylib]是硬性要求README 明确指出示例插件必须在Cargo.toml中包含如下配置[lib] crate-type [dylib]以 example_plugin/Cargo.toml 为完整参照[package] name example_coprocessor_plugin version 0.1.0 edition 2021 publish false license Apache-2.0 [lib] crate-type [dylib] [dependencies] coprocessor_plugin_api { workspace true }注意这里必须是dylib不能是cdylib或staticlib。原因在 components/coprocessor_plugin_api/src/lib.rs 的 crate 级文档中有明确解释后两者无法使用 TiKV 的分配器而插件与 TiKV 之间需要共享所有权数据例如Vecu8在插件与宿主之间传递必须让插件使用宿主进程的内存分配器。declare_plugin!宏正是通过#[global_allocator]将插件的分配器设置为转发到宿主 TiKV 的HostAllocator。2.2 构建产物落盘位置当某个 crate 把示例插件声明为依赖时cargo 会在target/profile目录下产出对应的dylib。例如debugprofile 对应target/debugreleaseprofile 对应target/release。产物文件名遵循平台动态库惯例Linuxlibpkgname.somacOSlibpkgname.dylibWindowslibpkgname.dll2.3 用pkgname_to_libname()精确解析产物名由于包名中的连字符会被 rustc 转换为下划线example-coprocessor-plugin→example_coprocessor_plugin直接拼接文件名很容易出错。coprocessor_plugin_api提供了统一入口 components/coprocessor_plugin_api/src/util.rspub fn pkgname_to_libname(pkgname: str) - String { let pkgname pkgname.to_string().replace(-, _); if cfg!(target_os windows) { format!({}.dll, pkgname) } else if cfg!(target_os macos) { format!(lib{}.dylib, pkgname) } else { format!(lib{}.so, pkgname) } }也就是说pkgname_to_libname(example-coprocessor-plugin)在 Linux 上返回libexample_coprocessor_plugin.so。在插件加载测试 src/coprocessor_v2/plugin_registry.rs 中测试正是通过该函数定位测试进程同目录下的动态库再交由LoadedPlugin::new()加载断言插件名与版本example_coprocessor_plugin、0.1.0。三、插件工程第二要素实现 CoprocessorPlugin trait3.1 最小实现example_plugin/src/lib.rs 给出了一个最小但结构完整的插件use std::ops::Range; use coprocessor_plugin_api::*; #[derive(Default)] struct ExamplePlugin; impl CoprocessorPlugin for ExamplePlugin { fn on_raw_coprocessor_request( self, _ranges: VecRangeKey, _request: RawRequest, _storage: dyn RawStorage, ) - PluginResultRawResponse { unimplemented!() } } declare_plugin!(ExamplePlugin);示例插件仅占位实现了on_raw_coprocessor_request直接unimplemented!()因为它的定位是验证加载链路而非业务逻辑。真实插件需要在该回调中实现完整逻辑。3.2 trait 语义请求、响应与存储访问CoprocessorPlugintrait 定义在 components/coprocessor_plugin_api/src/plugin_api.rs要求实现者必须是Send Syncpub trait CoprocessorPlugin: Send Sync { fn on_raw_coprocessor_request( self, ranges: VecRangeKey, request: RawRequest, storage: dyn RawStorage, ) - PluginResultRawResponse; }RawRequest与RawResponse都是Vecu8原始字节请求载荷与客户端通过RawCoprocessorRequest的data字段传入的数据完全一致编解码责任完全在插件侧——大多数场景建议使用 Protobuf也可以直接传原始字节ranges是本次请求覆盖的 Key 区间列表storage提供了对当前节点底层存储的访问能力见下文。3.3 RawStorage插件读写底层存储的通道RawStoragetrait 定义在 components/coprocessor_plugin_api/src/storage_api.rs是一个asynctrait包含 8 个操作方法且批量操作优先文档明确建议优先使用 batch 接口以提升性能方法语义get(key) - OptionValue单 Key 读不存在返回Nonebatch_get(keys) - VecKvPair多 Key 批量读scan(key_range) - VecKvPair区间读区间为[start, end)end 上界开区间put(key, value)单 Key 写入batch_put(kv_pairs)多对 KV 批量写delete(key)单 Key 删除batch_delete(keys)多 Key 批量删除delete_range(key_range)区间删除同样为[start, end)类型别名Key Vecu8、Value Vecu8、KvPair (Key, Value)也在同一文件中定义。需要注意这些操作都作用于当前节点的存储插件不应假设跨节点的一致性。3.4 错误处理约定PluginResultT与PluginError定义在 components/coprocessor_plugin_api/src/errors.rs。PluginError包含KeyNotInRegionKey 不在请求对应的 Region 内携带 region_id 与区间边界、Timeout、Canceled以及Other四类错误。文档特别强调业务逻辑层面的错误应由插件自行编解码进RawResponse返回PluginError仅用于框架层无法处理、需要回传给客户端并可能触发重试的情况。四、插件工程第三要素declare_plugin! 宏导出构造函数仅实现 trait 还不够插件必须向宿主暴露约定符号否则 TiKV 无法创建插件实例。coprocessor_plugin_api通过 declare_plugin! 宏 自动生成三个#[no_mangle]的extern C导出函数_plugin_create构造函数签名见PluginConstructorSignature——接收 TiKV 传入的HostAllocatorPtr宿主分配器的函数指针将其注入#[global_allocator]随后Box::new(plugin_ctor)并Box::into_raw返回裸指针_plugin_get_build_info返回BuildInfoapi_version/target/rustc三个static strTiKV 在加载时用它做 ABI 一致性校验_plugin_get_plugin_info返回PluginInfo插件名与版本号。宏提供三种调用形态// 形式一显式指定名称、版本与构造表达式完全自主控制 declare_plugin!(my_plugin, 1.0.0, MyPlugin::default()); // 形式二自动从 Cargo.toml 读取版本 declare_plugin!(my_plugin, MyPlugin::default()); // 形式三名称与版本均自动从 Cargo.toml 读取推荐 declare_plugin!(MyPlugin::default());三种形式在 util.rs 的宏文档 中均有说明。注意两个约束一个动态库只能声明一个插件因为宏生成的导出符号名是固定的重复声明会符号冲突宏会设置插件的#[global_allocator]为宿主的HostAllocator这使得 TiKV 与插件间传递自有数据owned data更简单代价是插件无法使用自定义分配器。构造函数参数HostAllocatorPtr在 components/coprocessor_plugin_api/src/allocator.rs 定义为#[repr(C)]结构体持有alloc_fn与dealloc_fn两个函数指针HostAllocator::set_allocator()必须在任何内存分配发生前调用否则会 panic该分配器内部使用AtomicOptionAllocFn存储指针见同文件第 22-50 行。五、把插件加入 jemalloc 白名单README 的最后一条规范是在 scripts/check-bins.py 中为不包含 jemalloc 的插件加入白名单。TiKV 的发布产物检查脚本会校验二进制不混用分配器插件动态库通过HostAllocator转发到宿主的 jemalloc因此插件自身不应再链接 jemalloc。在该脚本第 16 行的白名单中可以看到coprocessor_plugin_api, example_coprocessor_plugin, memory_trace_macros,example_coprocessor_plugin与coprocessor_plugin_api正是被登记在此。编写新插件时需要把自己的 crate 名同样追加进该白名单否则相关检查如scripts/test-all流水线中的二进制检查会失败。六、插件如何被 TiKV 加载与热加载虽然示例插件本身不涉及加载逻辑但理解其产物被消费的方式能帮助你正确构建与部署。6.1 从 dylib 到 LoadedPlugin加载入口是 src/coprocessor_v2/plugin_registry.rs 中的LoadedPlugin::new()流程为用libloading打开动态库文件解析_plugin_get_build_info、_plugin_get_plugin_info、_plugin_create三个符号在调用构造函数之前先做 ABI 校验err_on_mismatch依次比对api_version、rustc版本、target任何一项不一致都会拒绝加载分别返回ApiMismatch/CompilerMismatch/TargetMismatch错误见 plugin_registry.rs。这意味着插件必须与 TiKV 使用完全相同的 Rust 工具链与 coprocessor_plugin_api 版本编译解析插件名与版本版本号需符合 SemVer构造HostAllocatorPtr指向宿主std::alloc::alloc/dealloc调用_plugin_create得到*mut dyn CoprocessorPlugin再Box::from_raw收回所有权。6.2 目录热加载与配置启用PluginRegistry::start_hot_reloading()支持目录级热加载plugin_registry.rs启动时加载目录中已有的动态库并起一个后台线程通过文件系统 watcher轮询间隔 3 秒监听Create/Rename事件新放入的库文件会被自动加载文件被重命名时同步更新路径。需要注意两个限制被卸载/删除的库不允许再次加载ReloadError原因见 issue 注释且由于库被std::mem::forget泄漏删除文件不会真正卸载已运行的插件。插件目录通过配置文件启用见 etc/config-template.toml 的[coprocessor-v2]段[coprocessor-v2] ## Path to the directory where compiled coprocessor plugins are located. ## If the config value is not set, the coprocessor plugin will be disabled. # coprocessor-plugin-directory ./coprocessors对应结构体为 src/coprocessor_v2/config.rs 中的Config.coprocessor_plugin_directory: OptionPathBuf该项未设置时插件功能默认关闭设置后将该目录下的*.so/*.dylib/*.dll扩展名按平台判定见 is_library_file放入即可被加载。注意[coprocessor-v2]与同文件中[coprocessor]第 521 行是两套不同配置前者面向插件机制后者是传统 TiDB 计算下推的协处理器配置不要混淆。七、从示例到实战编写自己插件的完整步骤综合以上全部要素一个可被 TiKV 加载的插件需要新建 crate在[lib]中声明crate-type [dylib]依赖coprocessor_plugin_api建议通过 workspace 依赖与 TiKV 使用同一版本避免 ABI 校验失败实现CoprocessorPlugintrait在on_raw_coprocessor_request中自行解码RawRequest、处理ranges、借助RawStorage的读写接口完成业务并把结果编码为RawResponse返回调用declare_plugin!(MyPlugin::default())导出构造符号每个库只能声明一个插件编译得到target/profile下的libpkgname.so可用coprocessor_plugin_api::util::pkgname_to_libname()换算确切文件名将插件 crate 名追加进 scripts/check-bins.py 的 jemalloc 白名单部署把动态库放入[coprocessor-v2] coprocessor-plugin-directory指定的目录如./coprocessors重启 TiKV 或依赖热加载机制放入即可自动加载。构建与加载的一致性要求同一 rustc 工具链、同一coprocessor_plugin_api版本、同一 target 平台是实战中最常见的坑——这些约束并非约定俗成而是被 plugin_registry.rs 的 ABI 校验 强制执行的事实。参考资料示例插件 README示例插件 Cargo.toml 与 插件实现coprocessor_plugin_api crate 文档、plugin_api.rs、storage_api.rs、errors.rs、allocator.rs、util.rs插件注册与加载实现、插件目录配置、配置模板二进制检查脚本jemalloc 白名单【免费下载链接】tikvDistributed transactional key-value database, originally created to complement TiDB项目地址: https://gitcode.com/GitHub_Trending/ti/tikv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门