Files
chongming-core/docs/multi-language-plugin-plan.md
2026-09-04 14:50:34 +08:00

16 KiB
Raw Permalink Blame History

多语言插件演进统一规划(Rust 优先)

背景:团队技术栈杂糅,Python 在部分计算密集场景性能有限。 目标:在不破坏现有体系的前提下,开放进程插件多语言实现能力, Rust 优先落地,为后续其他语言(Go / C++ 等)铺路。

配套文档:进程插件线协议规范(语言无关)、 进程插件开发指南(Python)


1. 总体原则

# 原则 说明
P1 规范先行,协议是唯一契约 网关与插件之间只认线协议(plugin-wire-protocol.md)。任何语言实现均以规范为准,不得依赖其他语言 SDK 的内部行为
P2 网关零改动起步 阶段 1 的 Rust 插件按协议实现即可对接,网关代码不允许为适配单语言做改动
P3 混合部署兼容 同一 <type> 下可混布 Python 与 Rust 副本(协议相同、负载均衡透明);但流式语义、错误格式(非流式经 reply_err 通道)、超时行为必须与规范完全一致
P4 契约校验不旁路 Python SDK 的进程内契约校验(TOML ↔ 注册 ↔ 模型)是 Python 特有能力;其他语言实现须提供等价的自检(描述符字段完整性),网关侧强校验在阶段 2 补齐
P5 修改即更新规范 任何实现侧改动触及协议语义时,必须同步修订规范(含附录 A 事实来源索引)
P6 性能收益只认实测 语言选型以基准测试为准(见 §6),不凭直觉切换

2. 阶段划分总览

阶段 内容 产出 状态
0 协议规范定稿 docs/plugin-wire-protocol.md ✅ 已完成
1 Rust 参考实现 chongming-worker-rs(utils/worker-rs/)+ demo 参考插件,端到端验证通过 ✅ 已完成(见 §3.4 验证结果)
2 工具链与治理 build_plugin 支持非 Python、网关描述符强校验、跨语言互操作测试 ⏳ 未开始
3 推广与最佳实践 团队规范、性能基准、Go 等其他语言按需跟进 ⏳ 未开始

每阶段有明确的 Done 定义(见 §5),未达验收标准不得进入下一阶段。


3. 阶段 1:Rust 参考实现(chongming-worker-rs)

3.1 目标

用 Rust 实现一个最小但完整的进程插件 SDK 与参考插件,证明: 路由注册、RPC、流式、摘除、自愈、混合部署全链路可用,且与 Python SDK 行为等价。

参考实现(两形态,均已完成):

  • SDK 内示例:utils/worker-rs/examples/demo.rs(--type rsdemo / --type demo 混布对拍)
  • 独立插件形态:workers/rsdemo/(与 Python 示例 workers/demo/ 对位的完整插件 crate, 含生成模型与说明文档,新插件以此为模板)

3.2 crate 结构建议

chongming-worker-rs/            # 独立仓库(或 utils/worker-rs/,与 Python SDK 并列)
├── Cargo.toml                  # 依赖: zenoh / tokio / serde+serde_json / toml /
│                               #        serde_json5 / tracing / clap / redis(optional)
│                               #   features: redis-cache(共享缓存后端,多实例用)
└── src/
    ├── lib.rs
    ├── config.rs               # 读取 config.toml [zenoh].json5_path + JSON5 会话配置;
    │                           #   另有 [workers.config] 运行时读写(保留键校验 + 原子落盘)
    ├── descriptor.rs           # 描述符结构体(规范 §2.2 字段表)+ 端点 TOML 解析
    ├── publisher.rs            # RoutePublisher 等价:PUT / DELETE / 10s 重发布
    ├── serve.rs                # worker_serve 等价:liveliness token + Queryable + 生命周期
    ├── runtime.rs              # 托管副本模型:ReplicaController(index/epoch/restart/stop/drain)
    │                           #   + WorkerPool(按 target 动态扩缩,优雅排空)
    ├── admin.rs                # __admin__ Queryable:status/restart/stop/drain/get_config/
    │                           #   config_set/rescale(对齐 Python runtime._handle_admin_command)
    ├── cache.rs                # 配置缓存抽象:Redis 共享缓存(feature redis-cache)+ 无缓存回退;
    │                           #   实例登记/心跳/租约、quota 全局分摊、resolve_worker_count
    ├── envelope.rs             # 请求信封解析(meta.method / user_info / data)
    │                           #   与三种响应形态序列化(普通 / 流式 / 错误)
    ├── gen.rs                  # 模型生成:端点 TOML → Rust 结构体 + FINGERPRINT 指纹
    │                           #   (bin: gen_models,对齐 Python gen_models)
    ├── error.rs                # 非流式错误格式、流式 {"error":...} 收尾块
    ├── database.rs             # 数据库封装:SQLite/PostgreSQL 异步连接池(sqlx,feature `database`)
    │                           #   from_config/sqlite/from_url + initialize/pool/raw_execute/dispose
    │                           #   + database_for/init_database(对齐 Python chongming_database)
