首页 > 其他分享 >SpringCloud全链路灰色发布具体实现!

SpringCloud全链路灰色发布具体实现!

时间:2023-11-13 11:58:24浏览次数:35  
标签:灰色 Spring 服务 SpringCloud org 发布 灰度 链路 import

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

在传统的全量发布中,新版本的功能会一次性全部部署到所有的用户或节点上。然而,这种方式潜在的风险是,如果新版本存在缺陷或问题,可能会对所有用户或节点产生严重的影响,导致系统崩溃或服务不可用。

相比之下,灰度发布采用较小的规模,并逐步将新版本的功能引入到生产环境中,仅向一小部分用户或节点提供新功能。通过持续监测和评估,可以在发现问题时及时回滚或修复。这种逐步引入新版本的方式可以降低风险,并提高系统的稳定性和可靠性。

1.实现思路

灰色发布的常见实现思路有以下几种:

  • 根据用户划分:根据用户标识或用户组进行划分,在整个用户群体中只选择一小部分用户获得新功能。
  • 根据地域划分:在不同地区或不同节点上进行划分,在其中的一小部分地区或节点进行新功能的发布。
  • 根据流量划分:根据流量的百分比或请求次数进行划分,只将一部分请求流量引导到新功能上。

而在生产环境中,比较常用的是根据用户标识来实现灰色发布,也就是说先让一小部分用户体验新功能,以发现新服务中可能存在的某种缺陷或不足。

2.具体实现

Spring Cloud 全链路灰色发布的关键实现思路如下图所示:
image.png
灰度发布的具体实现步骤如下:

  1. 前端程序在灰度测试的用户 Header 头中打上标签,例如在 Header 中添加“grap-tag: true”,其表示要进行灰常测试(访问灰度服务),而其他则为访问正式服务。
  2. 在负载均衡器 Spring Cloud LoadBalancer 中,拿到 Header 中的“grap-tag”进行判断,如果此标签不为空,并等于“true”的话,表示要访问灰度发布的服务,否则只访问正式的服务。
  3. 在网关 Spring Cloud Gateway 中,将 Header 标签“grap-tag: true”继续往下一个调用服务中传递。
  4. 在后续的调用服务中,需要实现以下两个关键功能:
    1. 在负载均衡器 Spring Cloud LoadBalancer 中,判断灰度发布标签,将请求分发到对应服务。
    2. 将灰度发布标签(如果存在),继续传递给下一个调用的服务。

经过第四步的反复传递之后,整个 Spring Cloud 全链路的灰度发布就完成了。

3.核心实现思路和代码

灰度发布的关键实现技术和代码如下。

3.1 区分正式服务和灰度服务

在灰度发布的执行流程中,有一个核心的问题,如果在 Spring Cloud LoadBalancer 进行服务调用时,区分正式服务和灰度服务呢?

这个问题的解决方案是:在灰度服务既注册中心的 MetaData(元数据)中标识自己为灰度服务即可,而元数据中没有标识(灰度服务)的则为正式服务,以 Nacos 为例,它的设置如下:

spring:
  application:
    name: canary-user-service
  cloud:
    nacos:
      discovery:
        username: nacos
        password: nacos
        server-addr: localhost:8848
        namespace: public
        register-enabled: true 
        metadata: { "grap-tag":"true" } # 标识自己为灰度服务

3.2 负载均衡调用灰度服务

Spring Cloud LoadBalancer 判断并调用灰度服务的关键实现代码如下:

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 { // 服务不为空
            RequestDataContext dataContext = (RequestDataContext) request.getContext();
            HttpHeaders headers = dataContext.getClientRequest().getHeaders();
            // 判断是否为灰度发布(请求)
            if (headers.get(GlobalVariables.GRAY_KEY) != null &&
                    headers.get(GlobalVariables.GRAY_KEY).get(0).equals("true")) {
                // 灰度发布请求,得到新服务实例列表
                List<ServiceInstance> findInstances = instances.stream().
                        filter(s -> s.getMetadata().get(GlobalVariables.GRAY_KEY) != null &&
                                s.getMetadata().get(GlobalVariables.GRAY_KEY).equals("true"))
                        .toList();
                if (findInstances.size() > 0) { // 存在灰度发布节点
                    instances = findInstances;
                }
            } else { // 查询非灰度发布节点
                // 灰度发布测试请求,得到新服务实例列表
                instances = instances.stream().
                        filter(s -> s.getMetadata().get(GlobalVariables.GRAY_KEY) == null ||
                                !s.getMetadata().get(GlobalVariables.GRAY_KEY).equals("true"))
                        .toList();
            }
            // 随机正数值 ++i( & 去负数)
            int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
            // ++i 数值 % 实例数 取模 -> 轮询算法
            int index = pos % instances.size();
            // 得到服务实例方法
            ServiceInstance instance = (ServiceInstance) instances.get(index);
            return new DefaultResponse(instance);
        }
    }

以上代码为自定义负载均衡器,并使用了轮询算法。如果 Header 中有灰度标签,则只查询灰度服务的节点实例,否则则查询出所有的正式节点实例(以供服务调用或服务转发)。

3.3 网关传递灰度标识

要在网关 Spring Cloud Gateway 中传递灰度标识,只需要在 Gateway 的全局自定义过滤器中设置 Response 的 Header 即可,具体实现代码如下:

