交易、賬務(wù)系統(tǒng)去Oracle經(jīng)驗

2013 年 5 月,支付寶最后一臺小型機(jī)下線,去 “IOE” 取得里程碑進(jìn)展。支付寶(以及后來的螞蟻金服)走的是一條跟傳統(tǒng)金融行業(yè)不同的分布式架構(gòu)之路。要基于普通硬件資源實現(xiàn)金融級的性能和可靠性,有不少難題要解決。應(yīng)用層是無狀態(tài)的,借助 SOA 架構(gòu)還可以比較方便地擴(kuò)展。而數(shù)據(jù)層就沒那么簡單了,螞蟻金服在探索的過程中,積累了一些有用的數(shù)據(jù)層架構(gòu)設(shè)計經(jīng)驗,還是非常模式化的,可以分享出來供參考。
傳統(tǒng)銀行使用的高端硬件資源和商業(yè)數(shù)據(jù)庫,單機(jī)的性能和穩(wěn)定性肯定占有絕對的優(yōu)勢。互聯(lián)網(wǎng)分布式架構(gòu),則需要從架構(gòu)設(shè)計上做文章,提高系統(tǒng)整體的并發(fā)處理能力和容災(zāi)能力,其中容災(zāi)能力又主要有兩個指標(biāo):
RTO,Recovery Time Objective,恢復(fù)時間目標(biāo)。表示能容忍的從故障發(fā)生到系統(tǒng)恢復(fù)正常運轉(zhuǎn)的時間,這個時間越短,容災(zāi)要求越高。
RPO,Recovery Point Objective,數(shù)據(jù)恢復(fù)點目標(biāo)。表示能容忍故障造成過去多長時間的數(shù)據(jù)丟失,RPO 為 0 表示不允許數(shù)據(jù)丟失。
分布式領(lǐng)域 CAP 理論告訴我們,一致性、可用性、分區(qū)容忍性三者無法同時滿足。我們不要奢望尋找能解決所有問題的萬能方案,而應(yīng)該根據(jù)不同的場景作出取舍。雖然業(yè)務(wù)場景五花八門,但是根據(jù)實際經(jīng)驗,往往可以歸到有限的幾種模式中,處理策略也是相對固定的。
我們抽象一個簡化的支付系統(tǒng)模型來幫助理解,為了敘述方便,不一定跟支付寶的實際業(yè)務(wù)情況完全一致。它采用 SOA 架構(gòu),主要劃分了交易、賬務(wù)、用戶、運營支撐這幾個子系統(tǒng),各自有各自的數(shù)據(jù)庫。另外還有一個全局的配置庫,存放一些會被各處用到的配置數(shù)據(jù)。

