自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Spring Cloud 全鏈路灰度發(fā)布方案

開發(fā) 前端
灰度發(fā)布(又名金絲雀發(fā)布)是指在黑與白之間,能夠平滑過渡的一種發(fā)布方式。在其上可以進(jìn)行A/B testing,即讓一部分用戶繼續(xù)用產(chǎn)品特性A,一部分用戶開始用產(chǎn)品特性B,如果用戶對(duì)B沒有什么反對(duì)意見,那么逐步擴(kuò)大范圍,把所有用戶都遷移到B上面來。

實(shí)際生產(chǎn)中如有需求變更,并不會(huì)直接更新線上服務(wù),最通常的做法便是:切出線上的小部分流量進(jìn)行體驗(yàn)測(cè)試,經(jīng)過測(cè)試后無問題則全面的上線。

這樣做的好處也是非常明顯,一旦出現(xiàn)了BUG,能夠保證大部分的客戶端正常使用。

要實(shí)現(xiàn)這種平滑過渡的方式就需要用到本篇文章介紹到的全鏈路灰度發(fā)布。

圖片圖片

什么是灰度發(fā)布?

灰度發(fā)布(又名金絲雀發(fā)布)是指在黑與白之間,能夠平滑過渡的一種發(fā)布方式。在其上可以進(jìn)行A/B testing,即讓一部分用戶繼續(xù)用產(chǎn)品特性A,一部分用戶開始用產(chǎn)品特性B,如果用戶對(duì)B沒有什么反對(duì)意見,那么逐步擴(kuò)大范圍,把所有用戶都遷移到B上面來?;叶劝l(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時(shí)候就可以發(fā)現(xiàn)、調(diào)整問題,以保證其影響度。

為什么是全鏈路灰度發(fā)布?

在陳某前面一篇文章有介紹到網(wǎng)關(guān)的灰度發(fā)布實(shí)現(xiàn),僅僅是實(shí)現(xiàn)了網(wǎng)關(guān)路由轉(zhuǎn)發(fā)的灰度發(fā)布,如下圖:

圖片圖片

如上圖,網(wǎng)關(guān)灰度發(fā)布實(shí)現(xiàn)的是網(wǎng)關(guān)通過灰度標(biāo)記路由到文章服務(wù)B(灰度服務(wù)),至于從文章服務(wù)B到評(píng)論服務(wù)是通過openFeign內(nèi)部調(diào)用的,默認(rèn)無法實(shí)現(xiàn)灰度標(biāo)記grayTag的透?jìng)?,因此文章服?wù)B最終調(diào)用的是評(píng)論服務(wù)A,并不是評(píng)論服務(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的透?jìng)?,最終調(diào)用評(píng)論服務(wù)B

經(jīng)過以上分析,全鏈路灰度發(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ā)布(透?jìng)骰叶葮?biāo)記grayTag)。

網(wǎng)關(guān)層的灰度路由轉(zhuǎn)發(fā)

本篇文章將使用Ribbon+Spring Cloud Gateway 進(jìn)行改造負(fù)載均衡策略實(shí)現(xiàn)灰度發(fā)布。

實(shí)現(xiàn)思路如下:

  • 在網(wǎng)關(guān)的全局過濾器中根據(jù)業(yè)務(wù)規(guī)則給流量打上灰度標(biāo)記
  • 將灰度標(biāo)記放入請(qǐng)求頭中,傳遞給下游服務(wù)
  • 改造Ribbon負(fù)載均衡策略,根據(jù)流量標(biāo)記從注冊(cè)中心獲取灰度服務(wù)
  • 請(qǐng)求路由轉(zhuǎn)發(fā)

第一個(gè)問題:根據(jù)什么條件打上灰度標(biāo)記?

這個(gè)需要根據(jù)實(shí)際的業(yè)務(wù)需要,比如根據(jù)用戶所在的地區(qū)、使用客戶端類型、隨機(jī)截取流量.....

