feat: enforce durable queue resource limits and backpressure

This commit is contained in:
2026-09-17 17:25:56 +08:00
parent 3ce7d2c104
commit 5c8825e4bc
38 changed files with 1231 additions and 30 deletions

View File

@@ -1,6 +1,6 @@
# 数据模型与实现基线
更新:2026-09-17。数据库统一禁止外键。当前已实现任务执行、安全重试、运行中取消与计划调度;剩余资源控制、订阅及流程编排继续开发。
更新:2026-09-17。数据库统一禁止外键。当前已实现任务执行、安全重试、运行中取消、计划调度和队列限速/背压;应用配额、公平性、订阅及流程编排继续开发。
## 1. 模块与依赖
@@ -124,4 +124,6 @@ P2 服务端已新增会话隔离、两步派发、租约、并发许可、结
000008_no_foreign_keys 移除历史 70 个外键,保留索引、唯一约束、CHECK、触发器和数据。旧迁移已在回归库应用,按不可变校验和约定保留原文件;新装按顺序运行至当前版本,最终结构同样为零外键。后续迁移禁止再引入外键。升级旧库时先停用旧服务,应用完整迁移再启动新版;迁移需要 DDL 锁,应在目标部署环境演练维护窗口。
000009_queue_resources 增加 Queue.MaxUnfinishedTasks、UnfinishedTasks、StartWindowUntil、StartsInWindow,Go 字段与 SQL COMMENT 均有中文语义,实体仍为 32。未终结任务按 WAITING/LEASED/RUNNING/NEEDS_REVIEW 回填;窗口初始为空、计数为零。运行计数与任务/开始授权同事务维护,不改变队列配置 revision,配置变更不能重置它们。降额允许已有使用量暂时超过新上限,不删除任务或撤销已有许可;详见 [队列契约](api/queues.md) 和 [资源回归](reports/phase-3-queue-resources.md)。
命令与验证记录见 [开发计划](08-development-plan.md)、[基础阶段验证报告](reports/phase-0.md) 与 [取消及无外键回归](reports/phase-3-cancellation.md)。历史阶段报告描述当时结构,当前约束以本页为准。

View File