這幾個子系統(tǒng)涵蓋了幾種常見的模式,先簡要介紹它們的主要業(yè)務(wù):
賬務(wù):金融/支付系統(tǒng)中最核心的業(yè)務(wù),簡化后姑且認(rèn)為只保存每個賬戶的余額,主要操作是增減余額。它的特點是要求數(shù)據(jù)強(qiáng)一致,每一次對余額的增減必須基于一個絕對正確的當(dāng)前值,否則就會造成資損。
交易:負(fù)責(zé)記錄每筆交易的狀態(tài)和上下文。在電商系統(tǒng)中,它可能是商品訂單;在銀行系統(tǒng)中可能是轉(zhuǎn)賬流水。交易類的數(shù)據(jù)有生命周期,可能有創(chuàng)建、付款、發(fā)貨、確認(rèn)收貨、退款等狀態(tài)變遷。這些都不重要,重要的是它的業(yè)務(wù)特點:每一筆交易的創(chuàng)建是獨立的,不需要依賴其他交易的數(shù)據(jù);推進(jìn)一筆交易狀態(tài)的時候,要求這條數(shù)據(jù)是強(qiáng)一致的,但跟其他交易數(shù)據(jù)無關(guān)。
用戶:維護(hù)用戶的用戶名、密碼、郵箱、手機(jī)等非賬務(wù)信息,提供注冊、登錄、查詢業(yè)務(wù)。在執(zhí)行核心業(yè)務(wù)的時候,有多處需要讀用戶的基本信息,關(guān)鍵業(yè)務(wù)鏈路對其有讀強(qiáng)依賴。
運營支撐:供內(nèi)部工作人員用的后臺系統(tǒng),包括但不限于工作流、客服等功能。
配置數(shù)據(jù):這里是個寬泛的說法,籠統(tǒng)地表示各類變更不頻繁,但是在主業(yè)務(wù)流程中需要頻繁讀取的數(shù)據(jù),例如交易類目、機(jī)構(gòu)代碼、匯率。它們實際可能是散在各個業(yè)務(wù)系統(tǒng)中的,為了方便描述,單獨用一個配置數(shù)據(jù)庫來表示。
把數(shù)據(jù)庫按業(yè)務(wù)模塊進(jìn)行拆分,是典型的垂直擴(kuò)展思路,突破了單庫的能力限制,使得系統(tǒng)可以支撐更多的業(yè)務(wù)量。當(dāng)然這也引入了分布式事務(wù)的問題,另有專題介紹暫且不表。拆分開后,就方便不同的業(yè)務(wù)采取不同的架構(gòu)設(shè)計了。
與垂直拆分對應(yīng)的,自然就是水平拆分。分庫分表已經(jīng)是一種非常成熟的數(shù)據(jù)水平拆分方法。例如可以將賬號對 10 取模,將數(shù)據(jù)分散到 10 個邏輯分表中。這 10 個分表又映射到 10 個物理數(shù)據(jù)庫。分庫分表中間件可以屏蔽掉底層部署結(jié)構(gòu)和路由邏輯,應(yīng)用層仍然像使用普通單庫一樣寫 SQL。

拆分開后,“有數(shù)據(jù)庫出故障”的概率其實是大大增加的。假設(shè)其中一個賬務(wù)庫故障了,就意味著有至少 10% 的核心業(yè)務(wù)受影響了,實際還不止,因為一筆交易涉及雙方賬號。這種情況怎么辦,立即切換到備庫?不行的,前面說過賬務(wù)要求數(shù)據(jù)強(qiáng)一致,即 RPO=0。數(shù)據(jù)庫的主備復(fù)制一般有延時,不能保證數(shù)據(jù)無丟失。即使用 Oracle+ 共享存儲的方式保證不丟數(shù)據(jù),回放 Redo Log、檢查數(shù)據(jù)一致性、切換備庫,通常要花費數(shù)十分鐘,足夠用戶在社交網(wǎng)絡(luò)炸鍋的了。怎么辦?早期其實沒什么好辦法,情愿犧牲一些 RTO,也要保證 RPO。當(dāng)然可以做一些體驗上的優(yōu)化,例如界面展示余額時,可以使用只讀備庫,減少用戶恐慌,但不允許基于此余額做實際業(yè)務(wù),聊勝于無吧。
后來逐漸探索出了一套賬務(wù)容災(zāi)方案,需要業(yè)務(wù)層參與,還挺復(fù)雜的。這個話題足夠單獨成文,本文先不詳細(xì)介紹,只說一下基本思路:主備庫數(shù)據(jù)不一致無法避免,但可以想辦法鎖定有哪些賬號的數(shù)據(jù)是最近剛剛在主庫有過變更的,我們沒法確定這個變更是否已經(jīng)同步到備庫了,就把這些賬戶全部加入黑名單,數(shù)據(jù)庫恢復(fù)前不允許他們再做業(yè)務(wù),避免發(fā)生資損。可以采取一些手段,讓黑名單范圍盡量小,并且確保黑名單以外的賬戶一定是主備庫一致的,實踐中可以縮小到幾十幾百個賬戶。這樣,不可用范圍就從庫粒度一下子降到賬號粒度,不在黑名單中的賬戶,就可以基于備庫余額正常開展業(yè)務(wù)。
這套基于黑名單的容災(zāi)方案一直運行了好幾年,效果還不錯,缺點就是比較復(fù)雜,這是賬務(wù)類業(yè)務(wù)本身的特點決定的。直到自研數(shù)據(jù)庫 OceanBase 的誕生,情況有了改觀。OceanBase 是基于 Paxos 協(xié)議的分布式強(qiáng)一致數(shù)據(jù)庫,對于單節(jié)點故障,它提供 RPO=0,RTO<30 秒的容災(zāi)能力,致力于從數(shù)據(jù)庫層屏蔽容災(zāi)細(xì)節(jié),為應(yīng)用層提供簡單的使用方式。
交易數(shù)據(jù)也是非常適合水平拆分的,可以將交易單據(jù)號取模,做分庫分表。除此之外,根據(jù)交易類業(yè)務(wù)的特點,還有更有意思的玩法。除了正常的交易主庫之外,另外再準(zhǔn)備一組表結(jié)構(gòu)完全相同的空庫,稱為 Failover 庫(注意不是備庫,跟主庫沒有數(shù)據(jù)同步關(guān)系)。交易系統(tǒng)在創(chuàng)建一筆交易的時候,首先要生成交易單據(jù)號,其中有一位叫做彈性位,正常情況下它的值是 1,代表這筆數(shù)據(jù)應(yīng)該寫入主庫。后續(xù)根據(jù)交易單據(jù)號讀寫該條數(shù)據(jù)的時候,一看彈性位是 1,就知道到主庫找這條數(shù)據(jù)。