└── examples/
    └── demo.rs                 # demo 参考插件(对位 workers/demo 的 hello / storage 端点)

3.3 与协议规范的对应(实现对照)

协议项(规范章节) Rust 实现要点
§2 路由描述符 descriptor.rs 构造 + publisher.rs PUT/DELETE;route_id = subject.replace('.','_') + "__" + METHOD
§3 服务发现 session.liveliness().declare_token("worker/<type>/<id>"),--count 多副本 id 互异
§4 RPC declare_queryable("worker/<type>/<id>/<subject>") 异步 handler;信封解析 + 分派;流式多次 query.reply()
§4.3 异常 非流式:query.reply_err(...),载荷 {"result":0.0,"operation":"error","error":...,"worker":...};流式:{"error":...} 收尾块(普通 reply)
§5 配置 config.rs:TOML + JSON5 双重解析
生命周期 publisher.rs 10s tokio interval 重发布;tokio::signal 监听 SIGTERM/SIGINT → DELETE 全部 key

3.4 端到端验收场景(已全部通过 ✅)

# 场景 结果
1 路由注册 + HTTP 端点可用 ✅ 描述符 keyexpr/字段 15 项断言全过;网关动态挂载后 GET /api/v1/rsdemo/hello 返回 200
2 GET/POST RPC + user_info 透传 ✅ 信封分派、worker_id 注入、user_info 经 meta 透传(协议层验证);网关层 echo 端点按配置要求认证头(预期行为)
3 流式 SSE 逐块 + 流中异常收尾 ✅ tokens=5 逐块 5 块;tokens=60 第 26 块后 SSE 收到 {"error":...} 收尾块并终止
4 SIGTERM 摘除 + 重启自愈 ✅ 退出码 0、DELETE 全部描述符;重启后 ≤10s 重发布恢复
5 混布(Python + Rust 同 type) ✅ 描述符内容 hash 完全一致(网关幂等去重零告警);双副本统一 liveliness 发现;网关 HTTP 10 次调用 6:4 随机打到两实现(worker-0-54257=Rust / worker-0-54258=Python);互不干扰
6 错误描述符健壮性 ✅ Rust SDK 发布前自检跳过无 subject 描述符(不发布残缺路由);网关侧非法 JSON 丢弃为既有行为

验证命令(见 utils/worker-rs/README.md):

.venv/bin/python utils/worker-rs/scripts/verify_e2e.py --type rsdemo   # 协议级 15/15
.venv/bin/python utils/worker-rs/scripts/verify_mixed.py               # 混布对拍 6/6

3.5 与 Python SDK 的差异(有意为之)

差异 说明
契约校验 Python 进程内三方校验(硬失败退出);Rust 降级为发布前自检:无 handler 端点告警跳过,不发布残缺路由
模型生成 Rust 有 gen_models 等价物(--bin gen_models,命名/类型映射对齐 Python):从端点 TOML 生成强类型结构体,并做启动时指纹校验(TOML 变更未重生成即告警,比 Python 只查模型存在性更强)
handler 注入 worker_id / user_info 经 Request 结构注入(Python 为函数参数注入)

