SpringCloud的全鏈路灰度發(fā)布方案詳解
1.什么是灰度發(fā)布
灰度發(fā)布(又名金絲雀發(fā)布)是指在黑與白之間,能夠平滑過渡的一種發(fā)布方式。
在上面可以進(jìn)行A/B測試, 即讓一部分用戶繼續(xù)用產(chǎn)品特性A, 一部分用戶使用產(chǎn)品特性B, 如果用戶B沒有反對意見的話, 那么會(huì)逐漸擴(kuò)大范圍, 把用戶都往特性B 上改。
灰度發(fā)布可以保證系統(tǒng)的穩(wěn)定性, 在初始灰度的時(shí)候可以發(fā)現(xiàn), 調(diào)整問題, 保證其影響度。
2.什么是全鏈路灰度發(fā)布
網(wǎng)關(guān)灰度發(fā)布:
網(wǎng)關(guān)通過灰度標(biāo)記路由到文章服務(wù)B, 至于從文章服務(wù)B到評論服務(wù)是通過openfeign內(nèi)部調(diào)用的, 默認(rèn)無法實(shí)現(xiàn)灰度標(biāo)記grayTag的透傳, 因此文章服務(wù)B 最終調(diào)用的是評論服務(wù)A, 并不是評論服務(wù)B。

全鏈路灰度發(fā)布需要實(shí)現(xiàn)的是:
- 網(wǎng)關(guān)通過灰度標(biāo)記將部分流量轉(zhuǎn)發(fā)給文章服務(wù)B
- 文章服務(wù)B能夠?qū)崿F(xiàn)灰度標(biāo)記grayTag的透傳,最終調(diào)用評論服務(wù)B
全鏈路灰度發(fā)布需要實(shí)現(xiàn)兩個(gè)點(diǎn):
- 網(wǎng)關(guān)路由轉(zhuǎn)發(fā)實(shí)現(xiàn)灰度發(fā)布
- 服務(wù)內(nèi)部通過openFeign調(diào)用實(shí)現(xiàn)灰度發(fā)布(透傳灰度標(biāo)記grayTag)。
3.網(wǎng)關(guān)層的灰度路由轉(zhuǎn)發(fā)
Ribbon+Spring Cloud Gateway 進(jìn)行改造負(fù)載均衡策略實(shí)現(xiàn)灰度發(fā)布.
- 在網(wǎng)關(guān)的全局過濾器中根據(jù)業(yè)務(wù)規(guī)則給流量打上灰度標(biāo)記
- 將灰度標(biāo)記放入請求頭中,傳遞給下游服務(wù)
- 改造Ribbon負(fù)載均衡策略,根據(jù)流量標(biāo)記從注冊中心獲取灰度服務(wù)
- 請求路由轉(zhuǎn)發(fā)
根據(jù)什么條件打上灰度標(biāo)記
這個(gè)需要根據(jù)實(shí)際的業(yè)務(wù)需要,比如根據(jù)用戶所在的地區(qū)、使用客戶端類型、隨機(jī)截取流量
這里我將直接使用一個(gè)標(biāo)記grayTag,只要客戶端請求頭中攜帶了這個(gè)參數(shù),并且設(shè)置為true,則走灰度發(fā)布邏輯。
grayTag=true
為什么要在請求頭中添加灰度標(biāo)記傳遞給下游服務(wù)
這一步非常關(guān)鍵,實(shí)現(xiàn)灰度標(biāo)記透傳給下游服務(wù)的關(guān)鍵,將灰度標(biāo)記放在請求頭中,下游服務(wù)只需要從請求頭中獲取灰度標(biāo)記便知道是否是灰度發(fā)布,這個(gè)和令牌中繼一個(gè)原理。
灰度標(biāo)記如何請求隔離
Spring MVC中的每個(gè)請求都是開啟一個(gè)線程進(jìn)行處理,因此可以將灰度標(biāo)記放置在 ThreadLocal 中進(jìn)行線程隔離。
如何知道注冊中心的服務(wù)哪個(gè)是灰度服務(wù)
Nacos支持在服務(wù)中配置一些元數(shù)據(jù),可以將灰度標(biāo)記配置在元數(shù)據(jù)中,這樣就能區(qū)分哪些是灰度服務(wù),哪些是正常服務(wù)。
如何針對特定的服務(wù)進(jìn)行灰度發(fā)布

