微服務的灰度發(fā)布就該這樣設計
實際生產中如有需求變更,并不會直接更新線上服務,最通常的做法便是:切出線上的小部分流量進行體驗測試,經過測試后無問題則全面的上線。
這樣做的好處也是非常明顯,一旦出現(xiàn)了BUG,能夠保證大部分的客戶端正常使用。
要實現(xiàn)這種平滑過渡的方式就需要用到本篇文章介紹到的全鏈路灰度發(fā)布。
什么是灰度發(fā)布?
灰度發(fā)布(又名金絲雀發(fā)布)是指在黑與白之間,能夠平滑過渡的一種發(fā)布方式。在其上可以進行A/B testing,即讓一部分用戶繼續(xù)用產品特性A,一部分用戶開始用產品特性B,如果用戶對B沒有什么反對意見,那么逐步擴大范圍,把所有用戶都遷移到B上面來?;叶劝l(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時候就可以發(fā)現(xiàn)、調整問題,以保證其影響度。
為什么是全鏈路灰度發(fā)布?
在陳某前面一篇文章有介紹到網關的灰度發(fā)布實現(xiàn),僅僅是實現(xiàn)了網關路由轉發(fā)的灰度發(fā)布,如下圖:
如上圖,網關灰度發(fā)布實現(xiàn)的是網關通過灰度標記路由到文章服務B(灰度服務),至于從文章服務B到評論服務是通過openFeign內部調用的,默認無法實現(xiàn)灰度標記grayTag的透傳,因此文章服務B最終調用的是評論服務A,并不是評論服務B。
全鏈路灰度發(fā)布需要實現(xiàn)的是:
- 網關通過灰度標記將部分流量轉發(fā)給文章服務B
- 文章服務B能夠實現(xiàn)灰度標記grayTag的透傳,最終調用評論服務B
經過以上分析,全鏈路灰度發(fā)布需要實現(xiàn)兩個點:
- 網關路由轉發(fā)實現(xiàn)灰度發(fā)布
- 服務內部通過openFeign調用實現(xiàn)灰度發(fā)布(透傳灰度標記grayTag)。
網關層的灰度路由轉發(fā)
本篇文章將使用Ribbon+Spring Cloud Gateway 進行改造負載均衡策略實現(xiàn)灰度發(fā)布。
實現(xiàn)思路如下:
- 在網關的全局過濾器中根據(jù)業(yè)務規(guī)則給流量打上灰度標記
- 將灰度標記放入請求頭中,傳遞給下游服務
- 改造Ribbon負載均衡策略,根據(jù)流量標記從注冊中心獲取灰度服務
- 請求路由轉發(fā)
第一個問題:根據(jù)什么條件打上灰度標記?
這個需要根據(jù)實際的業(yè)務需要,比如根據(jù)用戶所在的地區(qū)、使用客戶端類型、隨機截取流量.....
這里我將直接使用一個標記grayTag,只要客戶端請求頭中攜帶了這個參數(shù),并且設置為true,則走灰度發(fā)布邏輯。
請求頭中攜帶:grayTag=true
第二個問題:為什么要在請求頭中添加灰度標記傳遞給下游服務?
這一步非常關鍵,實現(xiàn)灰度標記透傳給下游服務的關鍵,將灰度標記放在請求頭中,下游服務只需要從請求頭中獲取灰度標記便知道是否是灰度發(fā)布,這個和令牌中繼一個原理。
第三個問題:灰度標記如何請求隔離?
Spring MVC中的每個請求都是開啟一個線程進行處理,因此可以將灰度標記放置在ThreadLocal中進行線程隔離。
第四個問題:如何知道注冊中心的服務哪個是灰度服務?
Nacos支持在服務中配置一些元數(shù)據(jù),可以將灰度標記配置在元數(shù)據(jù)中,這樣就能區(qū)分哪些是灰度服務,哪些是正常服務。
第五個問題:如何針對特定的服務進行灰度發(fā)布?
比如我的《Spring Cloud Alibaba實戰(zhàn)》中涉及的一條調用鏈路如下圖:
需求:現(xiàn)在只對文章服務、評論服務進行灰度發(fā)布,其他服務依然使用線上正在運行的服務
此時的調用關系就變成了下圖:
我們知道網關路由中配置的服務很多,如何只針對文章服務進行灰度發(fā)布呢?
很簡單:只需要將自定義的Ribbon灰度發(fā)布規(guī)則只對文章服務生效。
這里涉及到Ribbon中的一個注解:@RibbonClients ,只需要在其中的value屬性指定需要生效的服務名稱,那么此時網關中的配置如下:
@RibbonClient可以指定多個,這個注解有如下兩個屬性:
- value:指定服務的名稱,在注冊中心配置的服務名稱
- configuration:自定義的負載均衡策略,這里是灰度發(fā)布的策略
@RibbonClients其中有一個屬性defaultConfiguration,一旦使用這個屬性,那么灰度發(fā)布的策略對網關路由中配置的所有服務都將生效。
第六個問題:說了這么多,具體如何實現(xiàn)?
網關中首先需要定義一個全局過濾器,偽代碼如下:
①處的代碼:從請求頭中獲取客戶端傳遞過來的灰度標記(這里根據(jù)自己業(yè)務需要自行更改),判斷是否是灰度發(fā)布
②處的代碼:GrayRequestContextHolder則是自定義的ThreadLocal實現(xiàn)的線程隔離工具,用來存放灰度標記
③處的代碼:將灰度標記放置在請求頭中,傳遞給下游微服務,這里是和令牌一個邏輯。
注意:這個全局過濾器一定要放在OAuth2.0鑒權過濾器之前,優(yōu)先級要調高
全局過濾器中已經將灰度標記打上了,放置在GrayRequestContextHolder中,下面只需要改造Ribbon的負載均衡的策略去注冊中心選擇灰度服務。
創(chuàng)建GrayRule,代碼如下:
邏輯很簡單,如下:
- 獲取灰度標記
- 從Nacos注冊中心獲取灰度服務和正常服務
- 根據(jù)灰度標記去判斷,如果灰度發(fā)布則選擇特定的灰度服務進行轉發(fā)
定義一個配置類,注入改造的灰度策略GrayRule,如下:
注意:這個GrayRuleConfig不能被掃描進入IOC容器,一旦掃描進入則全局生效
因為不僅僅網關需要用到這個灰度發(fā)布策略,凡是涉及到OpenFeign調用的微服務如果需要配置灰度發(fā)布都需要用到,因此這里陳某定義了一個公用的gray-starter。
經過上述步驟網關的灰度發(fā)布則已經配置完成,此時只需要通過@RibbonClients指定對應哪個服務灰度發(fā)布。
openFeign透傳灰度標記
上面在介紹網關的灰度發(fā)布配置時,是將灰度標記(grayTag=true)放在了請求頭中,因此在下游服務中需要做的就只是從請求頭中將灰度標記取出來,然后將其存入GrayRequestContextHolder上下文中。
這樣一來下游服務中的GrayRule則能從GrayRequestContextHolder獲取到灰度標記,從注冊中心獲取灰度服務進行調用了。
問題來了:如何從請求頭中取出灰度標記?
在介紹OAuth2.0相關知識時,曾經出過一篇文章:實戰(zhàn)!openFeign如何實現(xiàn)全鏈路JWT令牌信息不丟失?
其中介紹了令牌中繼的解決方案,使用的是openFeign的請求攔截器去配置請求頭信息。
如上圖:openFeign在調用時并不是用的原先的Request,而是內部新建了一個Request,其中復制了請求的URL、請求參數(shù)一些信息,但是請求頭并沒有復制過去,因此openFeign調用會丟失請求頭中的信息。
但是可以通過實現(xiàn)RequestInterceptor將原先的請求頭給復制過去,代碼如下:
①處的代碼:從請求頭中獲取灰度發(fā)布的標記,設置到GrayRequestContextHolder上下文中
②處的代碼:將這個請求頭設置到新的Request中,繼續(xù)向下游服務傳遞。
其實配置一下RequestInterceptor就已經完成了,關于灰度發(fā)布策略只需要復用網關的GrayRule
注意:也需要使用@RibbonClients注解去標注文章服務調用的哪些服務需要灰度發(fā)布。
代碼如下:
Nacos中服務如何做灰度標記
其實很簡單,分為兩種:
1、在配置文件中指定,如下:
2、在Nacos中動態(tài)的指定灰度標記
配置完成之后,在客戶端請求的時候只需要攜帶grayTag=true這個請求頭即可調用灰度服務。
總結
微服務中全鏈路灰度發(fā)布方案其實很簡單,重要的就是灰度打標,整體流程如下:
網關中通過全局過濾器實現(xiàn)灰度打標,將灰度標記放入請求頭中傳遞給下游服務。
網關通過自定義的負載均衡策略,從注冊中心獲取灰度服務,進行轉發(fā)。
在openFeign調用時需要從請求頭中獲取灰度標記,放入上下文中。
openFeign調用同樣是根據(jù)自定義的負載均衡策略從注冊中心獲取灰度服務,進行調用。