Files
orbit/docs/sdk/outbox-mysql.md

7.6 KiB
Raw Blame History

MySQL 事务 Outbox 适配包

1. 用途与安装

业务写库与提交 Orbit 是两次独立操作。Outbox 将业务变更和待发请求放在同一个业务数据库事务中,再通过后台发送器提交任务。提交响应丢失时复用原操作身份,避免业务记录已保存而任务丢失。

go get git.yuntuo.hk/k2safe/orbit/adapters/outbox/mysql@codex/orbit-foundation

Go 会将分支解析为固定版本写入 go.mod。上线应固定已验证的版本,不在每次部署时追踪分支。本包要求 Go 1.27.1,使用 SDK v0.1.0 和 MySQL 8.4 验证;不向核心 SDK 加入 SQL/ORM 依赖,也不依赖 Orbit 服务端模型。其他数据库和 ORM 专用适配尚未提供。

import (
    "context"
    "database/sql"
    "time"
    outbox "git.yuntuo.hk/k2safe/orbit/adapters/outbox/mysql"
    orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)

完整可编译工程在 HTTP 服务示例;旧 CLI 示例现在也调用本包,不维护第二份发送实现。

2. 数据库与事务约束

在业务数据库应用 schema.sql。表名保留 sender_outbox,兼容原示例已经初始化的表。中文模型见 Record。没有外键,不执行数据删除,不属于 Orbit 平台迁移。

  • New 不建表、不连接网络、不接管连接池关闭;显式 Init 只创建新表,遇到已有表报错。
  • 生产由业务迁移工具管理 SchemaSQL() 的内容;不要在每次启动时执行 Init。
  • db 和传入 tx 必须来自同一个业务库、同一个主写端;不支持跨库事务或读副本确认。
  • 自行创建连接池时配置 parseTime=true、UTC 会话时区和合理连接/读写超时;Open(ctx, dsn) 是可选的已配置连接池入口。
  • 事务锁序统一为 Outbox → 业务行。先调用 EnqueueTx,再写业务行;受理回调遵守同样顺序。不要持有业务行锁反向等待 Outbox。
  • 业务操作 ID 必须由调用方在首次请求前生成并保存,格式为 32 位小写十六进制;同命名空间唯一。HTTP 调用方丢响应后使用原 ID 与原内容重试。

3. 公共方法

方法 职责
Open(ctx, dsn) (*sql.DB, error) 可选的业务库连接池构造;不会自动建表
SchemaSQL() string 返回初始表结构供业务迁移管理
Init(ctx, db) error 显式创建新表,仅用于首次初始化
New(db, namespaceID, queueID, Options) (*Store, error) 固定目标及可选受理回调,可并发复用
EnqueueTx(ctx, tx, operationID, SubmitTaskRequest) (duplicate, error) 在调用方事务写入待发任务;不 Commit/Rollback、不发 HTTP
SendOnce(ctx, client) (Record, error) 有界领取并处理一条到期记录;无到期项返回 ErrIdle
Run(ctx, client, RunOptions) error 持续发送;取消后停止领取,限时收尾当前发送
Status(ctx, operationID) (Record, error) 只读固定命名空间及队列中的本地提交状态

基本构造不要求业务回调:

sender, err := outbox.New(db, namespaceID, queueID, outbox.Options{})
if err != nil { return err }

Options.OnAccepted 类型为:

type AcceptedHook func(context.Context, *sql.Tx, Record, orbit.SubmitTaskResult) error

回调与 Outbox 的 ACCEPTED 更新在同一个短事务内执行,并核对最新领取代次。可在此将平台 TaskID 关联到业务记录。回调必须幂等、响应 context,只做同库 SQL,不能 Commit/Rollback 或调用外部服务;失败整笔确认回滚,返回 ErrAcceptance。回调收到的 Record 仍是被锁定的 SENDING 记录,受理 TaskID 从 receipt 读取。不开启回调时,任务关联保存在 Outbox 自身,业务可按 OperationID 查询。