@@ -27,7 +27,7 @@
| P0 基础与数据模型 | Go workspace、独立 SDK module、公共/Pulse/Weave 模型、MySQL 迁移、配置、健康检查 | 编译/竞态/静态检查;真实 MySQL 建表、隔离、唯一性、预算、不可变版本、迁移失败防护;SDK 健康调用 | 已完成并通过阶段回归(见 P0 报告) |
| P1 任务受理闭环 | 应用凭据与作用域权限、队列、幂等提交、查询、待执行取消、受理事件;SDK 客户端 | API/SDK 创建查询闭环;重复键与内容冲突、响应未确认重报、跨命名空间拒绝、参数边界 | 已完成并通过阶段回归(见 P1 报告;真实断路/提交 ACK 丢失注入留后续故障阶段) |
| P2 Worker 执行闭环 | WSS 接入、能力/容量、领取与开始、租约、续租、结果确认、排空与恢复;Worker SDK | 真 Worker 执行;失联/重连/重复确认/旧尝试拒绝;业务效果未知转核查;节点故障接管 | 已完成并通过阶段回归:服务端、自动 SDK、历史查询、完整 Schema 与真实进程强杀恢复;测试拓扑和剩余生产故障矩阵见 P2 验收报告 |
| P3 Pulse 完整能力 | 安全重试、延时、Cron/固定间隔、漏跑策略、计划版本、运行中取消、并发许可、配额/背压、可靠结果订阅 | 多实例领取/触发竞争、重试上限、取消竞争、队列隔离、积压恢复、事件重复交付 | 进行中:安全重试、计划调度和运行中取消及 SDK 场景已实现并回归;配额/背压和可靠订阅待开发 |
| P3 Pulse 完整能力 | 安全重试、延时、Cron/固定间隔、漏跑策略、计划版本、运行中取消、并发许可、配额/背压、可靠结果订阅 | 多实例领取/触发竞争、重试上限、取消竞争、队列隔离、积压恢复、事件重复交付 | 进行中:安全重试、计划调度、运行中取消、队列控制/限速/积压背压及 SDK 场景已实现并回归;应用级配额、业务键许可、优先级/公平性和可靠订阅待开发 |
| P4 Weave 主流程 | SDK 定义构建与发布校验、实例、顺序/分支/结构化并行汇合、信号/定时等待、版本绑定 | 重启接续、早到/重复信号、并行竞争、内部事件重放、发布版本兼容;端到端通用流程 | 待开发 |
| P5 Weave 恢复与查询 | 有界查询、暂停继续、取消、人工核查恢复、控制门控与审计 | 轮次与尝试独立预算;暂停/开始/回执/超时竞争;恢复保留预算;未知结果不盲目重复 | 待开发 |
| P6 生产验证与运维 | 完整监控、凭据轮换、备份恢复、滚动升级、运行手册、可复现负载和故障工具 | 对照生产验收清单执行,报告环境/指标/遗留项;容量、RPO/RTO 按真实拓扑验证 | 待开发 |
@@ -44,6 +44,7 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
| 有限安全重试 | 明确失败 → 持久化退避 → 新尝试 → 查询完整历史 | P3,已实现完整示例 |
| 定期数据同步 | 发布计划 → 触发任务 → 暂停/恢复 → 查看触发记录 | P3,已实现通用快照示例 |
| 运行中取消 | 保存取消意图 → 停止处理器 → 确认结果 → 查询并排空 | P3,已实现完整示例 |
| 队列限速与背压 | 暂停 → 满额拒绝 → 原回执核对 → 释放空间 → 限速执行 | P3,已实现完整示例 |
| 并行生成结果 | 发布流程 → 并行执行 → 汇合 → 完成 | P4 |
| 审核与外部回执 | 提交流程 → 等待信号 → 授权信号注入 → 继续 | P4 |
| 外部作业轮询 | 提交一次 → 有界查询处理中状态 → 回执或最终结果 → 超限核查 | P5 |
@@ -80,6 +81,7 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
- [P3 安全重试阶段记录](reports/phase-3-safe-retry.md)
- [P3 计划调度阶段记录](reports/phase-3-schedules.md)
- [P3 取消与无外键回归](reports/phase-3-cancellation.md)
- [P3 队列资源控制回归](reports/phase-3-queue-resources.md)
- [P2 服务端阶段记录](reports/phase-2-server.md)
- [P2 SDK 协议阶段记录](reports/phase-2-sdk-protocol.md)
- [P2 自动 Worker 运行器记录](reports/phase-2-worker-runtime.md)

View File

@@ -2,7 +2,7 @@
- 文档版本:0.2
- 整理日期:2026-09-17
- 阶段:P0–P2 已通过阶段验收;P3 安全重试、计划调度与运行中取消已通过回归,数据库已改为无外键,P3–P7 继续推进,尚未完成全平台生产验收
- 阶段:P0–P2 已通过阶段验收;P3 安全重试、计划调度、运行中取消和队列资源控制已通过回归,数据库已改为无外键,P3–P7 继续推进,尚未完成全平台生产验收
- 适用范围:Orbit 统一平台,包含 Pulse 任务调度模块、Weave 流程编排模块与公共能力。平台面向多个业务系统复用。
## 阅读顺序

View File

@@ -11,7 +11,7 @@
| `/v1/tasks` 提交、查询、分页与待执行取消 | P1 已实现 | [任务接口](tasks.md) |
| `/v1/tasks/{task_id}/attempts` 与尝试详情 | P2 已实现 | [执行历史](execution-history.md) |
| `/v1/tasks/{task_id}/attempts/{attempt_id}/evidence` 与证据详情 | P2 已实现 | [执行历史](execution-history.md) |
| `/v1/queues` 创建与查询 | P1 已实现 | [队列接口](queues.md) |
| `/v1/queues` 创建、查询及 `:configure` 控制 | 已实现队列并发、开始速率、积压背压与暂停/恢复 | [队列接口与 SDK 场景](queues.md) |
| 凭据校验与初始化 CLI | P1 已实现 | [鉴权](authentication.md) |
| `/v1/worker/connect` | P2 已实现并通过阶段验收 | [Worker 契约](worker.md)、[Schema](worker-schema.md) |
| 任务冻结策略与 Worker `retryable` | P3 安全重试已实现 | [安全重试](retries.md) |

View File

