TigerBeetle Go 客户端实战指南:账户、转账、两阶段转账与批量操作
【免费下载链接】tigerbeetleThe financial transactions database designed for mission critical safety and performance.项目地址: https://gitcode.com/GitHub_Trending/ti/tigerbeetle
本指南以 Go 客户端官方文档 为主体,系统讲解在 Go 项目中接入 TigerBeetle 金融级事务数据库的完整流程:从环境准备、客户端初始化,到账户/转账的创建与查询、两阶段转账、链式(linked)事件、历史数据导入(imported events)以及性能关键的大型批量写入。阅读完本文,你将能够基于
github.com/tigerbeetle/tigerbeetle-go编写一套可投入生产的记账核心代码。
一、环境准备与工程初始化
TigerBeetle Go 客户端(tigerbeetle-go)通过 cgo 绑定到tb_client原生库(实现见 tb_client.go),因此对运行环境有明确要求:
- 生产环境仅支持 Linux >= 5.6;为便于开发,同时支持 macOS 与 Windows;
- Go >= 1.21(注意仓库内的 go.mod 声明
go 1.17,这是模块最低版本约束,官方文档推荐使用 1.21 及以上的工具链); - Windows 额外要求:安装 Zig 0.14.1,并将
CC环境变量设置为zig.exe cc的完整路径(原生库依赖 Zig 提供的交叉编译工具链)。
初始化一个全新的 Go 工程:
go mod init tbtest go get github.com/tigerbeetle/tigerbeetle-go注意:务必从
github.com/tigerbeetle/tigerbeetle-go导入包,而不是从仓库子目录本地导入。包名导入方式为点导入,例如import . "github.com/tigerbeetle/tigerbeetle-go",这是官方文档示例和 samples/basic/main.go 中采用的方式。
创建main.go并写入验证代码:
package main import ( "fmt" . "github.com/tigerbeetle/tigerbeetle-go" ) func main() { fmt.Println("Import ok!") }构建并运行:
go run main.go看到Import ok!即表示 cgo 链接与原生库加载全部正常。仓库 CI(ci.zig)中还会执行gofmt -l .、go vet、go test,并针对basic、two-phase、two-phase-many、walkthrough四个示例启动临时 TigerBeetle 实例做端到端验证,说明这套流程是经过完整测试的。
二、官方示例项目一览
Go 客户端仓库提供了四个可直接运行的示例(均有go.mod与main.go):
| 示例 | 路径 | 演示内容 |
|---|---|---|
| Basic | samples/basic | 创建两个账户,并在它们之间转移一笔金额 |
| Two-Phase Transfer | samples/two-phase | 创建两个账户,发起一笔 pending 转账后再 post 该转账 |
| Many Two-Phase Transfers | samples/two-phase-many | 创建两个账户,发起多笔 pending 转账,交替 post/void |
| Walkthrough | samples/walkthrough | 完整走查教程 |
其中 Basic 示例的运行流程是:创建账户1、2(Ledger=1,Code=1),向账户 2 转账10,最后用LookupAccounts校验账户 1 的debits_posted = 10、账户 2 的credits_posted = 10,可以作为最直接的上手参照。
三、创建 Client:集群 ID 与副本地址
客户端通过「集群 ID + 全部副本地址」与 TigerBeetle 集群建立连接。集群 ID 和副本地址由启动 TigerBeetle 集群的一方决定,客户端创建时原样传入即可。
tbAddress := os.Getenv("TB_ADDRESS") if len(tbAddress) == 0 { tbAddress = "3000" } client, err := NewClient(ToUint128(0), []string{tbAddress}) if err != nil { log.Printf("Error creating client: %s", err) return } defer client.Close()上面示例中集群 ID 为0,只有一个副本,地址从TB_ADDRESS环境变量读取,缺省端口为3000。
地址的合法写法(对应 tb_client.go 中NewClient用strings.Join以逗号拼接后交给tb_client_init的处理逻辑):
3000—— 解释为127.0.0.1:3000;127.0.0.1:3000—— 解释为127.0.0.1:3000;127.0.0.1—— 解释为127.0.0.1:3001(3001是缺省端口)。
多个副本地址以逗号分隔传入[]string即可,例如NewClient(ToUint128(0), []string{"3000", "3001", "3002"})。
线程安全与实例复用:Client 是线程安全的,多个并发任务应共享同一个实例。这样做的好处是事件可以在客户端侧被自动合并批处理(batching),显著提升吞吐。只有当需要连接多个 TigerBeetle 集群时,才创建多个客户端实例。
从源码看,NewClient的初始化失败会映射为 errors.go 中定义的错误,包括:
| 错误变量 | 含义 |
|---|---|
ErrUnexpected | 内部意外错误 |
ErrOutOfMemory | 客户端内部内存不足 |
ErrSystemResources | 客户端内部系统资源耗尽 |
ErrNetworkSubsystem | 网络子系统异常 |
ErrAddressLimitExceeded | 提供的地址过多 |
ErrInvalidAddress | 集群地址非法 |
ErrClientEvicted | 客户端被集群驱逐 |
ErrClientReleaseTooLow/ErrClientReleaseTooHigh | 客户端版本过旧 / 过新而被驱逐 |
ErrClientClosed | 客户端已关闭 |
ErrInvalidOperation | 内部操作非法 |
ErrTooMuchData | 单批次数据量过大 |
四、Uint128:128 位无符号整数的转换助手
TigerBeetle 的ID、UserData128、Amount、账户余额等字段都是128 位小端无符号整数。Go 客户端将其封装为Uint128类型(实现见 uint128.go),并提供一组转换函数:
ToUint128(uint64)—— 由 64 位整数构造(高位补零);BytesToUint128([16]byte)—— 由原始小端字节构造;HexStringToUint128(string)—— 由十六进制字符串解析(长度不得超过 32 个 hex 字符,自动补齐奇数位);BigIntToUint128(*big.Int)—— 由math/big.Int转换(负值会 panic);- 反向:
value.Bytes()、value.String()(十六进制,去掉前导零)、value.BigInt()、value.Uint64()(返回低 64 位与高 64 位两个值)。
另外还有一个全局常量AmountMax(定义于 tb_client.go),即 128 位全0xff的最大金额,常用于「post 整个 pending 金额」的场景,本文后面会用到。
五、创建账户(CreateAccounts)
账户的核心字段定义见 bindings.go 的Account结构体,完整字段及含义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
ID | Uint128 | 账户唯一 ID,推荐使用时间基 ID |
DebitsPending/DebitsPosted/CreditsPending/CreditsPosted | Uint128 | 四类余额,创建时通常为 0 |
UserData128/UserData64/UserData32 | Uint128 / uint64 / uint32 | 业务自定义数据,可用于查询过滤 |
Reserved | uint32 | 保留字段,必须为 0 |
Ledger | uint32 | 账本 ID,用于隔离不同业务账本 |
Code | uint16 | 业务代码(如账户类型) |
Flags | uint16 | 标志位位域 |
Timestamp | uint64 | 由服务端分配,创建时传 0 |
创建两个账户:
accountResults, err := client.CreateAccounts([]Account{ { ID: ID(), // TigerBeetle time-based ID. UserData128: ToUint128(0), UserData64: 0, UserData32: 0, Ledger: 1, Code: 718, Flags: 0, Timestamp: 0, }, }) // Results handling omitted.其中ID()是客户端内置的TigerBeetle 时间基 ID 生成器(uint128.go):基于 ULID 规范,由毫秒时间戳 + 80 位随机数构成,在按小端解释时保证单调递增,且可安全地被多个 goroutine 并发调用(内部有互斥锁保证顺序一致性)。关于推荐 ID 方案的更完整讨论,可参考仓库中的>account0 := Account{ ID: ToUint128(100), Ledger: 1, Code: 718, Flags: AccountFlags{ DebitsMustNotExceedCredits: true, Linked: true, }.ToUint16(), } account1 := Account{ ID: ToUint128(101), Ledger: 1, Code: 718, Flags: AccountFlags{ History: true, }.ToUint16(), } accountResults, err := client.CreateAccounts([]Account{account0, account1}) // Results handling omitted.
5.2 响应与错误处理
CreateAccounts返回与请求一一对应的结果数组,每个元素包含状态码(Status)与时间戳(Timestamp):
- 成功创建的账户返回
AccountCreated状态及服务端分配的Timestamp; - 已存在的账户返回
AccountExists及原对象的时间戳; - 校验失败的账户返回对应错误状态码及校验发生时的时间戳。
CreateAccountStatus的完整取值定义在 bindings.go,常用的有AccountCreated、AccountExists、AccountLinkedEventFailed、AccountLinkedEventChainOpen、AccountIDMustNotBeZero、AccountLedgerMustNotBeZero、AccountCodeMustNotBeZero、AccountFlagsAreMutuallyExclusive以及AccountExistsWithDifferent*系列(表示同 ID 账户字段不一致)。
推荐的批处理结果处理模式:
account0 := Account{ ID: ToUint128(102), Ledger: 1, Code: 718, Flags: 0, } account1 := Account{ ID: ToUint128(103), Ledger: 1, Code: 718, Flags: 0, } account2 := Account{ ID: ToUint128(104), Ledger: 1, Code: 718, Flags: 0, } accountResults, err := client.CreateAccounts([]Account{account0, account1, account2}) if err != nil { log.Printf("Error creating accounts: %s", err) return } for i, result := range accountResults { switch result.Status { case AccountCreated: log.Printf("Batch account at %d successfully created with timestamp %d.", i, result.Timestamp) case AccountExists: log.Printf("Batch account at %d already exists with timestamp %d.", i, result.Timestamp) default: log.Printf("Batch account at %d failed to create: %s", i, result.Status) } }六、账户查询(LookupAccounts)
账户查询同样支持批处理:传入全部要查询的 ID,返回匹配到的账户对象。
accounts, err := client.LookupAccounts([]Uint128{ToUint128(100), ToUint128(101)})关键语义:若某个 ID 没有匹配的账户,响应中就不会有对应对象,因此返回顺序不一定与请求顺序一致,应通过每个账户的ID字段来区分。这与 get_account_balances 等参考文档 描述的行为一致。
七、创建转账(CreateTransfers)
转账在两个账户之间创建一笔记账分录(journal entry)。Transfer结构体完整字段见 bindings.go:
| 字段 | 类型 | 说明 |
|---|---|---|
ID | Uint128 | 转账唯一 ID(幂等键) |
DebitAccountID/CreditAccountID | Uint128 | 借记方 / 贷记方账户 |
Amount | Uint128 | 转账金额 |
PendingID | Uint128 | 关联的 pending 转账 ID(post/void 时使用) |
UserData128/UserData64/UserData32 | 各类型 | 业务自定义数据 |
Timeout | uint32 | pending 转账的超时(秒),单位见源码注释,仅 pending 转账可用 |
Ledger | uint32 | 账本 ID,必须与两个账户一致 |
Code | uint16 | 业务代码(如转账类型) |
Flags | uint16 | 标志位位域 |
Timestamp | uint64 | 服务端分配,创建时传 0 |
创建一笔普通转账:
transfers := []Transfer{{ ID: ID(), // TigerBeetle time-based ID. DebitAccountID: ToUint128(101), CreditAccountID: ToUint128(102), Amount: ToUint128(10), Ledger: 1, Code: 1, Flags: 0, Timestamp: 0, }} transferResults, err := client.CreateTransfers(transfers) // Results handling omitted.与创建账户相同,CreateTransfers的响应同样包含状态码与时间戳:成功返回TransferCreated与服务端时间戳;已存在返回TransferExists与原时间戳;失败返回对应错误码。
CreateTransferStatus取值定义在 bindings.go,数量较多,可按类别理解:
- 基础校验类:
TransferIDMustNotBeZero、TransferLedgerMustNotBeZero、TransferCodeMustNotBeZero、TransferAccountsMustBeDifferent、TransferDebitAccountIDMustNotBeZero、TransferFlagsAreMutuallyExclusive等; - 账户相关:
TransferDebitAccountNotFound、TransferCreditAccountNotFound、TransferAccountsMustHaveTheSameLedger、TransferTransferMustHaveTheSameLedgerAsAccounts、TransferDebitAccountAlreadyClosed、TransferCreditAccountAlreadyClosed; - 余额约束类:
TransferOverflowsDebits、TransferOverflowsCredits、TransferExceedsDebits、TransferExceedsCredits等; - 两阶段转账相关:
TransferPendingTransferNotFound、TransferPendingTransferAlreadyPosted、TransferPendingTransferAlreadyVoided、TransferPendingTransferExpired、TransferExceedsPendingTransferAmount等; - 幂等冲突类:
TransferExistsWithDifferent*系列(同 ID 但字段不同)。
结果处理示例:
transfers := []Transfer{{ ID: ToUint128(1), DebitAccountID: ToUint128(101), CreditAccountID: ToUint128(102), Amount: ToUint128(10), Ledger: 1, Code: 1, Flags: 0, }, { ID: ToUint128(2), DebitAccountID: ToUint128(101), CreditAccountID: ToUint128(102), Amount: ToUint128(10), Ledger: 1, Code: 1, Flags: 0, }, { ID: ToUint128(3), DebitAccountID: ToUint128(101), CreditAccountID: ToUint128(102), Amount: ToUint128(10), Ledger: 1, Code: 1, Flags: 0, }} transferResults, err := client.CreateTransfers(transfers) if err != nil { log.Printf("Error creating transfers: %s", err) return } for i, result := range transferResults { switch result.Status { case TransferCreated: log.Printf("Batch transfer at %d successfully created with timestamp %d.", i, result.Timestamp) case TransferExists: log.Printf("Batch transfer at %d already exists with timestamp %d.", i, result.Timestamp) default: log.Printf("Batch transfer at %d failed to create: %s", i, result.Status) } }八、批量写入:TigerBeetle 性能的关键
TigerBeetle 的吞吐在批量 API 调用时达到最大:
- 共享客户端实例 + 并发:多个 goroutine 共享一个客户端时,客户端会自动把并发请求合并批处理;
- 单次调用尽量多传:应用仍应尽量在一次调用中塞入尽可能多的事件。
反例警示:如果逐条顺序插入 100 万笔转账,插入速率只会是理论上限的一个零头——因为客户端必须等每笔的应答才能发下一笔。所以结论是:永远尽可能地批量提交。
批量上限由 TigerBeetle 服务端配置决定,默认最大批量为 8191 个事件(README 中明确写 default 8191;实际以你部署的 TigerBeetle 服务端配置为准)。客户端在CreateTransfers/CreateAccounts等操作内部会校验批量大小,超限时报ErrTooMuchData(见 tb_client.go 的TB_PACKET_TOO_MUCH_DATA分支)。
按最大批量切分写入的推荐模式:
batch := []Transfer{} BATCH_SIZE := 8191 for i := 0; i < len(batch); i += BATCH_SIZE { size := BATCH_SIZE if i+BATCH_SIZE > len(batch) { size = len(batch) - i } transferResults, err := client.CreateTransfers(batch[i : i+size]) // Results handling omitted. _, _ = transferResults, err }8.1 队列与 Worker 场景
如果请求来自队列中拉任务的 worker,可以通过「一次拉取多个任务、合并后批量提交」的方式实现批量:例如从队列一次性取出 N 个任务而不是一次一个,攒够一批再调用一次CreateTransfers。
九、转账标志位(TransferFlags)
TransferFlags结构体及ToUint16()见 bindings.go,可用的位:
| 标志 | 位 | 用途 |
|---|---|---|
Linked | bit 0 | 与批内下一事件链接 |
Pending | bit 1 | 发起两阶段转账(先挂起) |
PostPendingTransfer | bit 2 | post 一笔 pending 转账 |
VoidPendingTransfer | bit 3 | void 一笔 pending 转账 |
BalancingDebit/BalancingCredit | bit 4/5 | 差额补平转账 |
ClosingDebit/ClosingCredit | bit 6/7 | 关户转账 |
Imported | bit 8 | 导入历史转账 |
例如链接transfer0与transfer1(同生共死):
transfer0 := Transfer{ ID: ToUint128(4), DebitAccountID: ToUint128(101), CreditAccountID: ToUint128(102), Amount: ToUint128(10), Ledger: 1, Code: 1, Flags: TransferFlags{Linked: true}.ToUint16(), } transfer1 := Transfer{ ID: ToUint128(5), DebitAccountID: ToUint128(101), CreditAccountID: ToUint128(102), Amount: ToUint128(10), Ledger: 1, Code: 1, Flags: 0, } transferResults, err := client.CreateTransfers([]Transfer{transfer0, transfer1}) // Results handling omitted.十、两阶段转账(Two-Phase Transfers)
TigerBeetle原生支持两阶段转账:发起时设置Pending标志,TigerBeetle 会把金额记入双方账户的credits_pending/debits_pending;随后必须再发送一笔对应的 post 或 void 转账来终结这笔 pending 转账。相关概念与记账语义可参考 two-phase-transfers 文档 与 debit-credit 文档。
10.1 Post 一笔 Pending 转账
设置post_pending_transfer标志后,TigerBeetle 会原子地回滚双方debits_pending/credits_pending的变化,并将其应用到debits_posted/credits_posted余额。
transfer0 := Transfer{ ID: ToUint128(6), DebitAccountID: ToUint128(101), CreditAccountID: ToUint128(102), Amount: ToUint128(10), Ledger: 1, Code: 1, Flags: TransferFlags{Pending: true}.ToUint16(), } transferResults, err := client.CreateTransfers([]Transfer{transfer0}) // Results handling omitted. transfer1 := Transfer{ ID: ToUint128(7), // Post the entire pending amount. Amount: AmountMax, PendingID: ToUint128(6), Flags: TransferFlags{PostPendingTransfer: true}.ToUint16(), } transferResults, err = client.CreateTransfers([]Transfer{transfer1}) // Results handling omitted.注意Amount传AmountMax(128 位最大值)表示「post 全部 pending 金额」;若传具体金额,则必须是全部或部分且不能超过 pending 金额,否则返回TransferExceedsPendingTransferAmount。
10.2 Void 一笔 Pending 转账
设置void_pending_transfer标志则相反:TigerBeetle 回滚debits_pending/credits_pending的变化,但不会应用到debits_posted/credits_posted(即这笔转账被作废)。
transfer0 := Transfer{ ID: ToUint128(8), DebitAccountID: ToUint128(101), CreditAccountID: ToUint128(102), Amount: ToUint128(10), Timeout: 0, Ledger: 1, Code: 1, Flags: TransferFlags{Pending: true}.ToUint16(), } transferResults, err := client.CreateTransfers([]Transfer{transfer0}) // Results handling omitted. transfer1 := Transfer{ ID: ToUint128(9), Amount: ToUint128(0), PendingID: ToUint128(8), Flags: TransferFlags{VoidPendingTransfer: true}.ToUint16(), } transferResults, err = client.CreateTransfers([]Transfer{transfer1}) // Results handling omitted.完整的两阶段生命周期验证可参考 samples/two-phase/main.go(含 post 前后 pending/posted 余额断言)和 samples/two-phase-many/main.go(5 笔 pending 转账交替 post/void,逐步断言DebitsPending从 1500 递减到 0)。
十一、转账查询(LookupTransfers)
transfers, err := client.LookupTransfers([]Uint128{ToUint128(1), ToUint128(2)})与账户查询语义一致:批量传入id,返回匹配的转账;未匹配的 ID 在响应中没有对应对象,返回顺序不一定与请求顺序一致,请依据响应中的id字段区分。
注意:转账查询目前只是精确查找,不是灵活的查询 API。TigerBeetle 正在开发查询 API,未来将新增转账查询方法。更多细节可参考 lookup_transfers 参考文档。
十二、预览版查询 API
以下四个 API 均为preview 状态,在稳定查询 API 落地前可能发生破坏性变更。它们的响应都按timestamp升序或降序排列。
12.1 GetAccountTransfers:账户转账流水
获取涉及某账户的转账,支持基础过滤与分页(对应操作TB_OPERATION_GET_ACCOUNT_TRANSFERS)。
filter := AccountFilter{ AccountID: ToUint128(2), UserData128: ToUint128(0), // No filter by UserData. UserData64: 0, UserData32: 0, Code: 0, // No filter by Code. TimestampMin: 0, // No filter by Timestamp. TimestampMax: 0, // No filter by Timestamp. Limit: 10, // Limit to ten transfers at most. Flags: AccountFilterFlags{ Debits: true, // Include transfer from the debit side. Credits: true, // Include transfer from the credit side. Reversed: true, // Sort by timestamp in reverse-chronological order. }.ToUint32(), } transfers, err := client.GetAccountTransfers(filter)AccountFilter结构体定义见 bindings.go,AccountFilterFlags的位:Debits(bit 0,包含借记侧)、Credits(bit 1,包含贷记侧)、Reversed(bit 2,时间倒序)。
12.2 GetAccountBalances:账户时点余额
获取某账户的时点余额(point-in-time balances),支持过滤与分页。
前提:只有创建账户时设置了
history标志的账户才保留历史余额快照(AccountFlags{History: true}.ToUint16())。余额响应同样按timestamp排序。
filter := AccountFilter{ AccountID: ToUint128(2), UserData128: ToUint128(0), // No filter by UserData. UserData64: 0, UserData32: 0, Code: 0, // No filter by Code. TimestampMin: 0, // No filter by Timestamp. TimestampMax: 0, // No filter by Timestamp. Limit: 10, // Limit to ten balances at most. Flags: AccountFilterFlags{ Debits: true, // Include transfer from the debit side. Credits: true, // Include transfer from the credit side. Reversed: true, // Sort by timestamp in reverse-chronological order. }.ToUint32(), } account_balances, err := client.GetAccountBalances(filter)返回的AccountBalance包含DebitsPending、DebitsPosted、CreditsPending、CreditsPosted与Timestamp(见 bindings.go)。参考文档见 get_account_balances。
12.3 QueryAccounts:按字段交集查询账户
按若干字段的交集和时间戳范围查询账户:
filter := QueryFilter{ UserData128: ToUint128(1000), // Filter by UserData UserData64: 100, UserData32: 10, Code: 1, // Filter by Code Ledger: 0, // No filter by Ledger TimestampMin: 0, // No filter by Timestamp. TimestampMax: 0, // No filter by Timestamp. Limit: 10, // Limit to ten accounts at most. Flags: QueryFilterFlags{ Reversed: true, // Sort by timestamp in reverse-chronological order. }.ToUint32(), } accounts, err := client.QueryAccounts(filter)12.4 QueryTransfers:按字段交集查询转账
filter := QueryFilter{ UserData128: ToUint128(1000), // Filter by UserData. UserData64: 100, UserData32: 10, Code: 1, // Filter by Code. Ledger: 0, // No filter by Ledger. TimestampMin: 0, // No filter by Timestamp. TimestampMax: 0, // No filter by Timestamp. Limit: 10, // Limit to ten transfers at most. Flags: QueryFilterFlags{ Reversed: true, // Sort by timestamp in reverse-chronological order. }.ToUint32(), } transfers, err := client.QueryTransfers(filter)QueryFilter与QueryFilterFlags定义见 bindings.go,QueryFilterFlags目前仅有Reversed(bit 0,倒序)。过滤条件的完整语义可参考 query_filter 参考文档 与 account-filter 参考文档。
十三、链接事件(Linked Events):原子链
在创建账户或转账时指定linked标志,即可把该事件与批内下一个事件链接起来,形成一条任意长度、全部成功或全部失败的事件链:
- 链的尾部是第一个没有
linked标志的事件——因此批内最后一个事件绝不能带linked标志,否则链是开放的、永不闭合; - 一个批次内可以同时存在多条链或独立事件,它们各自独立成功/失败;
- 链内事件按顺序执行,出错时整体回滚:链中每个事件的副作用对链内后续事件可见,整条链对链外后续事件要么整体可见要么整体不可见;
- 第一个破坏链的事件会获得独特的错误结果,链内其余事件统一返回
linked_event_failed(对应AccountLinkedEventFailed/TransferLinkedEventFailed)。
链式事件的完整示例(注意其中故意构造了失败链与成功链的对比):
batch := []Transfer{} linkedFlag := TransferFlags{Linked: true}.ToUint16() // An individual transfer (successful): batch = append(batch, Transfer{ID: ToUint128(1) /* ... rest of transfer ... */}) // A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false): batch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Commit/rollback. batch = append(batch, Transfer{ID: ToUint128(3) /* ... , */, Flags: linkedFlag}) // Commit/rollback. batch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Fail with exists batch = append(batch, Transfer{ID: ToUint128(4) /* ... , */}) // Fail without committing // An individual transfer (successful): // This should not see any effect from the failed chain above. batch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */}) // A chain of 2 transfers (the first transfer fails the chain): batch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */, Flags: linkedFlag}) batch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */}) // A chain of 2 transfers (successful): batch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */, Flags: linkedFlag}) batch = append(batch, Transfer{ID: ToUint128(4) /* ... rest of transfer ... */}) transferResults, err := client.CreateTransfers(batch) // Results handling omitted.链接事件的完整语义(链式提交/回滚、linked_event_failed传播)可参考 linked-events 文档。
十四、导入历史事件(Imported Events)
创建账户或转账时指定imported标志,即可以用户自定义时间戳导入历史事件(例如从旧系统迁移数据):
- 整个批次必须全部设置
imported标志(否则报AccountImportedEventExpected/AccountImportedEventNotExpected); - 推荐把整个批次作为一条
linked链提交:任何事件失败则整批不提交,集群时间戳保持不变; - 这样应用可以修正失败事件后,用相同的时间戳重新提交,而不会让集群时间戳发生回退(
imported事件必须满足时间戳单调递增等约束,见AccountImportedEventTimestampMustNotAdvance、TransferImportedEventTimestampMustNotRegress、TransferImportedEventTimestampMustPostdateDebitAccount等错误码)。
// External source of time. var historicalTimestamp uint64 = 0 historicalAccounts := []Account{ /* Loaded from an external source. */ } historicalTransfers := []Transfer{ /* Loaded from an external source. */ } // First, load and import all accounts with their timestamps from the historical source. accountsBatch := []Account{} for index, account := range historicalAccounts { // Set a unique and strictly increasing timestamp. historicalTimestamp += 1 account.Timestamp = historicalTimestamp account.Flags = AccountFlags{ // Set the account as `imported`. Imported: true, // To ensure atomicity, the entire batch (except the last event in the chain) // must be `linked`. Linked: index < len(historicalAccounts)-1, }.ToUint16() accountsBatch = append(accountsBatch, account) } accountResults, err := client.CreateAccounts(accountsBatch) // Results handling omitted. // Then, load and import all transfers with their timestamps from the historical source. transfersBatch := []Transfer{} for index, transfer := range historicalTransfers { // Set a unique and strictly increasing timestamp. historicalTimestamp += 1 transfer.Timestamp = historicalTimestamp transfer.Flags = TransferFlags{ // Set the transfer as `imported`. Imported: true, // To ensure atomicity, the entire batch (except the last event in the chain) // must be `linked`. Linked: index < len(historicalAccounts)-1, }.ToUint16() transfersBatch = append(transfersBatch, transfer) } transferResults, err := client.CreateTransfers(transfersBatch) // Results handling omitted.. // Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried // with the same historical timestamps without regressing the cluster timestamp.导入的顺序要求是先导入全部账户、再导入转账(转账引用的账户必须已存在)。相关错误码见 bindings.go 中AccountImportedEvent*与TransferImportedEvent*系列常量(如AccountImportedEventTimestampMustNotRegress、TransferImportedEventTimeoutMustBeZero,即导入的转账Timeout必须为 0)。
十五、超时与取消:可靠的幂等提交
客户端会无限期重试,不施加任何单请求超时;取消(cancellation)只是被提供的一种机制,具体取消策略由应用自行决定:
Client实例可在任何时刻Close();- 关闭后,所有在途请求被取消并向调用方返回错误(对应
ErrClientClosed,见 tb_client.go 中TB_CLIENT_INVALID与TB_PACKET_CLIENT_SHUTDOWN分支); - 即使调用方收到错误,请求仍可能已被 TigerBeetle 服务端处理——这正是重试场景下需要以 ID 做端到端幂等的原因。
为保证转账可安全重试,请使用稳定的转账ID实现端到端幂等:如果重发同 ID 的转账,服务端会返回TransferExists(或TransferExistsWithDifferent*提示字段不一致),从而避免重复入账。完整的可靠性策略见 reliable-transaction-submission 文档。
十六、与其他客户端的协同认知
本文介绍的 API 形态(CreateAccounts、CreateTransfers、LookupAccounts、LookupTransfers以及四个 preview 查询 API)与 TigerBeetle 其余语言客户端保持一致,底层都通过 cgo 绑定同一份 tb_client.h 原生头文件与预编译静态库(macOS/Linux/Windows 各平台各有对应的libtb_client_*目标文件,见 tb_client.go 中的 cgo 链接指令)。若需深入了解服务端请求处理与批次语义,可继续阅读 requests 参考文档、请求语义文档 与 数据建模文档;客户端接口全集(含实验性GetChangeEvents)见 tb_client.go 的Client接口定义。
结语
至此,你已经掌握了 Go 客户端操作 TigerBeetle 的全部核心能力:环境搭建与客户端初始化、账户与转账的批量创建和查询、两阶段转账的 post/void 生命周期、链式事件的原子性、历史数据的导入以及以批量化为核心的性能优化路径。把这些模式组合起来,即可构建一套面向关键任务场景、具备端到端幂等保障的记账与支付核心。
【免费下载链接】tigerbeetleThe financial transactions database designed for mission critical safety and performance.项目地址: https://gitcode.com/GitHub_Trending/ti/tigerbeetle
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考