4. 加入现有业务事务

下面是接入函数片段。完整业务重复核对与错误处理见 jobs/store.go。

func save(ctx context.Context, db *sql.DB, sender *outbox.Store,
    operationID string, request orbit.SubmitTaskRequest) error {
    tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
    if err != nil { return err }
    defer tx.Rollback()

    duplicate, err := sender.EnqueueTx(ctx, tx, operationID, request)
    if err != nil { return err } // 必须回滚整笔事务
    if !duplicate {
        // 在这里使用 tx 写入自己的业务表。
        // 每一个 SQL 错误都必须返回;禁止忽略错误后提交。
    } else {
        // 核对原业务记录与本次输入一致,不能再次执行扣款、计数等操作。
    }
    if err := tx.Commit(); err != nil {
        return outbox.ErrCommitUnconfirmed
    }
    return nil
}

EnqueueTx 返回 nil 只代表该事务内写入/核对完成。事务提交前另一发送器看不到该记录;调用方回滚会同时撤销待发任务与业务写入。同一事务重复入队返回 duplicate=true,但仍需由调用方确认 Commit。默认 REPEATABLE READ 下的重复核对使用共享锁当前读,不依赖旧快照,也不升级重复 INSERT 持有的共享锁。

COMMIT 错误不能解释成“肯定没保存”。保留原 ID 与内容,重做同一业务事务核对;不得生成新 ID 补发。冻结请求按字节摘要核对,目标、提交键、默认值和数字精度保持不变。

5. 发送器与生命周期

err := sender.Run(workerContext, client, outbox.RunOptions{
    PollInterval:    500 * time.Millisecond,
    ShutdownTimeout: 20 * time.Second,
})

RunOptions 默认轮询间隔 500ms(10ms–10s),关闭期限默认 20s(1ms–5min)。HTTP 在数据库事务外,单次网络阶段最多 15s、发送请求最多 10s;领取租约 30s,SQL 确认最多 5s。数据库时间控制首次入库后 24h、最多 100 次领取的总预算;进程重启不重置预算。

Run context 取消后停止新领取,在关闭期限内等待当前交换与本地确认,宿主必须等待 Run 返回。ErrShutdownTimeout 表示有未核对的持久记录,应在重启后沿原身份恢复。HTTP 暂时失败在库内退避;数据库错误或受理回调失败会让 Run 返回错误,由宿主降低就绪状态、关闭或按自身策略恢复。REVIEW 记录停止自动发送,需要监控 Status/数据库并人工核查。

允许多个 Run/进程共用同一范围,由 MySQL SKIP LOCKED 和递增代次协调。网络请求可能重复,平台使用原幂等键去重;不承诺业务外部效果恰好一次。

6. 状态与错误

状态/错误 含义及处理
PENDING / SENDING 未完成本地受理确认;不能据此认定 Orbit 没收到
ACCEPTED 已确认平台受理,TaskID/ReceiptJSON 可用;不是执行成功
REVIEW 权限、内容冲突、无效回执或预算耗尽;保留证据人工核对
ErrConflict 原操作的目标或内容不一致,不能覆盖
ErrCommitUnconfirmed 领取/确认 COMMIT 回执不明;保留原身份核对
ErrLeaseLost 领取代次已替换,旧发送器不得覆盖新结果;Run 可继续其他项
ErrAcceptance 本地受理关联回调失败,确认事务未获成功确认
ErrStorage 存储失败;当前进度以持久记录为准
ErrIdle / ErrNotFound 无到期记录 / 当前范围没有该操作

本版本只支持 SubmitTask,不提供流程启动/信号 Outbox 命令,不自动删除历史、重置发送预算或解除 REVIEW。任务完成另用可靠订阅与业务 Inbox,详见 完整 HTTP 示例。