@@ -19,7 +19,7 @@ go run ./cmd/orbit-admin bootstrap \
ORBIT_MYSQL_DSN 通过环境注入,命令不读取 .env。所有名称必填,以字母/数字开头,后续允许字母、数字、`_ . : -`;environment 最长 32,其余最长 128。输出文件必须不存在,以 0600 创建;原始凭据不会打印到 stdout/stderr。`.local/` 和 `*.credentials.json` 已被 Git 忽略。
输出 JSON 包含 application_id / namespace_id / queue_id / credential_id / token / expires_at。初始凭据有效期为 30 天,权限包括当前命名空间内的五个 P1 动作和全部队列。初始队列 max_concurrency=4、rate_per_second=10;这是开发初始配置,执行限流随 P2–P3 落地。
输出 JSON 包含 application_id / namespace_id / queue_id / credential_id / token / expires_at。初始凭据有效期为 30 天,权限包括当前命名空间内的五个 P1 动作和全部队列。初始队列 max_concurrency=4、rate_per_second=10、max_unfinished_tasks=100000;并发、开始限速与积压背压已生效,语义及管理方式见 [队列接口](queues.md)。这些默认值是开发基线,不是容量承诺。
资源、凭据摘要和初始化审计在一个事务中提交。交付文件先写入并同步到磁盘,成功后再提交事务。如果文件写入失败则回滚并移除文件;如果已经交付后提交无法确认,保留文件并报错,应使用其中凭据核对结果,不能直接覆盖或重复创建。

View File

