在聚合支付開發(fā)的初期,大家一般都是加班加點盡可能多的實現(xiàn)現(xiàn)階段各種豐富的支付方式,如H5支付、APP支付、小程序支付等,但隨著支付能力的提升,大家會發(fā)現(xiàn)其實聚合支付系統(tǒng)周邊的一些配套系統(tǒng)也尤其的重要,如支付賬戶系統(tǒng)、支付日志歸系統(tǒng)、支付業(yè)務(wù)數(shù)據(jù)統(tǒng)計系統(tǒng)等等,這些周邊的系統(tǒng)對支付核心系統(tǒng)出現(xiàn)問題后的定位與分析會有很大的幫助。
聚合支付在一定程度上與支付機構(gòu)有一定的相似度,大家為了更好的了解每個接入聚合支付的商戶的資金情況,在一定程度上都會引入賬戶體系,但是隨之也會出現(xiàn)一個問題,就是賬戶的高并發(fā)與熱點數(shù)據(jù)問題,以我在工作中所遇到的問題為例和大家分享,同樣也歡迎答應(yīng)大家一起進行討論。
一、產(chǎn)生問題:賬戶數(shù)據(jù)高并發(fā)與熱點數(shù)據(jù)
在聚合支付中我們會給每一個商家創(chuàng)建一個賬戶,當(dāng)商家有支付與退款的行為時,都會動態(tài)的更新余額,隨之會產(chǎn)生一條流水?dāng)?shù)據(jù),為了保證資金的準(zhǔn)確性我們使用了悲觀鎖,將查詢余額、更新余額、插入流水作封裝一個事務(wù),我知道數(shù)據(jù)庫的更新操作一個時刻只能有一個會話進行操作,所以在大量的并發(fā)操作下操作余額數(shù)據(jù)會出現(xiàn)數(shù)據(jù)庫行鎖的等待情況,嚴(yán)重的影響了系統(tǒng)的效率與吞吐量。
這時候大家可能會想到一種方案,就是采用異步的方式,在不影響主流程的情況下,用異步的方式來進行記賬,讓數(shù)據(jù)庫慢慢的處理,這也是解決該問題的一個思路,但是不能從根本上解決該問題。
二、嘗試解決:優(yōu)化業(yè)務(wù)流程、事務(wù)處理與SQL
A:問題分析
遇到這個問題我首先歸納了能夠更新該條余額記錄的操作,對于聚合系統(tǒng)來說主要應(yīng)該有三類。
- 支付流程:當(dāng)用戶支付成功時,在該商戶的余額上加相應(yīng)的金額;
- 預(yù)退款流程:用戶退款時,如果該商戶的賬面金額大于退款金額,則將退款金額先凍結(jié),然后進行退款,如金額不足則不允許退款;
- 退款流程:如用戶退款成功,則將凍結(jié)金額解凍,并對余額進行扣減。
B:優(yōu)化流程
針對以上的三種流程,我們提出的方案如下:
- 針對支付流程與退款流程:先在應(yīng)用中進行緩存,如3秒內(nèi)緩存累計需要增加或者扣減的余額,從而降低數(shù)據(jù)庫的更新操作的頻率,緩解數(shù)據(jù)庫行鎖的爭搶等待。
-
針對預(yù)退款流程:將原有的事務(wù)(下圖現(xiàn)狀部分)改造為非事務(wù)(下圖優(yōu)化部分),并優(yōu)化SQL,利用數(shù)據(jù)庫自身去競爭鎖資源,大體優(yōu)化流程見下圖。
預(yù)退款流程優(yōu)化
C.效果分析
針對這套優(yōu)化方案我們經(jīng)過測試后發(fā)現(xiàn)性能有一定的提升,但該方案還存在以下幾個問題。
- 支付流程與退款流程的優(yōu)化:由于緩存累加的操作在應(yīng)用上處理,如果單個應(yīng)用節(jié)點的應(yīng)用出現(xiàn)了問題會損失這部分?jǐn)?shù)據(jù),如果要是引入redis進行緩存,又要依賴一個組件。
- 預(yù)退款流程優(yōu)化:在事務(wù)上可以說將“悲觀鎖”改為“樂觀鎖”,但是通過測試發(fā)現(xiàn)效率沒有“質(zhì)變”的提升(畢竟update語句始終需要行鎖),而且優(yōu)化后更新與插入操作不在一個事務(wù)中,最終可能會導(dǎo)致資金的差異,影響數(shù)據(jù)的最終一致性。
三、推薦方案:利用分治的思想解決該問題
雖然第二部分的優(yōu)化方案在一定程度上緩解了高并發(fā)與熱點數(shù)據(jù)的問題,但是總感覺還不是一個最優(yōu)的方案,因為為了解決這個問題,引入了其他的問題,以及要考慮到應(yīng)用的高可用性。
話外音:有一天晚上打完球回來,我就在想支付機構(gòu)是如何做到這種資金的強一致性,正恰好一個小伙伴也在加班,所以和小伙伴簡單的討論了一下,的確也開闊了自己的思路,最后通過查閱資料,發(fā)現(xiàn)大部分都采用了“分治”的思想來解決賬戶數(shù)據(jù)的高并發(fā)與熱點數(shù)據(jù)問題。
A:數(shù)據(jù)緩沖(加頻賬戶)
與之前的思路大體一致,先緩沖數(shù)據(jù)然后再進行更新,減少數(shù)據(jù)庫行鎖的爭搶與等待時間,在有余額變動時,將所有加頻數(shù)據(jù)(增加金額)緩沖在數(shù)據(jù)庫的一張臨時表中。那就需要一個定時任務(wù)在單位時間內(nèi)對臨時表的數(shù)據(jù)進行匯總,然后將匯總的金額一次性更新到余額表中。當(dāng)然這其中也會出現(xiàn)一個問題,就是如果商戶的余額不足且臨時表中還有加頻數(shù)據(jù)緩沖時,那就需要主動對臨時表數(shù)據(jù)進行匯總并一次性同步到余額表中。這樣就會出現(xiàn)兩個線程同時發(fā)生匯總數(shù)據(jù)并更新余額的情況,這就需要使用鎖來進行控制(可以使用Redis來設(shè)置鎖)。