我們知道網(wǎng)關(guān)路由中配置的服務(wù)很多,如何只針對文章服務(wù)進(jìn)行灰度發(fā)布
只需要將自定義的Ribbon灰度發(fā)布規(guī)則只對文章服務(wù)生效 利用注解RibbonClients, 指定特定微服務(wù)
@RibbonClients(value ={
//只對文章服務(wù)進(jìn)行灰度發(fā)布
@RibbonClient(value = "article-server",configuration = GrayRuleConfig.class)
} )
@SpringBootApplication
public class GatewayApplication {
}@RibbonClients其中有一個(gè)屬性defaultConfiguration,一旦使用這個(gè)屬性,那么灰度發(fā)布的策略對網(wǎng)關(guān)路由中配置的所有服務(wù)都將生效。
具體如何實(shí)現(xiàn)
網(wǎng)關(guān)中首先需要定義一個(gè)全局過濾器:
public class GlobalGrayFilter implements GlobalFilter{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//① 解析請求頭,查看是否存在灰度發(fā)布的請求頭信息,如果存在則將其放置在ThreadLocal中
HttpHeaders headers = exchange.getRequest().getHeaders();
if (headers.containsKey(GrayConstant.GRAY_HEADER)){
String gray = headers.getFirst(GrayConstant.GRAY_HEADER);
if (StrUtil.equals(gray,GrayConstant.GRAY_VALUE)){
//②設(shè)置灰度標(biāo)記
GrayRequestContextHolder.setGrayTag(true);
}
}
//③ 將灰度標(biāo)記放入請求頭中
ServerHttpRequest tokenRequest = exchange.getRequest().mutate()
//將灰度標(biāo)記傳遞過去
.header(GrayConstant.GRAY_HEADER,GrayRequestContextHolder.getGrayTag().toString())
.build();
ServerWebExchange build = exchange.mutate().request(tokenRequest).build();
return chain.filter(build);
}
}GrayRule:
public class GrayRule extends ZoneAvoidanceRule {
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
@Override
public Server choose(Object key) {
try {
//從ThreadLocal中獲取灰度標(biāo)記
boolean grayTag = GrayRequestContextHolder.getGrayTag().get();
//獲取所有可用服務(wù)
List<Server> serverList = this.getLoadBalancer().getReachableServers();
//灰度發(fā)布的服務(wù)
List<Server> grayServerList = new ArrayList<>();
//正常的服務(wù)
List<Server> normalServerList = new ArrayList<>();
for(Server server : serverList) {
NacosServer nacosServer = (NacosServer) server;
//從nacos中獲取元素劇進(jìn)行匹配
if(nacosServer.getMetadata().containsKey(GrayConstant.GRAY_HEADER)
&& nacosServer.getMetadata().get(GrayConstant.GRAY_HEADER).equals(GrayConstant.GRAY_VALUE)) {
grayServerList.add(server);
} else {
normalServerList.add(server);
}
}
//如果被標(biāo)記為灰度發(fā)布,則調(diào)用灰度發(fā)布的服務(wù)
if(grayTag) {
return originChoose(grayServerList,key);
} else {
return originChoose(normalServerList,key);
}
} finally {
//清除灰度標(biāo)記
GrayRequestContextHolder.remove();
}
}
private Server originChoose(List<Server> noMetaServerList, Object key) {
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(noMetaServerList, key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}- 獲取灰度標(biāo)記
- 從Nacos注冊中心獲取灰度服務(wù)和正常服務(wù)
- 根據(jù)灰度標(biāo)記去判斷,如果灰度發(fā)布則選擇特定的灰度服務(wù)進(jìn)行轉(zhuǎn)發(fā)
定義一個(gè)配置類,注入改造的灰度策略GrayRule
public class GrayRuleConfig {
@Bean
public GrayRule grayRule(){
return new GrayRule();
}
}這個(gè)GrayRuleConfig不能被掃描進(jìn)入IOC容器,一旦掃描進(jìn)入則全局生效
4.openFeign透傳灰度標(biāo)記
如何從請求頭中取出灰度標(biāo)記
可以通過實(shí)現(xiàn)RequestInterceptor將原先的請求頭給復(fù)制過去
@Component
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
HttpServletRequest httpServletRequest = RequestContextUtils.getRequest();
Map<String, String> headers = getHeaders(httpServletRequest);
for (Map.Entry<String, String> entry : headers.entrySet()) {
//② 設(shè)置請求頭到新的Request中
template.header(entry.getKey(), entry.getValue());
}
}
/**
* 獲取原請求頭
*/
private Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
//將灰度標(biāo)記的請求頭透傳給下個(gè)服務(wù)
if (StrUtil.equals(GrayConstant.GRAY_HEADER,key)&&Boolean.TRUE.toString().equals(value)){
//① 保存灰度發(fā)布的標(biāo)記
GrayRequestContextHolder.setGrayTag(true);
map.put(key, value);
}
}
}
return map;
}
}也需要使用@RibbonClients注解去標(biāo)注文章服務(wù)調(diào)用的哪些服務(wù)需要灰度發(fā)布。
@RibbonClients(value = {
//指定對comments這個(gè)服務(wù)開啟灰度部署
@RibbonClient(value = "comments",configuration = GrayRuleConfig.class)
})
public class ArticleApplication {}5.Nacos中服務(wù)如何做灰度標(biāo)記
配置文件:
spring:
cloud:
nacos:
discovery:
metadata:
## 灰度標(biāo)記
grayTag: true在Nacos中動(dòng)態(tài)的指定灰度標(biāo)記