@@ -1,6 +1,6 @@
# 队列接口 v1
状态:P1 已实现创建与查询。更新、暂停、实际派发并发与速率控制随 P2–P3 实现;当前参数是后续执行所需的持久化配置。
状态:已实现创建、查询、配置替换、暂停/恢复、共享并发、开始速率与未终结任务背压。MySQL 是跨节点额度的权威存储;没有额外缓存或消息队列依赖。
## POST /v1/queues
@@ -11,6 +11,7 @@
| name | string | 1–128 字符,以 ASCII 字母/数字开头,其后允许字母、数字、`_ . : -`;命名空间内唯一 |
| max_concurrency | uint32 | 必填,1–100000 |
| rate_per_second | uint32 | 必填,1–1000000 |
| max_unfinished_tasks | uint32 | 可选,省略/零为 100000;有效范围 1–1000000,包含待核查任务 |
```sh
curl "$ORBIT_URL/v1/queues" \
@@ -30,7 +31,8 @@ curl "$ORBIT_URL/v1/queues" \
"queue":{
"id":"11111111111111111111111111111111",
"namespace_id":"33333333333333333333333333333333",
"name":"images","paused":false,"max_concurrency":8,"rate_per_second":100,"revision":"1"
"name":"images","paused":false,"max_concurrency":8,"rate_per_second":100,
"max_unfinished_tasks":100000,"unfinished_tasks":"0","revision":"1"
}
}
}
@@ -49,3 +51,97 @@ curl "$ORBIT_URL/v1/queues/$ORBIT_QUEUE_ID" -H "Authorization: Bearer $ORBIT_TOK
```
SDK:`Client.GetQueue(ctx, queueID)`。范围限制、重复创建和同名冲突已纳入 [P1 回归](../reports/phase-1.md)。这些配置上限不是生产容量承诺。
unfinished_tasks 为 uint64 的十进制字符串,表示当前 WAITING、LEASED、RUNNING、NEEDS_REVIEW 的总数。它是运行计数,变化不增加队列配置 revision。创建/配置回执保存操作时的快照,获取实时积压需要再次 GetQueue。
## POST /v1/queues/{queue_id}:configure
需要 queues:manage 及对应队列范围。完整替换控制参数,不改变名称、队列身份、已受理任务的冻结策略或历史结果。
| 字段 | 类型 | 要求 |
| --- | --- | --- |
| expected_revision | string | 必填,非零 uint64 的十进制字符串 |
| reason | string | 必填,非空白,最多 1024 个 Unicode 字符,写入审计 |
| paused | bool | 必须显式提供;省略/null 都拒绝 |
| max_concurrency | uint32 | 必填,1–100000 |
| rate_per_second | uint32 | 必填,1–1000000 |
| max_unfinished_tasks | uint32 | 必填,1–1000000;配置命令不使用零值默认 |
```sh
curl "$ORBIT_URL/v1/queues/$ORBIT_QUEUE_ID:configure" \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: queue-control-001' \
--data '{"expected_revision":"1","reason":"limit background traffic","paused":false,"max_concurrency":2,"rate_per_second":1,"max_unfinished_tasks":100}'
```
成功 200 的 data 形状与创建一致,队列配置 revision 加一。配置、并发限制同步、审计、事件和幂等回执在一个事务提交。原键、原 expected_revision 和原内容重报返回原快照,deduplicated=true;同键不同内容为 409 IDEMPOTENCY_CONFLICT,新键使用旧版本为 409 STATE_CONFLICT。权限每次重新检查。
SDK:`Client.ConfigureQueue(ctx, queueID, key, orbit.ConfigureQueueRequest{...})` 返回 CreateQueueResult。先 GetQueue 读取版本,网络结果不明时保留原命令核对;只有确认版本冲突并重新评估后,才建立新的配置操作。
## 并发、限速与背压的区别
| 控制 | 实际行为 |
| --- | --- |
| paused=true | 阻止新的领取和 Start;继续受理任务、计划生成、续租、结果确认、取消及恢复 |
| max_concurrency | 限制队列有效执行许可,包括 LEASED 和 RUNNING;许可在结果/取消/恢复事务中释放 |
| rate_per_second | 每个数据库一秒计数窗口内最多提交的 Start 授权数;续租和原回执重报不计数 |
| max_unfinished_tasks | 限制新任务进入队列;等待、退避、预占、运行、待核查都占一个名额 |
速率窗口由第一次成功 Start 建立,保存到 MySQL;所有节点共享。窗口结束后的下一次 Start 建立新窗口。一次窗口内可突发至设定值,窗口交界附近可能出现接近两倍的短时突发;这不是“任意滑动一秒”的承诺,也不限制业务处理器内部发出请求的速率。开始授权成功不证明外部业务效果已经发生。
领取还预留尚未开始尝试所需的窗口空间,额度不足时保持 WAITING,不创建额外尝试。若领取后管理员降低速率,Start 可返回 WS RATE_LIMITED;不批准开始、不写回执、不增加尝试次数。自动 SDK 在同一租约内按原消息身份退避核对,底层客户端也必须遵守剩余租约,不能绕过开始授权执行。限流不延长总截止、开始截止或尝试预算。
降并发不撤销已有许可,超出新上限时等待在途工作收尾再领取;降速率保留当前窗口已用次数;降积压上限保留已有任务,禁止新受理直到低于新上限。调整参数、暂停/恢复、断开连接和节点重启都不能清零使用量。
任务第一次进入 SUCCEEDED、FAILED 或 CANCELLED 时,积压额度同事务减一;重试和待核查不释放、不重复增加。运行中取消只保存意图,仍占名额。迟到证据及重复确认不改变计数。人工核查解除入口仍待 P5,不能为了释放额度把未知结果改成成功/取消。
### HTTP 429 QUEUE_FULL
满额时新的 Submit 返回 429,`Retry-After: 1`,错误信封为:
```json
{"request_id":"44444444444444444444444444444444","error":{"code":"QUEUE_FULL","message":"queue unfinished task limit reached; retry later using the original identity"}}
```
该请求没有受理任务,新增占位幂等记录也回滚。已受理请求的同身份重报优先返回原回执,不被当前满额拒绝。释放空间后使用被拒请求的原键、原内容有限重试;建议至少等待 Retry-After 并加入抖动/总截止。若绝对 available_at/start_deadline_at 已过期,原请求会被参数规则拒绝,不能无限重试。
SDK 返回 `*orbit.APIError`,Code=QUEUE_FULL、StatusCode=429、RetryAfter=time.Second。RetryAfter 解析服务端整数秒建议,缺失/无效/超过一小时为零;SDK 不自动重报业务写请求。取消、查询、结果上报和管理配置不受受理满额限制。
计划触发也使用相同名额。部分补跑已生成的任务正常提交;满额的原触发点保留为下一游标,不创建假任务或跳过记录。下一批继续使用冻结的漏跑策略,因此等待期间超过补偿窗口的点仍可能按策略记录 SKIPPED。队列暂停仅控制执行,要停止计划生成应另外暂停计划。
## 完整 SDK 场景
源码:[resources/main.go](../../sdk/go/examples/resources/main.go)。先完成当前迁移并启动服务,初始化带 Worker 权限且 queues:manage 范围为全部队列的专用开发凭据:
```sh
mkdir -p .local
chmod 700 .local
go run ./cmd/orbit-admin bootstrap \
--application resources-demo --namespace default --environment development \
--queue bootstrap --worker-access --output .local/resources.credentials.json
cd sdk/go
ORBIT_URL=http://127.0.0.1:8080 \
ORBIT_CREDENTIALS_FILE=../../.local/resources.credentials.json \
go run ./examples/resources
```
示例创建独立队列(积压 2、并发 2、每秒 1 次开始),暂停后提交两个任务;第三个被拒,重复第一个仍得到原回执;取消第一个释放空间,用第三个原身份重新提交,然后恢复并执行两个纯数据回显任务。
```text
backpressure=QUEUE_FULL http=429
duplicate=true rejected_request_retried=true
task=<第二个任务ID> state=SUCCEEDED attempts=1
task=<第三个任务ID> state=SUCCEEDED attempts=1
unfinished=0 rate_per_second=1 worker_drained=true
```
每次运行创建独立队列和任务,历史保留,不硬删除。无外部业务副作用;任务自带 20 秒总截止,失败非零退出并有界收尾。示例遇到未知网络结果会退出,不自动换键重建资源。
## 升级与未完成范围
000009 增加四个带中文说明的队列字段,回填所有既有未终结任务;旧配置版本及任务内容保持不变。先停止旧服务和写入,运行迁移,再启动新版。只读检查不能代替升级维护窗口;大表回填及混合版本升级待生产环境验证。
创建命令省略新字段时,幂等摘要保留旧字段集合。旧创建回执不含新字段,SDK 解码为零,只表示历史快照缺少信息,实时配置另行 GetQueue。配置接口是新能力;自动 SDK 已能处理 RATE_LIMITED,进行降额管理前应先升级 Worker。
本阶段验证见 [资源控制报告](../reports/phase-3-queue-resources.md)。仍需应用级跨命名空间配额、同业务键许可、可配置优先级及应用间公平性、可靠事件积压限制和容量验收;不能用单队列控制替代这些要求。