3.6 生命周期管理与多实例协同(已对齐 Python runtime.py)

阶段 1 之后补强的两块能力,使 Rust 进程插件在管理指令、配置热更、多实例 quota 分摊上与 Python SDK 平齐:

能力 Python(utils/worker/) Rust(utils/worker-rs/) 对齐结论
托管副本 WorkerController(index/epoch/exit_event/reload_cb)+ WorkerPool.set_target ReplicaController(index/epoch/request_restart/stop/drain)+ WorkerPool.set_target(runtime.rs) ✅ 机制等价
admin Queryable worker/<type>/<id>/__admin__,指令 status/restart/stop/drain/get_config/config_set/rescale 同一 keyexpr,admin.rs::handle_admin 全量分派 ✅ keyexpr 与指令一致
优雅排空 drain:隔离新请求 → 等在途归零(30s 超时强制关闭) draining 标记 + wait_inflight_zero(同 30s);先摘 liveliness/业务/admin 再等在途 ✅ 行为一致
配置热更 config_set:仅 index0 落盘(原子写)+ 触发重启,[workers.config] 保留键校验 persist_worker_config_section(tmp+rename 原子写)+ merge_confirmed/count_allowed 保留键校验 ✅ 语义一致
配置缓存 chongming-cache(redis / sqlite)作为 counts/configs 单一事实来源 Cache 抽象:sqlite(feature local-cache,本机与 Python 共享同一 .db)+ Redis(feature redis-cache)+ 无缓存回退 ✅ schema 一致,凡 JSON 值均可互通
quota 分摊 按在线网关/存活实例均分全局 count(余数给序较早方) distribute_quota / resolve_worker_count 同算法 ✅ 同协议键 worker:cm:*
CLI --config / --instance / --count examples/demo.rs:--config / --instance / --count ✅ 对齐

注:Rust 侧共享缓存已统一为与 Python chongming-cache 完全一致的 JSON 协议——本机走 sqlite(local-cache feature,跨语言共享同一 .db),跨主机走 Redis(redis-cache feature);缺省无缓存走 Cache::None。原 Python 本地 diskcache 后端已被 SQLite 后端 直接替换,不再存在「Rust 不可读其本地格式」的限制。

3.7 数据库对齐(chongming_database ↔ Rust database.rs)

Python 插件通过 chongming_database(aiosqlite/asyncpg + SQLAlchemy 引擎)落库; Rust 侧用 sqlx 在 utils/worker-rs 内提供等价封装(feature database,随插件按需开启), 使插件能对 SQLite / PostgreSQL 做异步读写。语义逐项对齐:

能力 Python(chongming_database) Rust(database.rs,sqlx) 对齐结论
配置声明 [workers.config].database + db_name(SQLite)/ url(PostgreSQL) 同一键 [workers.config.database]:type / database_path / url ✅ 同键
构造 Database.from_config / Database(Path) / from_url from_config / sqlite(path) / from_url(url) ✅ 三态等价
建池(建引擎) initialize()(幂等) initialize(max_connections)(幂等) ✅ connect_with 建池
取会话 get_session() 上下文 pool() 返回 Pool<Any>(Arc,可 Clone) ✅ 共享引用等价
建表 基类 _bootstrap + SQLModel create_all raw_execute("CREATE TABLE IF NOT EXISTS ...") ✅ 启动时 init_database
关断 dispose() dispose()(幂等) ✅ 对齐
降级 未配置时不建库(db_name = None) database_for/init_database 返回 None ✅ 语义一致