配置完成之后,在客戶端請求的時(shí)候只需要攜帶grayTag=true這個(gè)請求頭即可調(diào)用灰度服務(wù)。
6.總結(jié)
- 網(wǎng)關(guān)中通過全局過濾器實(shí)現(xiàn)灰度打標(biāo),將灰度標(biāo)記放入請求頭中傳遞給下游服務(wù)
- 網(wǎng)關(guān)通過自定義的負(fù)載均衡策略,從注冊中心獲取灰度服務(wù),進(jìn)行轉(zhuǎn)發(fā)
- 在openFeign調(diào)用時(shí)需要從請求頭中獲取灰度標(biāo)記,放入上下文中
- openFeign調(diào)用同樣是根據(jù)自定義的負(fù)載均衡策略從注冊中心獲取灰度服務(wù),進(jìn)行調(diào)用。
- Nacos進(jìn)行灰度服務(wù)調(diào)用
到此這篇關(guān)于SpringCloud的全鏈路灰度發(fā)布方案詳解的文章就介紹到這了,更多相關(guān)SpringCloud全鏈路灰度發(fā)布內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring Cloud 優(yōu)雅下線以及灰度發(fā)布實(shí)現(xiàn)
- SpringCloud實(shí)現(xiàn)灰度發(fā)布的方法步驟
- springcloud+nacos實(shí)現(xiàn)灰度發(fā)布示例詳解
- 關(guān)于SpringCloud灰度發(fā)布的實(shí)現(xiàn)
- SpringCloud灰度發(fā)布的設(shè)計(jì)與實(shí)現(xiàn)詳解
- Spring?Cloud實(shí)現(xiàn)灰度發(fā)布的示例代碼
- SpringCloud實(shí)現(xiàn)全鏈路灰度發(fā)布的示例詳解
- Spring Cloud Gateway實(shí)現(xiàn)灰度發(fā)布方案
相關(guān)文章
Spring Boot 添加MySQL數(shù)據(jù)庫及JPA實(shí)例
本篇文章主要介紹了Spring Boot 添加MySQL數(shù)據(jù)庫及JPA,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03
Java中一個(gè)線程執(zhí)行死循環(huán)有什么后果
這篇文章主要為大家詳細(xì)介紹了Java中一個(gè)線程執(zhí)行死循環(huán)有什么后果,當(dāng)一個(gè)線程在執(zhí)行死循環(huán)時(shí)會(huì)影響另外一個(gè)線程嗎,下面為大家揭曉2016-05-05
springboot多線程并發(fā)執(zhí)行任務(wù)
本文主要介紹了springboot多線程并發(fā)執(zhí)行任務(wù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-03-03
JAVA如何自動(dòng)下載SSL證書并導(dǎo)入到本地
這篇文章主要介紹了JAVA如何自動(dòng)下載SSL證書并導(dǎo)入到本地問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
解決Java?API不能遠(yuǎn)程訪問HBase的問題
這篇文章主要介紹了解決Java?API不能遠(yuǎn)程訪問HBase的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06

