實(shí)戰(zhàn):基于 ledger 與 linked 轉(zhuǎn)賬的多幣種建模方案)
TigerBeetle 貨幣兌換Currency Exchange實(shí)戰(zhàn)基于 ledger 與 linked 轉(zhuǎn)賬的多幣種建模方案【免費(fèi)下載鏈接】tigerbeetleThe financial transactions database designed for mission critical safety and performance.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ti/tigerbeetleTigerBeetle 以單一借貸轉(zhuǎn)賬single debit / single credit為原語通過ledger字段對賬戶進(jìn)行資產(chǎn)類型分區(qū)不同ledger的賬戶之間不允許直接轉(zhuǎn)賬。本文圍繞倉庫中的 Currency Exchange Recipe 展開講解如何用兩對不同 ledger 的賬戶 兩條原子鏈接的轉(zhuǎn)賬實(shí)現(xiàn)匯率無關(guān)的多幣種兌換并給出匯率舍入、Spread點(diǎn)差建模等實(shí)戰(zhàn)細(xì)節(jié)幫助讀者在 TigerBeetle 之上落地可審計(jì)、可防套利的外匯業(yè)務(wù)。為什么需要貨幣兌換這一層建模很多應(yīng)用天然涉及多種貨幣銀行可能同時(shí)持有多個(gè)幣種的余額支付平臺需要做跨境匯款。TigerBeetle 的數(shù)據(jù)模型由 Account、Transfer 和 ledger 三類概念組成其中l(wèi)edger是一個(gè) 32 位無符號整數(shù)標(biāo)識符用于把賬戶劃分成互不交易的集合見 Data Modeling: Ledgers。一個(gè)邏輯實(shí)體若要持有多個(gè)幣種每個(gè)幣種必須放在獨(dú)立的Account中而不同幣種對應(yīng)的賬戶則需要落在不同的ledger上。之所以不推薦在應(yīng)用層把多幣種統(tǒng)一折算成單一幣種是因?yàn)閰R率是波動(dòng)的——如果只記錄折合后的單一幣種余額交易當(dāng)時(shí)的真實(shí)匯率信息就會(huì)丟失審計(jì)與對賬將無從談起。因此 TigerBeetle 的設(shè)計(jì)選擇是ledger直接編碼幣種或其他資產(chǎn)類型把匯率語義留給應(yīng)用層去顯式建模。補(bǔ)充ledger還可以進(jìn)一步細(xì)分。例如多租戶場景下可以為每個(gè)客戶單獨(dú)分配一個(gè)ledger如果客戶同時(shí)持有多個(gè)幣種則該客戶會(huì)對應(yīng)多個(gè)ledger。ledger在 TigerBeetle 中只作為數(shù)字標(biāo)識存儲(chǔ)額外的元數(shù)據(jù)幣種符號、精度等建議放到控制面數(shù)據(jù)庫管理見 system-architecture.md。核心約束不同ledger的賬戶不能直接轉(zhuǎn)賬在 create_transfers 中TigerBeetle 強(qiáng)制校驗(yàn)一條規(guī)則轉(zhuǎn)賬雙方賬戶的ledger必須一致否則返回accounts_must_have_the_same_ledger轉(zhuǎn)賬自身的ledger也必須與兩個(gè)賬戶一致否則返回transfer_must_have_the_same_ledger_as_accounts。從源碼可以得到直接印證在 src/state_machine.zig 的create_transfer實(shí)現(xiàn)中if (dr_account.ledger ! cr_account.ledger) return .accounts_must_have_the_same_ledger; if (t.ledger ! dr_account.ledger) { return .transfer_must_have_the_same_ledger_as_accounts; }ledger字段本身的約束見 Account.ledger 與 Transfer.ledger包括類型為 32 位無符號整數(shù)且不能為零。因此跨幣種的資金移動(dòng)無法靠單條轉(zhuǎn)賬完成必須拆成多條同ledger內(nèi)的轉(zhuǎn)賬再通過原子鏈接綁定為一個(gè)整體。數(shù)據(jù)建模四個(gè)賬戶 兩條鏈接轉(zhuǎn)賬TigerBeetle 的單條Transfer只能借記一個(gè)賬戶、貸記一個(gè)賬戶且兩者必須在同一ledger。要實(shí)現(xiàn)貨幣兌換標(biāo)準(zhǔn)的 Recipe 做法是創(chuàng)建兩條原子鏈接的、分屬不同ledger的轉(zhuǎn)賬。一個(gè)最簡單的貨幣兌換涉及四個(gè)賬戶賬戶說明ledgerA?源賬戶source account即付款方1如 USDA?目標(biāo)賬戶destination account即收款方2如 INRL?源流動(dòng)性賬戶source liquidity account1如 USDL?目標(biāo)流動(dòng)性賬戶destination liquidity account2如 INR以及兩條鏈接轉(zhuǎn)賬轉(zhuǎn)賬T?從源賬戶A?轉(zhuǎn)到源流動(dòng)性賬戶L?都在ledger 1上轉(zhuǎn)賬T?從目標(biāo)流動(dòng)性賬戶L?轉(zhuǎn)到目標(biāo)賬戶A?都在ledger 2上。兩條轉(zhuǎn)賬的金額按匯率計(jì)算各不相同。L?與L?都屬于同一實(shí)體——流動(dòng)性提供者liquidity provider例如銀行或交易所而A?與A?既可以屬于同一實(shí)體也可以分屬不同實(shí)體視具體業(yè)務(wù)而定。為什么用兩條轉(zhuǎn)賬而不是一條關(guān)鍵在于 TigerBeetle 的賬本不變量系統(tǒng)要求所有賬戶的已過賬借方總額等于已過賬貸方總額見 Account Guarantees。只有同時(shí)記錄付出去的 USD和收進(jìn)來的 INR賬本才在全局保持平衡且兩條記錄不可分割——它們通過flags.linked綁定要么同時(shí)成功要么同時(shí)失敗。完整示例USD → INR 匯款假設(shè)要從賬戶A?以 USD 計(jì)價(jià)向賬戶A?以 INR 計(jì)價(jià)發(fā)送100.00 USD。匯率為1.00 USD 82.42135 INR則100.00 USD 8242.14 INR。在 TigerBeetle 中提交這兩條轉(zhuǎn)賬LedgerDebit AccountCredit AccountAmountflags.linkedUSDA?L?10000trueINRL?A?824214false注意兩點(diǎn)金額是整數(shù)。TigerBeetle 的金額與余額都是無符號 128 位整數(shù)詳見 Transfer.amount分?jǐn)?shù)金額需要按 Fractional Amounts and Asset Scale 的方式映射把該幣種的最小有用單位映射為1。例如1 USD 100分asset scale 為2所以100.00 USD記為整數(shù)100008242.14 INR記為824214INR 的 scale 為2。應(yīng)用在渲染或與外部系統(tǒng)對接時(shí)再按 scale 換算回小數(shù)而內(nèi)部計(jì)算一律使用整數(shù)避免浮點(diǎn)精度損失。鏈接鏈以最后一個(gè)不帶flags.linked的轉(zhuǎn)賬收尾。第一條轉(zhuǎn)賬帶flags.linked true表示結(jié)果綁定到下一個(gè)轉(zhuǎn)賬第二條不帶該標(biāo)志表示鏈到此結(jié)束。如果鏈中最后一個(gè)事件仍設(shè)置了linked服務(wù)端會(huì)返回linked_event_chain_open錯(cuò)誤見 create_transfers 狀態(tài)碼。鏈接語義的細(xì)節(jié)鏈接事件的具體規(guī)則在 Linked Events 中有完整說明請求內(nèi)的每個(gè)事件默認(rèn)獨(dú)立成功或失敗只有顯式設(shè)置flags.linked才會(huì)形成同生共死的鏈條鏈內(nèi)事件按順序執(zhí)行若中途出錯(cuò)則回滾使前序事件的效果對后續(xù)事件可見鏈內(nèi)第一個(gè)失敗的事件返回其真實(shí)錯(cuò)誤碼鏈內(nèi)其余事件統(tǒng)一返回linked_event_failed同一請求內(nèi)可以存在多條獨(dú)立的鏈各自獨(dú)立成敗鏈執(zhí)行完之后鏈接關(guān)系不會(huì)持久化在數(shù)據(jù)庫中——若要記錄這幾筆轉(zhuǎn)賬屬于同一次兌換需要把關(guān)聯(lián) ID 寫入某個(gè)user_data字段見 Data Modeling: user_data 與 Linked Events 末尾的提示。在源碼層面鏈接鏈的遍歷與回滾邏輯位于 src/state_machine.zig它以chain/chain_broken追蹤當(dāng)前鏈狀態(tài)鏈斷后后續(xù)事件直接返回linked_event_failed鏈完成遇到第一個(gè)非 linked 事件后統(tǒng)一提交。匯率舍入向流動(dòng)性賬戶傾斜以抑制套利在上述示例中由于L?與L?同屬流動(dòng)性提供者這一實(shí)體實(shí)體在該交易中不會(huì)虧損若匯率精確可整除實(shí)體不賺不虧break even若匯率無法整除應(yīng)用應(yīng)當(dāng)把舍入誤差傾向于流動(dòng)性賬戶一側(cè)從而抑制套利arbitrage。這個(gè)建議的直覺是如果舍入總是對交易者有利交易者就可以通過反復(fù)兌換在兩種貨幣間循環(huán)套利讓流動(dòng)性提供者持續(xù)承擔(dān)微小但可累積的虧損。把舍入固定到流動(dòng)性賬戶一側(cè)等價(jià)于把不可精確表示的那部分匯率差贈(zèng)送給了做市方從而消除套利動(dòng)機(jī)。收取點(diǎn)差Spread單獨(dú)轉(zhuǎn)賬而非篡改匯率上一節(jié)的例子是流動(dòng)性提供者不賺不虧的情形。如果流動(dòng)性提供者要收取手續(xù)費(fèi)即點(diǎn)差、SpreadRecipe 的推薦做法是把費(fèi)用作為一條單獨(dú)的轉(zhuǎn)賬加入linked鏈從源賬戶轉(zhuǎn)到源流動(dòng)性賬戶A? → L?。為什么不能用調(diào)整匯率來代替直接在匯率上做手腳例如把1.00 USD 82.42 INR調(diào)成對做市方有利的82.30雖然也能讓做市方盈利但會(huì)帶來一個(gè)致命問題匯率與點(diǎn)差被揉合在一起事后無法區(qū)分。而把點(diǎn)差建模為獨(dú)立轉(zhuǎn)賬等于在賬本上顯式記錄成交時(shí)的匯率和成交時(shí)的點(diǎn)差這兩個(gè)獨(dú)立事實(shí)——這是任何事后審計(jì)、對賬、風(fēng)控都需要的原始信息。TigerBeetle 賬本不可變、可追溯的特性賬戶與轉(zhuǎn)賬一經(jīng)創(chuàng)建不可修改刪除見 Account 與 Transfer保證了這條記錄永久可查。示例收取 0.10 USD 手續(xù)費(fèi)延續(xù)前面的場景流動(dòng)性提供者對這筆交易收取0.10 USD費(fèi)用。此時(shí)linked鏈由三條轉(zhuǎn)賬組成LedgerDebit AccountCredit AccountAmountflags.linkedUSDA?L?10000trueUSDA?L?10trueINRL?A?824214false逐條解讀第一條客戶本金100.00 USD10000個(gè)最小單位從A?轉(zhuǎn)入L?第二條手續(xù)費(fèi)0.10 USD10個(gè)最小單位同樣從A?轉(zhuǎn)入L?——這是做市方的收入第三條客戶應(yīng)收8242.14 INR824214從L?轉(zhuǎn)入A?鏈在此結(jié)束。三條轉(zhuǎn)賬綁定在同一linked鏈中任一失敗則整體回滾保證客戶不會(huì)出現(xiàn)本金被扣、外幣未到賬的半完成狀態(tài)。從賬本角度看客戶A?凈借記10010本金 手續(xù)費(fèi)做市方的L?獲得10010L?支付824214客戶A?貸記824214全局借貸仍然平衡。擴(kuò)展閱讀與關(guān)聯(lián)資源本 Recipe 屬于 docs/coding/recipes 系列同一模式控制賬戶 鏈接轉(zhuǎn)賬也用于 Multi-Debit, Multi-Credit Transfers單借多貸/多借多貸的復(fù)合分錄與 Balance-Conditional Transfers余額條件轉(zhuǎn)賬跨ledger關(guān)系的持久化把交易組 ID 寫入user_data_128多個(gè)賬戶間的歸屬關(guān)系也建議用user_data表達(dá)因?yàn)閕d在集群內(nèi)全局唯一、并非按ledger分區(qū)見 Data Modelingflags.linked的完整約束見 Transfer.flags.linked鏈接鏈的實(shí)現(xiàn)與回滾邏輯可閱讀 src/state_machine.zig 中create_transfer相關(guān)代碼各語言客戶端的createTransfers用法可參考 Node 示例以及 .NET、Java、Go、Python、Rust 等客戶端實(shí)現(xiàn)。小結(jié)TigerBeetle 用ledger劃分幣種、用flags.linked保證跨幣種兌換的原子性二者組合構(gòu)成了一套嚴(yán)謹(jǐn)?shù)亩鄮欧N建模方案四個(gè)賬戶兩兩成對、兩條轉(zhuǎn)賬一鏈綁定金額以整數(shù) asset scale 表達(dá)規(guī)避浮點(diǎn)誤差舍入向流動(dòng)性賬戶傾斜以抑制套利點(diǎn)差作為鏈內(nèi)獨(dú)立轉(zhuǎn)賬顯式記錄從而把匯率與費(fèi)用兩個(gè)事實(shí)永久固化在不可變賬本中。這套模式同時(shí)適用于多幣種銀行賬本、跨境匯款、交易所出入金等場景且與復(fù)合分錄、余額條件轉(zhuǎn)賬等 Recipe 組合使用可以覆蓋絕大多數(shù)真實(shí)的資金流轉(zhuǎn)需求。【免費(fèi)下載鏈接】tigerbeetleThe financial transactions database designed for mission critical safety and performance.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ti/tigerbeetle創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考