SpringCloud Alibaba微服务实战之灰度发布

SpringCloud Alibaba微服务实战之灰度发布

大家好,我是一安~

导读:在上一篇文章中我们讲解了如何整合sky-walking实现链路追踪,今天继续讲解如何通过网关和配置中心实现灰度发布。

简介

灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

灰度发布开始到结束期间的这一段时间,称为灰度期。灰度发布能及早获得用户的意见反馈,完善产品功能,提升产品质量,让用户参与产品测试,加强与用户互动,降低产品升级所影响的用户范围。

SpringCloud Alibaba微服务实战之灰度发布

正文

其实之前讲解配置隔离的时候,也可以通过groupnamespace实现服务注册的隔离,这种方式不再做介绍。

环境搭建测试

首先搭建两个web服务模拟生产和灰度环境(以用户模块为例,小编这里直接复制了一份),分别注册到nacos中,注意这里服务实例名称要一致:

生产环境配置:

spring:
  application:
    name: account-service
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  cloud:
    loadbalancer:
      nacos:
        enabled: true
      ribbon:
        enabled: false
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址
        group: DEFAULT_GROUP
        namespace: public
        metadata:
          # 0-正式环境 1-测试环境
          env: 0    

灰度环境配置:

spring:
  application:
    name: account-service
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  cloud:
    loadbalancer:
      nacos:
        enabled: true
      ribbon:
        enabled: false
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址
        group: DEFAULT_GROUP
        namespace: public
        metadata:
          # 0-正式环境 1-测试环境
          env: 1

Nacos中查看详情:SpringCloud Alibaba微服务实战之灰度发布修改用户服务查询接口日志输出:

    @SysLog(desc = "查询用户")
    @ApiOperation("查询用户")
    @GetMapping("/account/getByCode/{accountCode}")
    @SentinelResource(value = "getByCode")
    public Result<Account> getByCode(@PathVariable(value = "accountCode") String accountCode){
        log.info("get account detail,accountCode is :{}",accountCode);
        Account account = accountService.selectByCode(accountCode);
        return Result.ok(account).message("正式环境");
    }
    @SysLog(desc = "查询用户")
    @ApiOperation("查询用户")
    @GetMapping("/account/getByCode/{accountCode}")
    @SentinelResource(value = "getByCode")
    public Result<Account> getByCode(@PathVariable(value = "accountCode") String accountCode){
        log.info("get account detail,accountCode is :{}",accountCode);
        Account account = accountService.selectByCode(accountCode);
        return Result.ok(account).message("测试环境");
    }

测试(默认轮询)SpringCloud Alibaba微服务实战之灰度发布

网关优化

新增配置:

# 0-正式环境 1-测试环境
env: 0

解析配置:

@Data
@Configuration
@RefreshScope
public class EnvProperties {
    @Value("${env:0}")
    private String env;

}

自定义负载均衡策略(方式一):

@Slf4j
public class EnvGrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    @Autowired
    private EnvProperties envProperties;


    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private final String serviceId;
    private final AtomicInteger position;

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

    public EnvGrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                               String serviceId, int seedPosition) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.position = new AtomicInteger(seedPosition);
    }

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

    }


    private Response<ServiceInstance> processInstanceResponse(List<ServiceInstance> instances, Request request) {
        if (instances.isEmpty()) {
            log.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        } else {
            DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();
            RequestData clientRequest = (RequestData) requestContext.getClientRequest();
            //通过请求头获取环境参数
//            HttpHeaders headers = clientRequest.getHeaders();
//            String env = headers.getFirst("env");

            //通过配置文件获取环境参数
            String env = envProperties.getEnv();

            log.info("request  env : {}",env );
            if(StringUtils.isEmpty(env)){
                return processRibbonInstanceResponse(instances);
            }

            // filter service instances
            List<ServiceInstance> serviceInstances = instances.stream()
                    .filter(instance -> env.equalsIgnoreCase(instance.getMetadata().get("env")))
                    .collect(Collectors.toList());

            if(serviceInstances.size() > 0){
                return processRibbonInstanceResponse(serviceInstances);
            }else{
                return processRibbonInstanceResponse(instances);
            }
        }
    }

    /**
     * 负载均衡器
     * 参考 org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer#getInstanceResponse
     * @author javadaily
     */
    private Response<ServiceInstance> processRibbonInstanceResponse(List<ServiceInstance> instances) {
        int pos = Math.abs(this.position.incrementAndGet());
        ServiceInstance instance = instances.get(pos % instances.size());
        return new DefaultResponse(instance);
    }
}