View File

@@ -56,6 +56,8 @@ Cron 段顺序为分钟、小时、月日、月、星期;支持 `*`、列表
所有任务策略在 task 内冻结,字段同 SubmitTaskRequest;payload 最大 256 KiB,数字文本保持精确。task 中禁止绝对 available_at / start_deadline_at,每次生成时立即可受理,task_timeout_ms 从本次实际创建时间计算。计划结束不取消已经生成的任务,也不缩短其任务时限。
生成任务受 [队列积压上限](queues.md) 约束。满额时保留未受理触发点,本批较早已生成部分仍提交;释放空间后按冻结的漏跑策略接续,超过补偿窗口的点仍按策略跳过。队列 paused 仅停止领取/开始,停止计划生成需使用计划暂停接口。
创建请求示例,ID 和未来时间替换为实际值:
```json

View File

@@ -180,6 +180,7 @@ SDK:`Client.CancelTask(ctx, taskID, key, request)`。此操作不承诺回滚
| 409 STATE_CONFLICT | 当前状态或版本不允许操作,先查询 |
| 413 BODY_TOO_LARGE | 减小请求或改为传递业务对象引用 |
| 415 UNSUPPORTED_MEDIA_TYPE | 使用 application/json |
| 429 QUEUE_FULL | 本次未受理,按 Retry-After 有限退避并保留原身份;已受理的原回执不受当前满额限制 |
| 503 UNAVAILABLE | 结果可能未知;保留原幂等身份,有限退避后核对 |
```json

View File

