高頻交易支付架構並不複雜

 

寫在前面nginx

支付系統是整個交易系統中至關核心的一部分功能,以咱們的交易中臺爲例,經過領域方式的拆分,支付架構隸屬於訂單團隊,在整個用戶下單以後進行支付,支付以後成單進入交易履約流程。
web

支付系統因爲自己和金融相關,不像其餘高頻系統面對海量請求能夠大量使用緩存,異步mq等方式解決三高問題。支付系統對數據一致性要求更高,因此對於其架構設計原則仍是有本身特色的。redis

分庫分表算法

構建一個支撐每秒十萬只讀系統並不複雜,無非是經過一致性哈希擴展緩存節點,水平擴展web服務器等。每秒鐘數十萬數據更新操做,在任何數據庫上都是不可能的任務,首先須要對訂單表進行分庫分表。數據庫

在進行數據庫操做時,通常會用ID(UID)字段,因此選擇以UID進行分庫分表。緩存

分庫策略咱們選擇了「二叉樹分庫」,所謂「二叉樹分庫」指:在進行數據庫擴容時,以2倍數進行擴容。好比:1臺擴容2臺,2臺擴容4臺,以此類推。最後把Order庫分了8個庫中,每一個庫10個表。服務器

根據uid計算數據庫編號:微信

分庫信息 = (uid / 10) % 8 + 1 
根據uid計算表編號: 
表編號 = uid %10架構

訂單ID

訂單系統的ID必須具備全局惟一的特徵,簡單的方式是利用數據庫的序列,每操做一次就能得到一個全局惟一的自增ID,若是支持每秒10w訂單,那每秒至少須要生成10w訂單ID,經過數據庫自增ID顯然沒法完成上述請求。因此經過內存計算獲取全局惟一的訂單ID。併發

JAVA領域著名的惟一ID應該是UUID了,不過UUID太長且包含字母,不適合作訂單ID。

經過反覆比較篩選,借鑑Twitter的算法實現全局惟一ID。

三部分組成:

  • 時間戳

    時間戳的粒度是毫秒級,生成訂單ID時,使用System.currentTimerMillis()做爲時間戳。

  • 機器號

    每一個訂單服務器都被分配一個惟一的編號,生成訂單ID時,直接使用該惟一編號做爲機器便可。

  • 自增序號

    當同一服務器的同一號碼中有多個生成訂單ID的請求時,會在當前毫秒下自增此序號,下一個毫秒此序號繼續同0開始。如同一服務器同一毫秒生成3個訂單ID請求,這3個訂單ID的自增序號分別是0,1,2。

最終訂單結構:

分庫分表信息 + 時間戳 + 機器號 + 自增序號

仍是按照第一部分根據uid計算數據庫編號和表編號的算法,當uid=9527時,分庫信息=1,分表信息=7,將他們進行組合,兩位的分庫分表信息即爲」17」。

最終一致性

咱們經過對order表uid維度的分庫分表,實現了order表的超高併發寫入與更新,經過uid和訂單ID查詢訂單信息。

上面方案雖然簡單,可是保持兩個order表機器的數據一致是很麻煩的事情。

兩個表集羣顯然是在不一樣的數據庫集羣中,若是寫入與更新中引入強一致性的分佈式事務,這無疑會大大下降系統效率,增加服務響應時間,這是咱們所不能接受的,因此引入了消息隊列進行異步數據同步,爲了實現數據的最終一致性。

固然消息隊列的各類異常會形成數據不一致,因此咱們又引入了實時服務監控,實時計算兩個集羣的數據差別,並進行一致性同步。

數據庫高可用

所謂數據庫高可用指的是:

當數據庫因爲各類緣由出現問題時,能實時或快速的恢復數據庫並修補數據。

從總體集羣角度看,就像沒有出任何問題同樣,須要注意的是,這裏的恢復數據庫服務並不必定是指修復原有數據庫,也包括將服務切換到另外備用的數據庫。

數據庫高可用的主要工做是數據恢復月數據修補,通常咱們完成這兩項工做的時間長短,做爲衡量高可用好壞的標準。

 

