Documentation
¶
Overview ¶
Package outbox 提供事务发件箱:同事务登记,提交后主动发送,失败由项目 cron 补偿。 支持 MySQL/PostgreSQL;Store 借用主库,Worker 支持同步和异步发送,不启动后台调度。 构建标签 pixiu_bootstrap_no_mysql / pixiu_bootstrap_no_postgresql 同时排除对应后端的驱动错误适配, NewStore/NewDefaultStore 拒绝被禁用的后端;默认构建保留两种后端。
应用先通过 schemamigrate 显式登记 &outbox.Record{},再注册默认 Store 和 Worker:
_, err := outbox.NewDefaultStore(outbox.StoreConfig{
Backend: outbox.MySQL,
MasterDB: mysqlClient.MasterDB,
Location: env.TimeLocation(),
})
if err != nil {
return err
}
_, err = outbox.NewDefaultWorker(map[outbox.HandlerKey]outbox.Handler{
{
Kind: "project.notification",
Version: 1,
}: sendNotification,
})
if err != nil {
return err
}
var taskID int64
err = db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&order).Error; err != nil {
return err
}
var err error
taskID, err = outbox.EnqueueTx(tx, outbox.Task{
Kind: "project.notification",
Version: 1,
Payload: payload,
ExpiresAt: time.Now().Add(24 * time.Hour),
})
return err
})
if err != nil {
return err
}
outbox.SendAsync(taskID)
return nil
示例需导入 outbox、infra/env、gorm.io/gorm、time;ctx、db、mysqlClient、 order、payload 由应用提供。sendNotification 的签名为 func(context.Context, outbox.Delivery) error,同步发送成功才返回 nil。 db.Transaction 必须是最外层事务;提交后的发送失败不能回滚已提交业务。
每个进程只登记一个默认 Store 和 Worker,不提供 named 管理。NewDefaultWorker 自动绑定默认 Store, Handler 表在创建时复制,可包含多个 Kind/Version;完成全部初始化后再运行业务。 重复登记返回错误且不覆盖,初始化失败可重试;默认实例不支持替换、重置或运行期增改 Handler。 DefaultStore/DefaultWorker 返回已登记实例,未登记时 panic;包级 EnqueueTx/Get/Send/SendAsync/RetryPending 同样如此。 可通过 outbox.Get(ctx, taskID) 查询状态。仅登记任务的进程可以只初始化默认 Store。 NewStore/NewWorker 保留显式实例用法,不隐式设置默认实例;所有实例均不拥有数据库或 MQ 资源。
SendAsync(taskID) 在调用方检查默认 Worker,再直接启动 goroutine;显式实例可调用 worker.SendAsync(taskID)。 每次发送从 context.Background() 派生独立的 5 秒超时 context,不携带请求 context 的值, Handler 和发送日志从任务记录恢复 logId。异步入口不返回错误,失败额外记录 OutboxAsyncFailed; pending 任务由 cron 补偿,不使用 taskpool、不排队、不限制并发,也不在内部重试。 5 秒是 context 期限,Handler 须响应取消;结果回写有独立收尾过程,实际结束可能超过 5 秒。 如需同步等待结果,使用 outbox.Send(ctx, taskID);发送失败不能撤销已提交业务。
将补偿函数注册到已配置分布式锁的 cronx,频次由项目配置:
func retryOutbox(ctx context.Context) error {
return outbox.RetryPending(ctx, 100)
}
batchSize 是查询页大小,每轮按 ID 游标扫描,每条任务最多尝试一次。 发送失败保留 pending 和 LastError;成功为 succeeded;发送前已过期则为 failed。 ExpiresAt 零值表示不过期;已开始的发送按结果收尾,不会撤回。 Send 与 RetryPending 均使用 FOR UPDATE NOWAIT;不同任务可并发发送。 同一任务被锁时,Send 返回 ErrBusy,RetryPending 跳过;cronx 分布式锁协调补偿轮次。 每条任务的事务从加锁持续到 Handler 返回并回写;Handler 应响应 context,发送期间占用数据库连接。 持锁读取后按应用服务器时间判断过期,次数和发送结果只更新一次。结果 UPDATE 使用独立的 3 秒 context; MySQL 提交/回滚还依赖连接读写超时,整个收尾过程不保证在 3 秒内返回。 数据库生成 int64 自增 ID,重复登记会生成新任务;重试沿用 ID,回写失败可能重发,消费者须幂等。 旧字符串主键须显式迁移并处理存量消息去重标识,不能直接 AutoMigrate 切换。
Location 省略时为 UTC,两种数据库均保留绝对时刻,读取按配置时区表示。 MySQL 时间列为 TIMESTAMP(6),驱动须开启 parseTime,且驱动 Location 与会话 time_zone 一致; 推荐连接使用 UTC,展示时区可独立传入 env.TimeLocation()。PostgreSQL 使用 TIMESTAMPTZ(6)。 Record 的时间字段使用 Timestamp,通过 record.CreatedAt.Time 获取原生 time.Time。 MySQL 非空 ExpiresAt 的 UTC 整秒范围为 1 至 2147483647,越界返回 ErrInvalidArgument。 已有 MySQL DATETIME 表须先按原存储时区迁移为 TIMESTAMP,不能在未知时区下直接 AutoMigrate。 退出时先停止业务/cron,并等待同步发送及补偿调用返回,再关闭 MQ/数据库,最后关闭日志。 SendAsync 不提供等待或排空,退出可能中断本次尝试,pending 任务由后续 cron 补偿; 必须在关闭底层资源前确认发送结束时,应使用 Send 并由应用等待同步调用完成。
Index ¶
- Variables
- func EnqueueTx(tx *gorm.DB, task Task) (int64, error)
- func RetryPending(ctx context.Context, batchSize int) error
- func Send(ctx context.Context, id int64) error
- func SendAsync(id int64)
- type Backend
- type Delivery
- type Handler
- type HandlerKey
- type Key
- type Record
- type Status
- type Store
- type StoreConfig
- type Task
- type Timestamp
- type Worker
Constants ¶
This section is empty.
Variables ¶
var ( ErrInvalidArgument = errors.New("outbox: invalid argument") ErrNotTransaction = errors.New("outbox: transaction required") ErrBackendMismatch = errors.New("outbox: backend mismatch") ErrNotFound = errors.New("outbox: task not found") ErrBusy = errors.New("outbox: sender busy") ErrExpired = errors.New("outbox: task expired") )
Functions ¶
func EnqueueTx ¶
EnqueueTx 使用默认 Store 在调用方事务内登记,返回自增 ID;未登记 Store 时 panic。 不发布、不提交、不自动唤醒;最外层事务提交成功后才能使用 ID 发送。
func RetryPending ¶
RetryPending 使用默认 Worker 执行一轮补偿;未登记 Worker 时 panic。 batchSize 为正的查询页大小,应由配置分布式锁的 cronx 周期调用。
Types ¶
type HandlerKey ¶
type Record ¶
type Record struct {
ID int64 `` /* 167-byte string literal not displayed */
Kind Key `gorm:"size:64;not null;comment:任务类型,与版本共同匹配处理器"`
Version int32 `gorm:"not null;comment:任务协议版本,正整数"`
Payload []byte `gorm:"not null;comment:不可变任务内容,最大1MiB"`
Status Status `` /* 191-byte string literal not displayed */
Attempts int64 `gorm:"not null;comment:发送尝试次数,包含首次"`
ExpiresAt *Timestamp `gorm:"precision:6;comment:发送截止时间,空表示不过期"`
LastError string `gorm:"type:text;not null;comment:最近一次失败原因,最大4KiB,成功后清空"`
LogID string `gorm:"type:text;not null;comment:登记时的上下文logId"`
CreatedAt Timestamp `gorm:"precision:6;not null;autoCreateTime:false;comment:数据库生成的创建时间,按配置时区读取"`
UpdatedAt Timestamp `gorm:"precision:6;not null;autoUpdateTime:false;comment:数据库生成的更新时间,按配置时区读取"`
FinishedAt *Timestamp `gorm:"precision:6;index:pixiu_outbox_finished_idx,priority:2;comment:成功或过期的完成时间"`
}
Record 对应事务发件箱表 pixiu_outbox,显式交给 schemamigrate 迁移,同时作为 Get 返回的独立快照。 表注释在登记模型时通过 schemamigrate.Table.Comment 指定为“事务发件箱”。 状态修改须经 Store/Worker,发送互斥由数据库行锁保证。
type Status ¶
type Status string
Status 是持久化任务状态。
func (Status) GormDBDataType ¶
func (Status) GormDataType ¶
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store 借用配置中的主库,不创建连接、迁移表或启动后台服务。
func DefaultStore ¶
func DefaultStore() *Store
DefaultStore 返回显式登记的默认 Store;未登记时 panic。 NewStore 不设置默认实例,登记后不能替换或重置。
func NewDefaultStore ¶
func NewDefaultStore(cfg StoreConfig) (*Store, error)
NewDefaultStore 创建并登记唯一的默认 Store;失败不占用默认位置,重复登记返回错误。 应在启动阶段登记,完成 Store 和 Worker 初始化后再运行业务;数据库仍由调用方管理。
func NewStore ¶
func NewStore(cfg StoreConfig) (*Store, error)
type StoreConfig ¶
type StoreConfig struct {
Backend Backend
MasterDB func(context.Context) *gorm.DB
// Location 为 nil 时使用 UTC,控制查询结果与 Delivery 的时间表示。
// MySQL 驱动解析时区须与数据库会话时区一致,本配置不修改连接。
Location *time.Location
}
StoreConfig 借用已有主库,不管理连接生命周期。
type Task ¶
Task 保存不可变发送意图,Payload 不超过 1 MiB;登记时由数据库生成 ID。 ExpiresAt 为绝对截止时间,零值表示不过期。 MySQL 非空 ExpiresAt 的 UTC 整秒必须在 1 至 2147483647 范围内。
type Timestamp ¶
Timestamp 嵌入标准时间,为迁移提供 MySQL TIMESTAMP 与 PostgreSQL TIMESTAMPTZ 映射。 时间方法和 JSON 表示沿用 time.Time,原生值通过 Time 字段取得。 数据库参数传入 *Timestamp 或原生 Time;空指针表示 SQL NULL。
func (*Timestamp) GormDBDataType ¶
func (*Timestamp) GormDataType ¶
type Worker ¶
type Worker struct {
// contains filtered or unexported fields
}
Worker 执行主动发送与一轮补偿;支持同步 Send 和独立 5 秒超时的 SendAsync。 每条任务使用独立事务行锁;多实例补偿轮次由应用的 cron 分布式锁协调。
func DefaultWorker ¶
func DefaultWorker() *Worker
DefaultWorker 返回显式登记的默认 Worker;未登记时 panic。 NewWorker 不设置默认实例,登记后不能替换或重置。
func NewDefaultWorker ¶
func NewDefaultWorker(handlers map[HandlerKey]Handler) (*Worker, error)
NewDefaultWorker 创建并登记唯一的默认 Worker,绑定已登记的默认 Store。 未登记 Store、Handler 无效或重复登记均返回错误;失败可重试,不替换已有实例。 Handler 表在创建时复制;不启动后台服务,不管理数据库或消息客户端的生命周期。
func (*Worker) RetryPending ¶
RetryPending 按 ID 分页补偿本表的待发送任务,每条任务本轮最多尝试一次。 batchSize 为查询页大小;跳过已锁任务,失败继续下一条,扫描/回写错误或取消则结束本轮。 应挂载到配置分布式锁的 cronx,避免多实例补偿轮次重叠。
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package natsoutbox 将 JetStream 发布接入 outbox,发送成功以有效 PubAck 为准。
|
Package natsoutbox 将 JetStream 发布接入 outbox,发送成功以有效 PubAck 为准。 |