高可靠的跨系統(tǒng)轉(zhuǎn)賬如何設(shè)計(jì)
大家好,我是蝸牛哥,跨系統(tǒng)轉(zhuǎn)賬網(wǎng)上教程很多,但是都是講的比較淺,這個(gè)功能看似簡單,但是細(xì)節(jié)很多,要做好沒那么容易,因?yàn)樯婕暗椒植际绞聞?wù)、交易安全性等方面,做不好就出現(xiàn)資損,本文講一下如何設(shè)計(jì)一個(gè)高可靠跨系統(tǒng)轉(zhuǎn)賬,以及要關(guān)注的重點(diǎn)
示例說明
銀行A 轉(zhuǎn)賬給B銀行,銀行A進(jìn)行出金,銀行B進(jìn)行入金
這里只是為了便于理解,所以才把系統(tǒng)命名為銀行A/B,具體可能與銀行的流程有點(diǎn)細(xì)微區(qū)別
會(huì)遇到哪些問題?
轉(zhuǎn)賬失敗,不能直接回滾
要根據(jù)返回的異常來判斷,如果接收到的異常是一個(gè)業(yè)務(wù)異常,并且異常碼是雙方約定好的,那么可以進(jìn)行回滾,如果返回的不是一個(gè)明確的異常,,那么不能擅自回滾,因?yàn)榭赡苁蔷W(wǎng)絡(luò)超時(shí)異常,而網(wǎng)絡(luò)超時(shí),又分為響應(yīng)超時(shí)和請(qǐng)求超時(shí),如果是響應(yīng)超時(shí),對(duì)方系統(tǒng)可能已經(jīng)入賬了,所以要進(jìn)行重試操作確認(rèn)
面試題:超時(shí)異常,有哪幾種情況,怎么處理?
系統(tǒng)重試要保持冪等
假如網(wǎng)絡(luò)超時(shí)進(jìn)行重試,入金方的接口需要支持冪等,否則會(huì)出現(xiàn)可能重復(fù)入金,而冪等條件是根據(jù)出金方的業(yè)務(wù)流水號(hào)+渠道號(hào)
進(jìn)行查詢判斷
- 如果有記錄,并根據(jù)狀態(tài),來決定響應(yīng)結(jié)果
- 如果沒有記錄則進(jìn)行入金,在返回對(duì)應(yīng)的響應(yīng)結(jié)果
如果失敗,那么出金方需要進(jìn)行解凍回滾操作,如果成功,那么需要進(jìn)行解凍出金操作。
同時(shí)入金方還要設(shè)置此組合字段為唯一索引
,這樣可以避免重復(fù)插入的問題,比如:未查詢到數(shù)據(jù),則進(jìn)行插入,正好前面一筆請(qǐng)求事務(wù)未提交,如果不設(shè)置唯一索引就會(huì)導(dǎo)致出現(xiàn)重復(fù)插入的問題。
交易安全性
由于這種資產(chǎn)操作非常敏感,稍有失誤影響非常大,所以交易安全性是非常重要的,比如:有攻擊者知道B銀行的入金接口,那么直接調(diào)用,他的賬戶就會(huì)加錢。。。,所以要進(jìn)行以下安全措施
要進(jìn)行簽名調(diào)用
在轉(zhuǎn)賬前用私鑰對(duì)賬戶進(jìn)行簽名,然后給B銀行頒發(fā)一個(gè)公鑰,進(jìn)行入金的簽名驗(yàn)簽操作,來保證此請(qǐng)求是正常請(qǐng)求。
要對(duì)交易的時(shí)效性進(jìn)行校驗(yàn)
為了進(jìn)一步保證交易的安全性,雙方要約定好一個(gè)交易的時(shí)效性,比如5 分鐘,在進(jìn)行接口調(diào)用時(shí)攜帶請(qǐng)求時(shí)間,如果這個(gè)請(qǐng)求時(shí)間是5分鐘之前的進(jìn)行拒絕,等待重新發(fā)起。
要進(jìn)行系統(tǒng)對(duì)賬
除了簽名,雙方系統(tǒng)還要進(jìn)行對(duì)賬,而對(duì)賬又分為總賬對(duì)賬和明細(xì)對(duì)賬
總賬對(duì)賬
比如查看銀行A出金總額是否等于B銀行的入金總額,對(duì)賬頻率有小時(shí)、天不等,計(jì)算公式如下
轉(zhuǎn)賬給銀行B總額==接收到銀行A的入金總額 ?
明細(xì)對(duì)賬
除了總賬要進(jìn)行核對(duì),明細(xì)賬也要進(jìn)行核對(duì),因?yàn)榭傎~不平后,要確保那一個(gè)賬戶出現(xiàn)問題,為了實(shí)現(xiàn)明細(xì)對(duì)賬雙方系統(tǒng)要保留對(duì)方系統(tǒng)流水號(hào),這樣才能對(duì)應(yīng)起來,對(duì)賬頻率一般是天
要考慮并發(fā)扣款
在進(jìn)行賬戶操作時(shí),要考慮并發(fā)問題,進(jìn)行加鎖處理,否則會(huì)出現(xiàn)資損,例如
- 訂單a和訂單 b同一時(shí)間都查詢到了,賬戶余額為1000
- 訂單a扣款200,訂單b扣款 100
- 假如訂單 a先執(zhí)行,那么賬戶余額為800,訂單 b 修改為賬戶余額為900,最終為 900,反正則為 800,都不對(duì)
具體可以查看并發(fā)扣款,如何保證結(jié)果一致性
涉及到表可能有哪些?
出金方
轉(zhuǎn)賬流水表
此表可以進(jìn)行對(duì)賬,也可以進(jìn)行定時(shí)任務(wù)重新發(fā)起重試
- 主鍵
- 流水號(hào)
- 用戶 ID
- 方向:轉(zhuǎn)出轉(zhuǎn)入
- 金額
- 目標(biāo)方流水號(hào)
- 時(shí)間
- 狀態(tài) (等待調(diào)用、調(diào)用成功、調(diào)用失?。?/code>
賬戶表
此表的作用不用多說,主要說下凍結(jié)資金密度,防止真正扣款時(shí)賬戶上沒錢,導(dǎo)致交易失敗,所以一般都是先進(jìn)行凍結(jié),如果失敗則進(jìn)行解凍
- 用戶 id
- 總金額
- 凍結(jié)資金
- 賬戶狀態(tài)(正常 凍結(jié))
- 時(shí)間
凍結(jié)記錄表
記錄凍結(jié)流水,防止出問題沒法追溯
- 主鍵
- 流水號(hào)
- 用戶 Id
- 金額
- 類型:凍結(jié)、解凍
- 關(guān)聯(lián)的業(yè)務(wù)流水號(hào)
- 時(shí)間
入金方
以下表為最核心的表,但不是最全的表,比如應(yīng)該還有賬賬務(wù)流水表、賬務(wù)訂單、熱點(diǎn)賬戶表等
渠道轉(zhuǎn)賬流水表
此表可以進(jìn)行對(duì)賬,也可以進(jìn)行定時(shí)任務(wù)重新發(fā)起重試
- 主鍵
- 流水號(hào)
- 渠道
- 業(yè)務(wù)方流水號(hào) //后期冪等要根據(jù)此字段進(jìn)行判斷,所以此字段+渠道號(hào)為唯一索引
- 用戶 ID
- 方向:轉(zhuǎn)出轉(zhuǎn)入
- 金額
- 時(shí)間
- 狀態(tài) (1成功 2失?。?/code>
賬務(wù)表
- 用戶 id
- 總金額
- 凍結(jié)資金
- 賬戶狀態(tài)(正常 凍結(jié))
- 時(shí)間
最終流程應(yīng)該是什么樣的?
流程有4個(gè),分別為
- 正常的轉(zhuǎn)賬流程
- 補(bǔ)償轉(zhuǎn)賬流程
- 總賬對(duì)賬流程
- 明細(xì)對(duì)賬流程
其實(shí)這也是分布式事務(wù)最通用的實(shí)現(xiàn)方式,失敗就重試,直到最終成功,不管你是 tcc、還是其他的實(shí)現(xiàn)方式,只要出現(xiàn)異常,系統(tǒng)最終都要通過定時(shí)去重試,直到最終 一致,感興趣可以去看看 SEATA 源碼,遇到異常也是通過定時(shí)任務(wù)進(jìn)行重試。
轉(zhuǎn)賬流程
轉(zhuǎn)賬補(bǔ)償流程
這個(gè)流程是定時(shí)任務(wù)定時(shí)發(fā)起的,查詢小于等于當(dāng)前時(shí)間-指定時(shí)間,狀態(tài)為等待調(diào)用的轉(zhuǎn)賬記錄
并重新發(fā)起轉(zhuǎn)賬
select * from transfer_list where update_time <= #{queryEndDate}
總賬對(duì)賬流程
明細(xì)對(duì)賬流程
明細(xì)對(duì)賬,如果數(shù)量不大,一天天對(duì)沒問題,現(xiàn)在銀行大多數(shù)是基于這種做法,如果文件比較大,可以考慮使用Merkle樹,這里就說傳統(tǒng)的方式
直接查詢對(duì)比
這種方式最快,數(shù)據(jù)不大可以這樣搞,同時(shí)也需要對(duì)方系統(tǒng)提供接口支持
基于文件對(duì)比
這種方式也是比較常用的方式,適合數(shù)據(jù)量大的對(duì)比,一般銀行會(huì)這么做
總結(jié)
以上我們介紹了如何設(shè)計(jì)一個(gè)高可靠的系統(tǒng)轉(zhuǎn)賬,可以看到還是比較復(fù)雜的,細(xì)節(jié)很多,主要要考慮補(bǔ)償、安全、并發(fā)扣款幾方面,這幾方面做好才能設(shè)計(jì)一個(gè)高可靠的系統(tǒng)轉(zhuǎn)賬。