去哪兒網支付系統架構演進
去哪兒支付系統自2011年搭建以來,在五年的時間里逐漸從一個高耦合的單一系統發展為眾多子系統組成的高并發、高可用、支持多種交易支付業務的分布式系統。業務從最初的非代收到現在多種非代收、代收場景的支持,B2B業務的從無到有,支付方式從單一網銀支付到現在銀行卡、拿去花、代金券、紅包、立減、積分、趣游寶等多種的組合,訂單從單筆支付到多個訂單同時支付和多次付款。下面對整體的演變過程進行簡單的介紹。
1. 支付系統1.0
新的業務系統初建時,業務邏輯相對簡單,業務量也比較小,為了能夠快速實現功能,發布上線,大多數團隊都會把所有的邏輯都耦合在一個系統。這對于初期業務的快速迭代是有一定好處的。毫不例外,支付交易系統也采用了這樣的方式。如下圖所示。
一個支付系統不例外包括幾個重要組成部分:收銀臺、交易、支付、網關、賬務。
- 收銀臺:用于展示支付詳情、提供各種多樣支付方式的選擇
- 交易:收單規則和交易規則處理
- 支付:處理各種組合的支付方式,如銀行卡、用戶余額、信用付、拿去花、紅包、代金券、立減、積分等
- 賬務:用來記錄所有交易、資金往來的明細,財務會計記賬
- 網關:用于對接銀行通道、第三方支付通道(微信、支付寶)
在業務量不大的情況下,這樣的系統結構沒有問題。隨著更多業務的接入,各種復雜的功能邏輯加入,系統處理起來有點吃力,主要表現以下幾個方面:
- 系統容災能力:所有的功能都集中在一起,一但某個功能出問題,直接影響全局
- 系統擴容:在一個分布式系統中,決定系統性能的取決于最差的部分,整體擴容效果差
- 開發成本高:團隊成員的增加,功能的復雜,多個項目并行時,開發效率極低
- 更多更復雜業務:結構不合理,不能滿足業務發展需要
- 系統職責混亂:如收銀臺只是簡單維護銀行列表
在這樣的一些背景下,2.0系統應運而生。
2. 支付系統2.0
2.0時代是支付交易系統快速發展的一個重要時段。在此過程中,不僅要從系統架構上進行服務化的拆分,而且需要支持更復雜的業務。
2.1 服務化拆分
2.1.1 網關拆分
首先對相對比較獨立的網關進行拆分,網關在整個支付系統中屬于底層基礎服務,是比較重要的基礎設施。對外能夠提供怎么樣的支付交易服務,很多都取決于網關能力的建設。
網關有一些顯著特征,它是一個可高度抽象的業務。對外可以抽象到支付、退款、查詢這些標準的服務。因此優先將這部分拆分,一是為了能夠更好的打好基礎,二是其能夠獨立的發展,三是這部分也相對好實施。
網關的拆分路由系統起到至關重要的作用,對于多通道支付的支持和智能化選擇發揮著巨大作用。
2.1.2 賬務系統的拆分
做交易支付業務,重要的一件事要記清楚賬。記賬可以很簡單的記錄來往流水,也可以更加專業的記財務會計賬。在拆分前系統只是記錄了交易流水,拆分后實現了更加專業和復雜的復式記賬。
新賬務系統的一個簡單流程圖:
2.1.3 會員系統的獨立
會員系統與交易系統本身只是一個依賴關系,在交易支付系統看來只是一個業務系統。比如會員充值業務可以看做是一筆支付交易。為了擺正各自角色,對于會員部分從原有系統中獨立出來。這樣一來各自定位更加清晰明了,也方便了各自獨立發展。現在的會員系統不僅僅只有一個余額,而且引入實名服務、各種資產管理、交易管理等。
2.1.4 基礎服務的拆分
更多的系統拆分獨立后,原有公用的某些功能會多次復制重復。為方便集中管理維護,通過對各系統公用邏輯更能的統一,提供集中的基礎服務,如安全服務、加驗簽服務、通知服務、基礎信息查詢等,如下圖中talos系統。
上述幾個服務的拆分更多是為從業務方面或者技術驅動來考慮。而典型的交易支付過程是有一個時序過程的。比如下單->交易->收銀臺->支付->網關->銀行。這樣一個先后時序也是一個比較好的系統拆分方案。根據這樣的一個時序,我們針對性的對每個階段做了拆分(排除網關和銀行部分),如下過程:
1、交易核心(Apollo)
關注于收單方式和交易類型。
收單方面系統已經支持單筆訂單支付、批量訂單支付。交易類型目前支持直接交易、擔保交易、直接分賬交易、擔保分賬交易、預授權交易等。在批量訂單支付時各種交易類型可以進行混合。且分賬交易同時支持多個賬戶。交易類型除了上面正向交易外,系統還支持很多后續流程交易、如預授權確認、預授權撤銷、退款、擔保撤銷、二次分賬交易等。
多種多樣的交易源于各事業部業務的復雜性,比起標準化的支付系統,我們提提供了更多靈活方便的業務來支持。
2、支付核心(minos)
關注于支付方案的組合和執行。
支付方式:銀行卡、支付寶、微信、拿去花、趣游寶、余額、積分、紅包、代金券、會員紅包、立減等多種方式支付。
支付組合:可以單一使用,也可以進行組合使用。組合場景區分資金類型,如銀行卡、支付寶、微信每次只能選擇一個,其它類資金可多個同時使用。
在有上面基礎的支持下,對于同一批次交易訂單可也進行多次的組合支付扣款,如酒店信用住付款、拿去花還款等業務場景。下圖是支付核心(minos)在系統中的位置:
3、收銀臺
收銀臺直接面向用戶,因此支付體驗至關重要。據統計在支付環節放棄的訂單占比還比較大。因此一個方便、簡潔易用的收銀臺對于訂單轉換是有很大幫助的。目前系統支持的收銀臺主要有app(native)、app前置收銀臺、touch、PC預授權收銀臺、PC多單收銀臺、PC英文版收銀臺、PC標準收銀臺等。收銀臺在系統中的位置如下圖所示。
無線端收銀臺:
PC端收銀臺:
4、API接入層
交易系統更多的服務是通過后臺接口來完成的,這部分占到整體系統很大的業務比重。如支付后期的資金流轉、逆向操作退款等。但也有一些是用來查詢一些交易訂單相關性的信息。在此背景下,對于api接入層采用讀寫分離方式處理。如下圖ares系統,將底層的各dubbo服務包裝提供各種查詢類服務。Odin系統是可讀寫,更多的關注跟核心業務相關的寫,如解凍、退款、撤銷等。
截止目前,整體系統的一個大體結構如下圖所示:
2.2 服務化拆分帶來的挑戰
服務化拆分后,在系統結構上更加清晰了,但對于整體系統的開發管理和日常運營帶來更大的挑戰。比如下幾個方面:
2.2.1 如何提高開發效率
系統拆分后主要提供dubbo服務和對外http(https)服務
1. 針對Dubbo服務的約定
- 接口定義:粒度控制、邊界控制。一個接口不能存在模棱兩可的情況,只做其一
- 參數標準:復雜接口使用對象做參數(避免map)、統一父類、支持擴展屬性透傳、提供create/builder構造合法參數、使用枚舉限制參數范圍。有效避免調用端參數錯傳
- 返回值:統一QResponse封裝、錯誤碼管理(非數字形式含義明確、按業務區分避免重復等)
- 業務模板:定義標準業務處理流程、標準化異常處理
- 接口文檔化:定義好接口后,通過注解動態生成接口文檔
2. 針對http服務的約定
a)接口參數:command、校驗器、參數類型配置化。
command中定義接口信息,包括請求返回參數、每個參數的參數類型、參數的校驗器、參數類型的校驗器。校驗器可以組合使用,也可以自定義實現擴展。如下示例:
Command定義: <commands> <command name="forex_queryExchangeRate"> <cnName>匯率查詢接口</cnName> <version>20150808</version> <desc>查詢本幣和目標幣種匯率</desc> <request> <param name="localCurrType" required="true"> <validator id="CURID"/> </param> <param name="targetCurrType" required="true"> <validator id="CURID"/> </param> </request> <!-- 返回參數部分 --> <response> <param name="localCurrType"> <cnName>本幣</cnName> <required>true</required> </param> <param name="targetCurrType"> <cnName>目標幣種</cnName> <required>true</required> </param> <param name="sellingPrice"> <cnName>賣出價</cnName> <required>true</required> </param> <param name="buyingPrice"> <cnName>購買價</cnName> <required>true</required> </param> <param name="rateTime"> <cnName>匯率時間</cnName> <required>true</required> </param> </response> </command> </commands> 校驗器: <validators> <validator id="CURID" type="Regex"> <pattern>^[A-Z]{3}$</pattern> </validator> </validators> 參數類型: <paramTypes> <paramType name="merchantCode"> <cnName>商戶號</cnName> <desc>用來區分不同商戶</desc> <type>java.lang.String</type> <example>testbgd</example> <validator type="Regex"> <pattern>^[A-Za-z0-9]{1,20}$</pattern> </validator> </paramType> </paramTypes>
b)并發控制
在某些操作場景下,對于并發寫會有一些問題,此時可以通過依賴cache加鎖來控制。比如通過在接口增加注解來啟用。可以指定接口參數來作為鎖的lockKey ,指定鎖失效時間和重試次數,并指定異常時(lockGotExIgnore )的處理方案。
@RequestLock(lockKeyPrefix = "combdaikoupay:", lockKey = "${parentMerchantCode}_${parentTradeNo}", lockKeyParamMustExists = true, lockKeyExpireSecs = 5, lockUsedRetryTimes = 0, lockUsedRetryLockIntervalMills = 500, lockGotExIgnore = false)
c)流量控制
流控目前分兩種:qps、并行數。
qps分為節點、集群、接口節點、接口集群。通過對每秒中的請求計數進行控制,大于預設閥值(可動態調整)則拒絕訪問同時減少計數,否則通過不減少計數。
行數主要是為了解決請求橫跨多秒的情況。此時qps滿足條件但整體的訪問量在遞增,對系統的吞吐量造成影響。大于預設閥值(可動態調整)則拒絕訪問。每次請求結束減少計數
d)安全校驗
接口權限:對接口的訪問權限進行統一管理和驗證,粒度控制到訪問者、被訪問系統、接口、版本號
接口簽名:避免接口參數在傳遞過程中發生串改
e)統一監控
包括接口計數、響應時長和錯誤碼統計三個維度
f)接口文檔化
依賴前面command、校驗器、參數類型配置進行解析生成
2.2.2 如何管理多個系統?
- 接口監控模板化:http、Dubbo多系統統一模板,集中展示管理。
- 組件可監控化:Redis/Memcache、Mybatis 、Lock 、QMQ 、 EventBus 、DataSource 、JobScheduler
- 監控面板自動化生成:Python自動化生成腳本,新創建系統只需要提供系統名稱和面板配置節點即可生成標準監控面板
- 系統硬件資源、tomcat、業務關鍵指標可視化監控
2.2.3 如何高效日常運營?
對于各個場景的關鍵流程進行格式化日志輸出,集中收集處理。如orderLog、userLog、cardLog、binlog、busilog、tracelog、pagelog...
2.3 服務化拆分過程中DB處理
2.3.1 分表
隨著業務量增加,單表數據量過大,操作壓力大。因此分表勢在必行。常用的分表策略如按照時間來分表,如月表,季表,按照某個key來hash分表,也可以將兩種結合起來使用。分表的好處是可方便將歷史數據進行遷移,減少在線數據量,分散單表壓力。
2.3.2 分庫、多實例
多庫單實例,多業務單庫。部分業務存在問題會影響全局,從而會拖垮整個集群。因此在業務系統拆分后,db的拆分也是重要的一個環節。舉一個例支付庫拆分的例子。支付交易的表都在同一個庫中,由于磁盤容量問題和業務已經拆分,因此決定進行拆庫。穩妥起見,我們采用保守方案,先對目前實例做一個從庫,然后給需要拆分出來的庫創建一個新的用戶U,切換時先收回U的寫權限,然后等待主從同步完成,,確定相關表沒有寫入后將U切到新的實例上。然后刪除各自庫中無關的表。
2.3.3 讀寫分離、讀負載均衡
很多業務讀多寫少,使用MMM結構,基本上只有一臺在工作,不僅資源閑置且不利于整體集群的穩定性。引入讀寫分離、讀負責均衡策略。有效使用硬件資源,且降低每臺服務器壓力。
a)讀寫負載均衡
b)多動態源
c)多庫動態源讀負載均衡
2.4 異步化使用
- servlet3異步:釋放出http線程提高系統整體吞吐量,可隔離開不同業務的工作線程
- qmq:使用最廣泛也更靈活的異步
- dubbo:對于服務提供者響應比較慢的情況
servlet異步和qmq結合的場景如下圖所示。流程為http服務接到組合扣款請求,然后向后端交易系統下單并發起扣款,此時http服務進入輪詢等待,根據輪詢間隔定時發起對放在cache中的扣款結果查詢。交易系統則根據扣款規則以qmq的方式驅動扣款,直至走完所有流程為止(成功,失敗,部分支付)。每次扣款結束將結果放入cache中供http服務查詢。
輪詢式場景如上圖中使用,關鍵在于確定輪詢間隔
2.5 監控&報警
2.5.1 Java監控模塊
嵌入在應用中,指標數據可靈活配置發送方式到多個地方。也支持api接口直接拉取數據
2.5.2 離線監控框架
- python監控腳本框架,從db、java模塊api、redis等獲取數據,計算指標并發送
- 整體架構可插件化、有通用標準功能、也可定制化開發
- 指標可直接推送至watcher(dashboard)系統添加監控頁
- 報警方式有mail、sms、qtalk
python監控腳本框架主要包含四個重要組件:
- metric_manager:指標管理器
- graphite_sender:指標推送
- Dbpool:數據庫鏈接池管理
- Scheduler:調度器,定時執行指標數據獲取
2.5.3 數據流系統
采用xflume、kafka、storm、hdfs、hbase、redis、hive對業務日志、binlog等實時收集并處理。提供業務日志、訂單生命周期日志、各種格式化日志的查詢和一些監控指標的計算存儲和報警。整體大致流程如下圖所示:
2.5.4 報警
業務和系統結構復雜后報警尤為重要。甄別哪些指標是必須報警的和報警閥值的確定是個很復雜的問題。一般有兩種情況:一種是明確認為不能出現的,另一種是需要一定計算來決定是否要報警。當然有些基礎層的服務出現問題,可能會導致連鎖反應,那么如何甄別最直接的問題來報警,避免亂報影響判斷是比較難的事情。目前針對這種情況系統會全報出來,然后人工基本判斷下,比如接口響應慢報警,此時又出現了DB慢查詢報警,那基本可以確認是DB的問題。
A、明確失敗報警
日志NPE、業務FAIL、系統ERROR、Access (4xx\5xx)、接口異常、dubbo超時、fullgc、DB慢查詢等
B、計算類報警
調用量特別小,波動明細,沒有連續性,不具有對比性
期望值:如下圖所示,當前值與期望值偏差加大
3. 總結
截止目前交易支付系統從收銀臺、交易、支付、網關、賬務、基礎服務、監控等各個模塊的拆分并獨立完善發展,針對高復雜業務和高并發訪問的支撐相比以前強大很多。但還有很多不足的地方有待提高和完善。
來自:http://www.infoq.com/cn/articles/evolution-of-qunar-payment-system-architecture-