支付系統一般需要對接多個支付渠道,一是為了保證系統的可靠性,不能因為單一渠道的問題影響整個支付系統。二是為了提高支付能力,不同渠道提供支付能力不同。三是為了降低支付成本。
對接多個支付渠道以後,為了可以正確選擇支付渠道支付,因此設計渠道路由系統。
從上圖可以看到路由系統功能其實很簡單,分發支付請求到正確的渠道。但就是這個簡單系統,也經過幾次系統改造升級,最終才成為現在的樣子。下面就來說說這個系統是如何演進。
下面假設對接支付渠道為支付寶與微信。
初期
支付系統初期,這個階段業務需求較簡單,僅僅需要滿足一個支付場景(例如使用支付寶支付)。為了快速上線,設計方案就簡單粗暴,對外直接暴露支付服務介面,由業務系統發起直接呼叫。
系統設計圖如下:
這個階段由於只有一個支付渠道,所以也不需要有路由系統,直接由業務系統呼叫支付服務介面發起支付。
這個設計方案存在很多問題:
- 業務系統與支付系統位於同一個系統,系統任何一次變更都會影響整個系統。
- 擴充套件性問題。接入新支付渠道,如微信,需要新暴露一個微信支付服務介面。業務系統需要改動程式碼。從另一方面講,業務系統承擔路由系統的功能。
- 複用性。新支付渠道,其實除了與支付渠道互動相關程式碼之外,其他程式碼可以複用。
針對以上問題,將系統進行了相應改造。
首先是將支付系統與業務系統單獨拆分出來,成為兩套單獨的系統。支付系統對外暴露一組通用介面。業務系統僅對接這組介面。業務系統若想指定支付渠道支付,介面引數傳入渠道標識即可。這樣就將耦合在業務系統中路由功下沉到支付系統。
其次梳理渠道介面文件,抽象出共性介面。接入新支付渠道,只要繼承介面,實現相關方法即可,簡化渠道開發難度。
改版後的系統實現圖如下:
此時,路由系統知識支付系統的一個模組,具體實現如下。
首先定義通用渠道介面,其中 channelName 方法,返回渠道渠道唯一標識,如支付寶渠道返回 aliPay。
然後根據 Spring ApplicationContext getBeansOfType 方法,獲取實現同一個介面的所有 Bean.最後將其放入 Map 快取中,其中鍵值為 channelName 方法返回渠道標識。
這個階段方案的問題在於支付系統所有模組位於同一工程。有些模組需要頻繁釋出,而有些模組,如渠道模組,路由模組改動就很少。這樣就導致系統任一改動釋出,影響整個支付系統可用性。
中期
針對初期後面的問題,進行了相應改造。
首先還是進行拆分,將支付系統按照模組拆分。路由系統,渠道系統,成為獨立系統,獨立部署維護。
系統之間呼叫採用 RPC 通訊,使用 Dubbo 框架。
相關實現如下:
相關介面邏輯不變,只是將同一程式內呼叫變成跨系統的呼叫。
渠道系統提供服務:
這裡改動,將渠道標識放入 Dubbo 服務 group 欄位,藉助 Dubbo 分組功能標識中唯一的渠道系統。
路由系統引用渠道系統的服務:
這裡同樣需要設定 group 且需要和服務提供者一致。然後在路由系統中將服務註冊到快取中,使用渠道標識為 key,渠道服務名為 value。
最後路由系統藉助 Spring ApplicationContext getBean 獲取具體的服務。
這個設計的問題在於:
路由系統中需要手動引用渠道系統服務,然後再註冊。這樣在增加渠道系統就比較繁瑣。那是不是可以做到增加渠道系統時,無需修改路由系統,路由系統自動發現服務?
藉助 Dubbo API。
後期
檢視 Dubbo 文件,可以直接使用 ReferenceConfig 直接查詢服務提供者。
官方文件建議:
ReferenceConfig 例項很重,封裝了與註冊中心的連線以及與提供者的連線,需要快取。否則重複生成 ReferenceConfig 可能造成效能問題並且會有記憶體和連線洩漏。在 API 方式程式設計時,容易忽略此問題。
這裡使用ReferenceConfigCache,用於快取 ReferenceConfig 例項。
去除之前所有引用服務配置檔案以及快取註冊程式碼,引入 ReferenceConfigCache,改造如下。
總結
回顧上文路由系統,可以看到初期沒有路由系統,整個系統可以執行下去。但是隨著系統複雜度提高,初期系統架構已經不能滿足系統的高效執行,所以才一步步改進系統。改進的過程中,不斷發現方案不足處,然後一步步迭代演進。這個過程中,要善於利用現有框架的功能,加速功能的開發。