关键点:

  • 开启方式:SDK/插件 Cargo.toml 开启 database feature(同时引入 sqlx);打包 bash build.sh dist "database"。
  • handler 共享:init_database 返回 Option<Arc<Database>>,Clone 后捕获进 handler 闭包, 内部用 db.pool().await 拿共享连接池做 sqlx::query —— 与 Python 用模块级 db 访问对位。
  • 后端差异:SQLite 连接串 sqlite:<path>?mode=rwc(rwc 自动建库,同 aiosqlite);PostgreSQL 走 postgres://...。 SQL 占位符遵循各自方言(SQLite ?、PostgreSQL $1),SDK 不做 SQL 重写。
  • TLS:默认不启用 TLS 后端;PostgreSQL 走 SSL 时需追加 tls-rustls-ring 等 feature。
  • 参考实现:workers/rsdemo 的 db_ping 端点(写 + 读一表做连通性探测);gen_plugin --rust 模板已内置 注释版 [workers.config.database] 段与 database feature(见 rsdemo README)。

4. 阶段 2:工具链与治理

4.1 build_plugin 支持非 Python 插件

  • 现状:build_plugin 产物为 pyproject.toml + start.sh(uv run worker_serve --type <name>),纯 Python 链路
  • 目标:支持声明插件语言/入口,允许打包任意可执行产物 + 自定义 start.sh (Rust 插件 = 编译产物 + --type 参数约定),--type 语义跨语言保持一致
  • 约束:打包结构(zip 根 = 容器工作目录)不变

4.2 网关侧描述符强校验(补齐契约旁路)

  • 现状:描述符健壮性只有"非法 JSON 丢弃 / 缺字段取默认",字段语义错误会挂载错误路由
  • 目标:route_registry 对必填字段(subject / method / path)做校验,缺必填字段的 描述符丢弃并告警;为 schema_version 未知版本预留拒绝策略
  • 收益:非 Python 插件(无契约校验)的错误在网关侧被兜底,两端受益

4.3 跨语言互操作测试

  • 复用 test_process_mode_e2e.py 模式,新增协议一致性测试矩阵: Python SDK ↔ Rust SDK 对同一描述符/信封的序列化结果一致性(黄金样本对比)
  • 纳入 CI:任何实现侧协议改动,先跑互操作测试再合入(配合原则 P5)

5. 验收标准(Done 定义)

阶段 Done 定义
0 规范文档覆盖:描述符字段、信封、三种响应形态、生命周期、演进规则;任一语言实现者可仅凭规范写出插件
1 §3.4 六个场景全部通过;Rust 插件与 Python 插件行为对拍一致(普通/流式/错误/超时)
2 build_plugin 可打包 Rust 插件并部署运行;网关描述符强校验生效;互操作测试入 CI
3 性能基准文档发布;团队多语言插件开发规范定稿

6. 选型指引(何时用 Rust)

场景 建议 依据
个别计算热点慢(加解密/编解码/大体积处理) Python + PyO3 扩展(网关 jwt/password 已有先例) 保留 Python 全部工具链,改动最小
整个插件即高性能计算服务 / 高吞吐短请求 Rust 进程插件(本规划主线) 消除 GIL 与解释器开销,收益在 handler 侧
纯 I/O 编排(HTTP/DB/存储调用) 维持 Python 瓶颈在外部服务,换语言边际收益小
团队语言偏好 / 组件复用(已有 Rust 库) Rust 进程插件 混合部署兼容,可渐进替换

性能收益只发生在 handler 执行侧;Zenoh 传输与 JSON 序列化开销各语言相同。 落地前先做基准:同端点 Python vs Rust 的 p99 / 吞吐对比,以数据决策。


7. 风险与缓解

风险 等级 缓解
协议漂移(双实现维护) 高 规范为唯一契约 + 附录 A 事实索引 + P5 强制更新 + 阶段 2 互操作测试入 CI
行为差异导致混布异常(流式/错误/超时) 中 §3.4 场景 5/6 混布对拍;各实现须通过同一互操作测试矩阵
契约校验旁路 中 阶段 2 网关强校验兜底;Rust SDK 提供描述符自检(等价 contracts.py 的必填校验)
网关被不规范描述符影响 低 现有健壮性处理已覆盖(非法 JSON 丢弃、冲突保留先到者、防抖)
多语言 SDK 维护成本 中 阶段 1 只做最小 SDK(协议层),业务框架(路由/模型/契约)不复制,避免重复投入

8. 相关文档