feat: add durable business inbox example and consumer crash recovery

This commit is contained in:
2026-09-17 20:49:51 +08:00
parent b17d9b1977
commit a8a8dcfe1e
27 changed files with 1601 additions and 17 deletions

View File

@@ -1,6 +1,6 @@
# Orbit 功能说明:Pulse 与公共能力
> 版本 0.2 · 2026-09-17。Pulse 与 Weave 首版功能范围已确认;标注“建议”的细节待评审。Weave 具体范围另见流程设计。基础骨架已实现,任务业务能力仍待开发;实现状态见持续开发计划。
> 版本 0.2 · 2026-09-17。Pulse 与 Weave 首版功能范围已确认;标注“建议”的细节待评审。Weave 具体范围另见流程设计。基础、任务执行、调度、资源控制和可靠订阅已实现;批量受理、独立任务核查/人工重试及查询筛选等继续开发,实现状态见持续开发计划。
## 1. 目标与责任边界

View File

@@ -1,6 +1,6 @@
# 数据模型与实现基线
更新:2026-09-17。数据库统一禁止外键。当前已实现任务执行、安全重试、运行中取消、计划调度、队列限速/背压和应用配额;优先级及节点公平领取已实现;可靠订阅核心、HTTP/WSS、管理与消费 SDK 已实现,Inbox 场景及流程编排继续开发。
更新:2026-09-17。数据库统一禁止外键。当前已实现任务执行、安全重试、运行中取消、计划调度、队列限速/背压和应用配额;优先级及节点公平领取已实现;可靠订阅核心、HTTP/WSS、管理与消费 SDK 已实现,独立 Inbox 场景已实现,流程编排继续开发。
## 1. 模块与依赖
@@ -95,7 +95,7 @@
禁止“先查后另开事务写入”。需要防止并发状态改变的关联行在事务内持锁,统一锁顺序并保留必要索引。当前不提供硬删除;将来增加清理时,必须核验在途任务、引用、幂等恢复窗口与审计保留期,按有限批次处理,不能依赖数据库级联。直接绕过服务写 SQL 不受应用校验保护。
P1 已完成动作/队列授权、幂等摘要与原回执、受理事件及取消 CAS/审计事务;P3 扩展到运行中取消。订阅已实现服务层及持久化核心,包含事务内队列、所有者和交付归属校验;HTTP/WSS、管理与消费 SDK 已接入,业务 Inbox 示例待完成。流程目前仍只具备模型,后续引擎也必须补齐事务内父资源归属验证。
P1 已完成动作/队列授权、幂等摘要与原回执、受理事件及取消 CAS/审计事务;P3 扩展到运行中取消。订阅已实现服务层及持久化核心,包含事务内队列、所有者和交付归属校验;HTTP/WSS、管理与消费 SDK 已接入,业务 Inbox 示例已独立交付。流程目前仍只具备模型,后续引擎也必须补齐事务内父资源归属验证。
P2 服务端已新增会话隔离、两步派发、租约、并发许可、结果回执/证据和恢复扫描,见 [执行协议](api/worker.md)。底层及自动 Worker SDK、尝试/证据查询、Schema 与进程故障恢复已通过 [P2 阶段验收](reports/phase-2.md)。
@@ -151,8 +151,12 @@ P2 服务端已新增会话隔离、两步派发、租约、并发许可、结
## 13. 可靠结果订阅核心
000013_result_subscriptions 新增 EventStream、ResultEvent,扩展 Subscription 和 Delivery。289 个直接声明字段均有中文注释。生产事务在独立流行锁下分配序号,与任务、原事件和结果快照一起提交;消费事务不锁流行、任务、队列或应用额度。交付记录和扫描位置一起落库,每条交付单独持有租约和证明。重复确认不重复扣减积压,耗尽保留记录,人工追加预算不重置历史。详见 [订阅核心](09-result-subscription-core.md)。对外 HTTP/WSS、管理与消费 SDK 已实现,持久化 Inbox 完整场景继续推进。
000013_result_subscriptions 新增 EventStream、ResultEvent,扩展 Subscription 和 Delivery。289 个直接声明字段均有中文注释。生产事务在独立流行锁下分配序号,与任务、原事件和结果快照一起提交;消费事务不锁流行、任务、队列或应用额度。交付记录和扫描位置一起落库,每条交付单独持有租约和证明。重复确认不重复扣减积压,耗尽保留记录,人工追加预算不重置历史。详见 [订阅核心](09-result-subscription-core.md)。对外 HTTP/WSS、管理与消费 SDK 已实现,持久化 Inbox 完整场景已交付。
## 14. 交付历史查询索引
000014_delivery_history 增加按订阅及可选状态过滤的创建时间分页索引,领取、确认和重试不改变排序键。不增加模型字段或外键,仍为35个实体、289个中文注释字段。查询按凭据所有者、命名空间和订阅校验,游标绑定过滤条件;详情按来源事件ID读取不可变快照且不返回交付秘密。见 [订阅查询接口](api/subscriptions.md)。
## 15. 独立接收方示例模型
[result-inbox示例](examples/result-inbox.md) 在业务方自己的数据库建立receiver_inbox和receiver_task_views,2个实体、17个Go/SQL中文注释字段,无外键。它们属于独立示例模块,不计入平台35个实体,也不加入平台迁移。Inbox去重记录与业务视图同事务提交;视图仅按更高任务版本推进,重复和迟到事件不能重复更新或回退业务结果。

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 场景已实现并通过全量回归;优先级及节点公平派发已实现并通过全量回归;可靠订阅核心、HTTP/WSS、管理 SDK 和管理示例已实现并回归;消费 SDK、协议示例及两项节点强杀回归已完成;业务 Inbox 场景及消费者进程故障回归继续开发 |
| P3 Pulse 完整能力 | 安全重试、延时、Cron/固定间隔、漏跑策略、计划版本、运行中取消、并发许可、配额/背压、可靠结果订阅、批量受理、独立任务核查/人工重试、查询筛选与事务Outbox接入 | 多实例领取/触发竞争、重试上限、取消竞争、队列隔离、积压恢复、事件重复交付 | 进行中:安全重试、计划调度、运行中取消、队列控制/限速/积压背压、跨命名空间应用配额、显式资源键许可及 SDK 场景已实现并通过全量回归;优先级及节点公平派发已实现并通过全量回归;可靠订阅核心、HTTP/WSS、管理 SDK 和管理示例已实现并回归;消费 SDK、协议示例及两项节点强杀回归已完成;业务 Inbox 场景、COMMIT响应丢失及消费者进程故障回归已完成;批量受理、独立任务核查/人工重试、业务键/时间筛选与事务Outbox接入待完成 |
| P4 Weave 主流程 | SDK 定义构建与发布校验、实例、顺序/分支/结构化并行汇合、信号/定时等待、版本绑定 | 重启接续、早到/重复信号、并行竞争、内部事件重放、发布版本兼容;端到端通用流程 | 待开发 |
| P5 Weave 恢复与查询 | 有界查询、暂停继续、取消、人工核查恢复、控制门控与审计 | 轮次与尝试独立预算;暂停/开始/回执/超时竞争;恢复保留预算;未知结果不盲目重复 | 待开发 |
| P6 生产验证与运维 | 完整监控、凭据轮换、备份恢复、滚动升级、运行手册、可复现负载和故障工具 | 对照生产验收清单执行,报告环境/指标/遗留项;容量、RPO/RTO 按真实拓扑验证 | 待开发 |
@@ -35,6 +35,20 @@
SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最后才开始补文档。
### P3 功能范围核对
可靠订阅链路完成后,重新对照 [原功能范围](01-functional-spec.md) 和 [事务衔接设计](04-reliability-and-data.md),以下原定首版能力仍有缺口。不能只按已实现模块反推P3完成;先补齐并回归,再进入完整阶段验收。
| 剩余项 | 当前证据与后续交付 |
| --- | --- |
| 批量任务受理 | [任务API](api/tasks.md) 明确尚未提供;需有界逐项结果、独立幂等、部分失败补交、SDK与运行示例 |
| 独立任务人工核查与重试 | [重试API](api/retries.md) 仍未提供解除不确定性的核查接口;需证据、权限、版本、审计和历史保留,人工重试身份按关联新任务实现并冻结契约 |
| 任务查询筛选 | [当前ListTasks](../sdk/go/types.go) 仅队列/状态/分页;需按业务关联键及创建时间范围查询,游标绑定所有筛选,保持授权与有界索引扫描 |
| 业务到平台的事务Outbox接入 | [事务Outbox设计](04-reliability-and-data.md#61-业务系统到平台) 尚缺完整接入示例;需业务变更与待发请求同事务、稳定提交身份、响应不明核对和发送恢复,保持核心SDK无ORM/数据库依赖 |
凭据安全重绑、历史清理、全量监控/容量与MySQL故障切换仍按P6执行;Weave的通用执行门控/内部事件接续随P4–P5落实,不将外部结果订阅当作内部流程引擎。
## 4. SDK 与示例交付清单
| 场景 | 预期调用链 | 所属阶段 |
@@ -48,7 +62,7 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
| 跨命名空间应用配额 | first 受理 → second 全局满额 → 释放 → 原键成功 → 恢复配置 | P3,已实现完整示例 |
| 跨队列共享资源 | 两队列同键 → 一个有效许可 → 递增序号 → 资源端拒绝旧写入 | P3,已实现完整示例 |
| 优先级与应用隔离 | 近同时到期按优先级 → 旧任务等待补偿 → 应用/队列轮转 | P3,已实现完整示例 |
| 可靠结果订阅 | 冻结结果 → 持久化交付 → Inbox 去重 → 确认/补发 → 耗尽后人工重试 | P3,核心、HTTP/WSS、管理 SDK/示例已实现;消费 SDK 已实现,持久化 Inbox 场景待完成 |
| 可靠结果订阅 | 冻结结果 → 持久化交付 → Inbox 去重 → 确认/补发 → 耗尽后人工重试 | P3,核心、HTTP/WSS、管理及消费 SDK、独立Inbox示例与真实进程故障回归已完成 |
| 并行生成结果 | 发布流程 → 并行执行 → 汇合 → 完成 | P4 |
| 审核与外部回执 | 提交流程 → 等待信号 → 授权信号注入 → 继续 | P4 |
| 外部作业轮询 | 提交一次 → 有界查询处理中状态 → 回执或最终结果 → 超限核查 | P5 |
@@ -79,6 +93,7 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
- [P3 订阅核心回归](reports/phase-3-result-subscription-core.md)
- [P3 订阅通信与管理 SDK 回归](reports/phase-3-result-subscription-transport.md)
- [P3 消费 SDK 与节点故障回归](reports/phase-3-event-consumer-sdk.md)
- [P3 业务 Inbox 与消费者故障回归](reports/phase-3-result-inbox.md)
- [接口手册](api/README.md)
- [SDK 接入](sdk/README.md)
- [通用场景示例](examples/README.md)

View File

@@ -1,6 +1,6 @@
# 可靠结果订阅:持久化核心
状态:P3 的存储与服务层已实现并通过本地全量回归,见 [验证记录](reports/phase-3-result-subscription-core.md)。[HTTP/WSS、管理 SDK 与管理示例](api/subscriptions.md) 已接入。[事件连接 SDK 与自动消费器](sdk/event-consumer.md) 已实现,持久化 Inbox 示例继续开发;本页的存储方法属于服务端内部模块。
状态:P3 的存储与服务层已实现并通过本地全量回归,见 [验证记录](reports/phase-3-result-subscription-core.md)。[HTTP/WSS、管理 SDK 与管理示例](api/subscriptions.md) 已接入。[事件连接 SDK 与自动消费器](sdk/event-consumer.md) 已实现,[持久化 Inbox 示例](examples/result-inbox.md) 及消费者强杀回归已完成;本页的存储方法属于服务端内部模块。
## 1. 已实现范围
@@ -67,7 +67,7 @@ bootstrap 新增显式 --event-access,用于签发包含这三项动作的开
ACK 表示消费者已可靠保存或处理结果,不授予新业务执行权。当前最后一次秘密和代次即使过期,只要没有被新的交付替换,仍可用于迟到确认;新交付产生后,旧证明统一返回 DELIVERY_LEASE_LOST。确认和重新交付竞争通过订阅锁串行化,只会有一方有效。
服务端不能保证消费者业务恰好执行一次。后续 SDK 示例需展示接收方以 event_id 建立唯一 Inbox,在本地事务中保存结果/业务变更,提交后才 ACK。断线和 ACK 响应丢失可能造成重复投递,调用方不得按“收到过一次”就直接丢弃尚未可靠保存的内容。
服务端不能保证消费者业务恰好执行一次。[独立接收方示例](examples/result-inbox.md) 展示以 event_id 建立唯一 Inbox,在本地事务中保存结果/业务变更,提交后才 ACK。断线和 ACK 响应丢失可能造成重复投递,调用方不得按“收到过一次”就直接丢弃尚未可靠保存的内容。
每次交付授权、首次有效确认、订阅管理和人工重试均记录审计。交付审计包含事件标识、代次、次数、连接和租约截止,不记录交付秘密或结果正文。
@@ -91,4 +91,4 @@ retained_from_sequence 初始且目前固定为 1。未来实现清理后,如
## 8. 下一步
HTTP 创建/查询/分页、暂停/继续、交付与耗尽记录查询、人工重试,以及 orbit.events.v1 WSS、消息 Schema、管理 SDK 和管理示例已实现,见 [接口手册](api/subscriptions.md)。[事件连接 SDK 和自动消费器](sdk/event-consumer.md) 已实现;继续完成持久化 Inbox 去重示例与消费者进程故障回归,再验收 P3 的可靠订阅能力。
HTTP 创建/查询/分页、暂停/继续、交付与耗尽记录查询、人工重试,以及 orbit.events.v1 WSS、消息 Schema、管理 SDK 和管理示例已实现,见 [接口手册](api/subscriptions.md)。[事件连接 SDK 和自动消费器](sdk/event-consumer.md) 已实现;[持久化 Inbox 去重示例](examples/result-inbox.md) 与消费者进程故障回归已完成。可靠订阅各层均有对应证据,P3其他功能缺口见开发计划。

View File

@@ -1,6 +1,6 @@
# 安全重试 v1
状态:P3 的安全重试已实现。使用现有任务提交与 Worker 结果接口,无独立的自动重试路由;[Cron/固定间隔计划](schedules.md) 和 [运行中取消](cancellation.md) 已接入;人工恢复与可靠结果订阅继续按 [计划](../08-development-plan.md) 开发。验证证据见 [本阶段报告](../reports/phase-3-safe-retry.md)。
状态:P3 的安全重试已实现。使用现有任务提交与 Worker 结果接口,无独立的自动重试路由;[Cron/固定间隔计划](schedules.md) 和 [运行中取消](cancellation.md) 已接入;[可靠结果订阅](subscriptions.md) 与业务Inbox已实现;独立任务人工核查与重试继续按 [计划](../08-development-plan.md) 开发。验证证据见 [本阶段报告](../reports/phase-3-safe-retry.md)。
## 1. 三种不同的重报

View File

@@ -322,6 +322,6 @@ _, err = client.ConfigureSubscription(ctx, current.ID, pauseKey, orbit.Configure
return err
```
[完整管理示例](../../sdk/go/examples/subscriptions/main.go) 的启动、输入输出、失败和保留规则见 [场景文档](../examples/subscriptions.md)。它演示管理与幂等,不启动结果消费者。正式 [事件连接 SDK 与自动消费器](../sdk/event-consumer.md) 已提供接收、ACK、心跳、重连与有界收尾;[完整协议示例](../examples/event-protocol.md) 验证跨连接重报原确认。持久化 Inbox 示例继续开发。
[完整管理示例](../../sdk/go/examples/subscriptions/main.go) 的启动、输入输出、失败和保留规则见 [场景文档](../examples/subscriptions.md)。它演示管理与幂等,不启动结果消费者。正式 [事件连接 SDK 与自动消费器](../sdk/event-consumer.md) 已提供接收、ACK、心跳、重连与有界收尾;[完整协议示例](../examples/event-protocol.md) 验证跨连接重报原确认。[独立 MySQL Inbox 示例](../examples/result-inbox.md) 已实现并验证消费者进程退出后的去重。
机器可读消息契约见 [events.schema.json](../../protocol/eventsv1/events.schema.json),使用 Draft 2020-12,clientFrame/serverFrame 分方向约束。运行时额外拒绝重复 JSON 键、超范围 uint64 及非规范 base64url。Schema 不代替授权、状态机、租约及事务校验。

View File

@@ -114,7 +114,7 @@
- 步骤:创建 → 同键核对 → 暂停 → 恢复 → 查询与分页 → 最终暂停。
- 预期:duplicate=true、resumed_revision=3、final_enabled=false、revision=4、pending=0。
- 保留:每次运行留下一个已暂停订阅及审计;没有硬删除,受64个订阅上限约束。
- 范围:本例只演示管理SDK;自动消费 SDK 已实现,持久化Inbox例子继续开发。
- 范围:本例只演示管理SDK;自动消费 SDK 已实现,[持久化Inbox例子](result-inbox.md) 已独立交付。
## 已可运行:结果消费协议与跨连接确认
@@ -122,3 +122,11 @@
- 操作:创建订阅 → 提交并取消任务 → 接收冻结结果 → ACK → 重连重报原证明 → 核对原确认时间 → 暂停订阅。
- 输出:ack_replayed=true、same_receipt=true、pending=0、subscription_enabled=false。
- 范围:本例只做读取验证,不写业务数据;[自动消费器](../sdk/event-consumer.md) 的生产接入须提供事务Inbox,不能把示例当作业务恰好一次保证。
## 已可运行:独立业务 MySQL Inbox
- 独立项目:[result-inbox](../../examples/result-inbox/README.md),[完整前置、命令及故障说明](result-inbox.md)。
- 操作:接收冻结结果 → Inbox与业务视图同事务提交 → 重复核对 → ACK → 暂停本次演示订阅。
- 输出:inbox_duplicate=true、applied_events=1、ack_confirmed=1、pending=0。
- 覆盖:并发重复、旧版本保护、回滚、真实MySQL COMMIT响应丢失、业务提交后/ACK前消费者SIGKILL及新进程去重。
- 边界:只覆盖接收方本地SQL效果;核心SDK不依赖数据库,外部副作用需要接收方自己的幂等/Outbox设计。

View File

@@ -2,7 +2,7 @@
[完整代码](../../sdk/go/examples/event-protocol/main.go) 使用正式 Go SDK 创建取消结果订阅,提交并取消一个待执行任务,接收并验证冻结结果,然后 ACK;关闭连接后在新连接中重报原证明,验证原确认时间和去重回执,最后暂停本次订阅。
本例只读取并验证结果,没有业务数据库写入。生产业务应使用 [自动消费器及事务去重约定](../sdk/event-consumer.md)。持久化 Inbox 完整示例尚待交付。
本例只读取并验证结果,没有业务数据库写入。生产业务应使用 [自动消费器及事务去重约定](../sdk/event-consumer.md)。持久化接入见 [独立 MySQL Inbox 示例](result-inbox.md)。
## 启动

View File

@@ -0,0 +1,109 @@
# 独立业务数据库的结果 Inbox
[完整示例项目](../../examples/result-inbox/README.md) 演示:结果通过正式消费 SDK 到达接收方后,在自己的 MySQL 中将事件去重记录与任务结果视图一起提交,再允许 SDK ACK。重复事件不会再次推进视图,旧版本不会覆盖新版本。
示例不连接平台内部存储,不依赖业务 ORM,不创建外键或级联删除。它演示的是本地SQL事务中的去重,不保证任意外部HTTP副作用恰好一次。源码、schema与模型都在独立的 examples/result-inbox 模块内,核心 sdk/go/go.mod 没有新增数据库驱动。
## 1. 前置与初始化
需要 Go1.27.1、已启动的 Orbit,以及接收方自己的 MySQL8.4+ 专用空数据库,例如 receiver_example。不要把接收方表建到平台业务库。先用接收方管理员创建数据库并通过安全环境配置注入 ORBIT_RECEIVER_DSN;DSN必须显式包含库名。生产使用受信任TLS及自己的数据库持久化、备份策略。
初始化所需权限为CREATE TABLE,运行账号只需要这两张接收方表的SELECT/INSERT/UPDATE。应用运行不需要DELETE或修改平台库。示例初始化只执行初始DDL,已有表会报错;不自动修复部分DDL或覆盖已有结构,正式升级应采用接收方自己的前向迁移。
从仓库根目录准备一个专用示例队列和事件凭据(平台 ORBIT_MYSQL_DSN 已按初始化手册配置):
```sh
mkdir -p .local
chmod 700 .local
go run ./cmd/orbit-admin bootstrap \
--application inbox-example --namespace default --environment development \
--queue inbox-demo --event-access --output .local/inbox.credentials.json
```
随后从仓库根目录进入示例项目,ORBIT_RECEIVER_DSN由环境管理器提供:
```sh
cd examples/result-inbox
GOWORK=off go run ./cmd/receiver init
ORBIT_URL=http://127.0.0.1:8080 \
ORBIT_CREDENTIALS_FILE=../../.local/inbox.credentials.json \
GOWORK=off go run ./cmd/receiver demo
```
本地示例用回环HTTP;生产连接使用HTTPS/WSS。GOWORK=off让此独立示例模块不依赖根workspace。首次构建按go.mod/go.sum解析依赖。
## 2. 演示链路与输出
demo会创建一个只关心task.cancelled的订阅,提交并取消一个待执行任务,再启动EventConsumer。收到本次任务的事件后,连续两次调用相同业务事务:第一次推进结果视图,第二次核对相同已提交的Inbox记录。处理器返回nil后SDK确认交付,程序检查当前业务视图,再暂停本次订阅。
```text
receiver_schema_initialized=true foreign_keys=0
task=<本次任务ID> state=CANCELLED inbox_duplicate=true applied_events=1
ack_confirmed=1 pending=0 subscription_enabled=false
```
第一行来自init,后两行来自demo。demo总时限20秒,消费器收尾最多5秒。它只接收本次任务,需用专用隔离队列;队列中另一个程序的取消结果会被拒绝处理,避免演示混用业务数据。
demo失败时非零退出,保留已受理任务、订阅、交付和业务记录供核查。成功时仅暂停订阅,不硬删历史。每次运行新建身份,命名空间订阅上限64,重复演示宜使用可重建的测试环境。服务端故障和强杀不由demo命令自行注入,专门回归见下文。
## 3. 持续消费已有订阅
使用与订阅创建时相同的凭据,提供订阅ID。配置队列必须和该订阅一致;程序启动时先查询并核对,防止配置错队列后不断耗费投递预算。
```sh
ORBIT_URL=http://127.0.0.1:8080 \
ORBIT_CREDENTIALS_FILE=../../.local/inbox.credentials.json \
ORBIT_SUBSCRIPTION_ID='<已有订阅的32位ID>' \
RECEIVER_NAME=task-result-view \
GOWORK=off go run ./cmd/receiver consume
```
ORBIT_RECEIVER_DSN仍需由环境提供。ORBIT_TOKEN、ORBIT_QUEUE_ID可覆盖凭据文件对应值。持续消费需要events:read、events:consume及订阅绑定队列权限;demo另外需要events:manage、tasks:submit和tasks:cancel。程序输出稳定事件分类、事件ID与脱敏存储错误,不输出凭据、结果正文或租约证明。
RECEIVER_NAME默认task-result-view,最多64个ASCII字符,以字母或数字开头,其余允许字母、数字、点、下划线和连字符。它表示固定的接收用途;重启、扩容、多个实例必须复用同一名称和队列。不同用途是不同业务视图,不能每次启动随机换名来规避重复。
持续消费并发4,收到SIGINT/SIGTERM时取消在途处理器并最多收尾10秒。只关闭本进程,不暂停共享订阅;其他消费者仍可继续。单次业务事务最多5秒。死锁、数据库不可用、提交不明等返回错误,SDK不ACK,按服务端交付租约及预算恢复。
## 4. 数据模型与事务
[schema.sql](../../examples/result-inbox/internal/inbox/schema.sql) 与 [中文模型](../../examples/result-inbox/internal/inbox/model.go) 定义两个实体、17个字段;不计入平台本身的35个实体。
| 表 | 身份和用途 |
| --- | --- |
| receiver_inbox | `(consumer_name, queue_id, event_id)`唯一;保存冻结事件、SHA256摘要、是否推进视图和UTC受理时间 |
| receiver_task_views | `(consumer_name, queue_id, task_id)`唯一;保存最大任务版本、最后事件/状态/结果、实际推进次数及UTC时间 |
Apply的事务顺序:
1. 校验来源队列、事件/任务/版本/结果状态关联;规范化完整事件JSON并计算摘要。对象键和空白顺序不影响摘要;数字使用json.Number保持精度及原十进制表达,时间统一UTC。重复键、非法Unicode、无效JSON、超过512KiB的事件被拒绝。
2. 普通INSERT写Inbox唯一键。只对MySQL 1062重复键进入核对;其他错误都返回失败。唯一键竞争等待胜出事务提交或回滚,不把未提交占位当作成功。
3. 重复时结束本事务,再查询已提交记录并比较摘要。同身份同内容返回Duplicate=true;不同内容报冲突,不ACK。Result.Applied返回原事务是否推进过视图,不表示本次又执行一次。
4. 首次接收时原子建立或锁定单任务视图。事务内占位版本为0,正常提交后不留下占位状态。只接受更高任务版本;旧事件记录为applied=false,保留历史;同任务同版本但不同事件ID视为冲突并整体回滚。
5. 更新视图及Inbox的applied标志,在一个READ COMMITTED事务中COMMIT。业务版本、结果及推进次数与Inbox同时提交;成功后Handle才返回nil。
TaskView是接收方只读结果视图,不是平台权威任务状态。NEEDS_REVIEW必须保留为待核查,不能转译成成功。原事件快照不从后来的GetTask重新拼装。未提供result时保存SQL NULL,JSON大整数不会经float64转换。
## 5. 故障与核查
| 窗口 | 行为 |
| --- | --- |
| 业务更新失败 | 事务回滚,Inbox与视图都不残留;原事件仍可重试 |
| COMMIT响应未知 | 返回ErrCommitUnconfirmed,不ACK;之后按原事件身份查Inbox,已提交则去重,未提交则重新处理 |
| 业务提交后消费者被强杀 | 原交付未确认;新进程收到补发后使用相同消费者范围命中Inbox,再确认新交付证明 |
| 平台ACK回应丢失 | SDK保留原证明重报;进程也退出时,后续补发仍通过Inbox去重 |
| 同ID不同内容 / 同任务同版本不同ID | 返回冲突,不自动覆盖或确认;检查来源数据与接收范围,保留原记录 |
| 连续失败耗尽预算 | 平台保留EXPIRED并占用窗口;修复根因后通过管理API追加有限预算,不能换消费者名掩盖问题 |
Inbox保留期必须覆盖平台可补发、人工重试及备份恢复窗口。本例不清理或归档记录;删除去重记录后再次补发可能重新产生业务效果。生产还需要接收方权限、加密、备份恢复及容量治理。
本地SQL事务无法原子包含外部HTTP、邮件等效果。需要外部副作用时,应在同事务写接收方Outbox,再以稳定业务身份执行和核查;不能在持锁期间调用外部系统。此示例没有冒充通用Outbox框架。
## 6. 实际回归
根目录 `make integration` 创建临时MySQL,平台使用orbit_test,接收方使用独立orbit_receiver_test;结束清理容器。示例普通race/vet/build也纳入根Makefile。
- [存储回归](../../examples/result-inbox/internal/inbox/store_integration_test.go):8个并发相同事件只推进1次、内容冲突、旧版本与范围隔离、业务更新失败整笔回滚、两表17个中文列注释及零外键。
- [COMMIT响应丢失](../../examples/result-inbox/internal/inbox/commit_loss_integration_test.go):本地协议代理收到MySQL对COMMIT的真实OK后丢弃并断连,调用方报告提交不明;另一连接验证实际数据已提交,新的Store按相同身份核对,不重复更新。
- [消费者真实进程故障](../../examples/result-inbox/internal/inbox/consumer_process_integration_test.go):读取数据库证明业务已提交、平台仍LEASED且未ACK,再SIGKILL消费者并Wait确认退出;新进程在真实30秒租约到期后去重并确认,同一交付attempts=2,业务applied_events仍为1。
故障屏障仅在测试二进制中提供,正常CLI没有故障开关。COMMIT代理仅处理本地无TLS测试连接,不作为生产组件。测试没有模拟外部业务副作用,也不等同于跨地域或MySQL主从故障切换验收。

View File

@@ -0,0 +1,83 @@
# P3 业务 Inbox 与消费者故障回归
验证日期:2026-09-17。交付 [独立 MySQL Inbox 示例](../examples/result-inbox.md)。可靠订阅已具备生产者事务日志、HTTP/WSS、管理及消费 SDK、事务接收示例与对应故障证据。P3仍需补齐原功能范围中的批量受理、独立任务核查/人工重试、查询筛选和业务Outbox接入,持续目标保持进行中。
## 1. 交付与模块边界
- [独立示例模块](../../examples/result-inbox/go.mod) 引用正式SDK和MySQL驱动,不依赖平台内部模型/存储,不给核心SDK增加数据库依赖。GOWORK=off可单独构建、测试和运行。
- [业务存储](../../examples/result-inbox/internal/inbox/store.go) 以固定消费者用途、队列、事件身份去重;冻结文档摘要冲突拒绝确认。Inbox与任务结果视图同事务提交,视图仅接受更高任务版本。
- [接收方模型](../../examples/result-inbox/internal/inbox/model.go) 与 [独立SQL](../../examples/result-inbox/internal/inbox/schema.sql) 包含2个实体、17个字段,Go与SQL均有中文注释,无外键和级联删除。平台本身仍是35个实体、289个直接声明持久化字段,未增加平台迁移。
- [可运行CLI](../../examples/result-inbox/cmd/receiver/main.go) 提供init、demo、consume;初始化不覆盖已有表,持续消费核对订阅队列,演示核对业务重复、ACK及订阅暂停。
- Makefile加入独立示例的fmt/race/vet/build,统一集成脚本单独创建orbit_receiver_test数据库,与平台orbit_test隔离。
## 2. 验证环境与结果
macOS arm64、Go1.27.1、Docker MySQL8.4.10、一次性数据库、回环HTTP/WSS、真实Orbit和消费者子进程。数据库代理只用于本地无TLS的COMMIT故障注入,测试不会连接既有业务库;脚本结束清理自己的容器和临时凭据。
```sh
make fmt test vet build
make integration
cd examples/result-inbox
GOWORK=off go test -race ./...
GOWORK=off go vet ./...
```
| 分组 | 本次结果 |
| --- | --- |
| 全量平台MySQL集成 | PASS,91.331秒 |
| SDK Worker连接/自动运行器 | PASS,87.413秒 |
| SDK计划 / 队列 / 订阅管理 / 应用额度 | PASS,5.637 / 4.449 / 33.392 / 1.395秒 |
| SDK事件连接与自动消费 | PASS,50.959秒 |
| 新业务接收方六项集成 | PASS,35.512秒 |
| 其中真实COMMIT响应丢失 | PASS,0.02秒 |
| 其中消费者提交后/ACK前SIGKILL | PASS,34.20秒;使用真实30秒交付租约 |
| 原九项真实进程故障 | PASS,60.953秒;加上新消费者故障共十项 |
| 完整接入示例 | 15个全部通过,包括新的独立Inbox demo |
| 根及独立示例fmt/race/vet/build | PASS |
第一次专项启动脚本使用了不存在的测试名,导致未建平台表而失败;修正为实际的TestIntegrationSchemaAndConstraints后,专项及上表全量回归通过。没有把“无测试运行”的输出当成验证证据。
实际demo输出:
```text
receiver_schema_initialized=true foreign_keys=0
task=<本次任务ID> state=CANCELLED inbox_duplicate=true applied_events=1
ack_confirmed=1 pending=0 subscription_enabled=false
```
## 3. 正确性与数据证据
[真实存储测试](../../examples/result-inbox/internal/inbox/store_integration_test.go) 以8个并发事务处理同一事件:1次首次处理、7次已提交重复,Inbox只有1行,业务applied_events=1。相同事件ID改动内容返回冲突,原结果及推进次数保持不变。
迟到的低版本事件保存Inbox历史但不回退视图;同任务同版本不同事件ID整体回滚。不同消费者用途有独立去重范围,错队列被拒绝。测试用MySQL触发器使业务更新实际失败,验证Inbox及占位视图一起回滚,移除注入后原身份可重新提交。
[文档身份测试](../../examples/result-inbox/internal/inbox/store_test.go) 验证对象键顺序、空白和同一时刻的时区表达不改变摘要;业务数字变化会改变摘要。保留9007199254740993和HTML字符,拒绝重复JSON键、无效JSON/UTF-8和未配对UTF-16转义。
真实information_schema断言接收方2张表、17个中文列注释、0个外键。平台原v7升级回归继续确认70个历史外键最终为0、325项原索引列定义保留。
## 4. MySQL COMMIT响应丢失
[代理测试](../../examples/result-inbox/internal/inbox/commit_loss_integration_test.go) 转发真实MySQL握手、事务和语句。收到COMMIT对应的成功OK包后,代理丢弃该包并断开连接;没有伪造成功响应,也没有直接修改表制造已提交状态。
Apply返回ErrCommitUnconfirmed而非成功。测试用另一条直连查询确认Inbox及业务视图确实已经提交,再用新建Store处理同一冻结事件:命中相同摘要,返回Duplicate=true,业务推进次数保持1。测试日志中的MySQL unexpected EOF是这次有意断连的结果。
这证明本地事务提交不明时按稳定事件身份核对的路径,不是MySQL主从切换、磁盘故障或跨地域复制验收。
## 5. 消费者真实进程强杀
[消费者子进程测试](../../examples/result-inbox/internal/inbox/consumer_process_integration_test.go) 使用正式EventConsumer与Store:
1. 首个进程在Apply成功后停在测试屏障,尚未从处理器返回nil,因此SDK未发送ACK。
2. 父进程查询业务数据库,确认Inbox与视图均已提交、applied_events=1;查询平台,确认交付仍LEASED且acknowledged_at为空。
3. SIGKILL消费者,用Wait确认其确实因信号退出。重启时不会复用原进程内存、证明或去重集合。
4. 第二个全新进程使用相同消费者用途、队列和订阅,在原交付真实租约到期后收到补发。它读取业务数据库的原Inbox,Duplicate=true,然后ACK新证明并正常退出。
5. 最终event_id、delivery_id保持不变,平台attempts=2并ACKED,业务Inbox仍1行、applied_events仍1。
测试屏障只存在于测试二进制;正常示例CLI不提供故障开关。已经提交的业务结果不因强杀而回滚,恢复依靠持久化Inbox,而非把新进程视为从未执行过。
## 6. 剩余边界与计划
- Inbox保证本例SQL事务内的原子去重与版本推进,不涵盖邮件、外部HTTP等业务副作用;需要相应幂等和接收方Outbox设计。
- 核心SDK仍只有网络协议依赖,业务表不进入平台迁移。正式接收方需要自己的DDL升级、权限、加密、容量、保留及备份恢复方案。
- 历史清理、凭据重绑、全量监控告警、MySQL故障切换和生产容量/RPO/RTO仍按P6实施。
- 已重新核对[原功能范围](../01-functional-spec.md)与[事务衔接要求](../04-reliability-and-data.md),将尚缺的批量受理、独立任务核查/人工重试、业务键/时间筛选与业务Outbox接入明确列回[持续计划](../08-development-plan.md)。P3及整个平台均未宣称最终完成。

View File

@@ -188,4 +188,4 @@ P2 已提供底层协议、[自动 Worker 运行器及示例](worker-runtime.md)
## 订阅管理示例
[完整运行方法](../examples/subscriptions.md) 使用显式 --event-access 凭据,执行创建/同键核对/暂停/恢复/查询,再暂停本次订阅并保留审计。源码见 [examples/subscriptions](../../sdk/go/examples/subscriptions/main.go)。WSS 连接 SDK 与自动消费器见 [消费手册](event-consumer.md),可运行 [协议示例](../examples/event-protocol.md) 验证跨连接 ACK;持久化 Inbox 示例继续开发。
[完整运行方法](../examples/subscriptions.md) 使用显式 --event-access 凭据,执行创建/同键核对/暂停/恢复/查询,再暂停本次订阅并保留审计。源码见 [examples/subscriptions](../../sdk/go/examples/subscriptions/main.go)。WSS 连接 SDK 与自动消费器见 [消费手册](event-consumer.md),可运行 [协议示例](../examples/event-protocol.md) 验证跨连接 ACK;[持久化 Inbox 示例](../examples/result-inbox.md) 已独立交付,核心SDK不新增数据库驱动。

View File

@@ -2,7 +2,7 @@
对应 [HTTP/WSS 契约](../api/subscriptions.md)。SDK 是独立模块,只依赖 HTTP/WebSocket,不导入服务端模型、存储或数据库驱动。订阅先用管理 API 创建,并固定在创建凭据与队列范围内。
已提供 [EventConnection](../../sdk/go/event_connection.go)、[EventConsumer](../../sdk/go/event_consumer.go)、[公开类型](../../sdk/go/event_types.go) 与 [可运行协议示例](../examples/event-protocol.md)。业务 MySQL Inbox 示例和消费者进程强杀验证尚在后续阶段,不能把 SDK 内存状态作为持久化去重。
已提供 [EventConnection](../../sdk/go/event_connection.go)、[EventConsumer](../../sdk/go/event_consumer.go)、[公开类型](../../sdk/go/event_types.go) 与 [可运行协议示例](../examples/event-protocol.md)。[独立 MySQL Inbox 示例](../examples/result-inbox.md) 已提供业务事务去重,并验证消费者提交后、ACK前被强杀的恢复;SDK内存状态仍不能代替Inbox。
## 1. 自动消费