咱們認爲,數據庫運維應該和項目組分開,當數據庫出現問題時,應由DBA實現統一恢復,不須要項目組操做服務,這樣便於作到自動化,縮短服務恢復時間。

如上圖所示,web服務器將再也不直接鏈接從庫DB2和DB3,而是鏈接LVS負載均衡,由LVS鏈接從庫。

這樣作的好處是LVS能自動感知從庫是否可用,從庫DB2宕機後,LVS將不會把讀數據請求再發向DB2。

同時DBA須要增減從庫節點時,只需獨立操做LVS便可,再也不須要項目組更新配置文件,重啓服務器來配合。

再來看主庫高可用結構圖:

 

如上圖所示,web服務器將再也不直接鏈接主庫DB1,而是鏈接KeepAlive虛擬出的一個虛擬ip,再將此虛擬ip映射到主庫DB1上,同時添加DB_bak從庫,實時同步DB1中的數據。

正常狀況下web仍是在DB1中讀寫數據,當DB1宕機後,腳本會自動將DB_bak設置成主庫,並將虛擬ip映射到DB_bak上,web服務將使用健康的DB_bak做爲主庫進行讀寫訪問。

這樣只需幾秒的時間,就能完成主數據庫服務恢復。

組合上面的結構,獲得主從高可用結構圖:

數據庫高可用還包含數據修補,因爲咱們在操做核心數據時,都是先記錄日誌再執行更新,加上實現了近乎實時的快速恢復數據庫服務,因此修補的數據量都不大,一個簡單的恢復腳本就能快速完成數據修復。

數據分級

支付系統除了最核心的支付訂單表與支付流水錶外,還有一些配置信息表和一些用戶相關信息表。若是全部的讀操做都在數據庫上完成,系統性能將大打折扣,因此咱們引入了數據分級機制。

咱們簡單的將支付系統的數據劃分紅了3級:

  • 第1級:訂單數據和支付流水數據;這兩塊數據對實時性和精確性要求很高,因此不添加任何緩存,讀寫操做將直接操做數據庫。

  • 第2級:用戶相關數據;這些數據和用戶相關,具備讀多寫少的特徵,因此咱們使用redis進行緩存。

  • 第3級:支付配置信息;這些數據和用戶無關,具備數據量小,頻繁讀,幾乎不修改的特徵,因此咱們使用本地內存進行緩存。

使用本地內存緩存有一個數據同步問題,由於配置信息緩存在內存中,而本地內存沒法感知到配置信息在數據庫的修改,這樣會形成數據庫中數據和本地內存中數據不一致的問題。

爲了解決此問題,咱們開發了一個高可用的消息推送平臺,當配置信息被修改時,咱們可使用推送平臺,給支付系統全部的服務器推送配置文件更新消息,服務器收到消息會自動更新配置信息,並給出成功反饋。

粗細管道

舉個簡單的例子,咱們目前訂單的處理能力是平均10萬下單每秒,峯值14萬下單每秒,若是同一秒鐘有100萬個下單請求進入支付系統,毫無疑問咱們的整個支付系統就會崩潰,後續源源不斷的請求會讓咱們的服務集羣根本啓動不起來,惟一的辦法只能是切斷全部流量,重啓整個集羣,再慢慢導入流量。

咱們在對外的web服務器上加一層「粗細管道」,就能很好的解決上面的問題。

請看上面的結構圖,http請求在進入web集羣前,會先通過一層粗細管道。入口端是粗口,咱們設置最大能支持100萬請求每秒,多餘的請求會被直接拋棄掉。出口端是細口,咱們設置給web集羣10萬請求每秒。

剩餘的90萬請求會在粗細管道中排隊,等待web集羣處理完老的請求後,纔會有新的請求從管道中出來,給web集羣處理。

這樣web集羣處理的請求數每秒永遠不會超過10萬,在這個負載下,集羣中的各個服務都會高校運轉,整個集羣也不會由於暴增的請求而中止服務。

如何實現粗細管道?nginx商業版中已經有了支持,相關資料請搜索

nginx max_conns,須要注意的是max_conns是活躍鏈接數,具體設置除了須要肯定最大TPS外,還需肯定平均響應時間。

微信掃描二維碼,關注個人公衆號

相關文章
相關標籤/搜索