在微服务中如何实现全链路的金丝雀发布?

发布时间:2023年12月29日

目录

1. 什么金丝雀发布?它有什么用?

2.如何实现全链路的金丝雀发布

2.1 负载均衡模块

2.2?网关模块

2.3 服务模块

2.3.1 注册为灰色服务实例

2.3.2 设置负载均衡器

2.3.3 传递灰度发布标签

2.4 其他代码

2.4.1 其他业务代码

2.4.2 pom.xml 关键代码

2.4.3 application.yml 相关代码

3. 验证全链路金丝雀发布的实现效果


1. 什么金丝雀发布?它有什么用?

????????金丝雀发布(Canary Release,也称为灰度发布)是指在软件或服务发布过程中,将新版本的功能或服务以较小的比例引入到生产环境中,仅向部分用户或节点提供新功能的一种发布策略。

????????而在传统的全量发布中,新版本将会立即部署到所有用户或节点上。金丝雀发布的核心思想是逐步推进,监测新版本的稳定性和性能,以确保在全面发布之前能够解决潜在的问题。

假设某款在线多人游戏决定上线一个全新的多人模式功能。在传统的全量发布中,它会将这个新功能立即部署到所有玩家的游戏客户端中,然后在全面发布后等待用户的反馈。而使用金丝雀发布,它的发布流程就变成了这样:

  1. 内测阶段

    • 游戏开发团队首先将新多人模式功能引入到游戏的内测版本中,但仅向少数特定的内测玩家提供。
    • 这些内测玩家是经过筛选或自愿参与的,他们了解可能会遇到问题,并愿意分享反馈。
    • 内测玩家可以在一定时间内使用新功能,并向开发团队报告问题、提供建议和反馈意见。
  2. 监测和改进

    • 游戏开发团队密切关注内测玩家的游戏体验、性能和稳定性。
    • 如果在内测期间发现了问题,团队可以及时进行修复和改进,并确保新功能在全面发布前达到高质量标准。
  3. 逐步扩展

    • 在确认新功能在内测阶段表现良好后,开发团队逐步扩展金丝雀发布的范围。
    • 他们可以将新功能提供给更多的玩家,但仍然限制在一小部分,比如10%的玩家。
    • 这一阶段被称为金丝雀发布的初期阶段,新功能仅对一小部分用户可见。
  4. 全面发布

    • 在经过一系列逐步扩展和监测后,开发团队最终将新多人模式功能发布给了所有玩家。
    • 此时,新功能已经通过了多轮测试和改进,用户体验较好,且潜在问题得到了解决。

从上述游戏上线新功能的金丝雀发布流程中能看出,金丝雀发布相比传统的全量发布有以下好处:

  • 逐步引入新功能,降低全面发布的风险。
  • 及时获取内测玩家的反馈,加速问题的修复。
  • 确保新功能在全面发布时达到高质量标准。
  • 提供更好的用户体验,减少潜在问题对所有用户的影响。

2.如何实现全链路的金丝雀发布

Spring Cloud 全链路金丝雀发布的实现思路图如下:

?金丝雀发布的具体实现步骤大致分为以下几步:

  1. 前端程序在灰度测试的用户 Header 头中打上标签,例如在 Header 中添加 "gray-tag:true",表示要访问灰度服务,其他则为正式服务。(前端)
  2. 在负载均衡器 Spring Cloud LoadBalancer 中,拿到 Header 中的 "gray-tag" 进行判断,如果此标签不为空,并且等于 "true" 的话,则表示要访问灰度发布的服务,否则只访问正式的服务。(客户端负载均衡)
  3. 在网关 Spring Cloud Gateway 中,将 Header 标签 "gray-tag:true" 传递到下一个调用的服务。(网关)
  4. 后续的服务调用中,还需要做两件事:(内部服务)
    1. 在 Spring Cloud LoadBalancer 中,判断灰度发布标签,将请求分发给对应的服务。
    2. 在内部的服务调用过程中,传递灰度发布标签。

由此可见,全链路的灰色发布只需要解决两个大问题:

1. Gateway 中的问题

  • Gateway 的调度转发问题。
  • Gateway 灰色发布标签的传递问题。

2. 内部服务中的问题

  • 服务的灰度转发问题。
  • 服务内部灰色发布标签的传递问题。

【金丝雀发布代码案例】

根据 Spring Cloud 全链路金丝雀发布的实现思路图来编写代码,

创建 Spring 多模块项目,然后准备 7 个模块:user-service、new-user-service、order-service、log-service、new-log-service、gray-loadbalancer、gateway。

2.1 负载均衡模块

操作 gray-loadblancer 模块,这个模块作为一个公共模块,可以不需要启动类。

① 自定义负载均衡器

这里可以参考默认的轮询负载均衡策略里面的实现:

  1. 实现 ReactorServiceInstanceLoadBalancer 接口
  2. 复制其他代码,修改关键地方的类名
  3. 重写 getInstanceResponse 方法
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 定义灰度发布的负载均衡算法
 */

