docs: 说明文件重整(README 改使用导向,运维细节移入 docs/worker-management.md)

This commit is contained in:
2026-10-13 17:29:38 +08:00
parent 6d087fa4e3
commit fe96fc9b6c
7 changed files with 512 additions and 845 deletions
+8 -6
View File
@@ -1,11 +1,13 @@
# 多语言插件演进统一规划(Rust 优先)
> **本文是演进规划记录,不是使用指南**。要开发/使用插件请先看:
> [进程插件开发指南(Python)](./process-plugin.md)、
> [Rust 插件编写说明](./rust-plugin.md)、
> [进程插件线协议规范(语言无关)](./plugin-wire-protocol.md)。
> 背景:团队技术栈杂糅,Python 在部分计算密集场景性能有限。
> 目标:在不破坏现有体系的前提下,开放进程插件多语言实现能力,
> **Rust 优先落地**,为后续其他语言(Go / C++ 等)铺路。
>
> 配套文档:[进程插件线协议规范(语言无关)](./plugin-wire-protocol.md)、
> [进程插件开发指南(Python)](./process-plugin.md)
---
@@ -60,7 +62,7 @@ chongming-worker-rs/ # 独立仓库(或 utils/worker-rs/,与 Pyth
│ # 另有 [workers.config] 运行时读写(保留键校验 + 原子落盘)
├── descriptor.rs # 描述符结构体(规范 §2.2 字段表)+ 端点 TOML 解析
├── publisher.rs # RoutePublisher 等价:PUT / DELETE / 10s 重发布
├── serve.rs # worker_serve 等价:liveliness token + Queryable + 生命周期
├── serve.rs # 入口等价:chongming-worker(Python 侧):liveliness token + Queryable + 生命周期
├── runtime.rs # 托管副本模型:ReplicaController(index/epoch/restart/stop/drain)
│ # + WorkerPool(按 target 动态扩缩,优雅排空)
├── admin.rs # __admin__ Queryable:status/restart/stop/drain/get_config/
@@ -159,7 +161,7 @@ Rust 侧用 sqlx 在 `utils/worker-rs` 内提供等价封装(feature `database
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))。
注释版 `[workers.config.database]` 段与 `database` feature(见 [rsdemo README](../workers/rsdemo/README.md))。
---
@@ -167,7 +169,7 @@ Rust 侧用 sqlx 在 `utils/worker-rs` 内提供等价封装(feature `database
### 4.1 build_plugin 支持非 Python 插件
- 现状:`build_plugin` 产物为 pyproject.toml + start.sh(`uv run worker_serve --type <name>`),纯 Python 链路
- 现状:`build_plugin` 产物为 pyproject.toml + start.sh(`uv run chongming-worker --type <name>`),纯 Python 链路
- 目标:支持声明插件语言/入口,允许打包任意可执行产物 + 自定义 start.sh
(Rust 插件 = 编译产物 + `--type` 参数约定),`--type` 语义跨语言保持一致
- 约束:打包结构(zip 根 = 容器工作目录)不变
+4 -4
View File
@@ -237,11 +237,11 @@ worker/<type>/<id>/<subject>
| 协议项 | 唯一实现(文件:行号) |
| ---- | ---- |
| 描述符 keyexpr / route_id 编码 / 发布摘除 | `utils/worker/src/chongming_worker/routes_publisher.py:23-89` |
| 描述符字段定义与消费 | `src/chongming/app/core/route_generator.py:82-100`(RouteConfig) |
| 描述符字段定义与消费 | `utils/gateway/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/gateway/src/chongming/app/core/route_registry.py:44-261` |
| liveliness 发现 / 负载均衡 / 超时重试 / 缓存 | `utils/gateway/src/chongming/app/core/worker_manager.py:73-379` |
| 请求信封构造(meta.method / user_info) | `utils/gateway/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` |
+19 -51
View File
@@ -27,9 +27,7 @@
---
## 2. 运行原理
### 2.1 架构
## 2. 工作机制(简述)
```mermaid
graph TB
@@ -37,7 +35,7 @@ graph TB
REG[网关 RouteRegistry<br/>订阅 chongming/routes/**] -->|动态挂载/摘除| API
API --> WM[WorkerManager<br/>负载均衡 + 超时重试]
subgraph GW[网关容器 chongming]
subgraph GW[网关容器]
API
REG
WM
@@ -54,36 +52,18 @@ graph TB
WS -.->|SIGTERM 时 delete 摘除| REG
```
### 2.2 Worker 进程启动流程(`chongming_worker.process.worker_serve`)
`chongming-worker --type demo` 启动后的流程:
```text
chongming-worker --type demo
1. 读全局 config.toml(仅 [zenoh] 节)→ 打开 Zenoh 会话
2. 自读 config/workers/demo/config.toml 的 [workers] 节(load_plugin_config 展平)
3. import 插件模块(plugins.<name> / workers.<name>)→ 触发 @rpc_method 注册
4. 进程内契约校验(TOML ↔ 注册表 ↔ 生成模型),硬失败则退出(退出码 1)
5. create_workers 创建 Worker 副本(默认 count 个,--count 可覆盖)
6. RoutePublisher 发布路由描述符 → 网关动态挂载 HTTP 路由
7. 等待退出信号;期间每 10s 周期性重发布(网关后启动 / 样本丢失时自愈)
8. SIGTERM/SIGINT → 摘除路由 → cleanup → 取消副本任务 → 关闭会话
```
1. 读根 `config.toml` 的 `[zenoh]` 节打开会话,自读
`config/workers/demo/config.toml` 的 `[workers]` 节(含 `[workers.config]` 运行时配置)
2. import 插件模块触发 `@rpc_method` 注册 → **进程内契约校验**(不通过则退出码 1)
3. `create_workers` 创建副本(默认 `count` 个,`--count` 可覆盖),注册 liveliness
4. 经 Zenoh 把路由描述符 put 到 `chongming/routes/<type>/<route_id>`,网关动态挂载 HTTP 路由;
每 10s 重发布(网关后启动或样本丢失时自愈)
5. 收到 SIGTERM/SIGINT → delete 描述符(路由摘除)→ cleanup → 关闭会话
### 2.3 路由注册与摘除协议
- **发布**:路由描述符 put 到 `chongming/routes/<type>/<route_id>`,其中
`route_id = <subject 的 . 替换为 _>__<METHOD>`(如 `demo_hello__GET`),
描述符为 TOML 兼容 dict + `schema_version=1` + `type=<插件名>`
- **元信息**:插件声明了 `[[workers.mqtt_topics]]` 时,额外发布
`chongming/routes/<type>/__meta__`(含 mqtt_topics),网关据此动态订阅外部主题
- **自愈**:每 10s 重发布一次全部描述符(`ANNOUNCE_INTERVAL`),网关后启动或样本丢失可恢复
- **摘除**:优雅退出时 delete 全部已发布 key,网关 2s 防抖后摘除 HTTP 端点(请求变 404)
- **网关侧**:RouteRegistry 常驻订阅 `chongming/routes/**`,与盘扫解耦——
**网关容器无需放置 external 插件的配置文件**
### 2.4 服务发现
Worker 副本经 liveliness 注册 `worker/<type>/<id>`(与 inline 副本同一机制),
WorkerManager 据此做负载均衡,网关侧无需任何改动。
要点:网关容器**无需**放置 external 插件的配置文件(RouteRegistry 常驻订阅,与盘扫解耦);
描述符字段与 keyexpr 细节见 [线协议规范](plugin-wire-protocol.md)。
---
@@ -114,8 +94,7 @@ workers/<name>/ # 项目根 = zip 根 = 容器工作目录
└── start.sh # 容器入口(uv sync + uv run chongming-worker --type <name>)
```
> 参考示例:`workers/demo/`(完整项目,含 5 个端点,配合 `test_worker_isolation.py` /
> `test_process_mode_e2e.py` 使用)。
> 参考示例:`workers/demo/`(完整项目,含 5 个端点)。
---
@@ -291,16 +270,10 @@ uv run --project .. chongming-worker --type demo
> 若配置了 `load_mode` 非 `"external"`,会输出告警:网关也会在进程内加载该插件,
> 外部进程将与网关内副本**重复服务**。
### 第 7 步:验证
### 第 7 步:联调验证
```bash
# 1. 进程隔离性(阻断 chongming 导入 + AST 检查 import 语句;自动切到 demo 项目根)
uv run python test_worker_isolation.py
# 2. 端到端(需先启动网关 uv run serve):从 demo 项目根拉起 worker 进程,
# 路由动态注册 → 跨进程 RPC → 退出摘除(404) → 重启恢复
uv run python test_process_mode_e2e.py
```
启动网关(`uv run serve`)后调用插件路由即可验证端到端:网关日志可见路由动态挂载,
worker 退出后路由自动摘除(请求变 404),重启 worker 后恢复。
### 第 8 步:打包
@@ -442,11 +415,8 @@ API 一览(`chongming_worker.storage.StorageClient`):
- `zenoh` / `pydantic` 等第三方库
- 插件自身的扩展依赖(项目根 `pyproject.toml` 中声明)
由 `test_worker_isolation.py` 双重保证:
1. **运行时阻断**:`sys.modules["chongming"] = None` 后走完整链路
(SDK 导入 → 插件配置自读 → 插件 import → 契约校验),任何网关依赖立即 ImportError
2. **静态检查**:AST 解析 SDK 各模块源码,确认无 `chongming` / `chongming.*` import 语句
隔离性由框架保证:SDK 导入链路中一旦出现 `chongming` / `chongming.*` 的 import,
运行时立即 `ImportError`(启动即暴露),无需等到部署阶段。
---
@@ -476,7 +446,7 @@ Worker 进程启动时自读 TOML 并**在自己进程内**做三方一致性校
| 路由一直 404 | worker 是否已退出(优雅退出会摘除路由);进程被 SIGKILL 时描述符残留,重启 worker 后重发布自愈覆盖 |
| 找不到插件配置 | `chongming-worker` 须在**插件项目根** `workers/<name>/` 下运行(读 `config/workers/<name>/config.toml`,`--config` 可指定全局配置路径) |
| storage.* 端点报「storage 未配置」 | 插件 config.toml 未配置 `[workers.config.storage]`(或 `enabled = false` / 连接失败降级)。补齐配置后重启 worker;启动日志可见「对象存储已就绪」或「storage 连接失败」告警 |
| 容器内 import `workers.demo` 失败 | 确认在容器工作目录(zip 根)启动,`start.sh` 内含 `cd "$(dirname "$0")"`,worker_serve 会把 CWD 加入 sys.path |
| 容器内 import `workers.demo` 失败 | 确认在容器工作目录(zip 根)启动,`start.sh` 内含 `cd "$(dirname "$0")"`,启动入口会把 CWD 加入 sys.path |
---
@@ -489,8 +459,6 @@ uv run chongming-worker --type <name> # 本地调试独立 Worker
./start.sh # 与容器一致的启动方式(uv sync + 启动)
cd .. && uv run build_plugin <name> # 进程插件打包 → dist/<name>.zip
uv run gen_models workers/<name>/config/workers/<name> workers/<name>/workers/<name> # 生成模型
uv run python test_worker_isolation.py # 进程隔离性测试
uv run python test_process_mode_e2e.py # 端到端测试(先启动网关)
```
---
+9 -9
View File
@@ -281,28 +281,28 @@ unzip myplugin.zip -d /app && cd /app && ./start.sh
---
## 9. 本地验证
## 9. 本地联调
```bash
# Zenoh Router(替代 zenohd)
# 起一个本地 Zenoh Router(替代 zenohd)
.venv/bin/python utils/worker-rs/scripts/zenoh_router.py &
# 协议级 15 项断言(描述符/RPC/流式/错误/摘除/自愈)
# 协议级端到端自检(描述符 / RPC / 流式 / 错误 / 摘除 / 自愈)
.venv/bin/python utils/worker-rs/scripts/verify_e2e.py \
--type myplugin --bin workers/myplugin/target/debug/myplugin
```
---
## 10. 与 Python SDK 的差异(有意为之)
## 10. Rust 侧行为要点
| 差异 | 说明 |
| 要点 | 说明 |
| ---- | ---- |
| 契约校验 | Python 进程内三方校验(硬失败);Rust 为**发布前自检**:无 handler 端点告警跳过,不发布残缺路由 |
| 模型生成 | Rust 用 gen_models 生成强类型结构体 + **启动时指纹校验**(TOML 变更未重生成即告警,比 Python 更强) |
| 契约校验 | **发布前自检**:无 handler 的端点告警跳过,不发布残缺路由 |
| 模型生成 | `gen_models` 生成强类型结构体 + **启动时指纹校验**(TOML 变更未重生成即告警) |
| handler 注入 | worker_id 经 `Request.worker_id`、user_info 经 `Request.user_info` 注入(非函数参数) |
| 配置缓存后端 | Rust 支持 sqlite(`local-cache`,本机与 Python 共享同一 .db)+ Redis(`redis-cache`,跨主机);缺省无缓存回退 |
| 数据库 | Rust 走 sqlx(见 §7),配置键与 Python `chongming_database` 对齐 |
| 配置缓存后端 | `local-cache`(sqlite,与 Python 共享同一 .db)/ `redis-cache`(跨主机);缺省无缓存回退 |
| 数据库 | 走 sqlx(见 §7),配置键与 `chongming_database` 对齐 |
---
+163
View File
@@ -0,0 +1,163 @@
# Worker 管理与多实例运维
网关(chongming-gateway)对 Worker 副本的运维能力:管理指令与 REST 端点、扩缩容语义、
实例定向调用,以及多网关/多实例部署时的收敛前提。inline 与 external(进程)插件共用同一套
管理协议。
> 使用入口速览见主 [README](../README.md#worker-管理与扩缩容)。
## 1. 管理协议
每个用 Worker 框架创建的副本都会注册管理 Queryable:
```text
worker/<type>/<worker_id>/__admin__
```
| 指令 | 说明 |
| ---- | ---- |
| `status` | 查询副本状态(含 `instance_id`,实例定向据此寻址) |
| `restart` | 重启该副本:副本槽位由管理循环驱动,重新生成新 `worker_id` 并更新 liveliness |
| `stop` | 停止该副本 |
| `drain` | **优雅排空**:摘除 liveliness/业务 Queryable 隔绝新请求,等待在途请求完成后退出,管理循环不重建 |
| `rescale` | 调整副本数:不带 `instance_id` 为「全局对称分摊」;带 `instance_id + absolute=true` 且命中本实例时为「逐实例精细扩缩」,target 非本实例则安全忽略 |
| `config_set` | 更新 `[workers.config]` 运行时配置(路由配置除外);成功则写回配置并重启该类型副本,任一失败返回失败明细 |
> 旧版运行时未声明 `__admin__` 管理能力时,管理指令会超时(HTTP 502,日志有提示)。
## 2. REST 管理端点(仅超管)
| 方法 | 路径 | 说明 |
| ---- | ---- | ---- |
| `GET` | `/api/v1/admin/workers` | 盘点全部副本(inline 本地注册 + external liveliness 发现) |
| `POST` | `/api/v1/admin/workers/restart` | 重启单个副本(`worker_type` / `worker_id` 查询参数) |
| `POST` | `/api/v1/admin/workers/<type>/stop` | 停止单个副本 / 某插件全部副本 |
| `GET` | `/api/v1/admin/workers/<type>/config` | 读取现有 `[workers.config]`(响应含 `source`/`mode` 标注取值来路) |
| `POST` | `/api/v1/admin/workers/<type>/config` | 更新配置(body `{updates:{k:v}}`),下发并重启 |
| `POST` | `/api/v1/admin/workers/<type>/scale` | 调整副本数(body `{count:<全局总量>}`),全局对称分摊 |
| `GET` | `/api/v1/admin/workers/<type>/instances` | 按进程实例(instance_id)分组读取副本 |
| `POST` | `/api/v1/admin/workers/<type>/scale_instance` | 逐实例精细扩缩(body `{instance_id, count}`) |
## 3. 配置热更新
- 回写插件 `config.toml` 的 `[workers]` 段(保留其他段),拒绝写保留键(`type` / `module` /
`count` 等路由与加载控制键)。
- 插件按新配置先 `on_cleanup()` 释放旧资源、再 `on_init()` 重建客户端(AI/DB client 等),
配置**无需重启网关即生效**。
- 仅 index0 副本落盘避免并发写冲突;其余副本同样应用新配置。
## 4. 扩缩容语义
- **扩容**:新增副本槽位并注册 liveliness,随即参与负载均衡。
- **缩容**:多余副本走 `drain` 优雅排空——先隔绝新请求,等在途请求完成后关闭,避免中断业务。
- **持久化**:目标数量写回插件 `config.toml` 的 `[workers].count`,重启后按新数量加载。
- **全局对称 vs 逐实例**:`/scale` 调整某类型的**全局总量**(多实例下由各实例均分);
需单独调整**某一个**外部实例时用 `/scale_instance`。
## 5. 多实例收敛(副本数分摊 / 领导选举 / MQTT Bridge 单例)
多网关或多进程部署时,以下三件事都以**共享 `[cache]` 为单一事实来源**:
```toml
[cache]
backend = "redis" # 多实例必须共享(redis 或共享卷 sqlite);单机 sqlite 即可
launch_worker_count = "toml" # 副本数/运行时配置以 toml(默认)还是 cache 为准
```
**前提**:所有网关与 external 进程指向同一 cache 后端。若各实例各用本机 cache,
副本数无法收敛、锁跨不了主机,多实例行为会退化。
### 5.1 副本数(count)按在线实例分摊
- `count` 语义为**该类 Worker 的全局目标总量**,各参与者按在线数量自动分摊。
- 在线清单与心跳:网关启动登记、每 15s 心跳续约,超 60s 未续约的实例自动清理;关闭时注销。
同宿主多网关需显式设置 `CHONGMING_GATEWAY_ID`(缺省回退 hostname)。
- external 进程实例同样登记参与分摊,避免多实例把副本数翻倍;实例标识用
`--instance` / `CHONGMING_WORKER_INSTANCE`(缺省 `{hostname}:{pid}`,跨重启持续命中请显式指定)。
- `/scale` 写全局 count 后向该类型全部存活实例广播 `rescale`,各实例按同一份数据计算 quota,
重复下发幂等。
### 5.2 inline 领导选举(多 worker 兼容)
- 同一网关的多个服务器 worker(gunicorn `-w` 等)中,同一时刻仅一个进程托管 inline worker
(副本数精确等于 `count`,而非 worker 数 × count);其余进程只做 HTTP 转发。
- 持有者崩溃后,其余进程经周期性巡检抢锁接管并拉起 worker(`set_nx` 唯一胜出,无双持有)。
- key 按 `gateway_id` 隔离,多网关各自选举、互不抢占。
### 5.3 MQTT Bridge 单例(多实例免重复响应)
- Bridge 启动采用共享缓存分布式锁(`set_nx` + 租约续约),全局仅持锁网关消费 MQTT 请求,
避免共享外部 Broker 时「一条请求被各消费一次、各回一份响应」。
- 持有者失联后租约到期,其余网关经周期巡检(`bridge_supervise_interval`,默认 15s)自动接管重建。
- 内嵌 Broker 单例按宿主隔离(`mqtt_broker:<hostname>`):同宿主多进程仅启动一个 Broker,
接管时会清理前任遗留的孤儿 Broker 子进程。
### 5.4 部署形态速查
| 部署形态 | MQTT Broker | `[cache]` 后端 |
| -------- | ----------- | -------------- |
| 单机(含多 worker) | 内嵌 amqtt | 本地 `sqlite` |
| 多实例分布式 | **外部 Broker(必须)** | **redis**(或共享卷 `sqlite`) |
## 6. 逐实例精细扩缩
同一类型跑多个外部进程(多机/多容器)时,除全局对称扩缩外,可只对**某一个实例**精确扩缩:
- **语义**:把目标实例的副本数设为绝对值 N,其它实例不动;与全局 `scale` 共存——
全局对称是默认/一键复位(`scale` 会清除整张覆盖表回到均分),逐实例是定制覆盖。
- **覆盖表**:cache 键 `worker:cm:instcounts:<type>` = `{instance_id: 绝对副本数}`;
无记录的实例回退到全局均分。
- **命中判断**:`rescale` 命中本实例(`instance_id` 与登记一致)才执行,目标是其它实例则忽略。
- **REST / UI**:`POST /admin/workers/<type>/scale_instance` 下发;
`GET /admin/workers/<type>/instances` 供前端按实例分组展示。
## 7. 实例定向调用
默认逐请求**随机负载均衡**,适合无状态插件。若插件持有只存在于某个进程本地的状态
(本地 SQLite/文件、串口、设备连接),可用实例定向把请求固定到该进程:
- **指定方式**(三入口语义一致):
- HTTP 请求头 `X-Chongming-Instance: <实例id>`
- MQTT 信封载荷顶层 `target_instance`(网关读后摘除,业务侧不可见)
- 程序内 `worker_manager.call(...)` / `call_raw(...)` / `call_stream(...)` 的 `instance_id=` 参数
- **实例 id 取值**:`--instance` → `CHONGMING_WORKER_INSTANCE` → `CHONGMING_GATEWAY_ID` →
`{hostname}:{pid}`;容器环境(K8s 注入 `CHONGMING_GATEWAY_ID`)下两侧自动一致,
裸机同宿主多网关请显式指定。可读列表见 `GET /api/v1/admin/workers/<type>/instances`。
- **粒度**:定向到**进程实例**;实例内有多个副本时,请求仍在这几个副本间随机均衡。
- **实例离线时严格报错**(HTTP 503 / MQTT `{"error": ...}`),**不静默回退**到其它实例。
- **重试不跨实例**;流式端点不重试,实例离线即失败。
- **缓存按实例隔离**:定向且 `cacheable=true` 时缓存键带实例前缀。
```bash
# 把这次请求固定到实例 edge-01 的进程
curl -H 'X-Chongming-Instance: edge-01' http://127.0.0.1:8000/api/v1/demo/state
# MQTT:载荷顶层带 target_instance(网关转发前摘掉)
# topic: chongming/demo/state payload: {"meta":{}, "data":{}, "target_instance":"edge-01"}
```
## 8. 前端管理页
`Worker 管理`(`/workers`):
- **登录**:内置 `POST /auth/login` 领取管理 Token(不依赖 auth 插件),仅 `is_superuser` 可操作。
- **列表**:副本类型 / 副本 ID / 模式(inline|external)/ 状态,支持关键字、模式、副本数筛选与分页;
筛选同时作用于下方逐实例分组表。`worker_type == "auth"` 的配置项不展示、不可改。
- **操作**:单副本重启、批量重启、插件级重启、配置编辑(结构化 JSON 校验/格式化)、
**调整副本数量**(全局对称)、**逐实例精细扩缩**(按实例分组,仅调该实例)。
- **自动刷新**:每 10s 轮询列表与分组表,异步操作结果自动反映。
- 调小副本数会触发优雅排空,前端确认提示中会说明。
## 9. external 插件的可管理性前提
1. 插件容器运行含管理能力(`__admin__` + `reload` + `rescale`)的 `chongming-worker`;
2. `config_set` / `rescale` 由副本在**其进程 cwd** 下写 `config/workers/<type>/config.toml`,
容器需挂载与网关同款的配置目录才能写回(含 `[workers].count` 持久化);
3. 其它语言自行实现的进程插件需实现 `__admin__` Queryable + liveliness + 数量管理,
否则只能被发现,无法改配置或扩缩容。
## 10. 多 worker 与 worker 副本的分工
gunicorn `-w N` 提升的是**网关的 HTTP 接入/转发能力**;插件计算能力由 Worker **副本数**
(`count`)或 external 进程数决定。高并发场景按瓶颈扩:转发瓶颈加 `-w`,计算瓶颈加 `count`
或改跑 external。