
TigerBeetle Rust 客户端完全指南从账户、转账到两阶段事务与查询 API【免费下载链接】tigerbeetleThe financial transactions database designed for mission critical safety and performance.项目地址: https://gitcode.com/GitHub_Trending/ti/tigerbeetle本指南以 TigerBeetle 官方 Rust 客户端tigerbeetlecrate位于 src/clients/rust为对象系统讲解如何在 Rust 应用中连接 TigerBeetle 集群、创建账户与转账、实现两阶段事务、批量请求以及使用预览版查询 API。读完本文你将能够独立搭建 Rust 项目并编写出完整、可运行、具备错误处理与性能意识的 TigerBeetle 客户端代码。前置条件与运行环境TigerBeetle 官方文档明确了客户端的运行环境要求Linux 5.6 是唯一支持的生产环境为了便于开发同时支持 macOS 和 Windows。Rust 1.68Cargo.toml 中声明的rust-version 1.63是最低编译下限README 推荐使用 1.68 及以上的工具链。此外该 crate 会静态链接一个名为tb_client的非 Rust 静态库原生头文件见 assets/tb_client.h在官方支持的平台上链接工作由构建脚本自动完成无需手动配置。快速开始创建项目并运行第一个示例首先创建一个目录存放你的项目进入该目录后创建Cargo.toml[package] name tigerbeetle-test version 0.1.0 edition 2024 [dependencies] tigerbeetle.path ../.. futures 0.3说明tigerbeetle.path ../..表示以仓库内相对路径引用本客户端 crate若从 crates.io 安装则改为tigerbeetle 版本号。futures用于提供futures::executor::block_on以便在同步代码中驱动 async 客户端见下文在同步代码中使用一节。创建src/main.rsuse tigerbeetle as tb; fn main() - Result(), Boxdyn std::error::Error { futures::executor::block_on(main_async()) } async fn main_async() - Result(), Boxdyn std::error::Error { println!(hello world); Ok(()) }编译并运行cargo run如果输出hello world说明依赖与构建环境一切正常可以开始使用 TigerBeetle 了。仓库自带的示例项目仓库在 src/clients/rust/samples 下提供了四个可直接参考的完整示例basic创建两个账户并在它们之间转账最基础的读写闭环。two-phase创建两个账户发起一笔 pending挂起转账再通过 post 完成两阶段转账。two-phase-many创建两个账户后发起多笔挂起转账交替进行 post 与 void。walkthrough更完整的端到端演练。其中 basic/src/main.rs 展示了最小可用闭环创建账户 → 转账 →lookup_accounts校验余额。它通过TB_ADDRESS环境变量读取端口默认3000随后创建两个账户并完成一笔amount 10的转账最后断言账户 1 的debits_posted 10、账户 2 的credits_posted 10。创建客户端Client与地址解析客户端通过集群 IDcluster ID与所有副本的地址列表创建这两个信息由启动 TigerBeetle 集群的系统决定。客户端是线程安全的单个实例应在多个并发任务之间共享——这允许事件被自动批量提交从而提升吞吐当需要同时连接多个 TigerBeetle 集群时才需要创建多个客户端。以下示例使用集群 ID0、单个副本地址从TB_ADDRESS环境变量读取默认端口3000let cluster_id 0; let replica_address std::env::var(TB_ADDRESS) .ok() .unwrap_or_else(|| String::from(3000)); let client tb::Client::new(cluster_id, replica_address)?;合法的地址格式地址可以是 IP 地址、端口号或两者的组合以下形式均合法3000→ 被解析为127.0.0.1:3000127.0.0.1:3000→ 被解析为127.0.0.1:3000127.0.0.1→ 被解析为127.0.0.1:3001默认端口是3001底层实现src/clients/rust/src/lib.rs 中Client::new的文档注释进一步说明addresses是逗号分隔的字符串每个地址可为 IP4 地址、端口号或IP:port组合例如127.0.0.1,3002,127.0.0.1:3003表示连接三个副本默认 IP 为127.0.0.1默认端口为3001。这与 TigerBeetle CLI 支持的地址格式一致。若初始化失败Client::new返回InitStatus枚举包括Unexpected意外错误、OutOfMemory、AddressInvalid地址解析失败、AddressLimitExceeded地址数量超限、SystemResources文件描述符、线程、可锁内存等系统资源耗尽、NetworkSubsystem网络不可用或初始化失败。底层架构自含事件循环从源码结构看Rust 客户端对外呈现 async 接口但本身不依赖任何特定 Rust 异步运行时——它自带一个离线程off-thread事件循环与所有官方 TigerBeetle 客户端共享同一套tb_client原生实现见 lib.rs 顶部 crate 级文档。代价是每个请求都要做一次线程间上下文切换但其开销相对网络与磁盘 I/O 而言可以忽略。Client结构体内仅持有一个不透明指针*mut tbc::tb_client_t并显式unsafe impl Send/Sync因此可以放入Arc在多个线程/异步任务中并行使用。账户模型与创建账户TigerBeetle 账户通过Account结构体表示#[repr(C)]与协议 ABI 兼容。其字段包括id: u128——账户全局唯一标识debits_pending/debits_posted/credits_pending/credits_posted——四类余额均由服务端维护客户端提交时应为0user_data_128: u128、user_data_64: u64、user_data_32: u32——三档应用自定义数据reserved——保留字节使用Default::default()填充ledger: u32、code: u16——账本与业务编码flags: AccountFlags——位域标志timestamp: u64——由服务端分配创建账户时须为0。创建账户的最小示例let account_results client .create_accounts([tb::Account { id: tb::id(), ledger: 1, code: 718, ..Default::default() }])? .await?; // Result handling omitted.时间戳标识符tb::id()示例中的tb::id()用于生成 TigerBeetle推荐的时间戳标识符time-based identifiers其实现位于 src/clients/rust/src/time_based_id.rs将 128 位拆分为48 位毫秒级时间戳 80 位随机数ms_since_epoch 80 | random。这类 ID 具备字典序可排序、单调递增的特性能够在 TigerBeetle 的 LSM 树中启用写入优化。源码同时说明了两类退化场景系统时钟回拨ID 会从前一个 ID 开始顺序递增直到时钟追上来系统时间早于 Unix 纪元ID 会从 Unix 纪元加一个基础随机数开始顺序生成。其配套的单测同文件mod tests验证了正常时钟、随机数溢出、时钟回拨、回拨后追平、纪元前等多种情况下 ID 的单调性。更完整的 ID 方案设计见数据建模文档。账户标志AccountFlags账户flags字段是位域。在原生头文件 assets/tb_client.h 中定义如下TB_ACCOUNT_LINKED 1 0LinkedTB_ACCOUNT_DEBITS_MUST_NOT_EXCEED_CREDITS 1 1DebitsMustNotExceedCreditsTB_ACCOUNT_CREDITS_MUST_NOT_EXCEED_DEBITS 1 2CreditsMustNotExceedDebitsTB_ACCOUNT_HISTORY 1 3HistoryTB_ACCOUNT_IMPORTED 1 4ImportedTB_ACCOUNT_CLOSED 1 5ClosedRust 侧通过AccountFlagsbitflags 类型暴露可用|组合。常见组合示例AccountFlags::LinkedAccountFlags::DebitsMustNotExceedCreditsAccountFlags::CreditsMustNotExceedDebitsAccountFlags::HistoryAccountFlags::Linked | AccountFlags::History下面的例子创建两个链式关联的账户其中第一个账户额外带有debits_must_not_exceed_credits约束第二个账户开启历史余额let account0 tb::Account { id: 100, ledger: 1, code: 718, flags: tb::AccountFlags::DebitsMustNotExceedCredits | tb::AccountFlags::Linked, ..Default::default() }; let account1 tb::Account { id: 101, ledger: 1, code: 718, flags: tb::AccountFlags::History, ..Default::default() }; let account_results client.create_accounts([account0, account1])?.await?; // Result handling omitted.各标志的完整语义参见 Account 参考文档。响应与错误处理create_accounts的响应是数组包含批中每个账户的状态码status与时间戳timestamp创建成功的账户返回Created并携带服务端为该Account分配的时间戳已存在的账户返回Exists并携带原对象的时间戳校验失败的账户返回对应状态码及校验发生时的时间戳完整错误条件见 create_accounts 参考。逐结果处理的完整示例let account0 tb::Account { id: 102, ledger: 1, code: 718, ..Default::default() }; let account1 tb::Account { id: 103, ledger: 1, code: 718, ..Default::default() }; let account2 tb::Account { id: 104, ledger: 1, code: 718, ..Default::default() }; let account_results client .create_accounts([account0, account1, account2])? .await?; assert!(account_results.len() 3); for (index, result) in account_results.into_iter().enumerate() { match result.status { tb::CreateAccountStatus::Created { println!( Batch account at {} successfully created with timestamp {}, index, result.timestamp ); } tb::CreateAccountStatus::Exists { println!( Batch account at {} already exists with timestamp {}., index, result.timestamp ); } _ { eprintln!( Batch account at {} failed to create: {:?}, index, result.status ); } } }CreateAccountStatus枚举定义于 lib.rs覆盖了全部服务端状态码例如LinkedEventFailed、LinkedEventChainOpen、ImportedEventExpected、TimestampMustBeZero、IdMustNotBeZero、ExistsWithDifferentFlags、LedgerMustNotBeZero、CodeMustNotBeZero、DebitsPendingMustBeZero等。值得注意的是lib.rs 在create_accounts的文档中指出Exists状态应经常与Created同等对待因为它同样返回原账户的时间戳——这种结果可能出现在应用崩溃后重放请求等场景中。账户查询lookup_accounts账户查询与创建一样是批量的传入所有要查询的 ID返回匹配的账户。关键语义若某个 ID 没有匹配的账户响应中不会出现该账户因此响应中账户的顺序不一定与请求中 ID 的顺序一致需要通过响应的id字段来区分。let accounts client.lookup_accounts([100, 101])?.await?;若需要把请求 ID 与查询结果一一对应lib.rs 的文档给出了一个merge_lookup_accounts_results帮助函数将结果按id与请求序列对齐未命中的位置得到None从而区分找到了与未找到。创建转账create_transfers转账在两个账户之间创建一条账目journal entry对应Transfer结构体同样#[repr(C)]id: u128——转账全局唯一标识debit_account_id/credit_account_id——借方与贷方账户amount: u128——金额pending_id——两阶段转账时关联的挂起转账 ID普通转账须为0user_data_128 / 64 / 32——应用自定义数据timeout: u32——仅挂起转账可用ledger: u32、code: u16flags: TransferFlagstimestamp: u64——由服务端分配提交时应为0。最小转账示例let transfers vec![tb::Transfer { id: tb::id(), debit_account_id: 101, credit_account_id: 102, amount: 10, ledger: 1, code: 1, ..Default::default() }]; let transfer_results client.create_transfers(transfers)?.await?; // Result handling omitted.同样推荐使用tb::id()生成 ID详见数据建模文档。响应与错误create_transfers的响应同样是状态码 时间戳数组成功创建返回Created带分配的时间戳已存在返回Exists带原转账的时间戳失败返回状态码与校验发生时间完整错误条件见 create_transfers 参考。完整逐结果处理示例let transfers vec![ tb::Transfer { id: 1, debit_account_id: 101, credit_account_id: 102, amount: 10, ledger: 1, code: 1, ..Default::default() }, tb::Transfer { id: 2, debit_account_id: 101, credit_account_id: 102, amount: 10, ledger: 1, code: 1, ..Default::default() }, tb::Transfer { id: 3, debit_account_id: 101, credit_account_id: 102, amount: 10, ledger: 1, code: 1, ..Default::default() }, ]; let transfer_results client.create_transfers(transfers)?.await?; assert!(transfer_results.len() transfers.len()); for (index, result) in transfer_results.into_iter().enumerate() { match result.status { tb::CreateTransferStatus::Created { println!( Batch transfer at {} successfully created with timestamp {}, index, result.timestamp ); } tb::CreateTransferStatus::Exists { println!( Batch transfer at {} already exists with timestamp {}., index, result.timestamp ); } _ { eprintln!( Batch transfer at {} failed to create: {:?}, index, result.status ); } } }CreateTransferStatus枚举覆盖了全部转账相关状态包括DebitAccountNotFound、CreditAccountNotFound、AccountsMustHaveTheSameLedger、ExceedsCredits、ExceedsDebits、OverflowsDebitsPosted、PendingTransferNotFound、PendingTransferAlreadyPosted、DebitAccountAlreadyClosed等完整清单见 lib.rs 中的定义。批处理Batching与性能TigerBeetle 的吞吐只有在大量事件一次性提交时才能发挥到极致。客户端实例在跨线程/任务共享时会自动做内部合并但应用层仍然应当在单次调用中尽可能多地提交事件。例如若要插入 100 万笔转账若一次一笔顺序插入则每一笔都要等待回复后才发下一笔插入速率将只是理论值的极小一部分。因此尽可能批量提交。最大批大小由 TigerBeetle 服务端构建时配置决定默认值是 8189。超过该上限时请求 future 将返回PacketStatus::TooMuchData。let transfers: Vectb::Transfer vec![]; const BATCH_SIZE: usize 8189; for batch in transfers.chunks(BATCH_SIZE) { let transfer_results client.create_transfers(batch)?.await?; // Result handling omitted. }队列与消费者Workers场景如果是从队列中拉取任务并提交给 TigerBeetle可以通过让消费者一次处理多个队列任务来实现批量即每次从队列一次性拉取多条任务合并成一批请求提交而不是一条一条处理。转账标志TransferFlags与账户类似转账的flags字段也是位域。assets/tb_client.h 中的完整定义TB_TRANSFER_LINKED 1 0LinkedTB_TRANSFER_PENDING 1 1PendingTB_TRANSFER_POST_PENDING_TRANSFER 1 2PostPendingTransferTB_TRANSFER_VOID_PENDING_TRANSFER 1 3VoidPendingTransferTB_TRANSFER_BALANCING_DEBIT 1 4TB_TRANSFER_BALANCING_CREDIT 1 5TB_TRANSFER_CLOSING_DEBIT 1 6TB_TRANSFER_CLOSING_CREDIT 1 7TB_TRANSFER_IMPORTED 1 8常用组合TransferFlags::LinkedTransferFlags::PendingTransferFlags::PostPendingTransferTransferFlags::VoidPendingTransferTransferFlags::Linked | TransferFlags::Pending将transfer0与transfer1链式关联的示例let transfer0 tb::Transfer { id: 4, debit_account_id: 101, credit_account_id: 102, amount: 10, ledger: 1, code: 1, flags: tb::TransferFlags::Linked, ..Default::default() }; let transfer1 tb::Transfer { id: 5, debit_account_id: 101, credit_account_id: 102, amount: 10, ledger: 1, code: 1, ..Default::default() }; let transfer_results client.create_transfers([transfer0, transfer1])?.await?; // Result handling omitted.两阶段转账Two-Phase Transfers两阶段转账通过设置相应标志原生支持。发起挂起转账时TigerBeetle 会调整对应账户的credits_pending与debits_pending字段随后必须发送一条对应的post入账或void作废转账来完成收尾。完整背景可参考两阶段转账文档。挂起一笔转账挂起转账使用Pending标志。以 samples/two-phase/src/main.rs 为例创建两笔 500 的挂起转账后账户 1 的debits_pending变为 500账户 2 的credits_pending变为 500而*_posted余额仍为 0。Post入账一笔挂起转账将flags设为PostPendingTransfer即可入账TigerBeetle 会原子地将对应账户debits_pending/credits_pending的变动回滚并应用到debits_posted/credits_posted余额上。let transfer0 tb::Transfer { id: 6, debit_account_id: 101, credit_account_id: 102, amount: 10, ledger: 1, code: 1, ..Default::default() }; let transfer_results client.create_transfers([transfer0])?.await?; // Result handling omitted. let transfer1 tb::Transfer { id: 7, amount: u128::MAX, pending_id: 6, flags: tb::TransferFlags::PostPendingTransfer, ..Default::default() }; let transfer_results client.create_transfers([transfer1])?.await?; // Result handling omitted.注意post 转账的amount填u128::MAX表示全额入账也即与原挂起金额一致若填具体金额则该金额不得超过挂起转账金额否则返回ExceedsPendingTransferAmount。Void作废一笔挂起转账反之将flags设为VoidPendingTransfer会作废该转账TigerBeetle 回滚debits_pending/credits_pending的变动并且不会将其应用到debits_posted/credits_posted。let transfer0 tb::Transfer { id: 8, debit_account_id: 101, credit_account_id: 102, amount: 10, ledger: 1, code: 1, ..Default::default() }; let transfer_results client.create_transfers([transfer0])?.await?; // Result handling omitted. let transfer1 tb::Transfer { id: 9, amount: 0, pending_id: 8, flags: tb::TransferFlags::VoidPendingTransfer, ..Default::default() }; let transfer_results client.create_transfers([transfer1])?.await?; // Result handling omitted.注意void 转账的amount必须填0作废本身不移动金额。two-phase 示例 完整演示了整个生命周期挂起后断言pending余额 → post 后断言posted余额并核对转账标志转账 1 带Pending且不带PostPendingTransfer转账 2 反之。该示例使用tokio运行时驱动异步客户端并给出了 tokio 依赖声明方式[dependencies] tokio.version 1.38.1 tokio.features [rt-multi-thread] tigerbeetle.path ../.. [profile.release] overflow-checks true转账查询lookup_transfers注意转账查询目前是点查询而非灵活的查询 API项目正在开发查询 API未来将提供新的查询方法。转账查询与转账创建一样是批量操作传入所有id返回匹配的转账。与账户查询相同的语义未匹配的 ID 不返回对象因此响应顺序不一定与请求顺序一致需以响应中的id字段区分。let transfers client.lookup_transfers([1, 2])?.await?;预览版查询 API以下四个 API 均为预览preview功能在稳定查询 API 落地前可能发生破坏性变更get_account_transfers获取指定账户涉及的转账支持基础过滤与分页get_account_balances获取指定账户的某个时间点余额历史余额query_accounts按若干字段的交集与时间戳范围查询账户query_transfers按若干字段的交集与时间戳范围查询转账。这些范围查询的结果均按timestamp升序或降序排序。Get Account Transfers使用AccountFilter作为过滤条件let filter tb::AccountFilter { account_id: 2, user_data_128: 0, user_data_64: 0, user_data_32: 0, code: 0, reserved: Default::default(), timestamp_min: 0, timestamp_max: 0, limit: 10, flags: tb::AccountFilterFlags::Debits | tb::AccountFilterFlags::Credits | tb::AccountFilterFlags::Reversed, }; let transfers client.get_account_transfers(filter)?.await?;AccountFilterFlags在 tb_client.h 中定义为TB_ACCOUNT_FILTER_DEBITS 1 0、TB_ACCOUNT_FILTER_CREDITS 1 1、TB_ACCOUNT_FILTER_REVERSED 1 2。其中Debits与Credits表示该账户作为借方/贷方的转账都要返回Reversed表示结果按时间倒序返回。字段含义详见 AccountFilter 参考。Get Account Balances只有创建时设置了History标志的账户才会保留历史余额let filter tb::AccountFilter { account_id: 2, user_data_128: 0, user_data_64: 0, user_data_32: 0, code: 0, reserved: Default::default(), timestamp_min: 0, timestamp_max: 0, limit: 10, flags: tb::AccountFilterFlags::Debits | tb::AccountFilterFlags::Credits | tb::AccountFilterFlags::Reversed, }; let account_balances client.get_account_balances(filter)?.await?;返回的AccountBalance结构体包含debits_pending / debits_posted / credits_pending / credits_posted四类余额及timestamp。参考文档get_account_balances。Query Accounts / Query Transfers两者共用QueryFilterlet filter tb::QueryFilter { user_data_128: 1000, user_data_64: 100, user_data_32: 10, code: 1, ledger: 0, reserved: Default::default(), timestamp_min: 0, timestamp_max: 0, limit: 10, flags: tb::QueryFilterFlags::Reversed, }; let accounts client.query_accounts(filter)?.await?;let filter tb::QueryFilter { user_data_128: 1000, user_data_64: 100, user_data_32: 10, code: 1, ledger: 0, reserved: Default::default(), timestamp_min: 0, timestamp_max: 0, limit: 10, flags: tb::QueryFilterFlags::Reversed, }; let transfers client.query_transfers(filter)?.await?;QueryFilterFlags目前仅有TB_QUERY_FILTER_REVERSED 1 0。参考文档QueryFilter、query_accounts、query_transfers。范围查询的分页范围查询同样有结果数量上限标准构建配置下每次最多返回 8189 条。如果服务端返回了满批说明可能还有更多结果可通过移动时间戳窗口继续分页将timestamp_min正序或timestamp_max倒序设为上一批中最大/最小时间戳的1/-1以相同过滤条件重新查询直到服务端返回不满一批为止。lib.rs 的 crate 级文档提供了一个完整的get_account_transfers_paged分页流实现基于futures::stream::unfold在正序模式下用timestamp_last.checked_add(1)作为下一页timestamp_min在Reversed模式下用timestamp_last.checked_sub(1)作为下一页timestamp_max当返回条数小于limit时结束。链式事件Linked Events当账户创建或转账创建中某个事件的linked标志被设置时该事件与批中的下一个事件链接从而形成一条任意长度、要么全部成功要么全部失败的事件链。链的尾部由第一个不带linked标志的事件表示因此批中最后一个事件绝不能设置linked否则会形成未闭合的链。一个批中可以同时存在多条链或独立事件各自独立成败。链内事件按顺序执行出错时回滚因此链中每个事件的效果对链内后续事件可见链作为一个整体对链之后的事件要么完全可见、要么完全不可见第一个导致链失败的事件会得到唯一的错误结果链中其他事件的结果会被置为linked_event_failed。示例注意链内 ID 重复是为了演示失败与回滚语义let mut batch vec![]; let linked_flag tb::TransferFlags::Linked; // An individual transfer (successful): batch.push(tb::Transfer { id: 1, ..Default::default() }); // A chain of 4 transfers (the last transfer in the chain closes the chain with linkedfalse): batch.push(tb::Transfer { id: 2, flags: linked_flag, ..Default::default() }); batch.push(tb::Transfer { id: 3, flags: linked_flag, ..Default::default() }); batch.push(tb::Transfer { id: 2, flags: linked_flag, ..Default::default() }); batch.push(tb::Transfer { id: 4, ..Default::default() }); // An individual transfer (successful): // This should not see any effect from the failed chain above. batch.push(tb::Transfer { id: 2, ..Default::default() }); // A chain of 2 transfers (the first transfer fails the chain): batch.push(tb::Transfer { id: 2, flags: linked_flag, ..Default::default() }); batch.push(tb::Transfer { id: 3, ..Default::default() }); // A chain of 2 transfers (successful): batch.push(tb::Transfer { id: 3, flags: linked_flag, ..Default::default() }); batch.push(tb::Transfer { id: 4, ..Default::default() }); let transfer_results client.create_transfers(batch)?.await?; // Result handling omitted.链式事件的完整设计说明见链式事件文档。导入事件Imported Events当账户创建或转账创建中设置imported标志时允许以用户自定义的时间戳导入历史事件。要点整个批必须全部设置imported标志建议将整批作为一条linked链提交这样任何事件失败时整批都不会提交从而保持集群时间戳不变失败后应用有机会修正导入事件并用相同的时间戳重新提交整个批而不会使集群时间戳前移。示例模拟从外部数据源加载历史账户与转账// External source of time. let mut historical_timestamp: u64 0; let historical_accounts: Vectb::Account vec![]; // Loaded from an external source. let historical_transfers: Vectb::Transfer vec![]; // Loaded from an external source. // First, load and import all accounts with their timestamps from the historical source. let mut accounts_batch vec![]; for (index, mut account) in historical_accounts.into_iter().enumerate() { // Set a unique and strictly increasing timestamp. historical_timestamp 1; account.timestamp historical_timestamp; account.flags if index accounts_batch.len() - 1 { tb::AccountFlags::Imported | tb::AccountFlags::Linked } else { tb::AccountFlags::Imported }; accounts_batch.push(account); } let account_results client.create_accounts(accounts_batch)?.await?; // Result handling omitted. // Then, load and import all transfers with their timestamps from the historical source. let mut transfers_batch vec![]; for (index, mut transfer) in historical_transfers.into_iter().enumerate() { // Set a unique and strictly increasing timestamp. historical_timestamp 1; transfer.timestamp historical_timestamp; transfer.flags if index transfers_batch.len() - 1 { tb::TransferFlags::Imported | tb::TransferFlags::Linked } else { tb::TransferFlags::Imported }; transfers_batch.push(transfer); } let transfer_results client.create_transfers(transfers_batch)?.await?; // Result 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.导入相关的错误码如ImportedEventExpected、ImportedEventTimestampOutOfRange、ImportedEventTimestampMustNotRegress、ImportedEventTimestampMustPostdateDebitAccount等已包含在CreateAccountStatus/CreateTransferStatus枚举中。超时与取消客户端会无限期重试不施加任何逐请求超时。取消以机制形式提供具体取消策略由应用自行决定Client实例可在任意时刻关闭close关闭时所有在途请求都会被取消并向调用方返回错误即使返回了错误该请求仍可能已被 TigerBeetle 服务端处理。因此需要结合 ID 做端到端幂等使转账可安全重试。详见可靠事务提交文档。从源码看lib.rs 的close实现关闭是异步的close将原生句柄交给一个离线程的std::thread::spawn任务执行阻塞式tb_client_deinit通过 oneshot channel 返回结果而Drop实现则会隐式调用close并在 drop 后于离线程完成收尾。推荐的优雅关闭方式是先 await 所有在途请求的 future再调用close并 await 其返回值。在同步代码中使用客户端客户端是 async-only 的但可用futures::executor::block_on在同步代码中驱动use futures::executor::block_on; use tigerbeetle as tb; fn synchronous_function() - Result(), Boxdyn std::error::Error { block_on(async { let client tb::Client::new(0, 127.0.0.1:3000)?; let accounts [tb::Account { id: tb::id(), ledger: 1, code: 1, ..Default::default() }]; let results client.create_accounts(accounts)?.await?; Ok(()) }) }block_on会阻塞当前线程直到异步操作完成适合简单用例或为既有同步应用集成 TigerBeetle 的场景。客户端生命周期与并发注意事项请求一经提交即入队执行即使丢弃返回的 future 也不会取消请求可以在一部分请求 future 尚未完成时 dropClient此时在途请求会以PacketStatus::ClientShutdown完成即便客户端已关闭某些请求 future 仍可能返回成功结果服务端每个客户端同一时刻只允许一个在途请求客户端会在内部缓冲并发请求要真正并行需创建多个客户端注意服务端对同时连接的客户端数量有硬性上限Client实现了Send Sync可放入Arc跨线程/异步任务共享从而利用内部批处理合并来自多线程的事件但除此之外并无性能优势。底层实现ABI 兼容与状态码校验从源码结构看本客户端中的多个类型与底层协议二进制 ABI 兼容可非安全地直接与字节缓冲互转典型应用无需关心Account与AccountFlags、Transfer与TransferFlags、AccountBalance、AccountFilter与AccountFilterFlags、QueryFilter与QueryFilterFlags。这些 Rust 结构体声明为#[repr(C)]且Client::new会在初始化时通过assert_abi_compatibility()lib.rs断言其size_of与align_of与 C 侧tb_account_t、tb_transfer_t、tb_account_filter_t、tb_account_balance_t、tb_query_filter_t完全一致。状态码枚举不保证与协议 ABI 兼容需通过TryFrom/From转换。仓库中的测试 tests/status_codes_and_flags.rs 更进一步它会解析assets/tb_client.h中的 C 枚举定义对TB_CREATE_ACCOUNT_STATUS、TB_CREATE_TRANSFER_STATUS、TB_INIT_STATUS、TB_PACKET_STATUS以及四类 flags 做 Rust ↔ C 的往返一致性校验防止新增状态码导致应用层静默出错——这也解释了为什么CreateAccountStatus/CreateTransferStatus等枚举被标记为#[non_exhaustive]以强制外部匹配时保留兜底分支。溢出检查强烈建议TigerBeetle 的官方 Cargo.toml 特别建议Rust 应用务必在 release 构建中开启 overflow checks因为会计场景下溢出错误是灾难性的。该设置只对该 crate 自身的测试生效最终应用的Cargo.toml需要自行开启[profile.release] overflow-checks true参考文档索引账户与转账字段account.md、transfer.md请求协议create_accounts.md、create_transfers.md、lookup_accounts.md、lookup_transfers.md、get_account_transfers.md、get_account_balances.md、query_accounts.md、query_transfers.md过滤与会话account-filter.md、query-filter.md、account-balance.md、sessions.md建模与最佳实践data-modeling.md、linked-events.md、two-phase-transfers.md、requests.md、reliable-transaction-submission.md【免费下载链接】tigerbeetleThe financial transactions database designed for mission critical safety and performance.项目地址: https://gitcode.com/GitHub_Trending/ti/tigerbeetle创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考