重構(gòu)實戰(zhàn)解析)
做金融系統(tǒng)的朋友應(yīng)該都清楚“跨境匯款慢到懷疑人生”幾乎成了行業(yè)默認的體驗。一筆錢從中國匯到海外運氣好隔天到運氣不好三五天也沒動靜。我們今年接了一家大型商業(yè)銀行的跨境匯款系統(tǒng)重構(gòu)項目目標很直接把跨境匯款從“天級”拉到“秒級”口號叫“秒級跨境”。前期調(diào)研一做完說實話我第一反應(yīng)是這目標有點激進因為傳統(tǒng)跨境支付根本不是靠網(wǎng)絡(luò)延時就夠的鏈路里疊加了大量合規(guī)、清算、中轉(zhuǎn)環(huán)節(jié)。但分布式系統(tǒng)框架搭起來之后成果確實超出預(yù)期。這篇文章把我參與的整個重構(gòu)過程、架構(gòu)設(shè)計、關(guān)鍵取舍和踩過的坑都整理了一遍對正在做支付結(jié)算、金融分布式核心重構(gòu)的朋友應(yīng)該會有直接參考價值。1. 跨境匯款之痛為什么一筆款要等三五天要做重構(gòu)先得搞清楚慢在哪兒??缇硡R款的“慢”不是一個點慢而是整條鏈路串行下來的累積延遲。把鏈路拆開看每個環(huán)節(jié)都很真實。1.1 傳統(tǒng)跨境匯款的完整鏈路傳統(tǒng)跨境匯款是一條從“發(fā)起行-中間行-收款行”的接力路徑。假設(shè)客戶在境內(nèi)某銀行網(wǎng)點發(fā)起一筆匯往海外的款項流程大致是這樣:客戶匯款申請先進入發(fā)起行的核心系統(tǒng)系統(tǒng)做賬戶扣款、凍結(jié)然后生成一條跨境報文通過國際報文網(wǎng)絡(luò)發(fā)給下一家中間行。中間行可能是境外代理行也可能是一連串中轉(zhuǎn)行每一家都要做一次報文解析、合規(guī)篩查、頭寸校驗接著再轉(zhuǎn)發(fā)。最后報文到達收款行收款行完成入賬資金才能真正到客戶賬上。這條鏈路上最重的開銷不在網(wǎng)絡(luò)傳輸而在“逐筆校驗”。國際報文網(wǎng)絡(luò)的報文傳遞本身是秒級的但每一站都必須停下來核對付款人信息、收款人信息、用途描述是否觸發(fā)反洗錢或者制裁名單同時還要確認中間行在收款行有沒有足夠的代理行頭寸。一個環(huán)節(jié)出問題比如受益人姓名比照名單有疑似命中這筆業(yè)務(wù)就會被抽出來走人工復(fù)核一掛就是幾小時甚至幾天。我們項目組在調(diào)研期做過統(tǒng)計從客戶提交到收款行入賬平均耗時約28小時其中純報文傳輸時間不到10分鐘剩余全部耗在中轉(zhuǎn)節(jié)點的人工與半人工處理上。這個鏈路在業(yè)務(wù)量小時基本沒問題一天幾百筆人工復(fù)核也壓得住。但客戶體量上來后日間并發(fā)能到數(shù)千筆甚至上萬筆問題就暴露了。尤其是周末或節(jié)假日前后國際清算市場不開門跨境匯款直接躺平客戶只能干等。1.2 拖慢速度的四個真實瓶頸把慢的原因掰開揉碎我總結(jié)為四個核心瓶頸重構(gòu)時每一個都得針對性解決批量處理模式。傳統(tǒng)銀行核心系統(tǒng)受限于數(shù)據(jù)庫讀寫性能跨境匯款普遍采用“定時批量”方式處理每天幾個批點跑批量任務(wù)而不是實時逐筆處理??蛻舭滋焯峤坏膮R款很可能要到當天夜間批點才統(tǒng)一生成報文。這一下就損耗了十幾個小時等于把“可秒級傳輸”硬生生變成“日終清算”。中轉(zhuǎn)節(jié)點過多。一筆跨境匯款經(jīng)過3到5家中間行很常見每多一個節(jié)點就多一層解析、解碼、頭寸核查、合規(guī)比對。節(jié)點之間還存在時差問題報文到了對方那邊正好是夜間只能排隊等下一個工作日。人工合規(guī)篩查。制裁名單和反洗錢篩查目前依然是跨境業(yè)務(wù)最大的時間黑洞。很多銀行為了控風險采用“機檢人工復(fù)核”的雙重機制命中的概率雖然不是很高但每天海量業(yè)務(wù)推下來人工復(fù)核隊列永遠堆積如山。而且每家中間行都有自己的一套篩查規(guī)則一家放行不代表下一家也放行同一筆業(yè)務(wù)被反復(fù)篩查。代理行流動性不足。中間行之間互相開著代理戶口匯款的本質(zhì)其實是額度的劃轉(zhuǎn)。如果收款行或中間行在某一條路徑上沒有充足的頭寸需要等對方銀行內(nèi)部調(diào)撥資金或者走外匯交易平盤這個等待時間完全不確定很多時候要拖到T1甚至T2。理解了這四個瓶頸我們重構(gòu)的方向就清晰了解決批量延遲、縮短鏈路、自動化合規(guī)、前置流動性。方向?qū)ι狭思軜?gòu)才能落對場景。2. 系統(tǒng)重構(gòu)的起心動念從集中式到分布式目標明確了“為什么非要分布式系統(tǒng)”這個疑問就很自然了。傳統(tǒng)集中式架構(gòu)負責穩(wěn)定賬務(wù)但面對爆發(fā)式業(yè)務(wù)量和高實時性要求確實有點力不從心。2.1 集中式架構(gòu)的天花板被撞穿了原有系統(tǒng)是典型的“單體核心外圍外圍”結(jié)構(gòu)核心賬務(wù)系統(tǒng)用一個數(shù)據(jù)庫所有交易都在這個庫里做行級鎖競爭。日常業(yè)務(wù)還好但跨境的報文處理、合規(guī)檢查、額度管理都要讀寫同一批表一到日間高峰數(shù)據(jù)庫連接池直接被打滿CPU頻繁飆到90%以上。就性能而言我們做過一次壓測單庫跑跨境申請?zhí)幚碜罡咧荒芸傅郊s300筆/分鐘再多就開始出現(xiàn)事務(wù)等待和集群主備切換。這個吞吐量應(yīng)付陳舊業(yè)務(wù)模式足夠但要做實時清分、逐筆合規(guī)、即時入賬完全撐不住。而且集中式架構(gòu)擴展性很差想提升性能只能換更強的機器或者頻繁做讀寫分離但讀寫分離繞不過賬務(wù)數(shù)據(jù)的最終一致性銀行系統(tǒng)對賬務(wù)差錯零容忍一旦雙寫出分叉后果很嚴重。另外集中式系統(tǒng)還有一個隱性代價就是發(fā)布和升級太慢。跨境鏈路涉及數(shù)十個外圍網(wǎng)關(guān)和中間件任何一次核心改造發(fā)布會都要凍結(jié)業(yè)務(wù)窗口流程冗長導(dǎo)致很多優(yōu)化想上線一等就是兩三個月。業(yè)務(wù)部門指望我們快速迭代架構(gòu)卻拖了后腿。2.2 分布式系統(tǒng)能救場但不是免費的午餐分布式系統(tǒng)很大吸引力自然是水平擴展業(yè)務(wù)量漲了加節(jié)點就能解決問題。我們把整條跨境鏈路拆成若干個微服務(wù)受理服務(wù)、合規(guī)檢查服務(wù)、清算路由服務(wù)、流動性管理服務(wù)、通知服務(wù)每個服務(wù)獨立部署、獨立擴縮容。高可用也更有保障原來一臺機器崩了全鏈路癱瘓現(xiàn)在拆成服務(wù)后某個服務(wù)出現(xiàn)故障可以由其他實例接管。我們實測下來將一個核心服務(wù)切到多副本后壓測吞吐量直接從原先的300筆/分鐘提升到1500筆以上后續(xù)加節(jié)點還有繼續(xù)提升空間。但分布式架構(gòu)的坑也不少最大的坑是“數(shù)據(jù)一致性”金融系統(tǒng)最忌諱的就是錢賬不符。原來單庫里一筆事務(wù)可以原子完成扣款、凍結(jié)、報文生成拆到多個服務(wù)后一筆業(yè)務(wù)要跨服務(wù)多次調(diào)用任何一個環(huán)節(jié)失敗都可能導(dǎo)致扣款了但報文沒發(fā)或者報文發(fā)出去卻沒記錄。我們項目組差點在分布式事務(wù)設(shè)計上翻車這部分我會在后面章節(jié)詳細展開。成本和運維復(fù)雜性也不能忽視。微服務(wù)化之后光中間件就引入了消息隊列、分布式事務(wù)框架、配置中心、注冊中心運維團隊需要熟悉的組件數(shù)量翻了好幾倍。如果團隊沒有足夠人才儲備貿(mào)然上分布式很容易變成“不看路先跑”。2.3 我們落地的架構(gòu)選型綜合權(quán)衡后我們采用的方案是“核心賬務(wù)集中式保留外圍業(yè)務(wù)全分布式”。因為跨境匯款的核心賬務(wù)依然要求強一致性賬務(wù)數(shù)據(jù)庫留在集中式體系內(nèi)由專用賬務(wù)服務(wù)負責整體不變。但是受理、合規(guī)、清算、路由這些非賬務(wù)環(huán)節(jié)全部微服務(wù)化通過消息隊列與賬務(wù)服務(wù)解耦。具體技術(shù)選型上微服務(wù)框架選了業(yè)內(nèi)比較成熟的Spring Cloud體系消息中間件用高吞吐的Kafka分布式事務(wù)采用“本地消息表定時對賬”的方案合規(guī)策略引擎單獨落地把所有篩查規(guī)則剝離成可配置化。網(wǎng)關(guān)層加了一個統(tǒng)一的跨境前置將原來散落的代理行接口整合復(fù)用。這套架構(gòu)最大的好處是“穩(wěn)中求變”。賬務(wù)系統(tǒng)的強一致被保留避免大數(shù)據(jù)量下的事務(wù)撕裂外圍環(huán)節(jié)則可以彈性伸縮、快速迭代。重構(gòu)從啟動到第一批業(yè)務(wù)上線我們用了大概5個多月比整體替換方案節(jié)約了至少一半時間也大幅降低了并行運行的風險。3. “秒級跨境”可行嗎關(guān)鍵技術(shù)拆解與可行性分析口號喊出來容易能不能落地得看技術(shù)路徑。我們花了不少時間反復(fù)推演和壓測結(jié)論是“秒級跨境”在特定場景下可落地但不是全場景無條件秒級。要實現(xiàn)它必須突破三個關(guān)鍵點。3.1 實時支付的關(guān)鍵不是網(wǎng)絡(luò)而是流動性很多人直覺認為跨國通信有延遲所以快不了。其實國際報文網(wǎng)絡(luò)的單條報文傳輸延遲通常只有幾十毫秒真正的瓶頸完全在“頭寸”。一筆跨境匯款必須在發(fā)起行有足夠存款在中間行有足夠代理行頭寸在收款行有可用額度否則根本不敢劃撥。傳統(tǒng)模式下頭寸是按天調(diào)度各代理行日初確認當天可用余額后續(xù)每隔幾個小時復(fù)核一次。這就造成一個大問題白天新進來的匯款業(yè)務(wù)往往因為頭寸未預(yù)支而卡在中轉(zhuǎn)環(huán)節(jié)。我們重構(gòu)時做了一個關(guān)鍵改動——流動性預(yù)置與實時調(diào)度結(jié)合。具體來說和多家主力代理行提前簽署“日間透支額度”協(xié)議在本地流動性管理系統(tǒng)中預(yù)置資金池按照歷史數(shù)據(jù)動態(tài)計算每日所需頭寸將原本日結(jié)的額度拉平到日間可實時消耗。在測試環(huán)境上我們在資金池模式下將一筆跨境匯款從發(fā)起到入賬的總時長壓縮到了350毫秒左右扣掉合規(guī)檢查、清分和報文生成純賬務(wù)處理只占不到80毫秒。這說明只要頭寸前置做足網(wǎng)絡(luò)延遲真的不是核心矛盾。3.2 合規(guī)自動化的時間成本怎么壓合規(guī)篩查是肉眼可見的“時間黑洞”但也是難啃的骨頭。我們重構(gòu)后把原有的“機檢人工復(fù)核”改成了“策略規(guī)則引擎機器學習輔助決策”。規(guī)則引擎把所有制裁名單、風險國家、交易特征描述固化成可實時調(diào)用的策略集每筆交易進來后引擎在5毫秒內(nèi)返回風險評分和邏輯判斷結(jié)果。對于那些無法自動打分的模糊案例我們不再盲目丟給人工而是引入一個“風險分級隊列”低風險自動放行中風險進入簡化復(fù)核只有高風險才完整進入人工。實測下來人工處理筆數(shù)從總業(yè)務(wù)量的3.8%降低到0.6%單筆合規(guī)耗時從平均5分鐘壓到700毫秒效果非常顯著。自然合規(guī)自動化不能拍腦袋寫規(guī)則要反復(fù)用歷史數(shù)據(jù)回放調(diào)參避免因為規(guī)則過緊犧牲放行率或者因為規(guī)則過松導(dǎo)致風險漏檢。我們專門做了一套回測平臺把過去三年的跨境匯款報文灌進去校驗新規(guī)則精確測算每個策略組合的攔截率和誤殺率調(diào)整到可接受區(qū)間后才正式啟用了線上策略。3.3 從小時級到秒級我們還差幾步經(jīng)歷過重構(gòu)和調(diào)優(yōu)某些支付場景確實做到了秒級但必須誠實說清楚我們的“秒級跨境”目前主要集中在“標準小額匯款”和“同名賬戶快捷匯款”兩個場景單筆額度限制在5000美元以內(nèi)資金來源和用途項比較標準化。對于大額、復(fù)雜用途、非同名收款仍然要經(jīng)過更嚴格的人工審查鏈路時間在10到30分鐘不等距離秒級還有明顯距離。從行業(yè)現(xiàn)狀看跨境清算網(wǎng)絡(luò)的基礎(chǔ)設(shè)施和報文格式也還在逐步演進很多國家本地清算系統(tǒng)已經(jīng)支持7x24小時實時支付但跨境之間的互聯(lián)互通仍未完全打通。因此真正意義上的“全場景秒級跨境”更多是一種方向而不是當前每個銀行都能兌現(xiàn)的承諾。但我們通過架構(gòu)改造把原本28小時的平均到賬時間壓到了2分鐘以內(nèi)對客戶體驗而言已經(jīng)是一次脫胎換骨的變化。我個人判斷實現(xiàn)全場景秒級跨境還需要三個大前提受益方銀行完全接入實時清算體系各國監(jiān)管對自動化放行有統(tǒng)一容忍窗口跨境報文金融數(shù)據(jù)標準完成新一代升級。這三個前提缺一不可否則“秒級”只會是個營銷詞。4. 重構(gòu)現(xiàn)場實錄問題、排查與避坑指南理論上說得通的方案落地上一定會被現(xiàn)實毒打。重構(gòu)期間我們踩了不少坑有些坑直到現(xiàn)在回想起來還心有余悸。分享幾個低級的、高級的坑和排查思路。4.1 最大雷區(qū)分布式事務(wù)的一致性第一版微服務(wù)設(shè)計時我們把“扣款”和“生成報文”放在了兩個服務(wù)里通過同步Feign調(diào)用。結(jié)果剛上線試運行就出現(xiàn)了兩次賬實不符客戶發(fā)起了匯款賬戶扣款成功但報文因為網(wǎng)絡(luò)閃斷沒有生成于是出現(xiàn)“錢沒扣但報文發(fā)了”或者“錢扣了報文沒發(fā)”的亂象。后來我們放棄了強一致性的分布式事務(wù)方案改成“本地消息表”做法。核心賬務(wù)在本地事務(wù)里同時寫入賬務(wù)明細和一條待發(fā)送消息事務(wù)提交后由后臺任務(wù)輪詢將消息推到Kafka再由報文生成服務(wù)消費。消費端保證冪等性重復(fù)消息不會生成重復(fù)報文最終還有一套定時對賬任務(wù)來核對本地消息表與報文網(wǎng)關(guān)記錄。這個設(shè)計規(guī)避了跨服務(wù)事務(wù)的復(fù)雜協(xié)調(diào)也緩解了網(wǎng)絡(luò)抖動導(dǎo)致的死鎖。代價是報文生成會有最多10秒的異步延遲但業(yè)務(wù)上可接受畢竟我們需要的是“秒級”不是“毫秒級”。對賬任務(wù)每天跑四次保證所有數(shù)據(jù)最終一致。4.2 性能瓶頸的排查方法論搞定了事務(wù)壓測又發(fā)現(xiàn)新問題。我們部署了一個50節(jié)點的集群結(jié)果吞吐量只跑到預(yù)期的60%排查時發(fā)現(xiàn)大量線程阻塞在數(shù)據(jù)庫的同一張“任務(wù)狀態(tài)表”上。原來所有服務(wù)都去讀同一張表標記“待處理”鎖競爭異常嚴重。優(yōu)化方案是把單表拆成多表分片策略按業(yè)務(wù)ID哈希分到16張表里狀態(tài)更新互相不影響。配合Redis緩存一些不常變動的配置對象數(shù)據(jù)庫壓力降了不少壓測吞吐量從850筆/分鐘提升到1900筆/分鐘。這個案例提示我分布式系統(tǒng)性能瓶頸經(jīng)常不在CPU層面而是出在鎖、連接池、緩存穿透等底層細節(jié)。排查時先看等待鏈別急著盲目擴機器。4.3 監(jiān)控與容災(zāi)別讓秒級變成秒崩性能提上來了穩(wěn)定性反而成了頭號問題。重構(gòu)后系統(tǒng)上云化部署一次某云廠商的網(wǎng)絡(luò)抖動讓消息隊列出現(xiàn)大量重試賬務(wù)服務(wù)被擊穿竟然導(dǎo)致核心系統(tǒng)資源占用飆升。我們隨后緊急上線了全鏈路壓測和流量熔斷機制。現(xiàn)在每個微服務(wù)都配有“熔斷器”和“限流器”超過閾值就快速失敗而不是無限等待下游響應(yīng)。同時監(jiān)控體系拆成三個維度基礎(chǔ)資源監(jiān)控、應(yīng)用性能監(jiān)控、業(yè)務(wù)指標監(jiān)控再結(jié)合日志全鏈路追蹤確保一筆匯款從進到出都能追蹤到每一步的耗時。我們定了一個原則如果“秒級跨境”的目標跟穩(wěn)定性沖突優(yōu)先保穩(wěn)定性寧可慢兩步不能錯一分。4.4 常見問題速查表問題現(xiàn)象可能原因排查思路實際處理報文遲遲未發(fā)本地消息表積壓后臺輪詢?nèi)蝿?wù)停擺查消息表積壓情況查定時任務(wù)日志增加輪詢頻率改用多線程掃描重復(fù)入賬消費端重復(fù)消費消息檢查冪等標識看是否缺少去重表增加唯一業(yè)務(wù)ID約束重復(fù)消息直接丟棄合規(guī)耗時偏高規(guī)則引擎命中大量模糊案例分析歷史數(shù)據(jù)看規(guī)則權(quán)重設(shè)置調(diào)窄模糊規(guī)則區(qū)間引入機器學習評分下游代理行響應(yīng)超時外部接口不穩(wěn)定打印全鏈路Trace定位超時節(jié)點設(shè)置接口超時快速失敗并自動重試數(shù)據(jù)庫連接池耗盡并發(fā)過高導(dǎo)致連接被占滿查看連接池監(jiān)控分析慢SQL優(yōu)化SQL索引拆分熱點表這些問題是真實發(fā)生過的每個都對應(yīng)一次事故復(fù)盤。把它們記錄下來不是為了展示工作量而是希望后來者看到后能減少幾次線上“戰(zhàn)戰(zhàn)兢兢”。我在這個項目里最大的體會是分布式重構(gòu)不是技術(shù)越新越好而是每個選擇都在跟業(yè)務(wù)、風險、成本做妥協(xié)。秒級跨境的目標實現(xiàn)了一部分這已經(jīng)比很多銀行強太多但過程中付出的心力也不少。最后再分享一個小技巧重構(gòu)前一定要把現(xiàn)網(wǎng)兩三年的歷史報文數(shù)據(jù)完整保留下來做規(guī)則回測和壓測模型樣本都要靠它否則上線后任何一次調(diào)優(yōu)都會像盲人摸象。項目收尾后我們團隊內(nèi)部復(fù)盤時定了一條標準任何一次秒級性能調(diào)優(yōu)都必須能回答“如果失敗最壞影響是什么”。帶著這個底線做重構(gòu)才能真正把“秒級跨境”從PPT里落地到生產(chǎn)環(huán)境中。