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

233 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 多语言插件演进统一规划(Rust 优先)
> 背景:团队技术栈杂糅,Python 在部分计算密集场景性能有限。
> 目标:在不破坏现有体系的前提下,开放进程插件多语言实现能力,
> **Rust 优先落地**,为后续其他语言(Go / C++ 等)铺路。
>
> 配套文档:[进程插件线协议规范(语言无关)](./plugin-wire-protocol.md)、
> [进程插件开发指南(Python)](./process-plugin.md)
---
## 1. 总体原则
| # | 原则 | 说明 |
| - | ---- | ---- |
| P1 | **规范先行,协议是唯一契约** | 网关与插件之间只认线协议([plugin-wire-protocol.md](./plugin-wire-protocol.md))。任何语言实现均以规范为准,不得依赖其他语言 SDK 的内部行为 |
| P2 | **网关零改动起步** | 阶段 1 的 Rust 插件按协议实现即可对接,网关代码不允许为适配单语言做改动 |
| P3 | **混合部署兼容** | 同一 `<type>` 下可混布 Python 与 Rust 副本(协议相同、负载均衡透明);但流式语义、错误格式(非流式经 `reply_err` 通道)、超时行为必须与规范完全一致 |
| P4 | **契约校验不旁路** | Python SDK 的进程内契约校验(TOML ↔ 注册 ↔ 模型)是 Python 特有能力;其他语言实现须提供等价的自检(描述符字段完整性),网关侧强校验在阶段 2 补齐 |
| P5 | **修改即更新规范** | 任何实现侧改动触及协议语义时,必须同步修订规范(含[附录 A 事实来源索引](./plugin-wire-protocol.md)) |
| 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`):
```bash
.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](../../workers/rsdemo/README.md))。
---
## 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. 相关文档
- [进程插件线协议规范(语言无关)](./plugin-wire-protocol.md) —— 唯一契约
- [进程插件开发指南(Python)](./process-plugin.md) —— Python SDK 开发流程
- [Rust 插件编写说明](./rust-plugin.md) —— Rust SDK 开发流程(脚手架/端点/模型/handler/管理/数据库/打包)
- [主 README:进程插件与混合部署](../README.md)