@@ -129,7 +129,9 @@ receipt 是原操作的不可变确认;lease 是本次重新核验的执行权
平台在同一事务中分配队列并发许可、创建尝试、递增任务执行代次和次数、保存 LEASED 与派发事件。领取本身消耗一次 max_attempts 预算,包括最终未获开始授权的尝试,预算不会因重连或过期恢复重置。
运行中的进程总容量跨其所有旧/新会话统计。声明容量减少、断开连接或重复结果,都不能重复释放额度。当前已实施队列 max_concurrency;rate_per_second、其他配额和调度策略在 P3 完成。
运行中的进程总容量跨其所有旧/新会话统计。声明容量减少、断开连接或重复结果,都不能重复释放额度。已实施队列 max_concurrency、rate_per_second 及未终结任务上限;参数含义、窗口突发、暂停及降额语义见 [队列资源控制](queues.md)。应用级配额、业务键许可与公平性仍在 P3 推进。
领取前核对速率窗口及已有 LEASED 预留,满额时不新建尝试。Start 在同一队列事务内消费开始预算,原 ACK 重报不重复计数。领取后若配置降额,Start 可能返回 RATE_LIMITED;保持原消息身份在剩余租约内退避核对。续租、结果确认、取消及恢复不消费新开始额度。
每个节点每 2 秒发起一批恢复扫描,每批最多 100 项:

View File

@@ -72,6 +72,15 @@
- 异常:普通 context 错误、通知丢失和未配合停止不能自动判定取消成功;未知效果进入核查。CAS 冲突先查询,网络结果未知保留原身份核对。
- 清理:无外部业务副作用;历史保留,每次运行独立身份,失败非零退出并有界收尾。
## 已可运行:队列限速与积压背压
- 源码:[资源控制示例](../../sdk/go/examples/resources/main.go),[初始化、字段与完整输出](../api/queues.md)。
- 前置:当前迁移与服务,凭据具备全部队列的 queues:manage、任务及 Worker 权限。
- 步骤:创建并暂停积压上限为 2 的队列 → 第三个任务被 429 拒绝 → 满额下核对第一条原回执 → 取消释放空间 → 用被拒原身份再提交 → 恢复并按每秒 1 次开始执行。
- 预期:QUEUE_FULL、duplicate=true、两个任务 SUCCEEDED/attempts=1、unfinished=0。
- 异常:网络结果未知保留原身份,降额不清零用量,待核查任务仍占名额;跨节点及重启证据见 [资源回归](../reports/phase-3-queue-resources.md)。
- 清理:每次新建独立队列,任务 20 秒总截止;无外部副作用,历史保留,失败非零退出并有界收尾。
## 计划场景与验收标准
| 场景 | 输入与步骤 | 预期结果 | 必测异常 | 状态 |

View File

