7.9 KiB
Go 结果消费 SDK
对应 HTTP/WSS 契约。SDK 是独立模块,只依赖 HTTP/WebSocket,不导入服务端模型、存储或数据库驱动。订阅先用管理 API 创建,并固定在创建凭据与队列范围内。
已提供 EventConnection、EventConsumer、公开类型 与 可运行协议示例。业务 MySQL Inbox 示例和消费者进程强杀验证尚在后续阶段,不能把 SDK 内存状态作为持久化去重。
1. 自动消费
下面是可复用的应用函数。applyOnce 由接收方提供,必须在同一数据库事务内保存事件去重记录和业务变更;事务明确提交或读取到已经提交的同一事件后才返回 nil。它不能仅向内存队列投递后返回。
func consumeResults(
ctx context.Context,
client *orbit.Client,
subscriptionID string,
applyOnce func(context.Context, orbit.ResultEvent) error,
) error {
consumer, err := orbit.NewEventConsumer(client, orbit.EventConsumerOptions{
SubscriptionID: subscriptionID,
Capacity: 4,
Handle: applyOnce,
ShutdownTimeout: 30 * time.Second,
ReconnectMinDelay: 200 * time.Millisecond,
ReconnectMaxDelay: 5 * time.Second,
})
if err != nil { return err }
return consumer.Run(ctx)
}
所在文件需导入 context、time 和 orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"。同一 Consumer 只能 Run 一次;多实例可共同绑定同一个订阅,按服务端交付租约竞争,不保证事件处理/完成顺序。
| 配置 | 默认 / 范围 |
|---|---|
| SubscriptionID | 必填;有效的 32 位小写十六进制 ID |
| Handle | 必填;处理冻结的 ResultEvent,不接收交付秘密 |
| Capacity | 默认 4;1–32 个本地在途事件及处理器 |
| ShutdownTimeout | 默认 30 秒;1 毫秒至 5 分钟 |
| ReconnectMinDelay | 默认 200 毫秒;至少 10 毫秒 |
| ReconnectMaxDelay | 默认 5 秒;不小于起点且不超过 30 秒 |
每个连接另有 Capacity 条未读交付缓冲,满后关闭连接并按退避重连。处理器结果缓冲不超过 Capacity,本地观察缓冲 128;不会为积压无限创建 goroutine。自动消费器每条连接至多一个 ACK 在途,心跳独立并发,慢 ACK 不阻塞 WebSocket 读取。
2. 提交、补发和去重
推荐的接收方事务步骤:
- 使用固定消费者用途与事件 ID 建立唯一键,例如
(consumer_name, event_id)。不要使用连接 ID、交付代次或消息 ID 作为业务去重键。 - 在一个业务事务中插入 Inbox 记录并更新业务数据。唯一键竞争必须等待对方事务提交或回滚;遇到重复键时核对已有记录,而不是忽略所有 SQL 错误。
- 业务事务提交成功后返回 nil,SDK 再发送 ACK。提交结果未知时返回 error,保留核查信息,下次投递先按唯一键核对。
- ACK 已提交但回应丢失,SDK 保留原证明,在新连接上重报;在该本地事件仍保留期间不会重新调用处理器。
- 原证明已被新代次替代时,SDK 记录 proof_lost,等待新投递。新投递会再次进入处理器,必须命中持久化 Inbox 后才能确认新证明。
处理器 error、panic 或 Goexit 都不会 ACK,也不会在 SDK 内存中立即重新执行。平台在原 30 秒租约自然到期后,按退避和累计预算重新投递;耗尽后保留 EXPIRED,占用订阅窗口,需要管理 API 追加预算。仅在启用订阅有领取活动时触发恢复,当前尚无独立后台交付清扫器。
事件交付租约不提供业务独占执行授权,Ping 不延长它。长于租约的处理、网络分区或多个接收进程可能造成同一事件并行进入业务侧;本地 Capacity 和事件合并不能代替跨进程的唯一约束及事务。外部 HTTP 副作用也不能和 Inbox SQL 自动组成原子事务,需要接收方 Outbox、业务幂等及结果核查设计。
ResultEvent.Snapshot 是原事件时刻的冻结快照,不能通过后来查询到的当前任务状态替换。SDK 保留 RawMessage 中大整数及业务 JSON;例如 9007199254740993 不经 float64 重编码。task.needs_review 表示需要核查,不能按业务成功或最终失败处理。
3. 重连与关闭
自动心跳间隔为当前 v1 固定的 15 秒。心跳也重新验证凭据和订阅权限,暂停的订阅仍可心跳及确认已有交付。
网络中断、超时、接收缓冲耗尽、UNAVAILABLE、HTTP 408/429/5xx 使用指数退避及半区间抖动重连;延迟上限为 ReconnectMaxDelay。确认成功后重置退避。认证、权限、资源不存在、历史缺口、协议错误及其他永久错误停止消费,返回支持 errors.Is/As 的 EventConsumerRunError。
取消 Run 的 context 后,SDK 停止启动新处理器并取消已有处理器 context。在 ShutdownTimeout 内收集处理器返回,已提交的结果仍尝试 ACK 或重连核对;正常收尾返回 nil。未成功处理的交付保留给平台按预算恢复。
超时返回 ErrEventShutdownTimeout 包装错误:Unconfirmed 是本进程已知且未确认的事件数,Running 是仍未退出的处理器数。Go 不能强杀忽略 context 的函数,不能在收到超时后声称这些函数的业务效果已经撤销。永久连接错误也会取消并等待在途函数,等待受相同收尾时限约束。
调用方主动停止后,暂停订阅是独立管理操作;关闭一个 Consumer 不会暂停共享订阅。进程被强杀时,SDK 内存中的结果和证明不保证保留,接收方 Inbox 必须独立持久化。
4. 底层连接
| 方法 | 语义 |
|---|---|
| Client.DialEvents(ctx, EventRegistration) | 使用 Authorization 与 orbit.events.v1 握手;ctx 只控制握手,容量必须显式传 1–32 |
| Welcome() | 初始握手快照;不是实时订阅状态 |
| Next(ctx) | 等待交付;等待取消不关闭连接 |
| Ack(ctx, messageID, EventProof) | 确认原交付,可在同订阅的新连接中重报 |
| Ping(ctx, messageID) | 查询 Enabled 并维持连接,不续交付租约 |
| Done() / Err() | 等待读取循环退出及查询关闭原因 |
| Close() | 关闭传输并等待读取循环退出,不取消服务端交付 |
EventConnection 不自动心跳、重连或执行处理器。应用必须每 15 秒 Ping,并在退出时 Close。最多 32 个在途控制请求;超出返回 ErrEventBusy。控制请求总时限最多 10 秒,包含写入排队;握手最多 8 秒;每条文本帧最多 512 KiB。控制超时关闭连接,防止无法关联的迟到回应被当作另一次操作成功。
grant.Proof() 包含订阅、事件、交付、代次和秘密;SDK 本地核对订阅与回执事件,线上 ACK 只发送交付 ID、代次和秘密。重连后使用原 Proof,可通过 NewEventMessageID 生成新的关联 ID。ACK 持久化去重不依赖消息 ID。
SDK 严格验证消息方向、会话、回复关联、规范十进制 uint64、秘密编码、必填字段、重复键、字段大小写和交付/事件/快照关联。无效响应返回 ErrEventProtocol 并关闭连接;不会将无效回执当作已确认。
5. 观察与敏感信息
Stats() 返回进程内 Running、Started、HandlerFailures、Acknowledged、Reconnects 和 DroppedEvents;重启清零。Events() 返回有界本地通知,不读取也不会阻塞执行,Run 返回时关闭。它们不能替代数据库状态和持久化审计;Acknowledged 包括对已提交 ACK 的重报确认。
EventGrant、EventProof、EventConnection、EventConsumer 和 EventError 的常见 fmt 输出隐藏秘密、业务 JSON 或远端错误正文。显式 JSON 序列化 Grant/Proof 仍包含租约秘密,不可放到普通日志;若业务确需保存 Proof,应加密并限制访问。调用方记录自己的 handler error 时也应脱敏,SDK 观察事件只给出 HANDLER_FAILED 分类。