public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private static final Log log = LogFactory.getLog(GrayLoadBalancer.class);
    private final String serviceId;
    private AtomicInteger position;  // 下标
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public GrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.position = new AtomicInteger(new Random().nextInt(1000));
    }

    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next().map((serviceInstances) -> {
            return this.processInstanceResponse(supplier, serviceInstances,request);
        });
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
                                                              List<ServiceInstance> serviceInstances,
                                                              Request request) {
        Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances,request);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());
        }

        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances,Request request) {
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + this.serviceId);
            }

            return new EmptyResponse();
        } else {
            // 灰度业务的实现

            // 0.得到 Request 对象 [通过方法参数的传递拿到此对象]

            // 1.从 Request 对象的 Header 中得到灰度标签
            RequestDataContext requestDataContext = (RequestDataContext) request.getContext();
            HttpHeaders headers = requestDataContext.getClientRequest().getHeaders();
            // 获取名为 "gray-tag" 的头部信息的值
            List<String> headersList = headers.get(GlobalVariable.GRAY_TAGE);
            if (headersList != null && !headersList.isEmpty() &&
                    headersList.get(0).equals("true")) { // 灰度请求
                // 灰度列表
                List<ServiceInstance> grayList = instances.stream().
                        filter(i -> i.getMetadata().get(GlobalVariable.GRAY_TAGE) != null &&
                                i.getMetadata().get(GlobalVariable.GRAY_TAGE).equals("true")).
                        toList();
                if(!grayList.isEmpty()) {
                    instances = grayList;
                }
            } else {  // 正式节点
                // 2.将实例进行进行分组 【生产服务列表|灰度服务列表】
                instances = instances.stream(). // 取反
                        filter(i -> i.getMetadata().get(GlobalVariable.GRAY_TAGE) == null ||
                                !i.getMetadata().get(GlobalVariable.GRAY_TAGE).equals("true")).
                        toList();
            }
            // 3.使用负载均衡算法选择上一步列表中的某个节点
            int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
            ServiceInstance instance = instances.get(pos % instances.size());
            return new DefaultResponse(instance);
        }
    }
}
/**
 * 全局变量
 */
public class GlobalVariable {
    public static final String GRAY_TAGE = "gray-tag";
}

② 封装负载均衡器

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

/**
 * 封装灰度发布负载均衡器
 */
public class GrayLoadBalancerConfig {
    @Bean
    public ReactorLoadBalancer<ServiceInstance> grayLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        // 灰度发布的负载均衡器
        return new GrayLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name,
                        ServiceInstanceListSupplier.class), name);
    }
}

2.2?网关模块

通过全局过滤器,来判断或设置灰度标识,

import com.loadbalancer.gray.GlobalVariable;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class LoadBalancerFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 得到 request、response 对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 判断灰度标签
        String tag = request.getQueryParams().getFirst(GlobalVariable.GRAY_TAGE);
        if(tag != null) {
            // 设置灰度标识
            response.getHeaders().set(GlobalVariable.GRAY_TAGE,"true");
        }
        // 此步骤正常,执行下一步
        return chain.filter(exchange);
    }
}

2.3 服务模块

2.3.1 注册为灰色服务实例

将测试版的服务,注册为灰色服务实例:new-user-service、new-log-service

spring:
  application:
    name: user-service-gray
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        metadata: {"gray-tag": "true"}  # 金丝雀标识
server:
  port: 0

2.3.2 设置负载均衡器

在服务启动类上设置负载均衡和开启 OpenFeign 服务:user-service、new-user-service、order-service。

import com.loadbalancer.gray.GrayLoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
@LoadBalancerClients(defaultConfiguration =
        GrayLoadBalancerConfig.class)
public class UserServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }

}

?在网关模块中设置负载均衡,

import com.loadbalancer.gray.GrayLoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;

@SpringBootApplication
@LoadBalancerClients(defaultConfiguration =
        GrayLoadBalancerConfig.class)
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }

}

2.3.3 传递灰度发布标签

在服务内部传递灰度发布标签:user-service、new-user-service、order-service

方式一:传递request中所有的header,所有的header中就包含了灰度发布标签。

import feign.RequestInterceptor;
import feign.RequestTemplate;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Enumeration;

@Component
public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        // 从 RequestContextHolder 中获取 HttpServletRequest
        ServletRequestAttributes attributes = (ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 传递所有的 header,就包含了灰度发布标签
        Enumeration<String> headerNames = request.getHeaderNames();
        while(headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = request.getHeader(key);
            requestTemplate.header(key,value);
        }
    }
}

方式二:只传递header中的灰度发布标签

import feign.RequestInterceptor;
import feign.RequestTemplate;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;

@Component
public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        // 从 RequestContextHolder 中获取 HttpServletRequest
        ServletRequestAttributes attributes = (ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();
        // 获取 RequestContextHolder 中的信息
        Map<String,String> headers = getHeaders(attributes.getRequest());
        // 放入 openfeign 的 requestTemplate 中
        for(Map.Entry<String,String> entry : headers.entrySet()) {
            requestTemplate.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);
                map.put(key,value);
            }
        }
        return map;
    }
}

