Spring cloud 多版本控制及灰度發布

在我們使用spring mvc單體架構時, 我們可以通過uri,或者請求頭做多版本路由,雖然同一個功能需要維護多個版本的介面,但是對於系統而言,不會因為新增一個介面版本而影響到老用戶。當我們使用spring cloud構建微服務平台時,也希望能做到這一點,然而spring cloud並沒有提供這個功能。

在springcloud的微服務體系中,大多是使用eureka做為註冊中心,ribbon做為負載均衡,hystrix做為斷路器。但是在國內網路中卻鮮少關於spring-cloud的介面多版本控制的開源項目,而在國內,spring cloud做為越來越被創業公司認同的微服務框架,多版本控制的需求也越來越明顯,於是就有了fm-cloud-bamboo這個多版本控制的項目。在開發這個項目的過程,發現只要再做一些擴展,就可以實現灰度管理,於是又有了fm-cloud-graybunny。

Advertisements

多版本控制

該項目是在spring-cloud-ribbon的基礎上進行擴展,以實現介面的多個版本的調用及負載均衡,支持feign方式和斷路器(spring-cloud-hystrix)。

  • 場景

服務A部署了兩個實例 serivceA-1,serviceA-2,spring cloud ribbon默認是輪詢的方式將請求分別轉到兩個實例上。如果由於業務原因,服務需要從1.0升級到2.0。

場景1:將所有服務實例平緩的過度到2.0。

場景2:2.0的服務實例需要兼容1.0的服務介面。

  • 思路

在spring cloud微服務體系中,服務的請求來源無外乎兩個方面:

來源1:外部請求通過網關(zuul)轉發而來。

來源2:內部服務之間的調用請求。

Advertisements

不論網關轉發過來的請求,還是內部服務調用過來的請求,都需要ribbon做負載均衡,所以可以擴展ribbon的負載均衡策略從而實現不同版本的請求轉發到不同的服務實例上。

Java

網關的轉發過程是:zuul > hystrix > ribbon

內部服務調用的過程有兩種:

RestTemplate > hystrix > ribbon

Feign > hystrix > ribbon

而其中hystrix有一個線程池隔離的能力,會創建另一個線程去請求服務,擁有更好的控制併發訪問量、以及服務降級等能力,但是會出現一個問題,就是線程變數(ThreadLocal)的傳遞問題,這可以通過com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault對象解決。

  • 代碼設計

雖然整個項目實現起來代碼量不少, 但是在介面設計上,卻只有三個簡單的介面負責數據傳遞,路由的邏輯依然是封裝在實現了IRule介面的實現類中(後面分析)。

Java

  • BambooRibbonConnectionPoint:

這個介面是負責將bamboo跟ribbon連接起來的,將請求的信息, 以及根據業務需要添加的一些路由信息,和獲取請求介面的目標版本,還有觸發執行LoadBanceRequestTrigger等,都是由該介面的實現類DefaultRibbonConnectionPoint負責實現。

Java

  • RequestVersionExtractor:

這個介面負責獲取請求需要訪問的目標介面的版本。比如有些介面版本是放在路徑上,如:/v1/api/test/get。也有放在uri參數中:/api/test/get?v=1。也有可能放到header中,所以在bamboo抽象出來一個介面, 具體的實現由開發者根據業務去實現。

  • LoadBalanceRequestTrigger:

Ribbon請求的觸發器,在ribbon請求發起時, 會被執行。這個介面有三個方法,分別是判斷是否需要執行的方法(shouldExecute),以及請求之前執行(before)和請求完成之後執行(after),如果出現異常,after方法依然會被執行。

  • 代碼實現

上面三個介面只是簡單的實現了獲取請求的目標版本、觸發ribbon請求的觸發器,以及將信息向下一步傳遞。在這一段中,將介紹如何與zuul、feign、RestTemplate以及ribbon和hystrix銜接起來。

  • RestTemplate銜接

