支付路由系統
提到路由,打怪不免會想到網絡通信過程中起到數據包轉發的原創路由器。
而我們今天講到支付路由系統,從無到有也是支付之路起到類似的作用。路由系統本身并不處理具體業務,系統它的升級作用就是將支付請求轉發底層支付通道。
如上圖所示,打怪支付系統接入層,原創接收到支付請求之后,從無到有經過內部運算,支付之路最后將會通過路由系統轉發給具體底層的系統支付通道。
另外,除了路由轉發功能以外,路由器一般還會有一些額外的功能,比如防火墻等。
那我們其實也可以在支付路由系統加入額外功能,比如實時計算底層支付渠道的成功率,若低于一定的閾值,進行報警并且將該渠道下線。
這里需要說明一點,這里的路由系統可以是一個應用中子模塊,也可以是一個單獨子系統。
為什么需要路由系統
看到這里,可能會有一些小伙伴會思考,一定需要這個路由系統嗎?直接將請求發給支付通道不好嗎?
答案當然是可以的,如果當前只對接「一兩個支付渠道」,這么做沒問題,并且也推薦這么做。
這個階段由于業務量不大,支付系統可能只是一個單體應用,或者也可能是其他應用內一個子模塊而已。
那這個時候,業務很簡單,系統也很簡單,那我們不需要額外的路由系統,增加系統復雜度。
不過這樣的系統弊端也很明顯,如果后期再新增一個支付通道,我們需要再開發對接。
另外由于所有實現都在一個系統,假設系統應用發生問題,那么就是大家一起「死」,這其實也是單體系統最大弊端。
所以如果底層對接支付通道很多,像一般第三支付公司的支付系統,同一家銀行的可能會對接很多支付通道,比對銀聯無跳轉支付,網聯支付,也有可能是 XX 行自己提供的接口。
那么這種情況就非常需要單獨維護一個路由系統。
PS:630 政策之前,支付公司對接支付通道那真是一個多,同一家銀行,可能會對接四五個通道。
但是 630政策之后, 不允許支付機構直接對接銀行。
所以現在支付機構對接通道可能會比之前少很多。
實現方式
路由系統實現方式有很多,下面主要分享一下我所經歷過實現方案。
我們的路由系統經歷過三個階段的迭代,才有了現在的實現方案。
第一個階段-混沌初開
這個階段就跟上面講的場景一樣,業務需求較簡單,僅僅只需要對接一兩個支付通道。
為了快速上線,設計方案就簡單粗暴,所有業務都處于一個應用。
支付子模塊對內暴露暴露支付服務接口,由業務系統發起直接調用。
系統設計圖如下:
這個階段由于只有一個支付渠道,所以也不需要有路由系統,直接由業務系統調用支付服務接口發起支付。
不過隨著業務量增大之后,這個設計方案就暴露很多問題:
業務系統與支付系統位于同一個系統,系統任何一次變更都會影響整個系統。 擴展性問題。每接入一個新的支付通道如微信,就需要增加一個新的實現類。另外,業務系統的代碼同時也需要改動,需要調用新的實現類。
針對以上問題,我們進行第一版的改造。
首先我們將整個支付模塊從原來應用中拆分出來,成為一個獨立的子系統,專門運行支付業務。
這個系統對外提供一組支付接口,業務系統只需要調用這個接口,傳入必要的參數,無需關心支付系統到底是如何實現的。
如果業務系統想指定某個支付通道,比如支付寶,那么可以在接口傳入這個渠道標識,支付系統將會根據這個渠道標識調用相應的支付通道。
其次梳理渠道接口文檔,抽象出共性接口,每個支付通道實現都需要繼承這個接口。
這組通用渠道接口,其中 「channelName」方法,代表這個實現類具體代表哪個通道。比如說這個方法返回 「aliPay」,那么就代表這個實現類將會調用支付寶通道。
支付系統子應用中將會維護一個類似路由表,這里簡單使用 Map
存儲映射關系,「key」為上文提到的渠道唯一應用標識,而 「value」為具體的實現類。
應用初始化之后,將會調用 「Spring ApplicationContext getBeansOfType」方法,獲取同一個接口的所有實現類 ,最后將其放入 Map 緩存中。
每次支付調用都會根據渠道唯一標識從路由表獲取具體實現類,然后由具體的子類實現支付邏輯。
學過設計模式的同學,這里應該不會陌生,這其實是使用設計模式中的策略模式。
這個階段,由于業務還不是很復雜,系統還是挺簡單,路由系統還只是系統中的一個子模塊。
第二個階段-神功初成
經歷過一段時間,公司的業務量變的越來越大,這個階段我們開始追求系統的穩定性。
但是在第一階段設計方案中,支付系統所有模塊位于同一工程。有些模塊可能需要頻繁發布,這樣一旦發布就會影響所有系統功能。
第二點,系統功能全都耦合在一起,團隊開發也變的困難,分支沖突,代碼丟失也是經常的事。
第三點,一旦某些改動發布發生問題,整個系統都受到影響,真的是「要死一起死」。
所以這個階段針對以上的問題,我們進行了相應改造,開始將支付系統進行拆分。
首先按照功能,將支付系統拆分幾個獨立的子系統,路由系統,渠道系統,成為獨立系統,獨立部署維護。
每個支付通道單獨維護部署,成為一個單獨的子應用。
系統之間的調用關系,就從同一進程內調用,變成使用 「RPC」進行跨進程調用。
這個時候就會有個問題,渠道系統可能會因為發布而下線/上線,這時路由系統必須動態維護這種關系,在渠道系統某一節點下線時,自動刪除調用關系,而當應用上線時,新增調用關系。
「說白了,路由系統需要實現渠道服務動態發現?!?/strong>
看到這里不要怕,其實 Dubbo 框架已經自帶這個功能,我們沒必要自己再去實現了。
Dubbo 配置中有一個屬性-「group」,這個屬性可以用于服務分組。
當同一個接口有多個實現,我們就可以根據這個來區分不同渠道系統的實現。
因為渠道系統實現同一組接口之后,提供出 Dubbo 服務需要加上相應的 group 屬性,值為相應的渠道唯一標識。
如下所示:
路由系統只要引入這個 Dubbo 服務,設置相應的 「group」屬性 ,路由系統引用渠道系統的服務:
此時路由系統就跟第一階段一樣,內部維護一個路由表就好了。
這里采用了 XML 配置存儲渠道標識與 Dubbo 引用服務的映射關系,如下所示:
服務啟動之后解析這個 XML 文件,然后將其維護在 Map 中。每次支付調用都會根據渠道唯一標識從路由表獲取服務名,然后借助 「Spring ApplicationContext#getBean」獲取具體的 Dubbo 引用服務。
后續如果再新增渠道系統,路由系統不需要再修改任何代碼,只要在配置文件中新增 Dubbo 服務引用以及增加路由表引用關系即可。
第三階段-登峰造極
第二個階段路由系統基本上已經滿足現有階段業務實用,不過還是存在個問題,渠道應用新增時,還需要新增配置「重啟應用」。
之前有一次新增渠道,忘記了在路由系統新增配置,從而導致新的渠道應用無法被調用,找了很久的問題,才發現是這個問題。
所以第三階段,主要是優化路由系統,去掉上述配置文件,到達新增渠道應用,而不用重啟路由系統。
這個階段的改造,我們不再使用 XML 配置引用服務,而是借助 「Dubbo API」,動態引用 Dubbo 服務。
查看 ?Dubbo 文檔 ,可以直接使用 ReferenceConfig 直接查找服務提供者。
官方文檔建議:
ReferenceConfig 實例很重,封裝了與注冊中心的連接以及與提供者的連接,需要緩存。否則重復生成 ReferenceConfig 可能造成性能問題并且會有內存和連接泄漏。在 API 方式編程時,容易忽略此問題。
這里使用ReferenceConfigCache,用于緩存 「ReferenceConfig」實例。
改造之后,去除之前所有引用服務配置文件以及緩存注冊代碼,不用再使用 Map
存儲路由的映射關系。改造如下:
總結
回顧上文,可以看到初期沒有路由系統,整個系統可以運行下去。
但是隨著業務量不斷變大變復雜,最開始的系統架構已經不能適應當前的環境,所以我們才開始系統拆分,進行微服務改造,一步步改進系統。
改進的過程中,不斷發現方案不足處,然后一步步迭代演進。這個過程中,要善于利用現有框架的功能,加速功能的開發。
最后,本文給出了幾種不同階段路由系統實現方式,適合不同階段、不同類型的系統。
如果各位同學剛好也有類似需要,可以根據自己系統的情況借鑒參考。
特別推薦一個分享架構+算法的優質內容,還沒關注的小伙伴,可以長按關注一下:
長按訂閱更多精彩▼
如有收獲,點個在看,誠摯感謝
免責聲明:本文內容由21ic獲得授權后發布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯系我們,謝謝!