楊 帆
(歐特克(中國)軟件研發(fā)有限公司,上海 200127)
隨著互聯(lián)網(wǎng)時代的到來,云計算技術和物聯(lián)網(wǎng)技術快速發(fā)展,互聯(lián)網(wǎng)應用提供商的硬件資源與軟件資源越來越多地以服務的形式提供給用戶[1]。2014年學者Martin Fowler正式提出微服務架構的概念[2]:微服務架構以一套微小的服務的方式來開發(fā)和部署一個單獨的應用,這些微小的服務根據(jù)業(yè)務功能來劃分,通過自動化部署機制獨立部署運行在自己的進程中,微服務之間使用輕量級通信機制來進行通信。一個典型的微服務架構應該包括客戶端、微服務網(wǎng)關、服務發(fā)現(xiàn)、微服務原子層、數(shù)據(jù)庫、部署平臺等模塊,根據(jù)不同應用類型及服務規(guī)模,可以增加負載均衡 、權限認證、服務熔斷、日志監(jiān)控等模塊,來滿足服務的非功能性需求。企業(yè)紛紛邁向微服務架構的典型原因是業(yè)務的復雜性、可擴展性[3]。而 Spring Cloud是目前微服務架構領域的翹楚,也是一個全家桶式的技術棧,包含了很多組件,例如Eureka、Ribbon、Feign、Hystrix、Zuul這幾個組件。首先Spring Cloud是基于Spring Boot實現(xiàn)的云應用開發(fā)工具,它為基于JVM的云應用開發(fā)中涉及的配置管理、服務發(fā)現(xiàn)、斷路器、智能路由、微代理、控制總線、全局鎖、決策競選、分布式會話和集群狀態(tài)管理等操作提供了一種簡單的開發(fā)框架。
圖1 微服務系統(tǒng)架構Fig.1 micr oservices system architecture
而在互聯(lián)網(wǎng)企業(yè)快速迭代的開放和發(fā)布活動中伴隨業(yè)務的增加,微服務的子系統(tǒng)會變得越來越多,并伴隨著子系統(tǒng)間的業(yè)務調用路徑也會越來越復雜。隨之而來我們面臨著多版本子系統(tǒng)的訪問路徑控制、黑白名單和流量切換,微服務本身的多系統(tǒng)間的全路徑檢測、問題定位和 log收集,以及各個子系統(tǒng)的平滑的上下線發(fā)布和切換等問題。而我們通過開發(fā)出泳道系統(tǒng)來進行各系統(tǒng)間實時流量控制、多版本訪問路徑選擇等來解決各微服務間的問題。
微服務系統(tǒng)中每個服務代表著一個小的業(yè)務能力[4],而每個服務之間通過 API調用[5]。所以由于本身就存在多服務互相調用的特性,這就使得它存在著跨進程之間的事務、大量的異步處理、多個微服務之間相互發(fā)現(xiàn)選擇以及網(wǎng)絡延遲、容錯、消息序列化等問題。而隨著公司的成長和業(yè)務的拓展,各個系統(tǒng)會進行快速的多次迭代。這就會造成子系統(tǒng)的增加以及子系統(tǒng)的多版本共存等問題的出現(xiàn)。
而通過構建Spring Cloud系統(tǒng)構架,通過對Eureka、Ribbon、Feign、Hystrix、Zuul這幾個系統(tǒng)和組件的使用,可以解決一些如子系統(tǒng)發(fā)現(xiàn)[6],各子系統(tǒng)間訪問路由、負載等問題。但是這些還遠遠不夠,還面臨著如下問題的挑戰(zhàn):
(1)對多版本的子系統(tǒng)支持
(2)多版本的訪問路徑控制
(3)多版本的黑白名單和流量切換
(4)多系統(tǒng)間的全路徑檢測,問題定位和log收集各個子系統(tǒng)的平滑的上下線發(fā)布和切換。
為了解決微服務系統(tǒng)中的多版本子系統(tǒng)一系列問題,目前有兩種可行的解決方案:
(1)物理隔絕獨立部署。就是根據(jù)需要的支持的各個版本子系統(tǒng)獨立部署一整套微服務系統(tǒng),但是可以根據(jù)并發(fā)量減少部署的子系統(tǒng)個數(shù)。在最外層網(wǎng)關根據(jù)需求發(fā)出的版本號選擇對應的獨立系統(tǒng)[7]。
優(yōu)點:不會對系統(tǒng)的開發(fā)有任何影響,系統(tǒng)獨立流量切換和控制也相對容易,系統(tǒng)的集成測試相對友好,也便于開發(fā)人員發(fā)現(xiàn)問題,修改錯誤,對訪問控制也相對容易。
缺點:實施成本高昂,如果根據(jù)一個子系統(tǒng)版本一套獨立部署,需要把所有的其他子系統(tǒng)全部單獨部署一遍,會把一些訪問量較少的服務也單獨再部署一個,而且如某些版本本身訪問數(shù)量較少也需要一整套系統(tǒng)的支持,這就造成資源浪費和分配不均,會擠占高訪問率版本系統(tǒng)的所占資源。
(2)通過軟件自動選擇可以訪問的子系統(tǒng)。所有的子系統(tǒng)都配置自身的版本信息并且能夠自己識別和選擇需要訪問的下一個系統(tǒng)的版本[8]。
優(yōu)點:系統(tǒng)上下線靈活,不會擠占高訪問率版本系統(tǒng)的所占資源。實施成本相對較低,對運維占用也相對較少。
缺點:可能會對開發(fā)和測試產(chǎn)生較大的影響,可能會對已有系統(tǒng)造成代碼侵入問題,對于系統(tǒng)查錯提告了難度。
對大多數(shù)處于創(chuàng)業(yè)期的互聯(lián)網(wǎng)企業(yè)來說系統(tǒng)資源都是有限的,如何提升系統(tǒng)資源的最大利用率都是核心的問題。所以選擇第二種方案是大多數(shù)創(chuàng)業(yè)期的互聯(lián)網(wǎng)企業(yè)最佳選擇。而如何規(guī)避第二種方案所帶來的缺點則是對系統(tǒng)架構師的考驗了。
為了對于已有系統(tǒng)的影響最小化也列出了如下的要求:
(1)能夠對全路徑進行追蹤
(2)對已有系統(tǒng)的各個子系統(tǒng)沒有硬性的代碼侵入
(3)可以自由發(fā)布
(4)方便收集所有子系統(tǒng)的log記錄
(5)不需要對已有系統(tǒng)進行重構和改造
為了應對以上問題,我們主要通過開發(fā)泳道架構系統(tǒng)來解決。而泳道架構系統(tǒng)的設計思路是通過軟件讓各個子系統(tǒng)能夠自己選擇可以訪問的其他子系統(tǒng)。而在這當中需要對各個子系統(tǒng)最小化的代碼侵入,對全鏈路能夠有效的進行跟蹤和查找。而且能夠適用于消息隊列(MQ)和job調用甚至數(shù)據(jù)庫的存儲。為了滿足以上要求,泳道架構系統(tǒng)通過為了讓各個子系統(tǒng)引入 jar包來讓各個子系統(tǒng)能根據(jù)版本信息和設置的閾值自動選擇它可以訪問的其他子系統(tǒng),并能夠在所有的訪問請求中帶上唯一的trace Id,而且讓這個Id一直保存在所有的需要調用的子系統(tǒng)中。并且泳道架構系統(tǒng)還通過新開發(fā)的新服務來為所有子系統(tǒng)配置版本信息,控制各個子系統(tǒng)流量,設置用戶訪問控制名單和系統(tǒng)訪問控制名單。
實現(xiàn)細節(jié):
首先Spring Cloud的Eureka已經(jīng)提供了微服務的注冊和發(fā)現(xiàn)服務,可以通過對子系統(tǒng)的配置修改將各個子系統(tǒng)版本信息通過注冊服務發(fā)送給Eureka,而其他子系統(tǒng)則可以通過Eureka來獲得需要訪問的子系統(tǒng)的版本信息[9-10]。
統(tǒng)計得2001—2016年春季暴雨雨日共11 d,通過影響系統(tǒng)的分析發(fā)現(xiàn)(表1),在11次過程中,有8次暴雨都伴隨有低空切變和地面倒槽的存在;高空槽,低空急流,地面冷空氣也是重要的影響系統(tǒng);春季是冷空氣較為活躍的季節(jié),有4次過程是高空有槽東移或急流存在,中層配合切變或低空急流,而地面先為倒槽控制,之后冷空氣南下影響,冷暖空氣交匯導致暴雨的發(fā)生。另外東北冷渦也是非常重要的影響系統(tǒng),有4次暴雨與之有關。
其次在類似Spring Boot風格的基于簡易配置的微服務[9]中開發(fā)一個被所有子系統(tǒng)引用的jar包來完成trace Id的訪問透傳,和從Eureka獲取所有子系統(tǒng)的版本配置信息從而來進行訪問路徑選擇和計算。
持續(xù)改進方案
泳道系統(tǒng)第一版:
所有版本信息通過Eureka來進行存儲,而版本信息都是各個子系統(tǒng)存儲于自己的配置文件內。這樣可以對訪問進行版本訪問選擇控制,但是也無法進行主動的流量控制。
圖2 基于API的微服務調用Fig.2 API-Based microservices calling process
圖3 利用Erueka的微服務的注冊和訪問實現(xiàn)的1.0的服務調用Fig.3 version 1.0 microservices calling process base on Erueka’s registration and access
用戶的訪問進入網(wǎng)關并帶有用戶的信息如用戶APP的版本信息和用戶ID等。網(wǎng)關根據(jù)此用戶的信息生成該用戶此次訪問的Trace Id,并從注冊的Eureka獲取可以訪問的應用A的所有實例信息。再根據(jù)訪問的要求去選擇相對應的應用A的具體實例。例如:用戶A是要訪問1.0版本的應用,網(wǎng)關從Eureka處獲取到3個可以訪問的應用A實例,然后根據(jù)1.0的版本號選出其中2個可以使用的具體實例,最后選擇其中一個發(fā)出訪問并帶上生成的此次的Trace Id。應用A v1.0的其中一個實例收到這個訪問發(fā)現(xiàn)它需要調用應用B。應用A從Eureka處獲取到應用B沒有版本的具體要求,所以不需選擇就可以發(fā)生請求給應用B,但是該請求還是會帶上網(wǎng)關生成的Trace Id和應用的版本需求。而在應用B收到訪問請求在發(fā)現(xiàn)需要調用應用C時,它也會從Eureka處請求應用C的具體信息,然后根據(jù)訪問所需求的版本要求選擇應用C支持v1.0的具體實例,返送訪問請求。
泳道系統(tǒng)第二版:
引入 Zookeeper服務把各個子系統(tǒng)版本配置信息在上線時同步注冊到 Zookeeper上,再開發(fā)一個獨立系統(tǒng)對Zookeeper信息進行管理。
由于添加了兩個新的服務 Zookeeper和泳道控制應用,所以所有的服務在Eureka注冊時同時會把自己的版本信息也注冊到 Zookeeper上。而泳道控制應用就能實時觀察和控制其中每一個應用實例的訪問。其中的訪問流程跟第一版大體一致,但是在選擇該服務可以訪問的下一個服務的實例時,則不是光通過Eureka來獲取全部信息。而是先通過Eureka拿到全部可以訪問的下一個應用實例,然后再從 Zookeeper上去獲取具體的訪問規(guī)則,如可以訪問的版本信息,訪問的黑白名單,然后過濾從Eureka獲取的應用實例,選取符合以上規(guī)則的具體幾個實例,最后還能再根據(jù)實例的流量訪問規(guī)則來選取其中一個實例來訪問。例如還是用戶A發(fā)出的訪問v1.0的請求,再網(wǎng)關處跟上一版一樣生產(chǎn)對應的 Trace Id,從 Eureka獲取應用A的v1.0和v1.5的全部3個實例,然后再讀取 Zookeeper上的訪問版本規(guī)則和黑白名單,篩選出應用A的v1.0的2個實例,然后再根據(jù)2個實例的流量控制的百分比選出最后訪問的那一個實例。
計算流量的方式如下:
應用A的v1.0實例1的流量為40%,
應用A的v1.0實例2的流量為60%。
獲取一個100以內的隨機數(shù),如果隨機數(shù)小于等于40,選擇應用A的v1.0實例1,反之則選擇應用A的v1.0實例2.
泳道系統(tǒng)第三版:
去掉Zookeeper,把版本控制信息和流量調節(jié)信息控制系統(tǒng)前移。把信息存儲于進入網(wǎng)關,根據(jù)訪問控制把需要的子系統(tǒng)的訪問版本控制信息跟 trace Id一起封裝于訪問信息內來進行全系統(tǒng)透傳。
圖5 以網(wǎng)關注入信息3.0的服務調用Fig.5 version 3.0 microservices calling process of gateway information injection
第三個版本跟第一版很像,只是在網(wǎng)關上添加了一個泳道控制應用。它的所有功能跟第二版比一點也沒有減少,只是減少了系統(tǒng)的復雜度,去掉了Zookeeper,避免了可能會由于Zookeeper產(chǎn)生的問題(如非必要勿增實體)。全部的訪問規(guī)則跟第二版基本一致,但是不再從 Zookeeper獲取訪問規(guī)則,黑白名單和流量控制信息了。而是這些信息直接存儲在網(wǎng)關上,而網(wǎng)關則是把所有這些信息和Trace Id一樣存儲在每個分發(fā)出去的訪問需求上,而具體的應用實例在做訪問選擇時,不再從別的服務上獲取訪問信息和規(guī)則,直接從訪問信息中獲取,這樣減少了網(wǎng)路負擔和服務直接的訪問頻次。
通過泳道架構系統(tǒng)我們不需要對所有子系統(tǒng)進行代碼侵入的情況下,只通過引入jar就能實現(xiàn)子系統(tǒng)間的訪問選擇,黑白名單控制和訪問流量調節(jié)。以此為基礎可以實現(xiàn)A/B測試和藍綠部署、金絲雀發(fā)布,以及灰度發(fā)布、流量切分。