這里我將直接使用一個(gè)標(biāo)記grayTag,只要客戶端請(qǐng)求頭中攜帶了這個(gè)參數(shù),并且設(shè)置為true,則走灰度發(fā)布邏輯。

請(qǐng)求頭中攜帶:grayTag=true

第二個(gè)問題:為什么要在請(qǐng)求頭中添加灰度標(biāo)記傳遞給下游服務(wù)?

這一步非常關(guān)鍵,實(shí)現(xiàn)灰度標(biāo)記透?jìng)鹘o下游服務(wù)的關(guān)鍵,將灰度標(biāo)記放在請(qǐng)求頭中,下游服務(wù)只需要從請(qǐng)求頭中獲取灰度標(biāo)記便知道是否是灰度發(fā)布,這個(gè)和令牌中繼一個(gè)原理。

第三個(gè)問題:灰度標(biāo)記如何請(qǐng)求隔離?

Spring MVC中的每個(gè)請(qǐng)求都是開啟一個(gè)線程進(jìn)行處理,因此可以將灰度標(biāo)記放置在ThreadLocal中進(jìn)行線程隔離。

第四個(gè)問題:如何知道注冊(cè)中心的服務(wù)哪個(gè)是灰度服務(wù)?

Nacos支持在服務(wù)中配置一些元數(shù)據(jù),可以將灰度標(biāo)記配置在元數(shù)據(jù)中,這樣就能區(qū)分哪些是灰度服務(wù),哪些是正常服務(wù)。

第五個(gè)問題:如何針對(duì)特定的服務(wù)進(jìn)行灰度發(fā)布?

比如我的《Spring Cloud Alibaba實(shí)戰(zhàn)》中涉及的一條調(diào)用鏈路如下圖:

圖片圖片

需求:現(xiàn)在只對(duì)文章服務(wù)、評(píng)論服務(wù)進(jìn)行灰度發(fā)布,其他服務(wù)依然使用線上正在運(yùn)行的服務(wù)

此時(shí)的調(diào)用關(guān)系就變成了下圖:

圖片圖片

我們知道網(wǎng)關(guān)路由中配置的服務(wù)很多,如何只針對(duì)文章服務(wù)進(jìn)行灰度發(fā)布呢?

很簡(jiǎn)單:只需要將自定義的Ribbon灰度發(fā)布規(guī)則只對(duì)文章服務(wù)生效。

這里涉及到Ribbon中的一個(gè)注解:@RibbonClients ,只需要在其中的value屬性指定需要生效的服務(wù)名稱,那么此時(shí)網(wǎng)關(guān)中的配置如下:

@RibbonClients(value ={
        //只對(duì)文章服務(wù)進(jìn)行灰度發(fā)布
        @RibbonClient(value = "article-server",configuration = GrayRuleConfig.class)
} )
@SpringBootApplication
public class GatewayApplication {
   
}

@RibbonClient可以指定多個(gè),這個(gè)注解有如下兩個(gè)屬性:

  • value:指定服務(wù)的名稱,在注冊(cè)中心配置的服務(wù)名稱
  • configuration:自定義的負(fù)載均衡策略,這里是灰度發(fā)布的策略

@RibbonClients其中有一個(gè)屬性defaultConfiguration,一旦使用這個(gè)屬性,那么灰度發(fā)布的策略對(duì)網(wǎng)關(guān)路由中配置的所有服務(wù)都將生效。

第六個(gè)問題:說了這么多,具體如何實(shí)現(xiàn)?

網(wǎng)關(guān)中首先需要定義一個(gè)全局過濾器,偽代碼如下:

public class GlobalGrayFilter implements GlobalFilter{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
         //① 解析請(qǐng)求頭,查看是否存在灰度發(fā)布的請(qǐng)求頭信息,如果存在則將其放置在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)記放入請(qǐng)求頭中
   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);
    }
}

①處的代碼:從請(qǐng)求頭中獲取客戶端傳遞過來的灰度標(biāo)記(這里根據(jù)自己業(yè)務(wù)需要自行更改),判斷是否是灰度發(fā)布