@@ -0,0 +1,75 @@
# P3 队列资源控制回归记录
验证日期:2026-09-17。本阶段完成队列配置、并发降额、开始限速及未终结任务背压。P3 仍需应用级配额、业务键许可、优先级/公平性和可靠订阅;持续目标保持进行中。
## 1. 已实现范围
- `POST /v1/queues/{id}:configure` 完整替换暂停、并发、开始速率和未终结任务上限,带授权、CAS、稳定身份、事件和管理审计。
- 基于数据库时间的一秒开始计数窗口,多节点共享;Start 事务消费额度,旧回执重报不重复计数,节点更换不清零。
- 领取预留尚未开始的窗口空间,满额时保持 WAITING。领取后降额可使 Start 返回 RATE_LIMITED,自动 SDK 保留原消息身份退避核对。
- 未终结任务额度涵盖等待、预占、运行和核查;首次受理增加、首次终态减少,重试和迟到证据不重复变更。
- 满额受理返回 HTTP 429 / QUEUE_FULL / Retry-After: 1;原已受理请求仍可补取回执。SDK 提供类型化错误及 RetryAfter,不自动重报写请求。
- 计划补跑受相同积压上限限制,保留未受理的原触发点;释放空间后按冻结漏跑策略接续。
- 迁移 000009 增加四个队列字段并回填既有未终结任务,Go/SQL 均有中文说明,无数据库外键。
- 源码核对 257 个直接声明的持久化字段均有中文注释;实体仍为 32 个。
- [队列接口与完整 SDK 场景](../api/queues.md)、[resources 示例](../../sdk/go/examples/resources/main.go) 同步交付。
## 2. 环境及执行结果
macOS、Go 1.27.1、Docker 单实例 MySQL 8.4.10。使用独立临时数据库、随机凭据及回环端口,结束后清理测试容器,未使用业务数据库。
```sh
make fmt test vet build
make integration
cd sdk/go
GOWORK=off go test -race ./...
GOWORK=off go vet ./...
```
全部通过。完整集成后补充了并发开始竞争、Start 回滚、配置作用域及旧库额度回填断言,对 QueueResources 与 SafeRetryUpgrade 重新运行真实 MySQL 竞态回归并通过;修改的测试包再次通过 vet。没有用跳过数据库的普通单元结果代替真实集成证据。
| 测试组 | 实际结果 |
| --- | --- |
| 完整 MySQL 集成 | PASS,64.830 秒 |
| SDK Worker 全部连接/运行器回归 | PASS,80.819 秒 |
| SDK 计划生命周期 | PASS,含包开销 4.579 秒 |
| 新增 SDK 资源生命周期 | PASS,3.19 秒,含包开销 4.414 秒 |
| 真实服务/Worker 进程故障组 | PASS,21.425 秒,包含新增积压额度强杀恢复 |
| 补充资源与升级检查 | PASS,16.228 秒;资源用例 11.00 秒,升级用例 3.85 秒 |
| 九个完整示例 | health / tasks / worker-protocol / worker / history / retry / schedules / cancellation / resources 全部通过 |
上述为测试用时,不是吞吐或延迟指标。
## 3. 数据库及竞争证据
[资源集成用例](../../internal/storage/mysql/queue_resources_integration_test.go) 验证:
- 积压上限为 3 时,16 个并发提交恰好 3 个受理,其余为 QUEUE_FULL;拒绝请求不残留幂等占位,已受理原身份在满额下仍可查询回执。取消后可再次受理。
- 两个节点、两个独立 Worker 实例先各领取任务,随后将开始上限从 4 降到 2,四个并发 Start 恰好两个通过,其余限流;任务/许可保留。
- 强制 Start 事件写入失败后,任务仍为 LEASED,开始计数为零,原消息可在故障解除后重试。
- 跨命名空间及队列范围受限的配置修改被拒,拒绝操作不残留配置身份。
- 暂停禁止领取和 Start,但仍可提交与取消;配置同身份返回原快照,旧版本新操作冲突,成功变更各有一条审计。
- rate=1 时未开始预留阻止额外领取;Start ACK 重报不重复计数。切换服务节点并 Resume 后窗口仍有效,两次实际 authorized_at 至少相隔一秒。
- 从两张在途许可降到并发 1、积压 1,不撤销在途执行;超过上限时不再领取。已领取 Start 限流后,用原消息在下一窗口成功,不增加尝试次数。
- 强制任务事件写入失败时回滚积压增加;明确失败后重试保持一个名额,UNKNOWN 耗尽后核查继续占名额;WAITING 截止及重复恢复只释放一次。
- 计划在积压 1 下只生成一条任务,游标停在下一原触发点;取消释放空间后下一批准确接续该触发点。
既有取消、结果 ACK 丢失、许可恢复及计划事务回归同步通过。额外的计数核对直接比较队列 counter 与任务真实状态集合,避免只检查返回 DTO。
[既有 v4 升级用例](../../internal/storage/mysql/retry_upgrade_integration_test.go) 继续走正式迁移到当前版本,新增验证 WAITING 和 NEEDS_REVIEW 合计回填为 2、默认上限 100000、原队列 revision=1 不变。旧任务各字段、策略、状态及重复迁移检查仍通过。[无外键升级用例](../../internal/storage/mysql/no_foreign_keys_integration_test.go) 同步验证当前外键为零及既有索引保留。
## 4. HTTP、SDK 和进程恢复
[SDK 用例](../../sdk/go/queue_resources_integration_test.go) 对真实 HTTP/WS 服务创建独立队列,验证省略 paused 被拒、429/QUEUE_FULL/RetryAfter、满额下重复受理回执、取消释放、原被拒身份重试、暂停期间不消耗尝试、恢复后自动 Worker 完成两个任务及精确整数保留。读取实际开始历史验证 rate=1 的授权间隔,最终 unfinished_tasks=0,运行计数不改变配置 revision。
[进程用例](../../sdk/go/worker_process_integration_test.go) 的 TestIntegrationWorkerProcessQueueBackpressure 在任务受理后强杀节点 A,Wait 确认退出,再启动不同身份的 B:新提交仍被满额拒绝,原身份仍返回原任务;取消释放后,之前被拒的新任务可用原身份受理。该过程没有修改额度或任务状态来模拟恢复。
完整资源示例输出 QUEUE_FULL/429、duplicate=true,最终两个任务 SUCCEEDED/attempts=1、unfinished=0、worker_drained=true。业务处理仅回显通用输入,无外部副作用。原有服务强杀、Worker 强杀转核查及两节点调度补跑回归也全部通过。
## 5. 边界与后续
一秒计数窗口允许窗口内突发,窗口交界可能出现短时聚集;不保证任意滑动一秒上限。限速针对平台开始授权,不限制业务处理器的内部请求。降低配置不回滚已有外部效果。
队列积压计数与任务写入在同一事务,直接绕过服务修改数据库可能造成漂移。尚无自动修复计数或硬删除入口;迁移回填要求停止旧服务及写入,未做大表在线回填验收。历史回执新增字段的兼容规则见接口手册。
当前验证同机进程、多节点身份及单 MySQL,尚未覆盖数据库复制切换、跨故障域、混合版本滚动升级、热点队列吞吐及百万租户容量。应用级配额、同业务键并发许可、优先级/公平性、可靠交付积压限制与保留清理仍需继续实现和回归;本阶段不等于 P3 或平台最终验收完成。