ClientHttpRequestInterceptor是RestTemplate的攔截器介面,可以通過這個介面添加bamboo的邏輯, 從而將RestTemplate和bamboo銜接起來。

BambooClientHttpRequestIntercptor是ClientHttpRequestInterceptor介面的實現類,它加入了bamboo的邏輯。

Java

  • Feign銜接

BambooFeignClient類實現了feign.Client介面,該類是一個代理類,主要的Feign的調用邏輯依然由被代理的類去執行,在該類中添加了bamboo的邏輯,從而將Feign和bamboo銜接起來。

Java

  • Zuul銜接

實現兩個ZuulFilter介面,分別是pre和post類型,將bamboo的邏輯加入其中。Pre類型的ZuulFilter獲取請求信息,並執行LoadBalanceRequestTrigger#before方法。Post類型的ZuulFilter執行LoadBalanceRequestTrigger#after方法,並清除存在ThradLocal中的相關信息。

Java

  • Hystrix銜接

Hystrix實現降級、斷路器等功能,但是在使用線程池隔離時,ThreadLocal存儲的信息如何傳遞下去呢?使用HystrixRequestVariableDefault可以解決這個問題。可以查看com.netflix.hystrix.strategy.concurrency包下的HystrixContexSchedulerAction、HystrixContextCallable、HystrixContextRunnable,它們都有一段相同功能的代碼

Java

parentThreadState也是一個HystrixRequestContext對象,它是在hystrix創建線程之前的,也就是處理http請求的線程的HystrixRequestContext對象,我們一般也是維護這個對象。在使用線程池隔離時,hystrix會將parentThreadState中的信息復到到新線程中,實現跨線程的數據傳遞,從而在後面的邏輯中可以獲取到parentThreadState中維護的信息,包括ribbon的路由信息。在bamboo中,將一步驟的邏輯放到BambooRequestContext中,將BambooRequestContext實例本身傳遞下去。

Java

  • Ribbon 路由規則

Bamboo中的BambooZoneAvoidanceRule繼承了ZoneAvoidanceRule,所以它會有ZvoidanceRule的一切特性,在此基礎上,還加入了版本過濾的邏輯,這個邏輯主要是由BambooApiVersionPredicate實現。從BambooRequestContext中獲取請求的介面的版本,如果有該沒有獲取到版本,就返回true;如果有獲取到版本,就獲取服務實例的metadata中的version信息,並進行匹配校驗,返回結果。

Java

灰度發布

灰度發布是在多版本控制的基礎上進一步擴展實現出來的項目 -> fm-cloud-graybunny,抽象出灰度服務、灰度服務實例、灰度策略、灰度決策等。支持A/B test, 金絲雀 test。 灰度策略可以從request ip, request patameter, request header等方面進行去創建,也可以根據bamboo的LoadBalanceRequestTrigger結合graybuanny的介面去擴展灰度策略和灰度決策。

  • 場景

有兩個服務,共四個服務實例,分別是ServiceA-1, ServiceA-2,ServiceB-1。其中ServiceA-2是灰度實例。

場景1:所有請求頭usertype:old,ip:10.217.***.***的請求或者請求頭usertype:test, url 參數action:create的請求,都會被轉發到的灰度服務ServiceA-2 。

場景2:ServiceA-2通過一段時間的觀察,判定運行穩定,開始ServiceA-2刪除灰度標記,開始和ServiceA-1一樣會加入正常的負載均衡規則當中。

場景3:服務ServiceB發布新版本,ServiceB-2需要灰度註冊,註冊成功后所有的請求不能轉發到ServiceB-2, 在為ServiceB-2設置灰度策略后,符合策略的請求才會被轉發到ServiceB-2上。

  • 思路

從上面的場景分析,可以歸納出兩個對象:服務實例和調用請求;服務實例的灰度管理是基礎,調用請求時如何決策路由,都是根據服務實例的灰度策略去判斷的。既然有灰度管理這個概念,那麼從功能上分,就會有client-server之分,所以又可以從graybunny-client和graybunny-server去分析。接下來將一步一步去分析這四個方面。

  • 灰度實例