package com.example.gateway.config;

import com.loadbalancer.canary.common.GlobalVariables;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
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();
        if (request.getQueryParams().getFirst(GlobalVariables.GRAY_KEY) != null) {
            // 设置金丝雀标识
            response.getHeaders().set(GlobalVariables.GRAY_KEY,
                    "true");
        }
        // 此步骤正常,执行下一步
        return chain.filter(exchange);
    }
}

3.4 Openfeign 传递灰度标签

HTTP 调用工具 Openfeign 传递灰度标签的实现代码如下:

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 template) {
        // 从 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()) {
            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);
                map.put(key, value);
            }
        }
        return map;
    }
}

小结

灰度发布是微服务时代保证生产环境安全的必备措施,而其关键实现思路是:

1、注册中心区分正常服务和灰度服务;

2、负载均衡正确转发正常服务和灰度服务;

3、网关和 HTTP 工具传递灰度标签。

这样,我们就完整的实现 Spring Cloud 全链路灰度发布功能了。

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

标签:灰色,Spring,服务,SpringCloud,org,发布,灰度,链路,import
From: https://www.cnblogs.com/vipstone/p/17828800.html

相关文章

  • SpringCloud 集成 Sentinel 和使用小结
    Sentinel是阿里的一款微服务请求监控组件,能够通过配置实现流量控制,降级熔断,热点参数限流,授权规则限流,使用非常方便。官方默认只提供了流量控制配置规则持久化代码实现,这也是我们最常用的,上篇博客已经实现并进行了部署。本篇博客基于上篇博客搭建好的Sentinel和Nacos环境,介绍......
  • Linux Media 子系统链路分析
    一、概述Media子系统是一个用于处理多媒体设备的框架,它提供了一组API和驱动程序,用于管理和控制视频、音频和其他多媒体设备。而V4L2是media子系统的一部分,用于处理视频相关的功能。了解MIPI摄像头后,发现linux系统下的流媒体驱动很复杂,而MIPI摄像头主要分为感光模组......
  • H3C-Link-Aggregation.链路聚合
    简介以太网链路聚合通过将多条以太网物理链路捆绑在一起形成一条以太网逻辑链路实现增加链路带宽的目的,同时这些捆绑在一起的链路通过相互动态备份,可以有效地提高链路的可靠性。基本概念聚合组:链路捆绑是通过接口捆绑实现的,多个以太网接口捆绑在一起后形成一个聚合组成员端口:被捆绑......
  • 了解交换口的链路类型以及实际使用场景(access篇)
    作者:网络之路一天 首发公众号:网络之路博客(ID:NetworkBlog)VLAN在数据包中如何体现?在上一篇实际测试了,从PC2访问PC1的时候,ARP请求广播包,只从E0/0/2发送给E0/0/3,这是因为两个口都配置成了accessvlan10里面,那一个数据包过来交换机它具体是如何处理的呢?,这就要了解下VLAN以及access处理......
  • SpringCloudAlibaba引入Gateway统一网关
    一、概述网关是我们服务的守门神,是所有微服务的统一入口,一切请求都要先到网关,再到微服务,它可以帮助我们统一的进行一种操作,处理一些问题。网关的核心功能特性:1.请求路由、负载均衡一切请求都必须先经过gateway网关,但网关不处理业务,而是根据某种规则把请求转发到某个微服务,这个过程......
  • SpringCloud 基础
    SpringCloud基础微服务基础注意:此阶段学习推荐的电脑配置,至少配备4核心CPU(主频3.0Ghz以上)+16GB内存,否则卡到你怀疑人生。前面我们讲解了SpringBoot框架,通过使用SpringBoot框架,我们的项目开发速度可以说是得到了质的提升。同时,我们对于项目的维护和理解,也会更加的轻松。可见,Spr......
  • 了解交换口的链路类型以及实际使用场景(access篇)
    作者:网络之路一天 首发公众号:网络之路博客(ID:NetworkBlog)VLAN在数据包中如何体现?在上一篇实际测试了,从PC2访问PC1的时候,ARP请求广播包,只从E0/0/2发送给E0/0/3,这是因为两个口都配置成了accessvlan10里面,那一个数据包过来交换机它具体是如何处理的呢?,这就要了解下VLAN以及access处理......
  • APM建设踩了哪些坑?去哪儿旅行分布式链路追踪系统实践
    一分钟精华速览分布式链路追踪系统在企业的APM体系中扮演着重要的角色。本文分享了去哪儿旅行构建分布式链路追踪系统的实践经验。从APM整体架构设计入手,讲述了日志收集、Kafka传输和Flink任务处理等环节的性能优化实践和踩坑经验。同时,作者结合丰富的分布式系统架构经验,探讨了AP......
  • ==springCloud(一)==
    为什么选择SpringCloud作为微服务架构选型依据整体解决方案和框架成熟度社区热度可维护性学习曲线当前各大IT公司用的微服务架构有那些?阿里:dubbo+HFS京东:JFS新浪:Motan当当网:DubboX…SpringCloud概念Spring官网:https://spring.io/什么是微服务架构微服务有什么优点微服务就是将单......
  • 微服务SpringCloud父工程pom依赖
    <!--设置为pom,管理依赖--><packaging>pom</packaging><properties><java.version>1.8</java.version><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8<......