2.4 其他代码

2.4.1 其他业务代码

① user-service 模块的 controller

import com.example.userservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private OrderService orderService;

    @RequestMapping("/getname")
    public String getName() {
        String result = orderService.getOrder();
        return "正式版:User Service getName." +
                result;
    }
}

② user-service 模块的 service

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient("order-service-gray")
@Service
public interface OrderService {
    @RequestMapping("/order/getorder")
    public String getOrder();
}

③ new-user-service 模块的 controller

import com.example.newuserservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private OrderService orderService;

    @RequestMapping("/getname")
    public String getName() {
        String result = orderService.getOrder();
        return "测试版:User Service getName." +
                result;
    }
}

④ new-user-service 模块的 service

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient("order-service-gray")
@Service
public interface OrderService {
    @RequestMapping("/order/getorder")
    public String getOrder();
}

⑤ order-service 模块的 controller

import com.example.orderservice.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private LogService logService;

    @RequestMapping("/getorder")
    public String getOrder() {
        String result = logService.getLog();
        return "Do OrderService getOrder Method." +
                result;
    }
}

⑥?order-service 模块的 service

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

@Service
@FeignClient("log-service-gray")
public interface LogService {
    @RequestMapping("/log/getlog")
    public String getLog();
}

⑦ log-service 模块的 controller

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/log")
public class LogController {
    @RequestMapping("/getlog")
    public String getLog() {
        return "正式版:Log Service getLog";
    }
}

⑧ new-log-service 模块的 controller

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/log")
public class LogController {
    @RequestMapping("/getlog")
    public String getLog() {
        return "测试版:Log Service getLog";
    }
}

2.4.2 pom.xml 关键代码

① 父模块的 pom.xml

modules 中先加载服务调用链中靠后的服务,

<packaging>pom</packaging>

<!-- ....省略 -->

<!-- 注意打包顺序 -->
<modules>
    <module>gray-loadbalancer</module>
    <module>gateway</module>
    <module>new-log-service</module>
    <module>log-service</module>
    <module>order-service</module>
    <module>user-service</module>
    <module>new-user-service</module>
</modules>

<!-- ....省略 -->

<dependencies>
    <!-- nacos服务注册 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- 负载均衡 -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>gray-loadbalancer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

② user-service 模块的 pom.xml

<parent>
    <groupId>com.example</groupId>
    <artifactId>gray-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>

③ new-user-service 模块的 pom.xml

<parent>
    <groupId>com.example</groupId>
    <artifactId>gray-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>gray-loadbalancer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

③ order-service 模块的 pom.xml

<parent>
    <groupId>com.example</groupId>
    <artifactId>gray-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>gray-loadbalancer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

④ log-service 和 new-log-service 模块的 pom.xml

<parent>
    <groupId>com.example</groupId>
    <artifactId>gray-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

⑤ gray-loadbalancer 模块的 pom.xml

<parent>
    <groupId>com.example</groupId>
    <artifactId>gray-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

⑥ gateway 模块的 pom.xml

<parent>
    <groupId>com.example</groupId>
    <artifactId>gray-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>gray-loadbalancer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

2.4.3 application.yml 相关代码

① user-service 模块的 application.yml

spring:
  application:
    name: user-service-gray
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
server:
  port: 0

② new-user-servicce 模块的 application.yml

spring:
  application:
    name: user-service-gray
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        metadata: {"gray-tag": "true"}  # 金丝雀标识
server:
  port: 0

③ order-servicce 模块的 application.yml

spring:
  application:
    name: order-service-gray
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
server:
  port: 0

④ log-servicce 模块的 application.yml

spring:
  application:
    name: log-service-gray
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
server:
  port: 0

⑤ new-log-servicce 模块的 application.yml

spring:
  application:
    name: log-service-gray
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        metadata: {"gray-tag": "true"}  # 金丝雀标识
server:
  port: 0

⑥ gateway 模块的 application.yml

spring:
  main:
    web-application-type: reactive  # Spring Web 和 reactive web 冲突
  application:
    name: gateway-gray
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        register-enabled: false  # 网关不需要注册到 nacos
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service-gray
          predicates:
            - Path=/user/**
server:
  port: 10086

3. 验证全链路金丝雀发布的实现效果

按顺序启动 log、order、user 的正式及测试服务,以及 gateway 模块,

使用 Postman 来验证全链路金丝雀发布的实现效果:

1. 请求头中不带 "gray-tag" 灰度标签,访问正式版服务

验证结果:无论访问多少次,不管是否服务集群,只要请求头中不带 "gray-tag" 灰度标签,只能访问到正式版的服务。

2. 请求头中带上 "gray-tag" 灰度标签,并且值为 true,访问测试版服务

验证结果:无论访问多少次,不管是否服务集群,只要请求头中带上 "gray-tag" 灰度标签,并且值为 true ,就只能访问到测试版的服务。


至此,微服务中全链路的金丝雀发布就实现好了~

文章来源:https://blog.csdn.net/xaiobit_hl/article/details/135272563
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。