修复 BgFilter 发布窗口配置漂移

发布窗口内 callBudget 漂移改为告警计数并按 Worker 当前预算执行

Provision 验活地址改为从已校验的自定义端口派生

同步 BgFilter 调度方案与后端数据契约文档
This commit is contained in:
2026-07-22 12:10:40 +00:00
parent c03da0de0c
commit 070f8f8fa4
4 changed files with 65 additions and 42 deletions
@@ -152,7 +152,7 @@ Authorization: Bearer <internal-token>
- 如果未来确实支持多个 bucket,新增字段也必须由服务端 allowlist 校验;不能接受调用方提供任意下载 URL。
- `backgroundMode` 只允许 `flat / complex`;`segModel` 继续沿用当前 `birefnet / anime-seg` allowlist;complex 固定使用当前参数组合。
- `screenColor` 只对 flat 必填;complex 不得误接 flat 参数或熔断。
- `maxQueueWaitMs` 与 `callBudgetMs` 都是相对预算,不是跨机器绝对时间。前者从 admission 起约束排队阶段(worker 还会用 §5.2 的动态估计对其取 min);后者从取得 provider permit 起计时,覆盖签名、两次 attempt、结果校验和响应构造。`callBudgetMs` 必须等于 worker 本进程按 `N / est` 公式算出的值,不一致按 `invalid_request` 拒绝,用于快速暴露父子配置漂移。
- `maxQueueWaitMs` 与 `callBudgetMs` 都是相对预算,不是跨机器绝对时间。前者从 admission 起约束排队阶段(worker 还会用 §5.2 的动态估计对其取 min);后者从取得 provider permit 起计时,覆盖签名、两次 attempt、结果校验和响应构造。`callBudgetMs` 是父侧按 `N / est` 公式算出的“配置指纹”,仅作核对:worker 始终以自己按同一公式派生的值执行,不一致时不拒绝请求,而是记录 warn 日志并递增漂移指标。发布调优 N / est 时新旧进程共存的瞬态漂移因此不会误伤在途任务;持久性漂移的硬拦截由部署脚本的共享 env 对齐校验承担。
- JSON body 设置很小的固定上限;源图字节不进入该 JSON。
签名 URL 必须在取得 provider permit 后、每次 attempt 前生成,避免排队期间过期。签名 URL 只存在于子 worker 内存和发往 BgFilter 的请求中。
@@ -262,7 +262,7 @@ attempt 公式的依据:BgFilter 服务端高并发时单图处理约 `1-3s`
flat 调用还要由父侧从可分配预算中扣除 `39s`:阿里云 request timeout(默认 `30s`)与本地处理余量 `7s` 构成 `37s` fallback 窗口,另有 `2s` 内部响应传输窗,避免排队吃完全部预算后名义上有 fallback、实际上已无时间执行。
`N` 与 `est` 必须由父子进程使用同一份有效值:父侧要用它们算 `callBudgetMs` 与 client timeout,worker 要用它们算 attempt 与队列估时。两者都放在共享 API 基础环境中作为单一来源,worker 专属环境不得悄悄覆盖;worker 侧还应校验请求携带的 `callBudgetMs` 与本进程公式值一致,配置漂移时返回 `invalid_request` 快速暴露。
`N` 与 `est` 必须由父子进程使用同一份有效值:父侧要用它们算 `callBudgetMs` 与 client timeout,worker 要用它们算 attempt 与队列估时。两者都放在共享 API 基础环境中作为单一来源,worker 专属环境不得悄悄覆盖。worker 侧把请求携带的 `callBudgetMs` 与本进程公式值比对,不一致只告警并计指标、仍以本进程值执行——运行时校验必须容忍发布重启窗口内的瞬态漂移,持久漂移由部署脚本对齐校验在启动前拦截。
inline / External v1 当前没有显式 `RequestContext` deadline 时,内部 RPC 仍必须有界:`callBudgetMs` 同公式,`maxQueueWaitMs` 默认取与 `callBudgetMs` 等长的额度,因此默认父 client timeout 为 `2 × callBudgetMs + 2s`;后续若同步请求显式注入 deadline,再按同一 `maxQueueWaitMs` 公式从剩余预算派生。
@@ -405,6 +405,7 @@ BgFilter 成功二进制不是一份新的业务资产:
- `bgfilter_internal_waiting_requests`(即 admission 后等待 `N` permit 的队长)
- `bgfilter_internal_queue_timeout_total{bound}`(排队超时按触发边界 `estimate | parent` 分维度)
- `bgfilter_internal_call_budget_drift_total{mode}`(请求 `callBudgetMs` 与本进程公式值不一致;发布窗口内短暂非零正常,持续增长说明父子 N / est 真漂移)
- `bgfilter_internal_in_flight`
- `bgfilter_internal_request_seconds{mode,outcome}`
- `bgfilter_provider_http_seconds{mode,attempt,outcome}`
@@ -443,6 +444,8 @@ BgFilter 成功二进制不是一份新的业务资产:
4. 再重启使用内部 client 的 API / external-generation-worker / controller。
5. 确认所有父进程只访问内部 endpoint,BgFilter provider 日志中不再出现父进程直连。
第 3-4 步之间存在“新 worker + 旧父进程”共存窗口,恰好覆盖旧 external-generation-worker 的优雅排空阶段。调优 N / est 的发布会让窗口内请求携带旧公式的 `callBudgetMs`:worker 按 §4.1 只告警不拒绝、以自身公式值执行,在途任务照常完成或走既有 fallback。发布期间 `bgfilter_internal_call_budget_drift_total` 短暂增长属预期,窗口结束后应停止增长;持续增长才需要排查 env 漂移。
内部 worker 不可用时禁止自动 direct fallback。flat 仍可走业务已有阿里云 / 本地 fallback;complex 明确失败。需要整体回滚时回滚父、子进程版本和配置,不在运行中混用两种 BgFilter 调度方式。
`bgfilter-worker` 收到停止信号后先停止接收新请求,并让仍在排队等待 `N` permit 的请求立即以类型化错误收口(父侧 flat 走 fallback),只等待已取得 permit 的调用和已启动 provider attempt 排空。排空上界由 `callBudgetMs`(`N = 16`、`est = 5s` 时约 `321s`)决定,因此 systemd unit 固定使用 `TimeoutStopSec=900` 仍有充分余量;部署脚本的同步 `systemctl stop` 必须允许该窗口完成,不能沿用 systemd 常见的约 `90s` 默认值强杀在途调用。排队请求不参与排空等待——它们尚未发出任何外部请求,快速失败是安全的。
@@ -461,7 +464,7 @@ BgFilter 成功二进制不是一份新的业务资产:
- listener 在 body 解析前执行鉴权与 admission;保险丝 `Q`(默认 `2048`)触达后新请求立即返回 `overloaded`,内核 socket backlog 按独立固定值验证。
- 排队时间只消耗 `min((队长+5)×est×2, maxQueueWaitMs)`,不侵蚀 `callBudgetMs`;排队超时返回 `deadline_exceeded (phase=queue)` 且带触发边界;`callBudgetMs` 自取得 `N` permit 起算,deadline 到达后不开始新的 provider attempt。
- `maxQueueWaitMs <= 0` 时父侧不发送请求:flat 直接 fallback,complex 直接失败。
- `callBudgetMs` 与 worker 本进程公式值不一致时按 `invalid_request` 拒绝;attempt、callBudget、client timeout 全部由 `N / est` 运行时派生,代码不存在硬编码结果值。
- `callBudgetMs` 与 worker 本进程公式值不一致时不拒绝:worker 以自身公式值执行,记 warn 并递增漂移指标;发布调优 N / est 的新旧进程共存窗口内,在途 flat 任务仍能正常执行或走既有 fallback,不得因瞬态漂移触发 `invalid_request`(该码禁止 fallback)。attempt、callBudget、client timeout 全部由 `N / est` 运行时派生,代码不存在硬编码结果值。
- parent client timeout 精确取 `maxQueueWaitMs + callBudgetMs + 2s`,helper 保持 infallible;父绝对预算通过 `maxQueueWaitMs` 的派生公式预先约束,结果校验等待也必须 deadline-aware,不能只在校验完成后事后判超时。
- 第一次失败后预算不足时不开始第二次;父侧从不重试整次内部 RPC。
- 父业务预算仍有效时,flat 两次失败、熔断、overload、内部 RPC deadline 或断连仍走“阿里云 → 本地”;complex 任意失败直接失败且不读写熔断。
@@ -482,7 +485,7 @@ BgFilter 成功二进制不是一份新的业务资产:
- 父进程 `GENARRATIVE_BGFILTER_WORKER_BASE_URL`、子 worker `HOST / PORT` 和部署 readiness URL 必须指向同一个 `127.0.0.1:<port>` endpoint;旧非空配置不能因为“无需补默认值”而绕过一致性检查。
- `genarrative-bgfilter-worker.service` 必须保持 `TimeoutStopSec=900`,覆盖 `callBudgetMs` 排空上界(约 `321s`)与停止收口余量;停止时排队请求立即类型化失败,不参与排空。
- 发布目录切换前拒绝缺失、空、符号链接或权限错误的内部 Token 文件;父、子有效 Token 文件路径必须相同。
- 生产运行期巡检同时检查 `genarrative-bgfilter-worker.service` 为 active 且 `127.0.0.1:8083/readyz` 成功,不能只依赖 systemd 自动重启。
- 生产运行期巡检同时检查 `genarrative-bgfilter-worker.service` 为 active 且 worker `readyz` 成功(默认 `127.0.0.1:8083`),不能只依赖 systemd 自动重启。provision / deploy 的发布验活 URL 必须从已验证的 env `HOST/PORT` 派生,不得硬编码默认端口;若运维自定义 `GENARRATIVE_BGFILTER_WORKER_PORT`,必须同步覆盖巡检的 `GENARRATIVE_HEALTH_PATROL_BGFILTER_BASE_URL`(巡检是独立进程,不读取 worker env,默认值不会自动跟随)。
- `npm run dev` 启动独立 BgFilter 子进程并使用解析后的第五个端口;`all` 角色不内嵌 listener,单模块入口、watch、状态文件和退出清理没有遗留进程或硬编码端口。
本地全进程调度门禁使用已构建的 `api-server` binary 和 loopback mock provider,不读取真实 OSS / BgFilter 密钥,也不访问真实外部服务。`load-smoke` 固定验证 `R = 32 / 40 / 48、N = 16`(`Q` 用小值场景单独验证保险丝行为);`fault-smoke` 用独立 worker / mock 生命周期验证保险丝触达快速拒绝、queue timeout 双边界、`503 → 200` 顺序重试、两次 `503` 后 provider exhausted,以及 provider 成功响应 body 中途 reset 后第二次 attempt 串行成功。默认读取 `server-rs/target/debug/api-server(.exe)`;在 WSL 或自定义 target 目录运行时,通过 `GENARRATIVE_BGFILTER_SMOKE_BINARY` 指定 binary: