Files
orbit/docs/sdk/event-consumer.md

95 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Go 结果消费 SDK
对应 [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 示例](../examples/result-inbox.md) 已提供业务事务去重,并验证消费者提交后、ACK前被强杀的恢复;SDK内存状态仍不能代替Inbox。
## 1. 自动消费
下面是可复用的应用函数。`applyOnce` 由接收方提供,必须在同一数据库事务内保存事件去重记录和业务变更;事务明确提交或读取到已经提交的同一事件后才返回 nil。它不能仅向内存队列投递后返回。
```go
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. 提交、补发和去重
推荐的接收方事务步骤:
1. 使用固定消费者用途与事件 ID 建立唯一键,例如 `(consumer_name, event_id)`。不要使用连接 ID、交付代次或消息 ID 作为业务去重键。
2. 在一个业务事务中插入 Inbox 记录并更新业务数据。唯一键竞争必须等待对方事务提交或回滚;遇到重复键时核对已有记录,而不是忽略所有 SQL 错误。
3. 业务事务提交成功后返回 nil,SDK 再发送 ACK。提交结果未知时返回 error,保留核查信息,下次投递先按唯一键核对。
4. ACK 已提交但回应丢失,SDK 保留原证明,在新连接上重报;在该本地事件仍保留期间不会重新调用处理器。
5. 原证明已被新代次替代时,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 分类。