Java

實例註冊:服務實例添加到灰度管理中。

實例下線:服務實例下線,從灰度管理中刪除。

灰度開關:調整服務實例的灰度狀態,有啟用、禁用兩個狀態,禁用的實例不納入灰度列表中。

灰度策略:請求是否可以被轉發到該服務實例的條件,只有通過,請求才有可能會被轉發到該實例上。

  • 調用請求

Java

灰度決策:根據請求的信息去匹配灰度服務實例的灰度策略,如果匹配上,會將服務實例加入到通過列表中。如果都沒有匹配上,就按bamboo的路由規則去篩選非灰度的服務實例進行轉發。

  • 灰度客戶端

調用請求的服務消費者,和提供服務的服務提供者都可以是灰度客戶端,因為微服務中,大多服務實例既是服務提供者,同時也是服務消費者。

Java

灰度服務註冊:服務實例在啟動時,就會向灰度服務端發起請求,將實例自身的灰度開關打開。

灰度服務下線:在服務實例下線前,會觸發鉤子,向灰度服務端發起請求將實例自身從灰度列表中刪除。

接收灰度實例調整消息:接收由灰度服務端推送過來的灰度列表更新消息比如新增灰度實例,刪除灰度實例等,維護緩存在實例上的灰度列表。

定時拉取灰度列表:定時從灰度服務端拉取最新的灰度列表,維護實例自身緩存的灰度列表。

  • 灰度服務端

灰度服務端負表維護灰度列表,可以新增、刪除、編輯灰度信息。

Java

編輯灰度實例:新增灰度實例,刪除灰度實例,修改實例灰度狀態。

編輯灰度策略:新增實例灰度策略,刪除實例灰度策略,修改灰度策略狀態。

推送灰度服務調整消息:向灰度客戶端推送灰度列表變動消息,比如新增灰度實例,刪除灰度實例,修改實例灰度狀態等。

定時檢查服務實例是否下線:定時檢查灰度服務實例是否下線,下線的的實例將從灰度列表中刪除。

  • 代碼設計

根據上面的思路,設計以下對象和介面。共6個介面,4個模型對象。

Java

對象:

  • GrayService:

灰度服務

  • GrayInstance:

灰度實例,有狀態屬性

  • GrayPolicyGroup:

灰度策略組,有狀態屬性

  • GrayPolicy:

灰度策略

介面:

  • GrayManager:

灰度客戶端管理器,維護灰度列表,維護自身灰度狀態,創建灰度決策對象。抽象實現類AbstractGrayManager實現了基礎的獲取灰度列表,創建灰度決策對象的能力。BaseGrayManger在期基礎上進行了擴展,將灰度列表緩存起來,定時從灰度服務端更新灰度列表。

  • InformationClient:

該介面主要是負責和灰度服務端進行通信,獲取灰度列表,編輯灰度實例等能力。其實現類HttpInformationClient默認使用http方式訪問灰度服務端。

子類InformationClientDecorator是一個適配器類,RetryableInformationClient繼承了InformationClientDecorator類,實現了重試的功能。

  • GrayDecision:

該介面是灰度決策,用來判斷請求是否匹配灰度策略。實現了ip匹配、request parameter匹配、request header匹配、BambooRequestContext中的參數匹配器以及合併匹配等多個匹配能力。

  • GrayDecisionFactory:

灰度決策的工廠類,其默認實現類支持上述幾種灰度決策的創建。

  • GrayServiceManager:

灰度服務管理類,屬於服務端的類。主要是編輯服務實例,編輯灰度策略,以及維護最新的灰度列表。

  • GrayBunnyServerEvictor:

如果灰度服務實例下線后, 由於意外情況,沒有向灰度服務端發送刪除請求, 服務端會每隔一段時間調用該介面的方法,檢查灰度列表中的實例是否下線,如果實例已下線,就將其從灰度列表中刪除。

  • 代碼實現

