feat: serialize keyed tasks across queues with durable fencing tokens
This commit is contained in:
@@ -132,3 +132,11 @@ P2 服务端已新增会话隔离、两步派发、租约、并发许可、结
|
||||
## 12. 应用共享额度
|
||||
|
||||
000010_application_resources 新增 ApplicationResources,使用 BINARY(16) 应用主键,12 个持久化字段均有中文注释和 SQL COMMENT,无外键。共享未终结任务数、活跃尝试数、未开始预占数与一秒开始窗口;配置 revision 不随用量变化。停止旧写入节点后回填所有应用,包含跨命名空间的既有在途状态。关联身份行与额度行分开,认证后按应用额度 → 队列 → 任务/尝试 → 许可顺序持锁。后台恢复不因应用停用而停止释放到期尝试。详细字段、权限、SDK 场景及并发边界见 [应用配额接口](api/application-resources.md)。
|
||||
|
||||
## 13. 显式资源键许可
|
||||
|
||||
000011_keyed_concurrency 增加 Task.ConcurrencyKey / ConcurrencyKeyDigest、Attempt.ConcurrencyToken、WorkerSession.ConcurrencyKeys,共四个中文注释与 SQL COMMENT 字段。历史值分别为空/空、零和 false,原 BusinessKey 语义不变;33 个实体、273 个直接声明的持久化字段,最终外键数保持零。
|
||||
|
||||
资源限制复用 ConcurrencyLimit:scope_key=key,key_digest 为域分隔 SHA-256,命名空间内唯一;每键最大许可固定 1。原始 UTF-8 字节精确匹配,不依赖 MySQL 的文本排序规则。一个有键尝试持有队列和资源键两条 Permit,应用活跃尝试只计一次;续租、释放、恢复与状态同事务完成。
|
||||
|
||||
键行 Revision 在领取和释放时递增,领取后的值保存到 Attempt.ConcurrencyToken;同尝试重连不改变序号,资源行不得删除或重置。达到整数边界时拒绝新领取,禁止回绕。SDK 交付序号,资源端负责原子比较与业务幂等;历史未知效果与执行许可释放分别维护。字段、升级、调用和异常语义见 [资源键契约](api/keyed-concurrency.md)。
|
||||
|
||||
@@ -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 按真实拓扑验证 | 待开发 |
|
||||
@@ -46,6 +46,7 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
|
||||
| 运行中取消 | 保存取消意图 → 停止处理器 → 确认结果 → 查询并排空 | P3,已实现完整示例 |
|
||||
| 队列限速与背压 | 暂停 → 满额拒绝 → 原回执核对 → 释放空间 → 限速执行 | P3,已实现完整示例 |
|
||||
| 跨命名空间应用配额 | first 受理 → second 全局满额 → 释放 → 原键成功 → 恢复配置 | P3,已实现完整示例 |
|
||||
| 跨队列共享资源 | 两队列同键 → 一个有效许可 → 递增序号 → 资源端拒绝旧写入 | P3,已实现完整示例 |
|
||||
| 并行生成结果 | 发布流程 → 并行执行 → 汇合 → 完成 | P4 |
|
||||
| 审核与外部回执 | 提交流程 → 等待信号 → 授权信号注入 → 继续 | P4 |
|
||||
| 外部作业轮询 | 提交一次 → 有界查询处理中状态 → 回执或最终结果 → 超限核查 | P5 |
|
||||
@@ -84,6 +85,7 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
|
||||
- [P3 取消与无外键回归](reports/phase-3-cancellation.md)
|
||||
- [P3 队列资源控制回归](reports/phase-3-queue-resources.md)
|
||||
- [P3 应用共享配额回归](reports/phase-3-application-resources.md)
|
||||
- [P3 资源键许可回归](reports/phase-3-keyed-concurrency.md)
|
||||
- [P2 服务端阶段记录](reports/phase-2-server.md)
|
||||
- [P2 SDK 协议阶段记录](reports/phase-2-sdk-protocol.md)
|
||||
- [P2 自动 Worker 运行器记录](reports/phase-2-worker-runtime.md)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
- 文档版本:0.2
|
||||
- 整理日期:2026-09-17
|
||||
- 阶段:P0–P2 已通过阶段验收;P3 安全重试、计划调度、运行中取消、队列资源控制和应用共享配额已通过回归,数据库已改为无外键,P3–P7 继续推进,尚未完成全平台生产验收
|
||||
- 阶段:P0–P2 已通过阶段验收;P3 安全重试、计划调度、运行中取消、队列资源控制和应用共享配额已通过回归,资源键许可及真实进程恢复已通过全量回归,数据库已改为无外键,P3–P7 继续推进,尚未完成全平台生产验收
|
||||
- 适用范围:Orbit 统一平台,包含 Pulse 任务调度模块、Weave 流程编排模块与公共能力。平台面向多个业务系统复用。
|
||||
|
||||
## 阅读顺序
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
| `/v1/tasks/{task_id}/attempts/{attempt_id}/evidence` 与证据详情 | P2 已实现 | [执行历史](execution-history.md) |
|
||||
| `/v1/queues` 创建、查询及 `:configure` 控制 | 已实现队列并发、开始速率、积压背压与暂停/恢复 | [队列接口与 SDK 场景](queues.md) |
|
||||
| `/v1/application/resources` 查询及 `:configure` 管理 | P3 已实现跨命名空间应用配额 | [应用配额与 SDK 场景](application-resources.md) |
|
||||
| 任务 `concurrency_key` 与尝试 `concurrency_token` | P3 已实现跨队列资源键互斥 | [资源键接口与 SDK 场景](keyed-concurrency.md) |
|
||||
| 凭据校验与初始化 CLI | P1 已实现 | [鉴权](authentication.md) |
|
||||
| `/v1/worker/connect` | P2 已实现并通过阶段验收 | [Worker 契约](worker.md)、[Schema](worker-schema.md) |
|
||||
| 任务冻结策略与 Worker `retryable` | P3 安全重试已实现 | [安全重试](retries.md) |
|
||||
|
||||
@@ -54,6 +54,7 @@ curl "$ORBIT_URL/v1/tasks/$TASK_ID/attempts/$ATTEMPT_ID/evidence/$EVIDENCE_ID" \
|
||||
| --- | --- | --- |
|
||||
| id、task_id | string / 是 | 尝试与父任务标识 |
|
||||
| epoch | 十进制字符串 / 是 | 单任务执行代次;SDK uint64 |
|
||||
| concurrency_token | 十进制字符串 / 否 | 有显式资源键时返回跨任务单调许可序号;SDK uint64,无键时为零并省略;历史查询不能授权执行,见 [资源键契约](keyed-concurrency.md) |
|
||||
| state | string / 是 | LEASED、RUNNING、SUCCEEDED、FAILED、CANCELLED、EXPIRED、UNKNOWN;枚举不表示全部状态转换已实现 |
|
||||
| revision | 十进制字符串 / 是 | 记录版本,续租及归属变化可递增;SDK uint64 |
|
||||
| lease_until | UTC RFC 3339 / 是 | 数据库记录的租约期限,**查询不提供执行授权** |
|
||||
|
||||
117
docs/api/keyed-concurrency.md
Normal file
117
docs/api/keyed-concurrency.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 资源键互斥与单调许可序号
|
||||
|
||||
状态:P3 已实现。适合多个队列的任务更新同一份对象、文件或其他共享资源。数据库使用 MySQL 事务,不引入外键、Redis 或额外锁服务。
|
||||
|
||||
## 1. 四种身份各自的作用
|
||||
|
||||
| 字段 | 作用与范围 |
|
||||
| --- | --- |
|
||||
| Idempotency-Key | 一次平台受理命令的身份;原键原内容重报取回原回执 |
|
||||
| business_key | 业务追踪关联,保持原有语义,不限制并发、不自动去重 |
|
||||
| concurrency_key | 显式资源键;同一命名空间内跨队列共享一个有效尝试许可 |
|
||||
| concurrency_token | 此资源键的持久化单调序号;供资源端原子拒绝过时写入,不能代替授权或业务幂等 |
|
||||
|
||||
资源键必须由所有相关提交方按同一约定生成。省略键、使用不同键或处于不同命名空间的任务不会参与同一个互斥组。不要把凭据或敏感业务正文放入键中;任务详情与摘要会返回它。
|
||||
|
||||
平台控制有效许可,无法撤回已经发往外部的请求。租约到期后旧请求可能仍在运行;资源端需要结合序号与操作幂等保护实际写入。
|
||||
|
||||
## 2. HTTP 提交与查询
|
||||
|
||||
沿用 [任务接口](tasks.md) 的鉴权、动作、幂等、状态和错误信封,不增加专门的管理接口。
|
||||
|
||||
| 位置 / 字段 | 类型 / 默认 | 约束 |
|
||||
| --- | --- | --- |
|
||||
| POST /v1/tasks 的 concurrency_key | 可选 string,省略或空字符串表示关闭 | 非空时 1–191 个 Unicode 字符;有效 UTF-8,不含控制字符,首尾不能有 Unicode 空白;null 拒绝 |
|
||||
| 任务详情、列表摘要、task.assign.task 的 concurrency_key | string,可省略 | 返回受理时冻结值,不能事后换键 |
|
||||
| task.assign 的 concurrency_token | 非零 uint64 十进制字符串,有资源键时必填 | 无资源键时省略;SDK 为 uint64,不能转换为浮点数 |
|
||||
| 尝试列表、详情的 concurrency_token | 同上,有资源键时返回 | 历史查询用于核对,不授予执行权限 |
|
||||
| 计划版本 definition.task 的 concurrency_key | 与提交相同 | 版本固定值,生成任务时继承;无模板插值或动态表达式 |
|
||||
|
||||
键按 UTF-8 字节精确匹配,不做大小写折叠或 Unicode 规范化。例如 `object/A`、`object/a`、组合及分解形式的重音字符属于不同键。若业务认为它们相同,应在提交前统一生成规则。
|
||||
|
||||
```sh
|
||||
curl "$ORBIT_URL/v1/tasks" \
|
||||
-H "Authorization: Bearer $ORBIT_TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Idempotency-Key: update-object-42-operation-001' \
|
||||
--data '{"queue_id":"11111111111111111111111111111111","handler":"object.update","handler_version":"v1","payload_version":"v1","payload":{"operation_ref":"update-001","object_ref":"objects/42"},"concurrency_key":"object/42"}'
|
||||
```
|
||||
|
||||
响应是任务接口的完整信封,首次 201、重报 200;任务对象增加 `"concurrency_key":"object/42"`。原幂等键更换资源键返回 409 IDEMPOTENCY_CONFLICT。非法键返回 400 INVALID_ARGUMENT;权限或队列范围沿用任务接口。资源忙时受理仍可成功,任务保持 WAITING,不消耗尝试次数,但占用未完成任务配额并继续计算总截止时间。
|
||||
|
||||
## 3. 许可、释放与恢复
|
||||
|
||||
领取在同一事务中预占应用额度、队列许可和资源键许可,创建尝试并分配序号。有键任务同时持有两条许可记录,应用活跃尝试只计一次。许可覆盖 LEASED 和 RUNNING;业务代码必须等到 Start 确认当前执行权后才运行。
|
||||
|
||||
| 情况 | 资源键处理 |
|
||||
| --- | --- |
|
||||
| Worker 重连、Resume、续租 | 保留同一尝试和序号;续租同步延长许可 |
|
||||
| 结果重复确认、旧回执重放 | 不重复释放,不影响后续尝试的许可 |
|
||||
| 未开始取消、明确结束、到期恢复 | 同事务释放;后续尝试取得更大的序号 |
|
||||
| 安全重试退避 | 释放本次执行许可,等待下次领取;不在退避期间独占资源 |
|
||||
| UNKNOWN / NEEDS_REVIEW | 释放执行许可,保留未知效果和历史;待核查任务仍占积压配额 |
|
||||
| 暂停队列、停止派发、断开连接 | 不直接释放在途许可,仍按原租约和结果收尾 |
|
||||
|
||||
因此未知效果不会自动把整个资源键封锁到人工核查结束。若业务要求核查期间禁止其他操作,还需独立的业务冻结状态。当前不提供人工解锁或重置序号接口。
|
||||
|
||||
相同 `(namespace_id, concurrency_key)` 的新许可序号严格递增,允许间隔;内部领取和释放都推进资源行版本。不得删除或重置这条资源行,否则会破坏单调性。序号空间不足时拒绝领取,不能溢出回绕。不同资源之间不比较序号,单任务的 epoch 也不能替代跨任务资源序号。
|
||||
|
||||
领取查询在候选 LIMIT 前排除已占用键,避免一批阻塞任务挡住其他键。这不承诺 FIFO、跨队列公平性或最大等待时间。
|
||||
|
||||
## 4. Worker 与 SDK
|
||||
|
||||
新版 `NewWorker` 自动发送 `hello.concurrency_keys=true`,处理器读取 `e.Task.ConcurrencyKey` 和 `e.ConcurrencyToken`。底层接入者须显式设置 `registration.ConcurrencyKeys=true`,实现资源端校验后再启用。这是整个进程身份的固定注册属性;同进程重连改变它返回 STATE_CONFLICT,升级进程应创建新身份。
|
||||
|
||||
未声明支持的旧 Worker 继续消费无键任务,不领取带键任务。SDK 拒绝缺少序号、无键却携带序号、未声明支持却收到有键任务等协议错误。重复派发不能静默替换原键或原序号。
|
||||
|
||||
提交片段(`api`、`ctx`、`queueID` 已初始化):
|
||||
|
||||
```go
|
||||
accepted, err := api.SubmitTask(ctx, "update-object-42-operation-001", orbit.SubmitTaskRequest{
|
||||
QueueID: queueID, Handler: "object.update", HandlerVersion: "v1", PayloadVersion: "v1",
|
||||
Payload: json.RawMessage(`{"operation_ref":"update-001","object_ref":"objects/42"}`),
|
||||
ConcurrencyKey: "object/42",
|
||||
})
|
||||
```
|
||||
|
||||
资源端需要在**一次原子操作**里完成:核验命名空间、资源键和调用权限;拒绝 `token < highest_token`;提升最高序号;按稳定操作身份查重并写入实际结果。同序号的重复请求仍需要操作幂等。仅先查询最高序号、再分开写入会产生竞争;仅在 Worker 内用 mutex 无法保护多进程和外部系统。
|
||||
|
||||
最高序号校验只能拒绝资源端已经见过更高序号后的旧写入;更高序号尚未到达时,不能单靠本地最高值证明旧租约仍有效。需要更强授权语义时,资源端还需相应的授权校验或业务版本条件。平台序号也不能替代资源端的身份认证。
|
||||
|
||||
## 5. 完整可运行场景
|
||||
|
||||
源码:[keyed-concurrency/main.go](../../sdk/go/examples/keyed-concurrency/main.go)。示例创建两个专用队列,每条队列提交一个相同资源键的任务;两个自动 Worker 各自容量为 4。处理器调用一个受控内存资源模型,查询尝试确认序号增长,最后模拟旧请求验证它被拒绝。
|
||||
|
||||
从仓库根目录,在专用开发数据库完成迁移并启动服务:
|
||||
|
||||
```sh
|
||||
go run ./cmd/orbit-migrate up
|
||||
go run ./cmd/orbit-admin bootstrap --application keyed-example --namespace default \
|
||||
--environment test --queue initial --worker-access --output /tmp/orbit-keyed.credentials.json
|
||||
go run ./cmd/orbit
|
||||
```
|
||||
|
||||
上述命令使用预先设置的 `ORBIT_MYSQL_DSN`,服务使用 `ORBIT_HTTP_ADDR=127.0.0.1:8080`。示例凭据需具有全部队列范围的 queues:manage、tasks:submit/read 和 workers:connect/consume。另一个终端执行:
|
||||
|
||||
```sh
|
||||
cd sdk/go
|
||||
ORBIT_URL=http://127.0.0.1:8080 \
|
||||
ORBIT_CREDENTIALS_FILE=/tmp/orbit-keyed.credentials.json go run ./examples/keyed-concurrency
|
||||
```
|
||||
|
||||
正常输出:
|
||||
|
||||
```text
|
||||
queues=2 completed=2 tokens_increasing=true
|
||||
resource_writes=2 stale_write_rejected=true worker_drained=true
|
||||
```
|
||||
|
||||
示例不访问真实外部资源;内存 mutex 和 map 仅说明原子校验位置,不能复制为生产分布式资源存储。正常结束排空 Worker;每次运行创建独立队列、任务和资源键并保留历史。任务总时限 20 秒,失败非零退出并有界收尾,进程被强杀仍需按历史核对。
|
||||
|
||||
## 6. 升级与当前边界
|
||||
|
||||
停止旧服务写入,应用全部迁移到 000011,再启动新服务、升级 Worker SDK,最后提交带资源键的任务。旧节点不维护资源键许可,不能与新节点混跑;新 SDK 的 hello 扩展也不能发给尚未升级的严格旧服务。
|
||||
|
||||
000011 为历史任务填空键/空摘要、历史尝试填零序号、历史会话填 false,不改变原 business_key。新增四个模型字段均有中文注释和 SQL COMMENT,实体数仍为 33;数据库最终外键数为零。
|
||||
|
||||
当前基于一个 MySQL 主库及应用额度锁串行化事务,资源键记录随不同键数量持续增长。尚未提供可调的每键并发大于 1、资源管理/清理接口、优先级公平性或生产容量结论。验证证据见 [本阶段报告](../reports/phase-3-keyed-concurrency.md)。
|
||||
@@ -24,6 +24,7 @@ P2 进展:服务端执行内核、WSS 与独立自动 Worker SDK 已可产生
|
||||
| payload_version | string | 必填 | 输入契约版本,1–32 字符 |
|
||||
| payload | JSON | 必填 | 非 null JSON,最大 256 KiB;平台不解释业务字段 |
|
||||
| business_key | string | 可选 | 最多 191 个 Unicode 字符,无 NUL/换行;用于关联,不提供业务幂等 |
|
||||
| concurrency_key | string | 可选,空为关闭 | 1–191 个 Unicode 字符,无控制字符和首尾空白;显式资源互斥键,同命名空间跨队列共享,见 [资源键契约](keyed-concurrency.md) |
|
||||
| available_at | time / null | 受理时数据库时间 | 最早可执行时间;必须早于总截止时间,仅允许最多 1 秒的过去时间容差 |
|
||||
| start_deadline_at | time / null | 无独立限制 | 必须晚于当前时间,不早于 available_at,不晚于总截止时间 |
|
||||
| task_timeout_ms | uint64 | 0 或省略为 86400000 | 从受理时计算的总时限,最大 30 天(2592000000 毫秒) |
|
||||
@@ -90,7 +91,7 @@ curl -i "$ORBIT_URL/v1/tasks" \
|
||||
}
|
||||
```
|
||||
|
||||
可选响应字段:start_deadline_at、result、cancel_requested_at、completed_at、business_key,未设置时省略。effect_uncertain=true 表示存在尚未核实的授权执行效果,false 时省略;详情、摘要和 Worker 派发均可返回。等待重试时 result/completed_at 省略,历史结果在尝试中保留。终态有 completed_at。状态集合为 WAITING / LEASED / RUNNING / NEEDS_REVIEW / SUCCEEDED / FAILED / CANCELLED;P1 仅主动产生 WAITING 和 CANCELLED。
|
||||
可选响应字段:start_deadline_at、result、cancel_requested_at、completed_at、business_key、concurrency_key,未设置时省略。effect_uncertain=true 表示存在尚未核实的授权执行效果,false 时省略;详情、摘要和 Worker 派发均可返回。等待重试时 result/completed_at 省略,历史结果在尝试中保留。终态有 completed_at。状态集合为 WAITING / LEASED / RUNNING / NEEDS_REVIEW / SUCCEEDED / FAILED / CANCELLED;P1 仅主动产生 WAITING 和 CANCELLED。
|
||||
|
||||
### 幂等与失败处理
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ ACK 的 body 根据原请求选择:worker.capacity → welcome,worker.resume
|
||||
- instance_secret/lease_secret 必须是 32 字节秘密的严格无填充 base64url,长度 43;知道资源 ID 无法替代秘密。
|
||||
- 结果为非 null JSON;FAILED/UNKNOWN/CANCELLED 必须携带稳定 error_code;SUCCEEDED 省略或使用空 error_code。
|
||||
- hello.cancellation_notifications 可选且默认 false;Lease 可返回 cancel_requested_at。CANCELLED 需要事务核验已保存意图;通知、取消后执行边界与升级顺序见 [取消契约](cancellation.md)。
|
||||
- hello.concurrency_keys 可选且默认 false,未声明时不派发带资源键任务;task.assign.task.concurrency_key 与非零字符串 concurrency_token 必须同时出现。Schema 检查字段配对,SDK 另核验支持声明和键格式,事务维护许可;见 [资源键契约](keyed-concurrency.md)。
|
||||
- finish.retryable 为可选布尔值,默认 false;true 仅允许 FAILED,Schema 与服务端都校验。派发任务可带 effect_uncertain=true,表示此前授权执行的效果尚未核实。业务重试条件见 [安全重试](retries.md)。
|
||||
- 返回时间使用 UTC RFC 3339,最多微秒精度。验证器应开启 format 断言以校验实际日历日期。
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ go run ./cmd/orbit-admin bootstrap \
|
||||
| capabilities | 1–32 个 handler / handler_version / payload_version 精确版本组合,不可重复 |
|
||||
| capacity | 总执行槽数量,1–256,不是可用槽的累加值 |
|
||||
| cancellation_notifications | 可选 bool,默认 false;true 接收 task.cancel 通知,自动 Worker 默认开启 |
|
||||
| concurrency_keys | 可选 bool,默认 false;true 接收资源键和单调序号,自动 Worker 默认开启;同进程重连不可更改 |
|
||||
|
||||
重连必须保持凭据、队列、处理器集合和进程秘密,处理器集合的排列顺序不影响身份。新连接递增会话代次并使旧会话失效。知道 instance_id 或 task_id 不能接管执行。
|
||||
|
||||
@@ -77,7 +78,7 @@ worker.welcome 返回 session_id、epoch、server_time、expires_at、lease_dura
|
||||
| --- | --- | --- |
|
||||
| worker.resume | AttemptRef,字段见上方 task.start 示例 | 当前 LeaseStatus;成功迁移有效尝试到新会话,不创建尝试或延长期限 |
|
||||
| worker.capacity | sequence 字符串、capacity 0–256、drain bool | 更新后的 welcome 字段 |
|
||||
| task.assign | 服务端主动发送 | task 完整任务、attempt_id、epoch、lease_secret、lease_until、server_time |
|
||||
| task.assign | 服务端主动发送 | task 完整任务、attempt_id、epoch、lease_secret、lease_until、server_time;有资源键时带 concurrency_token 字符串 |
|
||||
|
||||
capacity 使用单调递增序号和覆盖值。同序号同内容可重复,同序号不同内容或旧序号冲突;capacity=0 不释放已有任务的并发许可。drain=true 是进程级持久化排空,重连保留;后续必须保持 drain=true,尝试清除返回 STATE_CONFLICT。新进程使用新实例身份。
|
||||
|
||||
@@ -129,7 +130,9 @@ receipt 是原操作的不可变确认;lease 是本次重新核验的执行权
|
||||
|
||||
平台在同一事务中预占应用并发与开始额度、分配队列并发许可、创建尝试、递增任务执行代次和次数、保存 LEASED 与派发事件。领取本身消耗一次 max_attempts 预算,包括最终未获开始授权的尝试,预算不会因重连或过期恢复重置。
|
||||
|
||||
运行中的进程总容量跨其所有旧/新会话统计。声明容量减少、断开连接或重复结果,都不能重复释放额度。已实施队列 max_concurrency、rate_per_second 及未终结任务上限;参数含义、窗口突发、暂停及降额语义见 [队列资源控制](queues.md)。[应用级配额](application-resources.md) 已同时生效;业务键许可与公平性继续推进。
|
||||
运行中的进程总容量跨其所有旧/新会话统计。声明容量减少、断开连接或重复结果,都不能重复释放额度。已实施队列 max_concurrency、rate_per_second 及未终结任务上限;参数含义、窗口突发、暂停及降额语义见 [队列资源控制](queues.md)。[应用级配额](application-resources.md) 与 [显式资源键许可](keyed-concurrency.md) 同时生效;公平性继续推进。
|
||||
|
||||
同命名空间的 concurrency_key 跨队列互斥,领取事务同时保存键许可和 concurrency_token;原 business_key 仅追踪。没有声明 concurrency_keys 的 Worker 不领取带键任务。资源端需用单调序号和操作幂等保护实际写入,平台无法撤回外部已发出的请求;未知结果释放执行许可并保留核查状态,详细边界和升级顺序见资源键契约。
|
||||
|
||||
领取前核对速率窗口及已有 LEASED 预留,满额时不新建尝试。Start 在同一事务内消费应用与队列的开始预算,原 ACK 重报不重复计数。领取后若配置降额,Start 可能返回 RATE_LIMITED;保持原消息身份在剩余租约内退避核对。续租、结果确认、取消及恢复不消费新开始额度。
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@
|
||||
|
||||
## 计划场景与验收标准
|
||||
|
||||
已可运行的 [跨队列资源互斥场景](../api/keyed-concurrency.md#5-完整可运行场景) 提供 [完整源码](../../sdk/go/examples/keyed-concurrency/main.go):两个队列的自动 Worker 操作同一资源,核对递增序号,并模拟资源端拒绝旧写入。输入使用显式 concurrency_key;预期 completed=2、resource_writes=2、stale_write_rejected=true。示例仅使用受控内存资源,无外部副作用;正常排空、保留历史,任务自带总时限。内存 mutex 不是生产分布式存储,生产必须在资源端原子持久化序号、操作身份和实际更新。
|
||||
|
||||
| 场景 | 输入与步骤 | 预期结果 | 必测异常 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 文件转换 | 对象引用 → 提交任务 → Worker 转换 → 结果引用 | 同一操作获得一次业务结果,历史可查 | 重复提交、Worker 在结果上报前中断 | 受理与执行框架已实现,实际转换示例待补齐 |
|
||||
|
||||
84
docs/reports/phase-3-keyed-concurrency.md
Normal file
84
docs/reports/phase-3-keyed-concurrency.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# P3 资源键许可与过时执行隔离回归
|
||||
|
||||
验证日期:2026-09-17。交付 [资源键互斥接口、SDK 和场景手册](../api/keyed-concurrency.md)。P3 优先级/公平性、可靠结果订阅及 P4–P7 继续推进,持续目标尚未完成。
|
||||
|
||||
## 1. 已实现内容
|
||||
|
||||
- 显式 concurrency_key 在命名空间内跨队列共享一个有效尝试许可;原 business_key 保持追踪含义,不改变既有任务并发行为。
|
||||
- 领取事务同时维护应用额度、队列许可、键许可、任务和尝试。资源占用时不消耗新的尝试;候选 LIMIT 前过滤忙碌键。
|
||||
- concurrency_token 使用持久化键行的单调版本,跨任务递增、允许间隔、拒绝溢出;同尝试重连保留。资源端必须原子比较并按稳定操作身份幂等处理实际写入。
|
||||
- 续租、取消、结果、退避及到期恢复同步维护两类许可。UNKNOWN 释放执行许可并保留核查与未知效果,旧结果不能释放后续尝试的许可。
|
||||
- Worker 显式协商 concurrency_keys;旧 Worker 不消费有键任务,自动 SDK 默认开启。协议 Schema、SDK 精确 uint64、尝试历史与计划模板同步支持。
|
||||
- [000011 迁移](../../internal/storage/mysql/migrations/000011_keyed_concurrency.up.sql) 新增四个中文注释字段,无外键。当前 33 个实体、273 个直接声明的持久化字段均有中文注释,SQL 同步 COMMENT。
|
||||
- [完整示例](../../sdk/go/examples/keyed-concurrency/main.go) 创建两个队列、两个 Worker、相同资源键,核对序号并模拟资源端拒绝旧写入。
|
||||
|
||||
## 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 ./...
|
||||
```
|
||||
|
||||
基础竞态测试、vet、三个 CLI 构建及独立 SDK module 验证通过。无集成环境的普通 go test 会跳过真实服务用例,实际集成证据来自 scripts/test-mysql.sh。
|
||||
|
||||
本次完整集成结果:
|
||||
|
||||
| 分组 | 结果 |
|
||||
| --- | --- |
|
||||
| 全部真实 MySQL 集成 | PASS,71.436 秒 |
|
||||
| 资源键七组数据库场景 | PASS,4.36 秒 |
|
||||
| v7 无外键升级 | PASS,3.43 秒,70 → 0,325 个既有索引列定义保留 |
|
||||
| v9 应用额度回填及当前迁移 | PASS,3.81 秒 |
|
||||
| 历史分页、权限、精确结果和证据 | PASS,0.48 秒 |
|
||||
| SDK Worker 连接及运行器 | PASS,82.224 秒 |
|
||||
| 其中资源键、四任务、双队列及 Start ACK 丢失 | PASS,3.32 秒 |
|
||||
| SDK 计划 / 队列资源 / 应用资源 | PASS,分别 5.634 / 4.697 / 1.372 秒(含包开销) |
|
||||
| 六项真实进程故障组 | PASS,25.173 秒 |
|
||||
| 其中资源键跨节点恢复 | PASS,2.27 秒 |
|
||||
| 十一个完整示例 | 全部通过 |
|
||||
|
||||
示例包括 health、tasks、worker-protocol、worker、history、retry、schedules、cancellation、resources、application-resources 和 keyed-concurrency。上述时长是回归执行时间,不代表吞吐或生产容量。
|
||||
|
||||
完整套件后进一步移除了空资源键判断对数据库文本排序规则的依赖,并补充仅含组合符和零宽字符的非空键回归。最终定向审计再次通过资源键八组场景、v7 零外键及索引/数据保留、v9 配额迁移和 v4 安全重试升级,总包耗时 21.104 秒。新旧普通任务、计划模板、特殊 UTF-8 键和回滚路径均在这些真实数据库用例中验证。
|
||||
|
||||
## 3. 数据库与协议证据
|
||||
|
||||
[数据库用例](../../internal/storage/mysql/keyed_concurrency_integration_test.go) 验证:
|
||||
|
||||
1. 老 Worker 对有键任务不创建尝试;相同 business_key 的无键任务仍可并行;显式启用后获得许可,原进程更改支持声明被拒绝,旧 false 注册摘要仍可重连。
|
||||
2. 两个节点、两个队列竞争同一键仅一个领取;队列和键各有一条许可,但应用只计一个尝试。未开始取消后另一个队列拿到更大序号;Start 重报、续租、尝试详情和完成后计数正确。
|
||||
3. 前面 20 个同键阻塞任务不遮挡后面的其他资源;阻塞项尝试次数为零。同应用另一个命名空间的相同键独立执行。
|
||||
4. 明确失败退避和 UNKNOWN 正确释放执行许可;待核查仍占积压。旧迟到结果仅保存证据,不能释放另一个新任务的许可;未开始过期同样回收。
|
||||
5. 领取事件写入失败时,尝试、键行、两类许可和应用计数一起回滚。大小写、组合及分解字符按精确字节区分;仅含组合符或零宽字符的非空键不被文本排序规则折叠为空。
|
||||
6. 接近 uint64 上限时仍完整保存序号,最后一次释放可成功;后续授权返回冲突且不消耗尝试,不回绕。
|
||||
7. 不可变计划模板传递资源键;连续触发同键任务按许可逐个执行。非法首尾空白、控制字符被拒绝。
|
||||
|
||||
[协议契约测试](../../internal/transport/httpserver/worker_contract_test.go) 同步检查 hello 扩展、带键派发、超过浮点精度的序号,以及缺键、缺序号、数值型或零序号。 [SDK 线协议测试](../../sdk/go/keyed_concurrency_test.go) 使用真实 TLS WebSocket 检查未协商支持、非法键、字段配对和大序号精度。
|
||||
|
||||
本阶段修复了尝试详情增加序号后 SELECT/Scan 字段数量不匹配。完整回归还暴露历史查询夹具假设单次全库恢复扫描就能处理指定任务;其他用例留下更早的到期任务后,该假设不成立。用例改为有界分批恢复,仍要求原任务进入 WAITING、第二次尝试成功、分页和证据全部一致,没有降低业务断言。
|
||||
|
||||
## 4. SDK 与真实故障
|
||||
|
||||
[SDK 集成用例](../../sdk/go/keyed_concurrency_integration_test.go) 通过真实服务运行四条同键任务,分布在两个队列,各 Worker 容量为 4。处理器同资源活跃数始终为 1;Start 已提交但 ACK 丢失后自动重连,处理器总共执行四次,每条任务只有一次尝试。处理器取得的序号与列表及详情中的序号完全一致。
|
||||
|
||||
真实进程测试强杀并 Wait 确认节点 A 退出,再启动不同 node_id 的 B。原 Worker 身份 Resume 原 RUNNING 尝试;另一队列不能领取同键任务,尝试数保持零。原任务完成后另一队列得到更大序号;旧 Finish 重放不破坏新尝试,后者仍可续租和正常完成。
|
||||
|
||||
完整示例实际输出:
|
||||
|
||||
```text
|
||||
queues=2 completed=2 tokens_increasing=true
|
||||
resource_writes=2 stale_write_rejected=true worker_drained=true
|
||||
```
|
||||
|
||||
## 5. 当前边界
|
||||
|
||||
- 只保证同一命名空间、显式相同资源键的有效许可互斥。省略键或另一个命名空间不参与;不承诺提交顺序或跨队列公平性。
|
||||
- 超时释放不会物理中断旧外部请求;UNKNOWN 不自动冻结整个资源。外部系统须实现原子序号校验、操作幂等和必要的业务冻结。仅有最高已见序号不能拒绝尚未见到更高序号时的所有旧写入。
|
||||
- 序号不是认证凭据或执行授权。处理器必须经当前 Start/Resume 授权;历史查询和 task.assign 不能直接授权业务执行。
|
||||
- 一个 MySQL 主库与应用额度锁是当前一致性边界。键行不能随任务清理而重置;高基数键增长、锁等待、吞吐和恢复延迟仍需 P6 负载验证。
|
||||
- 停止旧写入节点后迁移,再启新服务和新 SDK;混合旧写入节点、数据库复制切换、跨故障域网络分区、备份恢复和实际 RPO/RTO 尚未验收。
|
||||
- 示例资源模型使用进程内内存,仅用于清晰展示校验位置,不代表生产资源存储实现。Weave、可靠订阅及整个平台生产验收仍在开发计划中。
|
||||
@@ -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) 、[队列资源控制及示例](../api/queues.md) 和 [应用共享配额及示例](../api/application-resources.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) 和 [应用共享配额及示例](../api/application-resources.md),[资源键互斥和单调序号示例](../api/keyed-concurrency.md) 已实现,优先级/公平性和结果订阅继续开发;P4–P5 提供流程构建发布、信号、等待、轮询与人工恢复。每项随实现同步交付可运行示例及异常回归。
|
||||
|
||||
Reference in New Issue
Block a user