14 KiB
进程插件线协议规范(语言无关)
本规范定义网关与进程插件(
load_mode = "external")之间的唯一契约: Zenoh keyexpr + JSON 载荷。任何拥有 Zenoh 绑定的语言(Rust / Go / C / C++ / Java 等) 只要按本规范实现,即可与网关零改动对接,与 Python 插件共存于同一类型、参与同一套负载均衡。事实来源:本规范以
chongming_workerSDK 与网关代码为唯一事实来源 (见文末附录 A)。修改任何一侧实现必须同步更新本规范。
1. 总览
进程插件是一个独立进程,生命周期由 Docker/K8s 编排拉起(网关不负责 spawn)。 插件进程启动后:
- 打开 Zenoh 会话(配置见 §5)
- 发布路由描述符 → 网关 RouteRegistry 订阅后动态挂载 HTTP 端点
- 声明 liveliness token 与 Queryable → 网关 WorkerManager 发现并按负载均衡转发 HTTP 请求
- 退出时摘除路由描述符(DELETE),网关 2s 防抖后移除 HTTP 端点
HTTP 客户端 ──> 网关 FastAPI 动态路由
│ WorkerManager(15s liveliness 发现 + 随机负载均衡 + 超时重试)
▼
Zenoh Query: worker/<type>/<id>/<subject>
▲ │
│ ▼
插件进程 Queryable(解析信封 → 业务处理 → JSON 回复)
插件进程 ──PUT/DELETE──> chongming/routes/<type>/<route_id> (路由描述符)
插件进程 ──liveliness──> worker/<type>/<id> (服务发现)
关键时间参数
| 参数 | 值 | 语义 |
|---|---|---|
| 描述符重发布周期 | 10s | 插件侧周期性重发布全部描述符(自愈:网关后启动 / 样本丢失) |
| 网关路由防抖 | 2s | 网关收到描述符变更后防抖重建路由(合并重启风暴) |
| liveliness 刷新 | 15s | 网关 WorkerManager 轮询 worker/*/* |
| 优雅退出摘除 | 即时 | 插件收到 SIGTERM/SIGINT 后 DELETE 全部已发布 key |
2. 路由描述符协议(chongming/routes/**)
2.1 keyexpr 结构
chongming/routes/<type>/<route_id> # 端点描述符(PUT 发布 / DELETE 摘除)
chongming/routes/<type>/__meta__ # 插件级元信息(可选,仅在声明 MQTT 主题时发布)
<type>:插件类型(worker type),与config/workers/<type>/目录名一致<route_id>:<subject 的 "." 替换为 "_">__<METHOD>,如demo.hello+GET→demo_hello__GET- 载荷一律 UTF-8 JSON
2.2 描述符字段(schema_version = 1)
发布端在端点描述符上附加两个字段:
{
"schema_version": 1,
"type": "demo",
...以下字段来自端点定义(TOML 兼容 dict)
}
网关侧消费的字段(RouteConfig,缺失时取默认值,未知字段忽略):
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
subject |
string | 必填 | RPC 主题,如 demo.hello;与 worker/<type>/<id>/<subject> 对应 |
method |
string | "GET" |
HTTP 方法(GET/POST/PUT/PATCH/DELETE);"MQTT" 为纯 MQTT 端点(不注册 HTTP 路由);网关侧统一大写处理 |
path |
string | 空 | HTTP 路径,如 /demo/hello;含 {param} 时构成路径参数 |
params |
array[string] | [] |
参数声明,每项 "名称: 类型"(类型支持 str/int/float/bool/any、list[...]、dict[...]);无 : 的项按 str 处理 |
response_model |
object | {} |
响应模型声明:{"字段": {"type": "...", "required": bool, "default": ...}} 或 {"字段": "type"}(仅用于网关 OpenAPI 文档与响应模型校验) |
summary |
string | "" |
OpenAPI 摘要 |
docstring |
string | "" |
OpenAPI 描述 |
timeout |
number | 3.0 |
单次调用超时(秒);流式端点为其总寿命上限,须配大(如 300) |
cacheable |
bool | false |
网关侧结果缓存开关(成功响应按 (type, subject, payload) 缓存) |
validate |
bool | true |
网关是否解析并校验请求体(false = 原样透传,校验下沉 worker;仅对 POST/PUT/PATCH 非流式端点生效,见 §4.2) |
auth_required |
bool | true |
网关是否注入鉴权依赖(认证用户经信封 meta.user_info 透传给插件) |
internal |
bool | false |
内部端点:不注册 HTTP 路由,仅保留 Zenoh RPC 调用能力 |
stream |
bool | false |
流式端点:网关以 SSE 逐块转发多回复 |
stream_idle_timeout |
number | 10.0 |
流式空闲超时(秒):连续无数据块的最大间隔 |
route_name |
string | 无 | 端点名(TOML 文件名),契约校验用于推断模型名;网关运行时不消费 |
schema_version |
int | 1 | 协议版本(见 §7 演进规则) |
type |
string | 插件类型 | 插件类型,网关缺失时以 keyexpr 中的 <type> 兜底 |
描述符生成约定(Python SDK 行为,其他语言须输出等价结构):
method恒取自端点 TOML 节键并大写,不被 defaults 覆盖path缺省为router_prefix + "/" + 端点名subject缺省为<type>.<端点名>[worker.defaults]中的键作为字段缺省值合并进描述符(显式字段优先)
2.3 发布 / 摘除语义
| 操作 | 行为 |
|---|---|
| 发布 | PUT 单个端点描述符到 chongming/routes/<type>/<route_id>;多端点逐个 PUT |
| 元信息 | 声明了 [[workers.mqtt_topics]] 时,额外 PUT chongming/routes/<type>/__meta__,载荷 {"schema_version": 1, "mqtt_topics": [{"topic": "...", ...}]};网关据此动态订阅外部主题 |
| 重发布 | 每 10s 将全部描述符重新 PUT(幂等) |
| 摘除 | 优雅退出(SIGTERM/SIGINT)时 DELETE 全部已发布 key;进程被 SIGKILL 时描述符残留,重启后重发布覆盖 |
2.4 网关侧行为(实现方须知)
- 订阅
chongming/routes/**;PUT → upsert,DELETE → remove - 幂等去重:同一
route_id描述符内容 hash 相同视为多副本重复发布,忽略 - 冲突:同一
route_id内容不同 → 告警并保留先到者 - 防抖:type 级 2s 防抖,变更合并为一次路由重建
- 健壮性:非法 JSON / 非 dict 描述符 → 丢弃并告警,不影响网关
- 路由筛选:
method == "MQTT"或internal == true的端点不注册 HTTP 路由,但保留在路由表中
3. 服务发现协议(liveliness)
- 插件进程为每个副本声明 liveliness token:
worker/<type>/<id> <id>无格式约束(Python SDK 惯例worker-<i>-<pid>);网关只解析parts[1]作为类型- 网关每 15s 轮询
worker/*/*刷新存活列表,调用时随机选择副本(负载均衡)
4. RPC 调用协议(Queryable)
4.1 keyexpr
worker/<type>/<id>/<subject>
<subject> 与描述符的 subject 字段一致(多方法共享同一 subject 时,同一 keyexpr 由多个方法分派)。
4.2 请求信封
{"meta": {"method": "GET", "user_info": {...}}, "data": {...业务参数...}}
| 字段 | 说明 |
|---|---|
meta.method |
HTTP 方法(大写);缺失默认 GET |
meta.user_info |
网关注入的认证用户信息(auth_required = true 时存在,结构由网关鉴权决定) |
data |
业务参数:GET/DELETE 为查询/路径参数的 {name: value} 字典;POST/PUT/PATCH 为请求体字段 |
兼容规则:非信封载荷(不含 meta/data 键的 JSON 对象)整包视为业务 data,按 method = GET 处理。
透传模式(validate = false 端点):data 为请求体的原始 JSON 值(网关零解析/校验,
信封由字节拼接构造,结构不变)。插件收到的 data 形态与校验模式一致(由 SDK 解析为 dict);
参数类型转换/校验失败由插件侧暴露(框架异常经 reply_err → 网关 503)。空/空白请求体兜底为 {}。
4.3 响应
| 形态 | 行为 |
|---|---|
| 普通 | 单个回复,载荷为业务结果 JSON 对象 |
| 框架异常(非流式) | 单个 reply_err 回复,载荷 {"result": 0.0, "operation": "error", "error": "<异常信息>", "worker": "<worker_id>"};网关据此零解析判定失败 → 重试 → HTTP 503 |
| 业务错误 | 普通回复(如 {"code": 1, "error": "..."}),HTTP 200 透传,网关不解析 |
| 流式 | 对同一 Query 多次 reply 逐块推送;生成器正常结束 = 流结束(无结束哨兵);中途异常以 {"error": "<异常信息>"} 收尾块结束流(流式错误仍为普通回复,不走 reply_err);查询已终止(客户端断开)时停止回复 |
错误通道分层:框架异常(handler 抛错、无匹配方法、超时)→
reply_err; 业务错误(业务逻辑判定失败)→ 普通回复。插件作者语义:返回即业务、抛出即框架。兼容矩阵(新旧网关/插件任意组合均语义完好):
网关 \ 插件 旧(普通回复错误信封) 新(reply_err) 旧 HTTP 200 + 错误信封(历史行为) call()已处理 reply.err → 重试 → 503新 HTTP 200 + 错误信封(透传不解析,业务语义) reply_err → 重试 → 503(语义修正)
网关侧对应行为:
- 普通调用
WorkerManager.call_raw(HTTP 端点):取首个回复,原始字节零解析透传给 HTTP 响应;空回复 / reply.err / 超时 → 切换副本重试(默认重试 1 次),重试耗尽 → HTTP 503 call()(MQTT Bridge 等内部调用方):同上述重试逻辑,回复解析为 dict 后使用- 流式调用
WorkerManager.call_stream:逐块消费(ConsolidationMode.NONE);收到含error键的块视为终止信号转发后结束;reply.err(zenoh 层错误)同样终止流 - 无可用副本 → HTTP 503
5. 配置约定
插件进程自读两份配置(其他语言实现须兼容读取语义):
| 配置 | 位置 | 用途 |
|---|---|---|
| 全局配置 | config.toml(--config 可指定) |
仅 [zenoh].json5_path(默认 config/zenoh_config.json5),JSON5 格式的 Zenoh 会话配置 |
| 插件配置 | config/workers/<type>/config.toml |
[workers] 节(type/count/load_mode/mqtt_topics/运行时配置)与端点 TOML 定义(描述符来源) |
进程入口参数:--type <name> [--count N] [--config config.toml](Python SDK 约定;其他语言可自定入口,但 --type 语义应保持一致)。
非 Python 插件的配置读取完全由自身实现,网关不读取插件配置 (描述符经 Zenoh 发布,网关与插件配置解耦)。
6. 最小实现清单(其他语言插件)
一个合规的插件进程必须实现:
- 解析
config.toml的[zenoh].json5_path并打开 Zenoh 会话 - 为每个端点构造描述符(§2.2 字段表)并 PUT 到
chongming/routes/<type>/<route_id> - 声明
[[workers.mqtt_topics]]时 PUT__meta__描述符 - 为每个副本声明 liveliness token
worker/<type>/<id>(多副本时 id 互异) - 为每个 subject 声明 Queryable
worker/<type>/<id>/<subject> - 解析请求信封(§4.2),按
meta.method分派;回复符合 §4.3 三种形态 - 每 10s 重发布全部描述符
- 优雅退出时 DELETE 全部已发布 key
自检义务:网关侧不做描述符强校验(契约校验是 Python SDK 的进程内行为),
插件须自行保证描述符字段完整(尤其 subject / method / path / params / response_model),
否则会挂载出错误路由。
7. 版本与演进规则
- 描述符携带
schema_version(当前1),网关对未知版本暂不拒绝(缺失时兜底为 1) - 协议变更分级:
- 破坏性变更(keyexpr 结构、信封结构、错误格式、描述符语义)→
schema_version递增,网关须先发布兼容处理,再切换 - 新增可选字段 / 新增端点形态 → 允许同版本内演进,但必须同步更新本规范
- 破坏性变更(keyexpr 结构、信封结构、错误格式、描述符语义)→
- 本版同版本内演进说明(2026-08):
- 新增描述符字段
validate(§2.2,网关缺失时兜底 true,纯增量) - 非流式框架异常新增
reply_err错误通道(§4.3):信封结构与错误载荷均不变, 仅输出通道由普通 reply 改为 reply_err;网关call()/call_raw()历史上已处理 reply.err, 新旧组合兼容(见 §4.3 兼容矩阵),故不递增 schema_version - 网关普通调用由
call(dict 语义)切换为call_raw(bytes 透传):入参出参形态变化 为网关内部实现,不影响线上协议
- 新增描述符字段
- 修改即更新:任何实现侧改动(
routes_publisher.py/runtime.py/route_registry.py/worker_manager.py/route_generator.py)触及本规范描述的语义时,必须同步修订本规范
附录 A:事实来源索引
| 协议项 | 唯一实现(文件:行号) |
|---|---|
| 描述符 keyexpr / route_id 编码 / 发布摘除 | utils/worker/src/chongming_worker/routes_publisher.py:23-89 |
| 描述符字段定义与消费 | src/chongming/app/core/route_generator.py:82-100(RouteConfig) |
| 描述符生成约定(TOML → dict) | utils/worker/src/chongming_worker/route_config.py:25-103 |
| 网关订阅 / 防抖 / 幂等 / 冲突 / 筛选 | src/chongming/app/core/route_registry.py:44-261 |
| liveliness 发现 / 负载均衡 / 超时重试 / 缓存 | src/chongming/app/core/worker_manager.py:73-379 |
| 请求信封构造(meta.method / user_info) | src/chongming/app/core/route_generator.py:241-405 |
| 信封解析 / 分派 / 三种响应形态 | utils/worker/src/chongming_worker/runtime.py:27-220 |
| 进程生命周期(10s 重发布 / 优雅退出) | utils/worker/src/chongming_worker/process.py:35-197 |
| 配置读取(json5_path / 插件配置) | utils/worker/src/chongming_worker/process.py:39-136 |
| 契约校验语义(仅 Python SDK 进程内) | utils/worker/src/chongming_worker/contracts.py:150-189 |