支付結算系統如何應對高併發、熱點賬戶等問題
網際網路金融系統的核心是支付結算,而支付結算的基礎又是賬戶系統。金融賬戶系統的特點是併發量大、響應快、交易金額大,熱點賬戶問題突出。一個合格的賬戶系統既要解決上述問題,又必須絕對保證資金安全。作為宜信這家網際網路金融公司的支付結算中心,其賬戶系統也必須具備上述特徵。
一、賬戶體系
1.1 賬戶結構
宜信支付結算賬戶體系是客戶、使用者、賬戶三層結構,證件號和證件型別唯一確定一個客戶,客戶號和機構號確定一個使用者,一個使用者下可開多個不同型別的賬戶。如圖:
1.2 賬戶屬性
賬戶系統的基礎是賬戶,所有的操作都圍繞著賬戶進行,賬戶包含以下一些屬性:
- 會計科目:每個賬戶金額的變動要體現一些會計的屬性,以便會計核算。
- 賬戶類別:分為個人賬戶、企業賬戶、平臺類賬戶。
- 賬戶明細:賬戶的明細是反映賬戶餘額變動的每筆詳情,採用複式記賬法,包含本對方賬號、賬戶等資訊、摘要、借方的發生額及餘額等資訊。
- 賬戶餘額:記錄賬戶的實時餘額。
1.3 會計科目
賬戶下掛在最底層的會計科目下,會計科目決定了賬戶的含義及餘額變動方向。會計科目的一些屬性如下:
- 科目類別:資產類、負債類、所有者權益、成本類、損益類等。
- 科目級別:會計科目的級別,一級科目、二級科目、三級科目等。下級科目歸屬上級科目。
- 餘額方向:標示餘額是在借方還是貸方。
- 科目的期末餘額:每日日切後會彙總底層科目所有下掛賬戶在上一會計日的餘額總和,上級科目彙總下級科目的餘額總和。
1.4 科目樹
宜信支付結算賬戶系統採用科目樹的概念,每個機構都會繫結一個科目樹。科目樹的根節點是一級科目,底層的科目下掛賬戶,結構如下:
二、賬戶系統架構
宜信支付結算賬戶系統採用公司自研的分散式微服務框架,對外提供http json介面,內部各服務間採用redis實現的訊息佇列通訊。
2.1 賬戶系統功能架構
宜信支付結算賬戶系統分為接入模組、記賬子系統、開戶子系統、非同步記賬模組、查詢子系統、定時任務子系統、日終子系統、非同步日誌模組,下圖是賬務系統功能模組圖:
- 接入模組:提供報文解析、驗籤、引數校驗、許可權認證等公共服務,是賬戶系統的統一入口。
- 非同步日誌模組:非同步記錄業務系統請求報文。
- 記賬子系統:賬戶系統的核心模組,處理業務系統的記賬請求。
- 開戶子系統:處理業務系統的開戶請求。
- 首次開立賬戶:為個人或企業開立客戶、使用者及提前配置的預設開通的賬戶。
- 指定開立賬戶:個人或企業在首次開立賬戶後,可按科目號指定開立賬戶。
- 查詢子系統:提供賬戶、記賬的一些查詢功能。
- 非同步記賬模組:提供非同步記錄賬戶流水的功能。
- 定時任務子系統:處理失敗重試、熱點賬戶等的定時任務。
- 日終子系統:提供日切以及日終跑批的功能。
2.1.1 記賬處理
記賬處理是賬戶系統的核心功能,該功能對效能的要求比較高,高併發下熱點賬戶問題比較突出,資金的正確性也必須保證,並且根據業務不同,記賬的分錄也是五花八門,宜信支付結算賬戶系統如何應對這些問題,這裡重點介紹下:
- 賬戶系統記賬採用記賬服務的概念,每個記賬服務就是一個記賬分錄的模板,業務系統按照這個模板傳入記賬金額、賬戶號或者使用者號等資訊。
- 賬戶系統採用redis分散式鎖,防止業務系統重複提交請求。設定記賬訂單防重表,按照請求單號和機構號對記賬請求做冪等性校驗。
- 採用複式記賬法,按照會計規則按照借貸記錄流水,有借必有貸。
- 記賬處理時,更新賬戶餘額後同步返回結果給業務系統,非同步的處理記賬流水。同時設定補償機制,定時重試記賬流水處理失敗的訂單,重試三次失敗後報警人工介入。
- 記賬規則處理,每個記賬服務可以繫結一些記賬規則,賬戶系統根據記賬服務遍歷其繫結的規則,順序處理。
2.1.2 熱點賬戶問題
熱點賬戶問題是賬戶系統的痛點,也困擾了我們很久,這裡著重說下。
-- 充值時的記賬分錄是:
借方:三方支付待清算賬戶(+)
貸方:個人餘額賬戶(+)
當大量使用者充值時,三方支付的待清算賬戶就是熱點賬戶,頻繁的增加餘額。
-- 提現時的記賬分錄是:
借方:個人餘額賬戶(-)
貸方:三方支付資產賬戶(-)
當大量使用者提現時,三方支付的資產賬戶就是熱點賬戶,頻繁的減少餘額。
--業務收服務費的記賬分錄是:
借方:個人賬戶(-)
貸方:商戶服務費賬戶(+)
當大量向用戶收取服務費時,商戶服務費賬戶就是熱點賬戶,會頻繁增加餘額。
--業務服務費付款的記賬分錄是:
借方:商戶服務費賬戶(-)
貸方:個人賬戶(+)
當大量用服務費餘額向用戶付款時,商戶服務費賬戶就是熱點賬戶,會頻繁減少餘額。
記賬時,所有涉及的賬戶餘額都要做update更新,高併發情況下,當出現上述型別的熱點賬戶時,由於資料庫的行級鎖,對同一賬戶的更新餘額操作由並行變成序列,單個請求的響應時間變長,從而拖垮整個記賬服務。
宜信支付結算賬戶系統針對上述問題做了如下處理:
我們把熱點賬戶按照金額變動方向分為加頻賬戶(餘額增加頻繁)、減頻賬戶(餘額扣減頻繁)、雙頻賬戶(餘額增加扣減均頻繁)。
- 加頻賬戶處理
準實時更新餘額。先將金額變動插入臨時表中,由定時任務按照一定頻率彙總發生額,並更新賬戶餘額,而後刪除臨時記錄。當加頻賬戶減錢餘額不足時,主動去彙總發生額。這裡需要考慮主動彙總發生額和定時任務處理的併發情況,我們在該定時任務執行時設定redis鎖,防止併發,主動彙總時會去判斷這個redis鎖是否存在,如存在證明定時任務正在執行,無需主動彙總,可能是真的餘額不足。主動彙總同樣會設定redis鎖,定時任務同樣會判斷。
- 減頻賬戶處理
將減頻賬戶拆分多個子賬戶,減頻子賬戶設定金額報警,如果某個減頻子賬戶餘額不足觸發報警,會對該子賬戶做資金歸集,將其他子賬戶餘額歸集到該子賬戶(每個子賬戶設定可歸集金額限制)。如在交易過程中發現該子賬戶餘額不足,轉向使用其他子賬戶記賬。由於拆分子賬戶,餘額查詢時需要彙總各個子賬戶餘額返回;記錄主賬戶流水需要記賬後餘額,這裡需要非同步計算彙總。當減頻賬戶加錢時,需要平均分配入賬到不通的子賬戶。
- 雙頻賬戶處理
將雙頻賬戶拆分多個子賬戶。加錢時,準實時更新餘額,先將子賬戶金額變動插入臨時表中,由定時任務按一定頻率彙總發生額,將彙總的發生額更新進對應的子賬戶,並刪除金額變動記錄;減錢按照之前減頻賬戶的邏輯執行。
2.1.3 記賬死鎖問題
高併發情況下,當多個賬戶之前互相轉賬時,可能會出現死鎖問題。
例如:A餘額賬戶 —> B餘額賬戶(執行緒1) 和 B餘額賬戶—>A餘額賬戶(執行緒2) 兩個轉賬請求併發,賬戶系統對每個轉賬請求都會更新A、B餘額,這兩個更新需要在一個事務裡,正常流程執行緒1先更新A,再更新B,執行緒2先更新B,再更新A,執行緒1更新完A後會等待B的鎖,不提交事務,執行緒2更新完B後會等待A的鎖,不提交事務,這樣兩個執行緒互相等待鎖,造成死鎖。
宜信支付結算賬戶系統針對這種情況提出瞭解決辦法,對賬戶號進行排序後再更新餘額,這樣每個執行緒都是先更新A再更新B,解決了死鎖問題。
2.2 賬戶系統儲存層架構
宜信支付結算賬戶系統資料庫採用Mysql,快取採用redis。
-
Mysql資料庫採用主從架構,一主二從,主庫向從庫同步資料。針對一些資料量大的表進行分表,比較有代表性的是賬戶流水錶,既要按賬戶維度查詢,又要按時間維度彙總,所以針對這個特點,冗餘了一張表,一張按照賬戶分表,一張按照日期分表。
-
Redis採取叢集架構,叢集中每個點主備的形式。
2.3 賬戶系統的網路層架構
賬戶系統各個服務部署在同一機房,其中記賬子系統和非同步記賬模組部署在4個不同的物理機上,其他子系統和模組部署在2個不同物理機上。最前端採用nginx實現負載均衡。
作者:李銳 程留允