Files
chongming-core/docs/multi-language-plugin-plan.md
T

230 lines
16 KiB
Markdown
Raw Normal View History

2026-08-22 03:26:50 +08:00
# 多语言插件演进统一规划(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 插件按协议实现即可对接,网关代码不允许为适配单语言做改动 |
2026-08-22 03:53:31 +08:00
| P3 | **混合部署兼容** | 同一 `<type>` 下可混布 Python 与 Rust 副本(协议相同、负载均衡透明);但流式语义、错误格式(非流式经 `reply_err` 通道)、超时行为必须与规范完全一致 |
2026-08-22 03:26:50 +08:00
| 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 /
2026-08-31 16:34:13 +08:00
│ # serde_json5 / tracing / clap / redis(optional)
│ # features: redis-cache(共享缓存后端,多实例用)
2026-08-22 03:26:50 +08:00
└── src/
├── lib.rs
2026-08-31 16:34:13 +08:00
├── config.rs # 读取 config.toml [zenoh].json5_path + JSON5 会话配置;
│ # 另有 [workers.config] 运行时读写(保留键校验 + 原子落盘)
2026-08-22 03:26:50 +08:00
├── descriptor.rs # 描述符结构体(规范 §2.2 字段表)+ 端点 TOML 解析
├── publisher.rs # RoutePublisher 等价:PUT / DELETE / 10s 重发布
├── serve.rs # worker_serve 等价:liveliness token + Queryable + 生命周期
2026-08-31 16:34:13 +08:00
├── 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
2026-08-22 03:26:50 +08:00
├── envelope.rs # 请求信封解析(meta.method / user_info / data)
│ # 与三种响应形态序列化(普通 / 流式 / 错误)
├── gen.rs # 模型生成:端点 TOML → Rust 结构体 + FINGERPRINT 指纹
│ # (bin: gen_models,对齐 Python gen_models)
2026-08-31 16:34:13 +08:00
├── 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)
2026-08-22 03:26:50 +08:00
└── 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()` |
2026-08-22 03:53:31 +08:00
| §4.3 异常 | 非流式:`query.reply_err(...)`,载荷 `{"result":0.0,"operation":"error","error":...,"worker":...}`;流式:`{"error":...}` 收尾块(普通 reply) |
2026-08-22 03:26:50 +08:00
| §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 为函数参数注入) |
2026-08-31 16:34:13 +08:00
### 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 / diskcache)作为 counts/configs 单一事实来源 | `Cache` 抽象:Redis(feature `redis-cache`)+ 无缓存回退 | ✅ Redis 路径一致;本地 diskcache 因格式特性不可复用(多实例走 Redis) |
| quota 分摊 | 按在线网关/存活实例均分全局 count(余数给序较早方) | `distribute_quota` / `resolve_worker_count` 同算法 | ✅ 同协议键 `worker:cm:*` |
| CLI | `--config` / `--instance` / `--count` | `examples/demo.rs`:`--config` / `--instance` / `--count` | ✅ 对齐 |
> 注:Python 默认的本地 `diskcache` 后端为 Python 库,Rust 无法读其存储文件格式;故 Rust 侧多实例协同统一走 Redis(`redis-cache` feature),单实例走 `Cache::None`,与 Python「多实例必须 redis/共享卷」约束一致。
### 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))。
2026-08-22 03:26:50 +08:00
---
## 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 开发流程
2026-08-31 16:34:13 +08:00
- [Rust 插件编写说明](./rust-plugin.md) —— Rust SDK 开发流程(脚手架/端点/模型/handler/管理/数据库/打包)
2026-08-22 03:26:50 +08:00
- [主 README:进程插件与混合部署](../README.md)