②處的代碼:GrayRequestContextHolder則是自定義的ThreadLocal實(shí)現(xiàn)的線程隔離工具,用來存放灰度標(biāo)記

③處的代碼:將灰度標(biāo)記放置在請(qǐng)求頭中,傳遞給下游微服務(wù),這里是和令牌一個(gè)邏輯。

注意:這個(gè)全局過濾器一定要放在OAuth2.0鑒權(quán)過濾器之前,優(yōu)先級(jí)要調(diào)高

全局過濾器中已經(jīng)將灰度標(biāo)記打上了,放置在GrayRequestContextHolder中,下面只需要改造Ribbon的負(fù)載均衡的策略去注冊(cè)中心選擇灰度服務(wù)。

創(chuàng)建GrayRule,代碼如下:

/**
 * 灰度發(fā)布的規(guī)則
 */
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;
        }
    }
}

邏輯很簡(jiǎn)單,如下:

  • 獲取灰度標(biāo)記
  • 從Nacos注冊(cè)中心獲取灰度服務(wù)和正常服務(wù)
  • 根據(jù)灰度標(biāo)記去判斷,如果灰度發(fā)布則選擇特定的灰度服務(wù)進(jìn)行轉(zhuǎn)發(fā)

定義一個(gè)配置類,注入改造的灰度策略GrayRule,如下:

/**
 * 灰度部署的負(fù)載規(guī)則配置類
 * 注意:這個(gè)類一定不要被Spring Boot 掃描進(jìn)入IOC容器中,一旦掃描進(jìn)入則對(duì)全部的服務(wù)都將生效
 */
public class GrayRuleConfig {
    @Bean
    public GrayRule grayRule(){
        return new GrayRule();
    }
}

注意:這個(gè)GrayRuleConfig不能被掃描進(jìn)入IOC容器,一旦掃描進(jìn)入則全局生效

因?yàn)椴粌H僅網(wǎng)關(guān)需要用到這個(gè)灰度發(fā)布策略,凡是涉及到OpenFeign調(diào)用的微服務(wù)如果需要配置灰度發(fā)布都需要用到,因此這里陳某定義了一個(gè)公用的gray-starter。

經(jīng)過上述步驟網(wǎng)關(guān)的灰度發(fā)布則已經(jīng)配置完成,此時(shí)只需要通過@RibbonClients指定對(duì)應(yīng)哪個(gè)服務(wù)灰度發(fā)布。

openFeign透?jìng)骰叶葮?biāo)記

上面在介紹網(wǎng)關(guān)的灰度發(fā)布配置時(shí),是將灰度標(biāo)記(grayTag=true)放在了請(qǐng)求頭中,因此在下游服務(wù)中需要做的就只是從請(qǐng)求頭中將灰度標(biāo)記取出來,然后將其存入GrayRequestContextHolder上下文中。

這樣一來下游服務(wù)中的GrayRule則能從GrayRequestContextHolder獲取到灰度標(biāo)記,從注冊(cè)中心獲取灰度服務(wù)進(jìn)行調(diào)用了。

問題來了:如何從請(qǐng)求頭中取出灰度標(biāo)記?

在介紹OAuth2.0相關(guān)知識(shí)時(shí),曾經(jīng)出過一篇文章:實(shí)戰(zhàn)!openFeign如何實(shí)現(xiàn)全鏈路JWT令牌信息不丟失?

其中介紹了令牌中繼的解決方案,使用的是openFeign的請(qǐng)求攔截器去配置請(qǐng)求頭信息。

圖片圖片

如上圖:openFeign在調(diào)用時(shí)并不是用的原先的Request,而是內(nèi)部新建了一個(gè)Request,其中復(fù)制了請(qǐng)求的URL、請(qǐng)求參數(shù)一些信息,但是請(qǐng)求頭并沒有復(fù)制過去,因此openFeign調(diào)用會(huì)丟失請(qǐng)求頭中的信息。

