黃 晨,柏路平
(中移信息技術有限公司,深圳 518048)
Spring Boot 框架[1,2]因其簡潔的配置、內嵌的容器、自動的依賴管理、組件化的功能配置,能夠讓開發者快速地搭建項目、編碼與新增功能,加快整個系統開發流程.以統一支付系統應用為背景,系統開發使用了Spring Boot 框架,快速支撐了支付業務上線.然而隨著業務的發展,接入統一支付系統的外部商戶數量不斷增加、業務種類、業務吞吐量、需求功能都在不斷的增長,現有的開發部署架構在應用頻繁迭代上線、故障定位方面支持度較低,也比較難以適應業務量月初、節假日等波動性增長.
微服務[3,4]是將一個系統拆分成許多較小的、松散耦合的組件和服務的架構,具有組件共享、故障可控、彈性擴展、易于部署等優點,近年來,隨著云平臺技術與容器管理技術的成熟,基于微服務框架來構建大型應用系統成為互聯網行業的趨勢.為解決支付系統Spring Boot 架構現存的問題,開發人員同步開發了Spring Cloud 微服務版本并準備上線.變更如此大的版本上線前必然要經過充分的調試,并且之前的Spring Boot 架構的支付系統也還要存留一段時間以備出現意外情況時可以回退,因此聯調環境需同時使用這兩種架構的系統應用供對外商戶調用并進行聯調驗證.
圖1是統一支付系統聯調環境部署示意圖.在DMZ 域[5,6]部署一臺Nginx,同時開通443和8080 兩個端口的公網接入口,與統一支付聯調系統提供的兩大類業務相對應,通過后端的http 請求接入API 業務(對外8080 端口),通過前臺的https 請求接入的H5 業務(對外443 端口),也有部分商戶通過https 對接API 業務,由于H5與API 接口處理支付請求的流程不同,將它們開發成不同的應用組件,并以不同的請求path 加以區分.

圖1 Spring Boot 版本的統一支付系統聯調部署
為了能夠保證新開發的Spring Cloud 架構應用的質量,有必要將其部署在聯調環境,經過外部商戶的測試報文來驗證其所有功能.Spring Cloud 架構的系統如何與原來的Boot 架構的系統在聯調環境并存需要考慮如下問題:首先,Spring Cloud 版本是統一支付系統內部的優化版本,整個對外聯調過程應該對外部商戶透明,即不應讓外部商戶感知到統一支付系統是調測的哪個架構的應用;其次為保證內部系統安全,應盡量減少公網暴露面,還有DMZ 域的機器資源使用已經飽和,新部署的Cloud 支付系統只能跟其他系統的應用共用機器.
在Nginx 作為公網接入口,并充分考慮上述限制問題的基礎上,本文提出一種基于Nginx-F5[7-9]的雙架構(大版本)應用的并行方案,既不增加對外聯調的端口又可以使兩種Boot和Cloud 架構的應用得到高效的調測,如圖2所示.

