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

啓動服務後,訪問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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章