1. 程式人生 > >基於Spring cloud ribbon實現多版本控制

基於Spring cloud ribbon實現多版本控制

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

在spring cloud的微服務體系中,大多是使用eureka做為註冊中心,ribbon做為負載均衡,hystrix做為斷路器。但是在國內網路中卻鮮少關於spring-cloud的介面多版本控制的開源專案,而在國內,spring cloud做為越來越被創業公司認同的微服務框架,多版本控制的需求也越來越明顯,於是就有了fm-cloud-bamboo這個多版本控制的專案。

多版本控制

該專案是在spring-cloud-ribbon的基礎上進行擴充套件,以實現介面的多個版本的呼叫及負載均衡,支援feign方式和斷路器(spring-cloud-hystrix)。

場景

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

  • 來源1:
    外部請求通過閘道器(zuul)轉發而來。
  • 來源2:
    內部服務之間的呼叫請求。

不論閘道器轉發過來的請求,還是內部服務呼叫過來的請求,都需要ribbon做負載均衡,所以可以擴充套件ribbon的負載均衡策略從而實現不同版本的請求轉發到不同的服務例項上。
這裡寫圖片描述

閘道器的轉發過程是:zuul > hystrix > ribbon
內部服務呼叫的過程有兩種:
RestTemplate > hystrix > ribbon
Feign > hystrix > ribbon

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

程式碼設計

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

這裡寫圖片描述

  • BambooRibbonConnectionPoint:
    這個介面是負責將bamboo跟ribbon連線起來的,將請求的資訊, 以及根據業務需要新增的一些路由資訊,和獲取請求介面的目標版本,還有觸發執行LoadBanceRequestTrigger等,都是由該介面的實現類DefaultRibbonConnectionPoint負責實現。
    這裡寫圖片描述

  • 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。

  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的配置:
這裡寫圖片描述

專案地址