圖2 基于Nginx-F5的雙架構應用系統并行方案
與之前的部署架構圖相比,除了增加Cloud 架構的應用外,還新增了一套F5 負載均衡器.該F5是一個操作系統模擬器,需要單獨一臺虛擬機來安裝F5的操作系統,由于DMZ 域機器資源有限,原來的Nginx 宿主機還安裝有很多其他的應用,不能將其鏟除,F5 模擬器可以安裝在機器資源充足的內網核心域.將F5 模擬器部署于原來的Nginx 之后,支付聯調應用之前.主要使用F5的7 層轉發功能,細粒度地控制外部商戶的模擬報文在Boot 系統與Cloud 系統之間切換,在不影響線上Boot 架構系統不停迭代新需求的同時,可以隨時驗證新的Cloud 架構系統的基本功能.
如不使用F5 模擬器,直接使用原來的Nginx的添加Lua 腳本模塊或者使用OpenResty 版本的Nginx,也能實現根據報文體中的某個屬性標識轉發請求,但由于Nginx 流量轉發方式的變更需要修改配置文件或者加載的Lua 腳本并且重載才能生效,這個重載機制并不是每次配置變更都會生效,并且沒生效也沒有提示.這時只能強制殺掉Nginx的主進程后,重新啟動使變更的配置生效.而且Nginx的健康檢查機制是通過將后端服務器成員狀態的檢查結果打印到日志文件反映,當有后端成員掛掉也不能及時發現.而F5 模擬器具有可視化的Web 操作界面,后端服務器成員的啟停狀態能夠很直觀地看到,也很方便將后端成員的狀態設置開啟與關閉,其提供的負載規則irule[10],可以自定義各種基于報文頭和報文體的流量轉發策略,并且各種變更都是實時生效,且不會使正在活動的連接斷開.
因為生產環境的Boot 架構的應用還在不斷的迭代新需求,而新的Cloud 架構的應用也需要調測基本的功能,并且在某個時刻追加Boot 架構新迭代的全部需求,本文基于Nginx-F5的并行聯調架構在下面的F5 加載irule 規則的流量切換方法下,能夠在不改變原有的業務入口的條件下,提高兩種架構應用的聯調效率,更好地支撐生產系統的不斷升級優化.
圖2的雙架構應用系統并行方案中,F5 模擬器開啟443和8080 兩個端口的虛擬服務,分別作為原接入Nginx的443和8080 服務的各自的后端服務器.因為Nginx 在443 端口已經卸載了https 證書,發送到F5的443 端口的已經是http 協議的報文,所以在F5的443 端口的虛擬服務不需要再安裝證書.設置Nginx的443 端口的以https 服務進來的API 請求固定地轉發到F5的8080 端口,這樣經過F5的443 端口的業務就只有H5 類型,在F5的443 端口做7 層負載分發時就不用考慮API 類型的業務,簡化了F5的流量控制過程.
統一支付系統的外部商戶各自的業務特點不盡相同,因此有必要將某個或者某些商戶的業務請求分離出來,并單獨發送至不同后端應用系統.
因為外部商戶的發送的請求都是結構化的報文,即以XML和JSON 格式存在,XML 中是屬性及屬性值的集合,而JSON是鍵/值的集合,可以用屬性值或者鍵值的具體取值來代表一類商戶請求.因此可以通過F5的負載規則irule 編寫處理邏輯來改變請求分發方式.
irule是F5 功能強大的擴展組件,通過TCL (tool command language)語言,可以編寫基于事件觸發的代碼段,對接收報文的關鍵字或分發需求進行分析,以改變進入F5的網絡請求的默認分發方式,達到流量按需切換的目的.
結合外部商戶報文的特點,編寫了高效的基于報文屬性值的irule 分發規則,如規則1.

規則1.基于報文屬性值的流量分發規則1) 設置屬性標識列表iKeyList.2) 當接收到請求并解析到報文體時,屬性標識列表中的元素按先后順序分別遍歷報文體,當搜索到屬性標識元素iKey 時就結束遍歷.3) 按照屬性值的位數從請求報文中截取iKey 對應的屬性值iValue.4) 設置流量切換商戶標識列表sList.5) 在sList 搜索報文的屬性取值iValue,如果搜索到則說明該請求報文是由該商戶發出,就將該筆報文發送到Cloud 應用系統.以驗證某個商戶的微服務版本業務是否正常,如果沒有搜索到則發往默認的Boot 系統.
當規則1的報文屬性取某個具有業務意義的值時,就可以把報文分成兩類,分別發送到不同的系統,下面以報文屬性取sysNo的值為例,描述如何根據外部商戶的商戶編碼將支付請求報文在Boot 系統和Cloud系統之間切換.圖3為irule 按照報文屬性值的流量分發的處理流程圖.

圖3 基于報文屬性值的流量分發處理流程
設置屬性標識列表的原因是除了要兼容XML與JSON 格式的請求報文外,還要考慮特殊字符是否經過url 編碼,統一支付系統的外部商戶數量接近100 個,為了管理方便,每個商戶都分配有一個四位數值的編碼,作為一個匯集系統應當兼容不同的報文格式.對于商戶編碼sysNo 這一個屬性來說,屬性標識的列表有4 個元素,分別是sysNo>、sysNo%22、sysNo"、sysNo%3E 四個字符串,只有搜索到具體是列表中的哪一個元素后,才能在報文中按屬性值位數截取屬性的值.根據不同測試需求,修改商戶標識列表sList的元素取值,可以實現只把一個商戶或者多個商戶的流量同時切換到微服務系統,如sList={0069,0075,0089}表示把sysNo 取為0069、0075與0089 對應3 個商戶的流量全部切換到微服務系統.當把該irule與F5的443 或者8080 端口綁定時,對應的虛擬服務就按照irule 定義的轉發邏輯分發流量,解除綁定則直接轉發到虛擬服務配置的默認default 服務組.
使用F5的irule 規則除了按照外部商戶編碼來分發流量之外,還可以按照流量的比例來進行分發.很多情況下Nginx和F5與后端服務器之間是一對多的關系,即負載均衡器將接收到的請求分發到多個后端服務器,通常一個虛擬服務具有一個默認服務組,服務組里面的各個成員默認都是按照輪詢的方式分發流量,各成員接收到的請求數都大致相同.也可以設置服務組的負載均衡模式為加權輪詢,組內的每個成員都自帶有權重值,每個成員接收到的流量比例為該成員的權重值在組內所有成員權重和的比重.
結合F5的加權輪詢負載模式,以及自定義irule腳本篩選出的特定屬性報文,可以實現兩種不同粒度的比例分發,如圖4所示.將Boot 應用和Cloud 應用添加到同一個服務組,稱作混合服務組,這個服務組的負載均衡類型按成員權重分發,通過修改Boot與Cloud應用的權重則可控制轉發到微服務應用的流量比例在0-100%之間變化.一種方式是直接將混合服務組添加到F5 虛擬服務的默認服務組,則經過該虛擬服務的所有請求都將按照比例分發,屬于較粗的比例控制.另一種是將混合服務組與請求報文中具體的屬性標識相結合,在規則irule 中將外部商戶編碼屬于sList 集合的報文發往混合服務組(對應圖3),即可將經過該虛擬服務的某個商戶或某幾個商戶的流量按照一定的比例分發到微服務應用,這種流量分發控制更為精細.