View File

@@ -179,4 +179,4 @@ task=<任务ID> state=SUCCEEDED attempts=1 evidence=1 reason=RESULT_CONFLICT dis
## 7. 后续交付
P2 已提供底层协议、[自动 Worker 运行器及示例](worker-runtime.md) 与历史查询,并通过 [Schema 及进程故障阶段验收](../reports/phase-2.md);P3 已实现 [安全重试](../api/retries.md)、[计划调度](../api/schedules.md) 和 [运行中取消及示例](../api/cancellation.md),资源配额和结果订阅继续开发;P4–P5 提供流程构建发布、信号、等待、轮询与人工恢复。每项随实现同步交付可运行示例及异常回归。
P2 已提供底层协议、[自动 Worker 运行器及示例](worker-runtime.md) 与历史查询,并通过 [Schema 及进程故障阶段验收](../reports/phase-2.md);P3 已实现 [安全重试](../api/retries.md)、[计划调度](../api/schedules.md)、[运行中取消](../api/cancellation.md) 和 [队列资源控制及示例](../api/queues.md),应用级配额、业务键许可、优先级/公平性和结果订阅继续开发;P4–P5 提供流程构建发布、信号、等待、轮询与人工恢复。每项随实现同步交付可运行示例及异常回归。

View File

@@ -81,6 +81,8 @@ SDK 的 Run 每个实例只能调用一次,包括已经正常退出的实例
Run 的重连恢复同一次控制操作。平台的 [安全重试](../api/retries.md) 使用同一 Task.ID 派发新的 AttemptID/Epoch,运行器按新的有效授权调用处理器,预算与退避由服务端持久化管理。
Start 返回 EXECUTION_PAUSED 或 RATE_LIMITED 时,运行器保留原 start 消息身份退避核对,不调用处理器;有限授权到期后按服务端恢复规则处理。新领取已受 [共享队列额度](../api/queues.md) 约束,正常等待限流不消耗新的尝试预算。配置降额不意味着已在执行的 goroutine 已停止。
## 5. 结果与错误分类
| 处理器结果 | SDK 报告 / 服务端行为 |

View File

@@ -117,6 +117,7 @@ Result 是不超过 256 KiB 的非 null JSON,可传对象引用;原始异常
## 6. 错误与有界行为
- 握手 HTTP 拒绝返回 `*HTTPError`;业务控制拒绝返回 `*WorkerError`,通过 Code 分支处理。Error() 只显示分类,避免未知错误正文进入日志。
- Start 的 RATE_LIMITED 表示当前窗口不允许开始;保留原消息身份在剩余租约内退避,不能调用业务处理器。队列控制、降额与窗口定义见 [队列接口](../api/queues.md)。
- 传输中断返回 `ErrWorkerDisconnected`;消息格式/版本不兼容返回 `ErrWorkerProtocol`;主动关闭为 `ErrWorkerClosed`;超时/取消支持标准 context 错误。
- 最多 32 个在途控制请求,达到上限返回 `ErrWorkerBusy`,请求未发送。同一消息 ID 已在途时也拒绝第二个并发等待者。
- 派发缓冲最多 8 条;读取不及时会以 `ErrWorkerBackpressure` 关闭连接。持久化预占由服务端恢复,不无限积压内存。