一、能用階段
早期業(yè)務(wù)流量還不是很大,渠道網(wǎng)關(guān)系統(tǒng)業(yè)務(wù)邏輯也很簡單,一句話總結(jié)就是:讓用戶在交易的時候,能順利把錢給付了。做的事情可簡單概括成3件:發(fā)起支付請求、接收支付成功通知以及用戶要求退款時原路退回給用戶的支付賬戶。這個階段系統(tǒng)實踐比較簡單,主要就是“短、平、快”,快速接入新的第三方支付渠道并保證能用。系統(tǒng)架構(gòu)如圖1。
二、可用階段
在系統(tǒng)演進初期的快速迭代過程中,接入的第三方支付渠道不多,系統(tǒng)運行還算比較平穩(wěn),一些簡單問題也可通過開發(fā)人員人工快速解決。但隨著接入的第三方支付渠道不斷增多,逐漸暴露出一些新的問題:
(1) 所有的業(yè)務(wù)邏輯都在同一個物理部署單元,不同業(yè)務(wù)之間互相影響(例如退款業(yè)務(wù)出現(xiàn)問題,但是與此同時把支付業(yè)務(wù)也拖垮了);
(2) 隨著業(yè)務(wù)流量的增大,數(shù)據(jù)庫的壓力逐漸增大,數(shù)據(jù)庫的偶爾波動造成系統(tǒng)不穩(wěn)定,對用戶的支付體驗影響很大;
(3) 支付、退款等狀態(tài)的同步很大程度上依賴第三方支付渠道的異步通知,一旦第三方支付渠道出現(xiàn)問題,造成大量客訴,用戶體驗很差,開發(fā)、運營都很被動。
針對(1)中的業(yè)務(wù)之間互相影響問題,我們首先考慮進行服務(wù)拆分,將之前一個大的物理部署單元拆成多個物理部署單元。有兩種明顯的可供選擇的拆分策略:
按照渠道拆分,不同的第三方支付渠道獨立一個物理部署單元,例如微信一個部署單元,支付寶一個部署單元等;
按照業(yè)務(wù)類型拆分,不同的業(yè)務(wù)獨立一個物理部署單元,例如支付業(yè)務(wù)一個部署單元,退款業(yè)務(wù)一個部署單元等。
考慮到在當(dāng)時的流量規(guī)模下,支付業(yè)務(wù)優(yōu)先級最高,退款等業(yè)務(wù)的優(yōu)先級要低;而有些渠道的流量占比很小,作為一個獨立的部署單元,會造成一定的資源浪費,且增加了系統(tǒng)維護的復(fù)雜度?;诖耍覀冏隽艘粋€符合當(dāng)時系統(tǒng)規(guī)模的trade-off:選擇了第2種拆分策略 — 按照業(yè)務(wù)類型拆分。
針對(2)中的DB壓力問題,我們和DBA一起分析原因,最終選擇了Master-Slave方案。通過增加Slave來緩解查詢壓力;通過強制走Master來保證業(yè)務(wù)場景的強一致性;通過公司的DB中間件Zebra來做負載均衡和災(zāi)備切換,保證DB的高可用性。
針對(3)中的狀態(tài)同步問題,我們對不同渠道進行梳理,在已有的第三方支付渠道異步通知的基礎(chǔ)上,通過主動查詢定時批量同步狀態(tài),解決了絕大部分狀態(tài)同步問題。對于仍未同步的少量Case,系統(tǒng)開放出供內(nèi)部使用的API,方便后臺接入和開發(fā)人員手動補單。
在完成上述的實踐之后,渠道網(wǎng)關(guān)系統(tǒng)已達到基本可用階段,通過內(nèi)部監(jiān)控平臺可以看到,核心服務(wù)接口可用性都能達到99.9%以上。演化之后的系統(tǒng)架構(gòu)如圖2。
三、柔性可用階段
在解決了業(yè)務(wù)隔離、DB壓力、狀態(tài)同步等問題后,渠道網(wǎng)關(guān)系統(tǒng)度過一段穩(wěn)定可用的時期。但架不住業(yè)務(wù)飛速增長的壓力,之前業(yè)務(wù)流量規(guī)模下的一些小的系統(tǒng)波動、流量沖擊等異常,在遭遇流量洪峰時被急劇放大,最終可能成為壓垮系統(tǒng)的最后一根稻草。在新的業(yè)務(wù)流量規(guī)模下,我們面臨著新的挑戰(zhàn):
(1) 隨著團隊的壯大,新加入的同學(xué)在接入新的渠道或者增加新的邏輯時,往往都會優(yōu)先選用自己熟悉的方式完成任務(wù)。但熟悉的不一定是合理的,有可能會引入新的風(fēng)險。特別是在與第三方渠道對接時,系統(tǒng)目前在使用的HTTP交互框架就有 JDK HttpURLConnection/HttpsURLConnection、Httpclient3.x、Httpclient4.x(4.x版本內(nèi)部還分別有使用不同的小版本)。僅在這個上面就踩過好幾次慘痛的坑。
(2) 在按業(yè)務(wù)類型進行服務(wù)拆分后,不同業(yè)務(wù)不再互相影響。但同一業(yè)務(wù)內(nèi)部,之前流量規(guī)模小的時候,偶爾波動一次影響不大,現(xiàn)在流量增大后,不同渠道之間就開始互相影響。例如支付業(yè)務(wù),對外統(tǒng)一提供分布式的支付API,所有渠道共享同一個服務(wù)RPC連接池,一旦某一個渠道的支付接口性能惡化,導(dǎo)致大量占用服務(wù)RPC連接,其他正常渠道的請求都無法進來;而故障渠道性能惡化直接導(dǎo)致用戶無法通過該渠道支付成功,連鎖反應(yīng)導(dǎo)致用戶多次重試,從而進一步導(dǎo)致惡化加劇,最終引起系統(tǒng)雪崩,拒絕服務(wù),且重啟后的服務(wù)還有可能被大量的故障渠道重試請求給再次擊垮。
(3) 目前接入的第三方支付渠道,無論是第三方支付公司、銀行或是其他外部支付機構(gòu),基本都是通過重定向或SDK的方式引導(dǎo)用戶完成最終支付動作。在這條支付鏈路中,渠道網(wǎng)關(guān)系統(tǒng)只是在后端與第三方支付渠道進行交互(生成支付重定向URL或預(yù)支付憑證),且只能通過第三方支付渠道的異步通知或自己主動進行支付查詢才能得知最終用戶支付結(jié)果。一旦某個第三方支付渠道內(nèi)部發(fā)生故障,渠道網(wǎng)關(guān)系統(tǒng)完全無法得知該支付鏈路已損壞,這對用戶支付體驗造成損害。
(4) 現(xiàn)有的渠道網(wǎng)關(guān)的DB,某些非渠道網(wǎng)關(guān)服務(wù)仍可直接訪問,這對渠道網(wǎng)關(guān)系統(tǒng)的DB穩(wěn)定性、DB容量規(guī)劃等帶來風(fēng)險,進而影響渠道網(wǎng)關(guān)系統(tǒng)的可用性,內(nèi)部戲稱被戴了“綠帽子”。
(5) 對于退款鏈路,系統(tǒng)目前未針對退款異常case進行統(tǒng)一收集、整理并分類,且缺乏一個清晰的退款鏈路監(jiān)控。這導(dǎo)致用戶申請退款后,少量用戶的退款請求最終未處理成功,用戶發(fā)起客訴。同時由于缺乏監(jiān)控,導(dǎo)致這種異常退款缺乏一個后續(xù)推進措施,極端情形下,引起用戶二次客訴,極大損害用戶體驗和公司信譽度。
為最大程度解決問題(1)中描述的風(fēng)險,在吸取踩坑的慘痛教訓(xùn)后,我們針對第三方渠道對接,收集并整理不同的應(yīng)用場景,抽象出一套接入框架。接入框架定義了請求組裝、請求執(zhí)行、響應(yīng)解析和錯誤重試這一整套網(wǎng)關(guān)交互流程,屏蔽了底層的HTTP或Socket交互細節(jié),并提供相應(yīng)的擴展點。針對銀行渠道接入存在前置機這種特殊的應(yīng)用場景,還基于Netty抽象出連接池(Conn Pool)和簡單的負載均衡機制(LB, 提供Round Robin路由策略)。不同渠道在接入時可插入自定義的組裝策略(擴展已有的HttpReq、HttpsReq或NettyReq),執(zhí)行策略[擴展已有(Http、Https或Netty)Sender/Receiver],解析策略(擴展已有的HttpResp、HttpsResp或NettyResp),并復(fù)用框架已提供的內(nèi)容解析(binary/xml/json parser)、證書加載(keystore/truststore loader)和加解密簽名(encrypt/decrypt/sign/verify sign)組件,從而在達到提高渠道接入效率的同時,盡可能減少新渠道接入帶來的風(fēng)險。接入框架的流程結(jié)構(gòu)如圖3。
為解決問題(2)中渠道之間相互影響,一個簡單直觀的思路就是渠道隔離。如何隔離,隔離到什么程度?這是2個主要的問題點:
如何隔離 考慮過將支付服務(wù)進一步按照渠道拆分,將系統(tǒng)繼續(xù)做小,但是拆分后,支付API的調(diào)用端需要區(qū)分不同渠道調(diào)用不同的支付API接口,這相當(dāng)于將渠道隔離問題拋給了調(diào)用端;同時拆分后服務(wù)增多,調(diào)用端需要維護同一渠道支付業(yè)務(wù)的多個不同RPC-API,復(fù)雜度提高,增加了開發(fā)人員的維護負擔(dān),這在當(dāng)前的業(yè)務(wù)流量規(guī)模下不太可取。所以我們選擇了在同一個支付服務(wù)API內(nèi)部進行渠道隔離。由于共用同一個支付服務(wù)服務(wù)API連接池,渠道隔離的首要目標(biāo)就是避免故障渠道大量占用AP連接池,對其他正常渠道造成株連影響。如果能夠自動檢測出故障渠道,并在其發(fā)生故障的初期階段就快速失敗該故障渠道的請求,則從業(yè)務(wù)邏輯上就自動完成了故障渠道的隔離。
隔離到什么程度 一個支付渠道下存在不同的支付方式(信用卡支付、借記卡支付、余額支付等),而有些支付方式(例如信用卡支付)還存在多個銀行。所以我們直接將渠道隔離的最小粒度定義到支付渠道 -> 支付方式 -> 銀行。
基于上述的思考,我們設(shè)計并實現(xiàn)了一個針對故障渠道的快速失?。╢ail-fast)機制:
將每一筆支付請求所附帶的支付信息抽象為一個特定的fail-fast路徑,請求抽象成一個fail-fast事務(wù),請求成功即認為事務(wù)成功,反之,事務(wù)失敗。
在fail-fast事務(wù)執(zhí)行過程中,級聯(lián)有2個fail-fast斷路開關(guān):
靜態(tài)開關(guān),根據(jù)人工配置(on/off),斷定某個支付請求是否需快速失敗。
動態(tài)開關(guān),根據(jù)歷史統(tǒng)計信息,確定當(dāng)前健康狀態(tài),進而斷定是否快速失敗當(dāng)前支付請求。
動態(tài)斷路開關(guān)抽象了3種健康狀態(tài)(closed-放行所有請求;half_open-部分比例的請求放行;open-快速失敗所有請求),并依據(jù)歷史統(tǒng)計信息(總請求量/請求失敗量/請求異常量/請求超時量),在其內(nèi)部維護了一個健康狀態(tài)變遷的狀態(tài)機。狀態(tài)變遷如圖4。
狀態(tài)機的每一次狀態(tài)變遷都會產(chǎn)生一個健康狀態(tài)事件,收銀臺服務(wù)可以監(jiān)聽這個健康狀態(tài)事件,實現(xiàn)支付渠道的聯(lián)動上下線切換。
每一筆支付請求結(jié)束后都會動態(tài)更新歷史統(tǒng)計信息。
經(jīng)過線上流量模擬壓測觀察,fail-fast機制給系統(tǒng)支付請求增加了1~5ms的額外耗時,相比第三方渠道的支付接口耗時,占比1%~2%,屬于可控范圍。渠道故障fail-fast機制上線之后,結(jié)合壓測配置,經(jīng)過幾次微調(diào),穩(wěn)定了線上環(huán)境的fail-fast配置參數(shù)。
在前不久的某渠道支付故障時,通過公司內(nèi)部的監(jiān)控平臺,明顯觀察到fail-fast機制起到很好的故障隔離效果,如下圖5。
為解決問題(3)中支付鏈路可用性監(jiān)測,依賴公司內(nèi)部的監(jiān)控平臺上報,實時監(jiān)控支付成功通知趨勢曲線;同時渠道網(wǎng)關(guān)系統(tǒng)內(nèi)部從業(yè)務(wù)層面自行實現(xiàn)了支付鏈路端到端的監(jiān)控。秒級監(jiān)控支付鏈路端到端支付成功總量及支付成功率,并基于這2個指標(biāo)的歷史統(tǒng)計信息,提供實時的支付鏈路郵件或短信報警。而在流量高峰時,該監(jiān)控還可通過人工手動降級(異步化或關(guān)閉)。這在很大程度上提高了開發(fā)人員的核心支付鏈路故障響應(yīng)速度。
為解決問題(4)中的“綠帽子”,渠道網(wǎng)關(guān)系統(tǒng)配合DBA回收所有外部系統(tǒng)的DB直接訪問權(quán)限,提供替換的API以供外部系統(tǒng)訪問,這給后續(xù)的提升DB穩(wěn)定性、DB容量規(guī)劃以及后續(xù)可能的異步多機房部署打下基礎(chǔ)。
針對問題(5)中退款case,渠道網(wǎng)關(guān)系統(tǒng)配合退款鏈路上的其他交易、支付系統(tǒng),從源頭上對第三方渠道退款異常case進行統(tǒng)一收集、整理并分類,并形成退款鏈路核心指標(biāo)(退款當(dāng)日成功率/次日成功率/7日成功率)監(jiān)控,該部分的系統(tǒng)實踐會隨著后續(xù)的“退款鏈路統(tǒng)一優(yōu)化”一起進行分享;
隨著上述實踐的逐步完成,渠道網(wǎng)關(guān)系統(tǒng)的可用性得到顯著提高,核心鏈路的API接口可用性達到99.99%,在公司的917大促中,渠道網(wǎng)關(guān)系統(tǒng)平穩(wěn)度過流量高峰,并迎來了新的記錄:提交第三方渠道支付請求的TPS達到歷史新高。且在部分渠道接口發(fā)生故障時,能保證核心支付API接口的穩(wěn)定性,并做到故障渠道的自動檢測、恢復(fù),實現(xiàn)收銀臺對應(yīng)渠道的聯(lián)動上下線切換。同時,通過核心支付鏈路支付成功率監(jiān)控,實現(xiàn)第三方渠道內(nèi)部故障時,渠道上下線的手動切換。至此,基本保證了在部分第三方渠道有損的情況下,渠道網(wǎng)關(guān)系統(tǒng)的柔性可用。演化后的此階段系統(tǒng)架構(gòu)如圖6。
四、經(jīng)驗與總結(jié)
在整個渠道網(wǎng)關(guān)系統(tǒng)一步步的完善過程中,踩過很多坑,吃過很多教訓(xùn),幾點小的收獲:
1.堅持核心思想,拆分、解耦,大系統(tǒng)做小,做簡單;
2.系統(tǒng)總會有出問題的時候,重要的是如何快速定位、恢復(fù)、解決問題,這是一個長期而又艱巨的任務(wù);
3.高可用性的最大敵人不僅是技術(shù),還是使用技術(shù)實現(xiàn)系統(tǒng)的人,如何在業(yè)務(wù)、系統(tǒng)快速迭代的過程中,保證自我驅(qū)動,不掉隊;
4.高流量,大并發(fā)對每一個工程師既是挑戰(zhàn),更是機遇。