但是可以通過實(shí)現(xiàn)RequestInterceptor將原先的請(qǐng)求頭給復(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è)置請(qǐng)求頭到新的Request中
            template.header(entry.getKey(), entry.getValue());
        }
    }

    /**
     * 獲取原請(qǐng)求頭
     */
    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)記的請(qǐng)求頭透?jìng)鹘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;
    }
}

①處的代碼:從請(qǐng)求頭中獲取灰度發(fā)布的標(biāo)記,設(shè)置到GrayRequestContextHolder上下文中

②處的代碼:將這個(gè)請(qǐng)求頭設(shè)置到新的Request中,繼續(xù)向下游服務(wù)傳遞。

其實(shí)配置一下RequestInterceptor就已經(jīng)完成了,關(guān)于灰度發(fā)布策略只需要復(fù)用網(wǎng)關(guān)的GrayRule

注意:也需要使用@RibbonClients注解去標(biāo)注文章服務(wù)調(diào)用的哪些服務(wù)需要灰度發(fā)布。

代碼如下:

@RibbonClients(value = {
        //指定對(duì)comments這個(gè)服務(wù)開啟灰度部署
        @RibbonClient(value = "comments",configuration = GrayRuleConfig.class)
})
public class ArticleApplication {}

Nacos中服務(wù)如何做灰度標(biāo)記

其實(shí)很簡(jiǎn)單,分為兩種:

1、在配置文件中指定,如下:

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          ## 灰度標(biāo)記
          grayTag: true

2、在Nacos中動(dòng)態(tài)的指定灰度標(biāo)記

圖片圖片

配置完成之后,在客戶端請(qǐng)求的時(shí)候只需要攜帶grayTag=true這個(gè)請(qǐng)求頭即可調(diào)用灰度服務(wù)。

總結(jié)

微服務(wù)中全鏈路灰度發(fā)布方案其實(shí)很簡(jiǎn)單,重要的就是灰度打標(biāo),整體流程如下:

  1. 網(wǎng)關(guān)中通過全局過濾器實(shí)現(xiàn)灰度打標(biāo),將灰度標(biāo)記放入請(qǐng)求頭中傳遞給下游服務(wù)
  2. 網(wǎng)關(guān)通過自定義的負(fù)載均衡策略,從注冊(cè)中心獲取灰度服務(wù),進(jìn)行轉(zhuǎn)發(fā)
  3. 在openFeign調(diào)用時(shí)需要從請(qǐng)求頭中獲取灰度標(biāo)記,放入上下文中
  4. openFeign調(diào)用同樣是根據(jù)自定義的負(fù)載均衡策略從注冊(cè)中心獲取灰度服務(wù),進(jìn)行調(diào)用。
責(zé)任編輯:武曉燕 來源: 碼猿技術(shù)專欄
相關(guān)推薦

2025-03-04 08:53:10

2024-01-05 00:29:36

全鏈路灰度發(fā)布云原生

2021-11-18 10:01:00

Istio 全鏈路灰度微服務(wù)框架

2023-11-21 09:35:49

全量部署微服務(wù)

2023-11-13 10:41:44

Spring微服務(wù)

2022-08-31 22:25:53

微服務(wù)架構(gòu)DevOPs

2023-02-20 10:13:00

灰度發(fā)布實(shí)現(xiàn)

2023-11-14 09:04:15

用戶節(jié)點(diǎn)不可用

2009-10-29 16:46:12

LinkProof多鏈路負(fù)載均衡

2018-07-03 15:56:59

騰訊

2023-01-30 22:34:44

Node.js前端

2022-01-05 08:27:17

C++全鏈路追蹤

2022-12-26 11:57:41

數(shù)據(jù)庫治理

2022-01-19 18:31:54

前端灰度代碼

2023-10-16 23:43:52

云原生可觀測(cè)性

2021-11-08 14:10:37

分布式Spring鏈路

2021-06-04 08:48:46

Spring ClouMaven Centr版本

2021-07-20 10:22:11

京東云零售云

2022-12-05 09:08:12

微服務(wù)灰度發(fā)布

2022-04-27 10:53:34

web優(yōu)化性能
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)