95 lines
8.0 KiB
Markdown
95 lines
8.0 KiB
Markdown
# 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 分类。
|