將模型抽象成介面和對象設計出來之後,實現思路就清晰了。

  • 灰度路由:

灰度路由是客戶端必須要實現的能力,graybunny是在bamboo的基礎上擴展的,所以graybunny的路由規則對象GrayLoadBalanceRule繼承了BambooZoneAvoidanceRule,

邏輯是這樣的:

1、判斷目標服務是否有灰度實例。

2.1、 如果沒有, 執行父類邏輯。結束。

2.2、有灰度實例,先將灰度實例和非灰度實例篩選出來。

3、挑選灰度實例, 篩選調用請求匹配上灰度實例的策略。

4.1、如果沒有匹配的灰度實例, 將非灰度實例列表傳遞過去執行父類的篩選邏輯。結束。

4.2、 如果有匹配的灰度實例, 從其中按輪詢的方式挑選出一個實例。結束。

Java

灰度決策的執行代碼在GrayDecisionPredicate中

Java

  • 灰度管理:

灰度管理是灰度服務端的功能,主要是維護灰度列表。其實現類DefaultGrayServiceManger有一個Map, 用來維護GrayService,key是serviceid。並且每隔一段時間就調用EurekaGrayBunnyServerEvictor,檢查列表中的實例是否下線,將下線的服務從灰度列表中刪除。

Java

EurekaGrayBunnyServerEvictor是依賴EurekaClient來檢查服務實例是否下線。

Java

使用指導

多版本控制

在使用多版本控制時,需要修改服務提供方的兩個文件,分別是pom.xml和application.yaml。

1、將bamboo-start項目添加到maven中。

Java

2、在application.yaml中添加versions屬性,標明服務支持哪些版本。

Java

在服務消費方,只需要在pom.xml添加bamboo-starter到maven中即可。

在一個名為eureka-client的項目中加入1,2兩個步驟, 啟動服務。網關做為服務消費方,在pom.xml中加入fm-cloud-starter-bamboo, 並在application.yaml中加入zuul的配置:

Java

啟動服務后,訪問http://localhost:10002/gateway/client/api/test/get?version=2會返回數據,因為eureka-client支持version=2

Java

如果訪問http://localhost:10002/gateway/client/api/test/get?version=3會報錯, 因為找不到支持版本3的服務實例

Java

灰度管理

灰度管理的配置和bamboo的配置是一樣的,配置方式差別不大。下面先說gray-server的配置。

Gray-Server:

在項目的pom.xml加入spring-boot相關的依賴,再加入bamboo-start、graybunny-server-starter,然後啟動就可以了。

Java

在啟動類中,需要僱用服務發現。

Java

啟動后,可以訪問http://localhost:10202/swagger-ui.html#/service-gray-resouce查看介面列表,也可以調用其中的介面。

Java

以上介紹完了gray-server的配置,下面再看gray-client的配置。

Gray-Client:

1、在pom.xml中加入gm-cloud-graybunny。

Java

1、在application.yaml中加入灰度配置。

Java

1、在啟動類中加入灰度客戶端的註解@EnableGrayBunny

Java

這樣灰略度的服務端和客戶端都配置好了, 只要在灰度服務端開啟灰度實例和灰度策,在灰度客戶端就會自動進行灰度路由。

不足

graybunny目前只有灰度管理的基本功能, 像數據持久化,高可用,推送灰度調整消息等,都沒有實現。 也沒有界面化, 僅僅只有介面列表。

擴展思考

graybunny目前僅僅只支持springcloud eureka, 但是在spring cloud中,eureka只是做為其中一個註冊中心,如果要做spring cloud的灰度管理, 就還需要兼容其中的註冊中心, 比如zookeeper, consul等。

Java學習資料獲取(複製下段連接至瀏覽器即可)

data:text/html;charset=UTF-8;base64,5p625p6E5biI5a2m5Lmg6LWE5paZ5YWN6LS56aKG5Y+W6K+35Yqg5omj5omj5Y+35pivMTAxODkyNTc4MA==

Advertisements

你可能會喜歡