圖4 兩種粒度的流量比例分發控制
為驗證在本文架構下流量切換方法的正確性,采用的方法是模擬各外部商戶系統發送測試報文到聯調系統的Nginx 入口,查看報文經過F5的7 層負載后,最終是分發到了Boot 系統還是Cloud 系統,是否與F5 虛擬服務加載的irule 定義的分發規則一致.
實驗1:驗證規則1 基于報文屬性值流量分發對于單個屬性取值的應用效果,將sList 設置為{0230},即只將0230 商戶的請求切換到Cloud 系統,并將該irule 規則與F5的8080 虛擬服務綁定,然后模擬0230,0280,0069 三個商戶分別發送1 000 筆支付請求報文,通過F5 提供的日志以及查看應用日志統計確認,0280,0069 發送的2 000 筆報文都發送到了F5的API 虛擬服務的默認Boot 應用系統,而0230 商戶的這1 000 筆報文都Cloud 應用系統,說明該規則對單個商戶的切換有效,正確率達到了100%.
實驗2:驗證該分發規則對于多個屬性取值時的應用效果,將sList的取值設置為{0230 0280 0069},模擬這3 個商戶各發送1 000 筆報文,經確認這3 000 筆報文都發送到了irule 指定的Cloud 服務組,并且無其他商戶的報文發送到該服務組,說明該規則對多個商戶的同時切換也同樣有效.
實驗3:驗證基于報文屬性值的比例分發方法,保持sList的取值設置為{0230 0280 0069}不變,修改irule 規則,將查找到切換商戶取值列表中的元素后轉發的服務組更換為混合服務組,該服務組里面既有Boot 應用成員,又有Cloud 應用的成員,將Boot 應用成員的權重值設置為9,Cloud 應用成員的權重值設置為1,這樣Cloud 應用的占比為10%,模擬這3 個商戶各發送1 000 筆報文,發現Cloud 應用接收到302 筆報文,并且全部來自0230,0280,0069 這3 個商戶,Boot 應用收到2 698 筆來自這3 個商戶的報文,Cloud 應用接收到的報文比例大約10%,說明該規則與加權輪詢的負載均衡算法結合使用能夠達到更細粒度的流量比例控制.
表1是以上3 個實驗的流量分發數據表,實驗結果說明本文提出的Nginx-F5 架構下的流量切換方法,在規則屬性設置為單個和多個值的情況下,都能夠將流量正確地分發到報文屬性值對應Boot 系統和Cloud系統,能夠很好地支撐雙架構應用系統的并行聯調.

表1 3 種實驗的不同商戶報文接收情況
為使統一支付系統能夠透明地對眾多外部商戶同時提供Spring Boot 架構的應用和Spring Cloud 架構應用的聯調,本文提出了基于Nginx-F5的Spring Boot和Spring Cloud 兩種架構應用的并行方案,以及一種基于報文屬性值的流量分發規則,能夠將接入F5 虛擬服務滿足某個屬性值的請求報文發送到Cloud 應用系統,而不是默認的Boot 應用系統.將該流量分發規則與加權輪詢的服務組聯合使用,可實現更細粒度的百分比例分發,如只將某一個或某幾個外部商戶的約5%的流量轉發到Cloud 系統.采用這種雙架構應用的并行聯調方案,能夠做到既不影響生產Boot 系統的不斷需求迭代更新,又能使新的Cloud 系統得到充分完整的對外調測,有效地支撐生產系統不斷升級優化.
本文中的Boot 及Cloud 兩種系統可理解為同一系統的不同版本,本文所提出的并行架構及流量切換方法也可用于其他任何系統多版本并行及聯調.流量分發規則中的報文屬性可變更為其他應用系統有意義的分類標識,如用戶歸屬省、業務類型等,可推廣到其他系統的建設或者優化演進.