Spring cloud 多版本控制及灰度發布
在我們使用spring mvc單體架構時,我們可以通過uri,或者請求頭做多版本路由,雖然同一個功能需要維護多個版本的介面,但是對於系統而言,不會因為新增一個介面版本而影響到老用戶。當我們使用spring cloud構建微服務平台時,也希望能做到這一點,然而spring cloud並沒有提供這個功能。
在springcloud的微服務體系中,大多是使用eureka做為註冊中心,ribbon做為負載均衡,hystrix做為斷路器。但是在國內網路中卻鮮少關於spring-cloud的介面多版本控制的開源項目,而在國內,spring cloud做為越來越被創業公司認同的微服務框架,多版本控制的需求也越來越明顯,於是就有了fm-cloud-bamboo這個多版本控制的項目。在開發這個項目的過程,發現只要再做一些擴展,就可以實現灰度管理,於是又有了fm-cloud-graybunny。
多版本控制
該項目是在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:內部服務之間的調用請求。
不論網關轉發過來的請求,還是內部服務調用過來的請求,都需要ribbon做負載均衡,所以可以擴展ribbon的負載均衡策略從而實現不同版本的請求轉發到不同的服務實例上。
網關的轉發過程是:zuul > hystrix > ribbon
內部服務調用的過程有兩種:
RestTemplate > hystrix > ribbon
Feign > hystrix > ribbon
雖然整個項目實現起來代碼量不少, 但是在介面設計上,卻只有三個簡單的介面負責數據傳遞,路由的邏輯依然是封裝在實現了IRule介面的實現類中(後面分析)。
這個介面是負責將bamboo跟ribbon連接起來的,將請求的信息, 以及根據業務需要添加的一些路由信息,和獲取請求介面的目標版本,還有觸發執行LoadBanceRequestTrigger等,都是由該介面的實現類DefaultRibbonConnectionPoint負責實現。
這個介面負責獲取請求需要訪問的目標介面的版本。比如有些介面版本是放在路徑上,如:/v1/api/test/get。也有放在uri參數中:/api/test/get?v=1。也有可能放到header中,所以在bamboo抽象出來一個介面, 具體的實現由開發者根據業務去實現。
Ribbon請求的觸發器,在ribbon請求發起時, 會被執行。這個介面有三個方法,分別是判斷是否需要執行的方法(shouldExecute),以及請求之前執行(before)和請求完成之後執行(after),如果出現異常,after方法依然會被執行。
上面三個介面只是簡單的實現了獲取請求的目標版本、觸發ribbon請求的觸發器,以及將信息向下一步傳遞。在這一段中,將介紹如何與zuul、feign、RestTemplate以及ribbon和hystrix銜接起來。
ClientHttpRequestInterceptor是RestTemplate的攔截器介面,可以通過這個介面添加bamboo的邏輯, 從而將RestTemplate和bamboo銜接起來。
BambooClientHttpRequestIntercptor是ClientHttpRequestInterceptor介面的實現類,它加入了bamboo的邏輯。
BambooFeignClient類實現了feign.Client介面,該類是一個代理類,主要的Feign的調用邏輯依然由被代理的類去執行,在該類中添加了bamboo的邏輯,從而將Feign和bamboo銜接起來。
實現兩個ZuulFilter介面,分別是pre和post類型,將bamboo的邏輯加入其中。Pre類型的ZuulFilter獲取請求信息,並執行LoadBalanceRequestTrigger#before方法。Post類型的ZuulFilter執行LoadBalanceRequestTrigger#after方法,並清除存在ThradLocal中的相關信息。
parentThreadState也是一個HystrixRequestContext對象,它是在hystrix創建線程之前的,也就是處理http請求的線程的HystrixRequestContext對象,我們一般也是維護這個對象。在使用線程池隔離時,hystrix會將parentThreadState中的信息復到到新線程中,實現跨線程的數據傳遞,從而在後面的邏輯中可以獲取到parentThreadState中維護的信息,包括ribbon的路由信息。在bamboo中,將一步驟的邏輯放到BambooRequestContext中,將BambooRequestContext實例本身傳遞下去。
Bamboo中的BambooZoneAvoidanceRule繼承了ZoneAvoidanceRule,所以它會有ZvoidanceRule的一切特性,在此基礎上,還加入了版本過濾的邏輯,這個邏輯主要是由BambooApiVersionPredicate實現。從BambooRequestContext中獲取請求的介面的版本,如果有該沒有獲取到版本,就返回true;如果有獲取到版本,就獲取服務實例的metadata中的version信息,並進行匹配校驗,返回結果。
灰度發布
灰度發布是在多版本控制的基礎上進一步擴展實現出來的項目 -> 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去分析。接下來將一步一步去分析這四個方面。
實例註冊:服務實例添加到灰度管理中。
實例下線:服務實例下線,從灰度管理中刪除。
灰度開關:調整服務實例的灰度狀態,有啟用、禁用兩個狀態,禁用的實例不納入灰度列表中。
灰度策略:請求是否可以被轉發到該服務實例的條件,只有通過,請求才有可能會被轉發到該實例上。
灰度決策:根據請求的信息去匹配灰度服務實例的灰度策略,如果匹配上,會將服務實例加入到通過列表中。如果都沒有匹配上,就按bamboo的路由規則去篩選非灰度的服務實例進行轉發。
調用請求的服務消費者,和提供服務的服務提供者都可以是灰度客戶端,因為微服務中,大多服務實例既是服務提供者,同時也是服務消費者。
灰度服務註冊:服務實例在啟動時,就會向灰度服務端發起請求,將實例自身的灰度開關打開。
灰度服務下線:在服務實例下線前,會觸發鉤子,向灰度服務端發起請求將實例自身從灰度列表中刪除。
接收灰度實例調整消息:接收由灰度服務端推送過來的灰度列表更新消息比如新增灰度實例,刪除灰度實例等,維護緩存在實例上的灰度列表。
定時拉取灰度列表:定時從灰度服務端拉取最新的灰度列表,維護實例自身緩存的灰度列表。
灰度服務端負表維護灰度列表,可以新增、刪除、編輯灰度信息。
編輯灰度實例:新增灰度實例,刪除灰度實例,修改實例灰度狀態。
編輯灰度策略:新增實例灰度策略,刪除實例灰度策略,修改灰度策略狀態。
推送灰度服務調整消息:向灰度客戶端推送灰度列表變動消息,比如新增灰度實例,刪除灰度實例,修改實例灰度狀態等。
定時檢查服務實例是否下線:定時檢查灰度服務實例是否下線,下線的的實例將從灰度列表中刪除。
根據上面的思路,設計以下對象和介面。共6個介面,4個模型對象。
對象:
灰度服務
灰度實例,有狀態屬性
灰度策略組,有狀態屬性
灰度策略
介面:
灰度客戶端管理器,維護灰度列表,維護自身灰度狀態,創建灰度決策對象。抽象實現類AbstractGrayManager實現了基礎的獲取灰度列表,創建灰度決策對象的能力。BaseGrayManger在期基礎上進行了擴展,將灰度列表緩存起來,定時從灰度服務端更新灰度列表。
該介面主要是負責和灰度服務端進行通信,獲取灰度列表,編輯灰度實例等能力。其實現類HttpInformationClient默認使用http方式訪問灰度服務端。
子類InformationClientDecorator是一個適配器類,RetryableInformationClient繼承了InformationClientDecorator類,實現了重試的功能。
該介面是灰度決策,用來判斷請求是否匹配灰度策略。實現了ip匹配、request parameter匹配、request header匹配、BambooRequestContext中的參數匹配器以及合併匹配等多個匹配能力。
灰度決策的工廠類,其默認實現類支持上述幾種灰度決策的創建。
灰度服務管理類,屬於服務端的類。主要是編輯服務實例,編輯灰度策略,以及維護最新的灰度列表。
如果灰度服務實例下線後, 由於意外情況,沒有向灰度服務端發送刪除請求, 服務端會每隔一段時間調用該介面的方法,檢查灰度列表中的實例是否下線,如果實例已下線,就將其從灰度列表中刪除。
將模型抽象成介面和對象設計出來之後,實現思路就清晰了。
灰度路由是客戶端必須要實現的能力,graybunny是在bamboo的基礎上擴展的,所以graybunny的路由規則對象GrayLoadBalanceRule繼承了BambooZoneAvoidanceRule,
邏輯是這樣的:
1、判斷目標服務是否有灰度實例。
2.1、 如果沒有, 執行父類邏輯。結束。
2.2、有灰度實例,先將灰度實例和非灰度實例篩選出來。
3、挑選灰度實例, 篩選調用請求匹配上灰度實例的策略。
4.1、如果沒有匹配的灰度實例, 將非灰度實例列表傳遞過去執行父類的篩選邏輯。結束。
4.2、 如果有匹配的灰度實例, 從其中按輪詢的方式挑選出一個實例。結束。
灰度決策的執行代碼在GrayDecisionPredicate中
灰度管理是灰度服務端的功能,主要是維護灰度列表。其實現類DefaultGrayServiceManger有一個Map, 用來維護GrayService,key是serviceid。並且每隔一段時間就調用EurekaGrayBunnyServerEvictor,檢查列表中的實例是否下線,將下線的服務從灰度列表中刪除。
EurekaGrayBunnyServerEvictor是依賴EurekaClient來檢查服務實例是否下線。
使用指導
多版本控制
在使用多版本控制時,需要修改服務提供方的兩個文件,分別是pom.xml和application.yaml。
1、將bamboo-start項目添加到maven中。
2、在application.yaml中添加versions屬性,標明服務支持哪些版本。
在服務消費方,只需要在pom.xml添加bamboo-starter到maven中即可。
在一個名為eureka-client的項目中加入1,2兩個步驟, 啟動服務。網關做為服務消費方,在pom.xml中加入fm-cloud-starter-bamboo, 並在application.yaml中加入zuul的配置:
啟動服務後,訪問http://localhost:10002/gateway/client/api/test/get?version=2會返回數據,因為eureka-client支持version=2
如果訪問http://localhost:10002/gateway/client/api/test/get?version=3會報錯, 因為找不到支持版本3的服務實例
灰度管理
灰度管理的配置和bamboo的配置是一樣的,配置方式差別不大。下面先說gray-server的配置。
Gray-Server:
在項目的pom.xml加入spring-boot相關的依賴,再加入bamboo-start、graybunny-server-starter,然後啟動就可以了。
在啟動類中,需要僱用服務發現。
啟動後,可以訪問http://localhost:10202/swagger-ui.html#/service-gray-resouce查看介面列表,也可以調用其中的介面。
以上介紹完了gray-server的配置,下面再看gray-client的配置。
Gray-Client:
1?在pom.xml中加入gm-cloud-graybunny。
1?在application.yaml中加入灰度配置。
1?在啟動類中加入灰度客戶端的註解@EnableGrayBunny
這樣灰略度的服務端和客戶端都配置好了, 只要在灰度服務端開啟灰度實例和灰度策,在灰度客戶端就會自動進行灰度路由。
不足
graybunny目前只有灰度管理的基本功能, 像數據持久化,高可用,推送灰度調整消息等,都沒有實現。 也沒有界面化, 僅僅只有介面列表。
擴展思考
graybunny目前僅僅只支持springcloud eureka, 但是在spring cloud中,eureka只是做為其中一個註冊中心,如果要做spring cloud的灰度管理, 就還需要兼容其中的註冊中心, 比如zookeeper, consul等。
項目地址:https://github.com/saleson/fm-cloud
TAG:SpringCloud社區 |