假設(shè) 3 號主庫突然故障了,這時就需要自動或手動給交易系統(tǒng)推送一個指令,告訴它以后第 3 分片的新數(shù)據(jù)應(yīng)該插入 Failover 庫。以后生成的第 3 分片的交易單據(jù)號,彈性位就是 2,代表 Failover 庫,后續(xù)讀寫這條數(shù)據(jù),也可以根據(jù)這一位自動找到 Failover 庫。這時候主庫的存量數(shù)據(jù)是無法修改的,已創(chuàng)建未付款的交易,用戶可以放棄,重新創(chuàng)建一筆,就會落到 Failover 庫正常處理。已經(jīng)付款的交易,就暫時不能做發(fā)貨、確認(rèn)收貨等狀態(tài)推進(jìn)了,但這不是關(guān)鍵業(yè)務(wù),遲一點做也問題不大。當(dāng)主備庫數(shù)據(jù)一致性檢查通過,主備切換完成,落在主庫的老數(shù)據(jù)又可以繼續(xù)處理了。這時再推送指令給交易系統(tǒng):3 號庫恢復(fù)正常狀態(tài),以后新數(shù)據(jù)落主庫。Failover 機(jī)制讓主業(yè)務(wù)(創(chuàng)建交易、付款)在很短的時間內(nèi)恢復(fù)可用,放棄非關(guān)鍵業(yè)務(wù)(存量數(shù)據(jù)的狀態(tài)推進(jìn)),為主備切換爭取了時間。分庫分表、Failover 的邏輯,都可以由數(shù)據(jù)訪問層封裝,業(yè)務(wù)層并不用感知。
這期間在 Failover 3 號庫創(chuàng)建的、彈性位為 2 的數(shù)據(jù)怎么處理?答案是不用特殊處理,根據(jù)彈性位 2,以后仍然可以在 Failover 庫訪問到這條數(shù)據(jù),經(jīng)過一段時間后,主庫、彈性庫的數(shù)據(jù)最終都會遷移到歷史庫去。Failover 庫主要用于臨時接管主庫的新增數(shù)據(jù),只要保持表結(jié)構(gòu)一致即可,容量可以低于主庫。當(dāng)然彈性位也可以啟用 3、4、5 更多編號,來靈活切換更多存儲,這也是“彈性”的含義所在。
配置數(shù)據(jù)很好理解,讀多寫少,讀可靠性要求高,非常適合采用讀寫分離方案。根據(jù)具體業(yè)務(wù),可以采用讀從庫、分布式緩存、內(nèi)存緩存等方式。
用戶數(shù)據(jù)跟賬務(wù)數(shù)據(jù)有緊密的對應(yīng)關(guān)系,直觀地想,也應(yīng)該跟賬務(wù)數(shù)據(jù)采用同樣的處理策略,甚至合并到賬務(wù)庫中。這的確也是可行的,但在實踐中,我們根據(jù)它的業(yè)務(wù)特性,采取的卻是跟配置數(shù)據(jù)類似的處理策略,沒有做水平拆分,而是做全量復(fù)制、讀寫分離。理由有如下這些:
用戶數(shù)據(jù)更新較少,寫操作不在關(guān)鍵路徑,讀操作在關(guān)鍵路徑,跟配置數(shù)據(jù)的特性非常相似
對數(shù)據(jù)一致性要求沒那么高,可以接受少量延遲同步,沒有必要用賬務(wù)數(shù)據(jù)那么強(qiáng)的一致性保障,賬戶余額不可信時,希望至少不影響登錄
不全是按賬號精確查找,可能有郵箱、手機(jī)號等維度的查詢,按賬號水平拆分后,難以路由
所以用戶系統(tǒng)實際采用的方案是:一個寫庫,全量異步復(fù)制到多個讀庫,再加上分布式緩存。如果寫庫故障,則不能注冊新用戶、更新個人信息;個別讀庫故障,不影響業(yè)務(wù)。
這里用運營支撐系統(tǒng)舉例子,實際上是想代表這么一類業(yè)務(wù)數(shù)據(jù):讀寫比差不多,業(yè)務(wù)流程依賴寫操作,也不適合做水平拆分。這種稱之為全局狀態(tài)型數(shù)據(jù)。全局狀態(tài)型數(shù)據(jù)一般是輔助型的非關(guān)鍵業(yè)務(wù),一旦數(shù)據(jù)庫故障,“要么等,要么忍”——犧牲 RTO 等待數(shù)據(jù)庫主備切換,或者犧牲 RPO 立即強(qiáng)切備庫。在做架構(gòu)設(shè)計時,需要盡量避免關(guān)鍵業(yè)務(wù)強(qiáng)依賴全局狀態(tài)型數(shù)據(jù)。如果真的有關(guān)鍵業(yè)務(wù)是全局狀態(tài)型的,只能依靠 OceanBase 這樣的多副本強(qiáng)一致數(shù)據(jù)庫產(chǎn)品了。
歸納一下,業(yè)務(wù)數(shù)據(jù)主要可以歸為三大類:
狀態(tài)型:讀寫比相當(dāng),必須保證可寫才有意義,每一次寫操作必須基于前一個正確的狀態(tài)。這是最棘手的一種數(shù)據(jù),難以完美兼顧 PTO 和 RPO 。關(guān)鍵業(yè)務(wù)的狀態(tài)型數(shù)據(jù),應(yīng)盡量想辦法把維度拆細(xì),一是提高并發(fā)處理能力,二是方便隔離故障影響。
流水型:不斷產(chǎn)生新的數(shù)據(jù),各條數(shù)據(jù)間是獨立的,可以隨時切換新數(shù)據(jù)的存儲位置,每條數(shù)據(jù)的主鍵自包含存儲位置信息。單條數(shù)據(jù)的更新需要保證強(qiáng)一致性。流水型數(shù)據(jù)很方便做水平擴(kuò)展。
配置型:讀寫比大,強(qiáng)依賴讀,弱依賴寫,不要求嚴(yán)格的讀一致性。可以采用讀寫分離、一寫多讀的方式保證讀操作的性能和高可靠。
在做架構(gòu)設(shè)計的時候,如果能準(zhǔn)確地識別出業(yè)務(wù)數(shù)據(jù)的“模式”,可以幫助更合理地劃分業(yè)務(wù)模塊,更方便套用特定模式的性能擴(kuò)展和可靠性保障策略,乃至將公共邏輯抽象成通用組件。一個沒有經(jīng)過數(shù)據(jù)類型拆分的系統(tǒng),可以先當(dāng)成最壞的情況:全是全局狀態(tài)型數(shù)據(jù)。然后識別出其中的流水型數(shù)據(jù)、配置型數(shù)據(jù),逐漸分離出去,狀態(tài)型數(shù)據(jù)盡量做水平拆分。最后肯定還是會存在無法規(guī)避的全局狀態(tài)型數(shù)據(jù),則要想辦法盡量降低它的重要性,避免關(guān)鍵鏈路對它的強(qiáng)依賴。
金融/支付系統(tǒng)中,最重要的往往就是類似賬務(wù)的這種狀態(tài)型數(shù)據(jù),是必須要面對的難題。螞蟻金服的經(jīng)驗是將所有“類賬務(wù)”的業(yè)務(wù)(例如余額、余額寶、花唄)做抽象化、平臺化,封裝黑名單容災(zāi)等固定的業(yè)務(wù)邏輯,減少重復(fù)開發(fā)。當(dāng)然,OceanBase 數(shù)據(jù)庫上線后,把復(fù)雜度封裝在數(shù)據(jù)庫層內(nèi)部,在性能和容災(zāi)能力(RTO/RPO)上達(dá)到了業(yè)務(wù)期望的平衡,對業(yè)務(wù)開發(fā)是一個不小的福音。目前支付寶的核心系統(tǒng)已經(jīng) 100% 運行在 OceanBase 數(shù)據(jù)庫上。
本文介紹的幾種數(shù)據(jù)模式,基本可以覆蓋常見的業(yè)務(wù),可以作為架構(gòu)設(shè)計的參考。但也不必過于拘泥,業(yè)務(wù)本身是復(fù)雜多樣的,不一定是單純的某種模式。舉兩個例子:
我們?nèi)粘T谥Ц秾毥缑嫔峡吹降南M記錄,并不是直接查詢交易系統(tǒng),而是從一個專門的消費記錄系統(tǒng)查詢的。交易系統(tǒng)會通過異步消息把數(shù)據(jù)復(fù)制到消費記錄系統(tǒng)。雙 11 高峰期消費記錄展示可能會有少許延遲,就是這個道理。交易是典型的流水型業(yè)務(wù),但交易系統(tǒng)和消費記錄系統(tǒng)組成的體系,用的卻是讀寫分離思想。
賬務(wù)系統(tǒng)的余額是狀態(tài)型數(shù)據(jù),但每個賬戶的變更明細(xì),卻是流水型數(shù)據(jù),可以適用流水型 Failover 容災(zāi)方案。
本文只討論了節(jié)點級的水平擴(kuò)展以及容災(zāi)能力,沒有提及訪問距離帶來的延時問題。金融系統(tǒng)往往要求機(jī)房級甚至城市級的擴(kuò)展能力和容災(zāi)能力,螞蟻金服就運行在異地多活架構(gòu)上。
這時候數(shù)據(jù)訪問延時就無法忽略了,會冒出很多原本不是問題的問題,架構(gòu)設(shè)計將更加復(fù)雜。對數(shù)據(jù)類型的分類,其實是一個重要的基礎(chǔ),不同類型的數(shù)據(jù)在異地架構(gòu)下也有相應(yīng)的處理模式,后續(xù)異地多活的系列文章還會深入討論。
來源公眾號:肉眼品世界
版權(quán)申明:內(nèi)容來源網(wǎng)絡(luò),版權(quán)歸原創(chuàng)者所有。除非無法確認(rèn),我們都會標(biāo)明作者及出處,如有侵權(quán)煩請告知,我們會立即刪除并表示歉意。謝謝!

