TigerBeetle Go 客户端实战指南:账户、转账、两阶段转账与批量操作
2026/9/14 18:04:11 网站建设 项目流程

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 vetgo test,并针对basictwo-phasetwo-phase-manywalkthrough四个示例启动临时 TigerBeetle 实例做端到端验证,说明这套流程是经过完整测试的。

二、官方示例项目一览

Go 客户端仓库提供了四个可直接运行的示例(均有go.modmain.go):

示例路径演示内容
Basicsamples/basic创建两个账户,并在它们之间转移一笔金额
Two-Phase Transfersamples/two-phase创建两个账户,发起一笔 pending 转账后再 post 该转账
Many Two-Phase Transferssamples/two-phase-many创建两个账户,发起多笔 pending 转账,交替 post/void
Walkthroughsamples/walkthrough完整走查教程

其中 Basic 示例的运行流程是:创建账户12(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 中NewClientstrings.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:30013001是缺省端口)。

多个副本地址以逗号分隔传入[]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 的IDUserData128Amount、账户余额等字段都是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结构体,完整字段及含义如下:

字段类型说明
IDUint128账户唯一 ID,推荐使用时间基 ID
DebitsPending/DebitsPosted/CreditsPending/CreditsPostedUint128四类余额,创建时通常为 0
UserData128/UserData64/UserData32Uint128 / uint64 / uint32业务自定义数据,可用于查询过滤
Reserveduint32保留字段,必须为 0
Ledgeruint32账本 ID,用于隔离不同业务账本
Codeuint16业务代码(如账户类型)
Flagsuint16标志位位域
Timestampuint64由服务端分配,创建时传 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,常用的有AccountCreatedAccountExistsAccountLinkedEventFailedAccountLinkedEventChainOpenAccountIDMustNotBeZeroAccountLedgerMustNotBeZeroAccountCodeMustNotBeZeroAccountFlagsAreMutuallyExclusive以及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:

字段类型说明
IDUint128转账唯一 ID(幂等键)
DebitAccountID/CreditAccountIDUint128借记方 / 贷记方账户
AmountUint128转账金额
PendingIDUint128关联的 pending 转账 ID(post/void 时使用)
UserData128/UserData64/UserData32各类型业务自定义数据
Timeoutuint32pending 转账的超时(秒),单位见源码注释,仅 pending 转账可用
Ledgeruint32账本 ID,必须与两个账户一致
Codeuint16业务代码(如转账类型)
Flagsuint16标志位位域
Timestampuint64服务端分配,创建时传 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,数量较多,可按类别理解:

  • 基础校验类TransferIDMustNotBeZeroTransferLedgerMustNotBeZeroTransferCodeMustNotBeZeroTransferAccountsMustBeDifferentTransferDebitAccountIDMustNotBeZeroTransferFlagsAreMutuallyExclusive等;
  • 账户相关TransferDebitAccountNotFoundTransferCreditAccountNotFoundTransferAccountsMustHaveTheSameLedgerTransferTransferMustHaveTheSameLedgerAsAccountsTransferDebitAccountAlreadyClosedTransferCreditAccountAlreadyClosed
  • 余额约束类TransferOverflowsDebitsTransferOverflowsCreditsTransferExceedsDebitsTransferExceedsCredits等;
  • 两阶段转账相关TransferPendingTransferNotFoundTransferPendingTransferAlreadyPostedTransferPendingTransferAlreadyVoidedTransferPendingTransferExpiredTransferExceedsPendingTransferAmount等;
  • 幂等冲突类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 调用时达到最大:

  1. 共享客户端实例 + 并发:多个 goroutine 共享一个客户端时,客户端会自动把并发请求合并批处理;
  2. 单次调用尽量多传:应用仍应尽量在一次调用中塞入尽可能多的事件。

反例警示:如果逐条顺序插入 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,可用的位:

标志用途
Linkedbit 0与批内下一事件链接
Pendingbit 1发起两阶段转账(先挂起)
PostPendingTransferbit 2post 一笔 pending 转账
VoidPendingTransferbit 3void 一笔 pending 转账
BalancingDebit/BalancingCreditbit 4/5差额补平转账
ClosingDebit/ClosingCreditbit 6/7关户转账
Importedbit 8导入历史转账

例如链接transfer0transfer1(同生共死):

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.

注意AmountAmountMax(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包含DebitsPendingDebitsPostedCreditsPendingCreditsPostedTimestamp(见 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)

QueryFilterQueryFilterFlags定义见 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事件必须满足时间戳单调递增等约束,见AccountImportedEventTimestampMustNotAdvanceTransferImportedEventTimestampMustNotRegressTransferImportedEventTimestampMustPostdateDebitAccount等错误码)。
// 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*系列常量(如AccountImportedEventTimestampMustNotRegressTransferImportedEventTimeoutMustBeZero,即导入的转账Timeout必须为 0)。

十五、超时与取消:可靠的幂等提交

客户端会无限期重试,不施加任何单请求超时;取消(cancellation)只是被提供的一种机制,具体取消策略由应用自行决定:

  • Client实例可在任何时刻Close()
  • 关闭后,所有在途请求被取消并向调用方返回错误(对应ErrClientClosed,见 tb_client.go 中TB_CLIENT_INVALIDTB_PACKET_CLIENT_SHUTDOWN分支);
  • 即使调用方收到错误,请求仍可能已被 TigerBeetle 服务端处理——这正是重试场景下需要以 ID 做端到端幂等的原因。

为保证转账可安全重试,请使用稳定的转账ID实现端到端幂等:如果重发同 ID 的转账,服务端会返回TransferExists(或TransferExistsWithDifferent*提示字段不一致),从而避免重复入账。完整的可靠性策略见 reliable-transaction-submission 文档。

十六、与其他客户端的协同认知

本文介绍的 API 形态(CreateAccountsCreateTransfersLookupAccountsLookupTransfers以及四个 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询