B:賬戶拆分(減頻賬戶)
將賬戶進行拆分,一條賬戶拆分為N個子賬戶,這樣再減頻(扣減金額)數(shù)據(jù)時,可以減少對原本一條記錄的鎖進行爭搶與等待,同時需要設(shè)置一個余額告警,當(dāng)有子賬戶的資金不夠時,可以將其他子賬戶的金額歸集一部分到該子賬戶中或?qū)⒃摴P交易轉(zhuǎn)移至其他賬戶余額充足的子賬戶進行操作,當(dāng)然這其中也有一個問題,就是查詢該賬戶總金額時,需要對所有子賬戶的金額進行匯總操作。
C:雙頻賬戶
在大多數(shù)情況賬戶不單單的只增加金額或者扣減金額,一般情況下都是兩者相結(jié)合,就跟大家的銀行卡賬戶余額一樣,你在消費的同時,可能有會給你轉(zhuǎn)賬。這樣大家就可以將A、B兩種思路相結(jié)合,將賬戶進行拆分,臨時表在匯總金額時,可以一次性將增加的金額按照比例分到不同的子賬戶中。
D:數(shù)據(jù)的一致性
這種加頻和減頻賬戶數(shù)據(jù)的方法雖然很有效,但是由于一些客觀的原因,可能會導(dǎo)致余額的不準(zhǔn)確性,那我們可以再準(zhǔn)備一張記賬流水表,定期的對數(shù)據(jù)進行稽核,來保證賬戶數(shù)據(jù)的最終的一致性。
小結(jié)
面對這種高并發(fā)與熱點問題,總結(jié)起來就是利用“分治”的思想,一條記錄經(jīng)常出現(xiàn)鎖爭搶與鎖等待,那就想辦法降低爭搶鎖和所等待的情況,我們可以較少對該熱點數(shù)據(jù)進行更新或?qū)⒋鬅狳c數(shù)據(jù)分為小熱點數(shù)據(jù)。
PS:祝大家十月一假期愉快?
相關(guān)文章:https://yq.aliyun.com/articles/718383