编写配置类:

public class EnvLoadBalancerConfiguration {


    @Bean
    ReactorLoadBalancer<ServiceInstance> envGrayLoadBalancer(Environment environment,
                                                                 LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new EnvGrayLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

启动类新增注解

@LoadBalancerClient(value = "account-service", configuration = EnvLoadBalancerConfiguration.class)

自定义负载均衡策略(方式二):

@Slf4j
public class EnvServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {

    @Autowired
    private EnvProperties envProperties;


    public EnvServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
        super(delegate);
    }


    @Override
    public Flux<List<ServiceInstance>> get() {
        return delegate.get();
    }

    @Override
    public Flux<List<ServiceInstance>> get(Request request) {

//        return delegate.get(request).map(instances -> filteredByVersion(instances,getEnv(request.getContext())));

        return delegate.get(request).map(instances -> filteredByVersion(instances,envProperties.getEnv()));
    }

    /**
     * filter instance by requestVersion
     */
    private List<ServiceInstance> filteredByVersion(List<ServiceInstance> instances, String env) {
        log.info("DelegatingServiceInstanceListSupplier version is {}",env);
        if(StringUtils.isEmpty(env)){
            return instances;
        }

        List<ServiceInstance> filteredInstances = instances.stream()
                .filter(instance -> env.equalsIgnoreCase(instance.getMetadata().getOrDefault("env","")))
                .collect(Collectors.toList());

        if (filteredInstances.size() > 0) {
            return filteredInstances;
        }

        return instances;
    }

    private String getEnv(Object requestContext) {
        if (requestContext == null) {
            return null;
        }
        String env = null;
        if (requestContext instanceof RequestDataContext) {
            env = getVersionFromHeader((RequestDataContext) requestContext);
        }
        return env;
    }

    /**
     * get version from header
     */
    private String getVersionFromHeader(RequestDataContext context) {
        if (context.getClientRequest() != null) {
            HttpHeaders headers = context.getClientRequest().getHeaders();
            if (headers != null) {
                return headers.getFirst("env");
            }
        }
        return null;
    }
}

编写配置类:

public class EnvLoadBalancerConfiguration2 {
    @Bean
    ServiceInstanceListSupplier serviceInstanceListSupplier(ConfigurableApplicationContext context) {
        ServiceInstanceListSupplier delegate = ServiceInstanceListSupplier.builder()
                .withDiscoveryClient()
                .withCaching()
                .build(context);
        return new EnvServiceInstanceListSupplier(delegate);
    }
}

启动类新增注解:

@LoadBalancerClient(value = "account-service", configuration = EnvLoadBalancerConfiguration2.class)

测试:

SpringCloud Alibaba微服务实战之灰度发布至此我们已经通过自定义负载均衡策略实现灰度发布。



如果这篇文章对你有所帮助,或者有所启发的话,帮忙 分享、收藏、点赞、在看,你的支持就是我坚持下去的最大动力!

SpringCloud Alibaba微服务实战之灰度发布

为了偷懒,自己封装了一个自适配的数据单位转换工具类


深入剖析Spring Boot 的SPI机制,提升程序的可扩展性


SpringBoot+Vue+Aitiviti7实现前后端分离OA办公管理系统(微信公众号)

SpringCloud Alibaba微服务实战之灰度发布

原文始发于微信公众号(一安未来):SpringCloud Alibaba微服务实战之灰度发布

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/145044.html

(0)
青莲明月的头像青莲明月

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!