在咱們使用spring mvc單體架構時, 咱們能夠經過uri,或者請求頭作多版本路由,雖然同一個功能須要維護多個版本的接口,可是對於系統而言,不會由於新增一個接口版本而影響到老用戶。當咱們使用spring cloud構建微服務平臺時,也但願能作到這一點,然而spring cloud並無提供這個功能。git
在spring cloud的微服務體系中,大可能是使用eureka作爲註冊中心,ribbon作爲負載均衡,hystrix作爲斷路器。可是在國內網絡中卻鮮少關於spring-cloud的接口多版本控制的開源項目,而在國內,spring cloud作爲愈來愈被創業公司認同的微服務框架,多版本控制的需求也愈來愈明顯,因而就有了fm-cloud-bamboo這個多版本控制的項目。github
該項目是在spring-cloud-ribbon的基礎上進行擴展,以實現接口的多個版本的調用及負載均衡,支持feign方式和斷路器(spring-cloud-hystrix)。spring
在spring cloud微服務體系中,服務的請求來源無外乎兩個方面:api
不論網關轉發過來的請求,仍是內部服務調用過來的請求,都須要ribbon作負載均衡,因此能夠擴展ribbon的負載均衡策略從而實現不一樣版本的請求轉發到不一樣的服務實例上。
網絡
網關的轉發過程是:zuul > hystrix > ribbon
內部服務調用的過程有兩種:
RestTemplate > hystrix > ribbon
Feign > hystrix > ribbon架構
而其中hystrix有一個線程池隔離的能力,會建立另外一個線程去請求服務,擁有更好的控制併發訪問量、以及服務降級等能力,可是會出現一個問題,就是線程變量(ThreadLocal)的傳遞問題,這能夠經過com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault對象解決。併發
雖然整個項目實現起來代碼量很多, 可是在接口設計上, 卻只有三個簡單的接口負責數據傳遞,路由的邏輯依然是封裝在實現了IRule接口的實現類中(後面分析)。mvc
BambooRibbonConnectionPoint:
這個接口是負責將bamboo跟ribbon鏈接起來的,將請求的信息, 以及根據業務須要添加的一些路由信息,和獲取請求接口的目標版本,還有觸發執行LoadBanceRequestTrigger等,都是由該接口的實現類DefaultRibbonConnectionPoint負責實現。
app
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的邏輯。
Feign銜接
BambooFeignClient類實現了feign.Client接口, 該類是一個代理類,主要的Feign的調用邏輯依然由被代理的類去執行,在該類中添加了bamboo的邏輯,從而將Feign和bamboo銜接起來。
Zuul銜接
實現兩個ZuulFilter接口,分別是pre和post類型,將bamboo的邏輯加入其中。Pre類型的ZuulFilter獲取請求信息,並執行LoadBalanceRequestTrigger#before方法。Post類型的ZuulFilter執行LoadBalanceRequestTrigger#after方法,並清除存在ThradLocal中的相關信息。
Hystrix銜接
Hystrix實現降級、斷路器等功能,可是在使用線程池隔離時,ThreadLocal存儲的信息如何傳遞下去呢?使用HystrixRequestVariableDefault能夠解決這個問題。能夠查看com.netflix.hystrix.strategy.concurrency包下的HystrixContexSchedulerAction、HystrixContextCallable、HystrixContextRunnable,它們都有一段相同功能的代碼
parentThreadState也是一個HystrixRequestContext對象,它是在hystrix建立線程以前的,也就是處理http請求的線程的HystrixRequestContext對象,咱們通常也是維護這個對象。在使用線程池隔離時,hystrix會將parentThreadState中的信息復到到新線程中,實現跨線程的數據傳遞,從而在後面的邏輯中能夠獲取到parentThreadState中維護的信息,包括ribbon的路由信息。在bamboo中,將一步驟的邏輯放到BambooRequestContext中,將BambooRequestContext實例自己傳遞下去。
Ribbon 路由規則
Bamboo中的BambooZoneAvoidanceRule繼承了ZoneAvoidanceRule,因此它會有ZvoidanceRule的一切特性,在此基礎上,還加入了版本過濾的邏輯,這個邏輯主要是由BambooApiVersionPredicate實現。從BambooRequestContext中獲取請求的接口的版本,若是有該沒有獲取到版本,就返回true;若是有獲取到版本,就獲取服務實例的metadata中的version信息,並進行匹配校驗,返回結果。
在使用多版本控制時,須要修改服務提供方的兩個文件,分別是pom.xml和application.yaml。
在一個名爲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的服務實例
https://github.com/saleson/fm-cloud/tree/master/fm-cloud/fm-cloud-plugins/fm-cloud-bamboo