SpringCloud全链路灰度发布

日升时奋斗,日落时自省 

目录

1、实现框架

2、负载均衡模块

3、封装负载均衡器

4、网关模块

5、服务模块

5.1、注册为灰度服务实例

5.2、设置负载均衡器

5.3、传递灰度标签


1、实现框架

Spring Cloud全链路灰色发布实现构架:

灰度发布的具体实现 :

前端程序在灰度测试用户Header头中打上标签,例如Header中添加一个参数"gray-tay:true",其表示要进行会灰度测试的(访问灰度服务),而其他则为正式服务

在负载均衡器Spring Cloud LoadBalancer中,拿到Header中的"gray-tag"进行判断,如果此标签不为空,并等于"true"的话,表示要访问灰度发布的服务,否则只访问正式的服务

在网关Spring Cloud Gateway中,Header标签"gray-tag:true"继续往下一服务中传传递(因为通过网关的时候,是从新发送请求,Header中是没有灰度标签的)

在后续的调用服务中,需要实现以下两个关键功能:

  • 在负载均衡器Spring Cloud LoadBalancer中,判断灰度发布标签,将请求分发到对应服务
  • 将灰度发布标签(如果存在),继续传递给下一个调用的服务

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

2、负载均衡模块

实现ReactorServiceInstanceLoadBalancer接口

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 requestContext = (RequestDataContext) request.getContext();
            HttpHeaders headers = requestContext.getClientRequest().getHeaders();
            List<String> headersList = headers.get(GlobalVariable.GRAY_TAGE);
            if (headersList != null && headersList.size() > 0 &&
                    headersList.get(0).equals("true")) { // 灰度请求
                // 灰度列表  这里采用lambda的方法进行过滤
                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.size() > 0) { // 存在灰度服务节点
                    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 = (ServiceInstance)instances.get(pos % instances.size());
            return new DefaultResponse(instance);
        }
    }
}

3、封装负载均衡器

负载均衡器修改之后,我们需要使用修改后的负载均衡器

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);
    }
}

4、网关模块

网关是能收到灰度发布标识,但是调用的服务是不能获得Header中的灰度标签,需要在网关这里传递给调用的服务

@Component // 实现全局过滤器 进行过滤设置
public class LoadBalancerFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 事件处理 exchange 获取请求头
        ServerHttpRequest request=exchange.getRequest();
        // 事件处理 exchange 获取响应头
        ServerHttpResponse response = exchange.getResponse();
        // 请求头参数 获取的参数 获取 key 对应的 value值
        if(request.getQueryParams().getFirst(GlobalVariable.GRAY_TAGE)!=null){
            //获取响应请求头 设置灰度标签的参数 为 true
            response.getHeaders().set(GlobalVariable.GRAY_TAGE,"true");
        }
        // 返回 一个调用链 过滤条件的
        return chain.filter(exchange);
    }
}

5、服务模块

5.1、注册为灰度服务实例

在负载均衡器上获得了metaData数据,这个数据就是从灰色服务实例中获取,数据存储在nacos中

spring:
  application:
    name: user-service-gray
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        metadata: {"gray-tag":"true"}  #metadata可以自定义一些元数据
server:
  port: 0

5.2、设置负载均衡器

在服务启动类设置负载均衡器和开启OpenFeign服务:

defaultConfiguration = GrayLoadBalancerConfig.class全局负载均衡器的配置

@SpringBootApplication
@EnableFeignClients  //启用Fegin客户端使用
//这里需要调用我们写的负载均衡器,我们自定义的负载均衡器主要就是为了检测gray参数
@LoadBalancerClients(defaultConfiguration = GrayLoadBalancerConfig.class)  
public class NewUserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(NewUserServiceApplication.class, args);
    }

}

5.3、传递灰度标签

针对其他服务实例需要处理请求头信息

RequestInterceptor是一个Spring Cloud Feign中的接口,它用于在发起Feign请求前和请求后执行一些自定义的操作,例如添加请求头信息、记录请求日志、实现认证授权等

RequestInterceptor接口中包含一个方法:

apply方法:在发送请求之前执行的操作,比如添加请求头、修改请求路径或请求参数等

@Component
public class FeignRequestInterceptor implements RequestInterceptor {
    //相当于是一个前置操作 处理请求头信息,记录请求日志、实现认证授权
    @Override
    public void apply(RequestTemplate requestTemplate) {
        //获取请求参数
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        //拿到请求  
        HttpServletRequest request = attributes.getRequest();
        //获取请求头参数  当然调用上面这条代码的 request是一样的
        Enumeration<String> headerNames = attributes.getRequest().getHeaderNames();
        //将获取的请求中的所有参数
        while(headerNames.hasMoreElements()){
            //将key先获取
            String key=headerNames.nextElement();
            //通过key 获得 value
            String value=request.getHeader(key);
            //将获取的参数 放到header头中采用键值 key:value 放到请求模板中
            requestTemplate.header(key,value);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/327454.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++ Primer Plus】2.1 进入C++

代码示例 #include <iostream> // a preprocessor directive 预处理器指令 int main () // function header { // start of function bodyusing namespace std; // make definitions visiblecout << "hello!"; // message…

动态路由综合实验-RIP

一.要求 1、R1--R3地址为192.168.1.0/24:请合理分配 2、R3的环回为3.3.3.0/24&#xff0c;该网段不能在rip中宣告 3、整个网络使用RIPV2&#xff0c;全网可达&#xff0c;路由表汇总&#xff0c;防止环路&#xff0c;保障更新安全&#xff0c;加快收敛速度 网络拓扑结构&…

服务器感染了.DevicData-P-XXXXXXXX勒索病毒,如何确保数据文件完整恢复?

引言&#xff1a; 在当今数字化时代&#xff0c;勒索病毒已成为网络安全威胁的一个严峻问题。其中&#xff0c;.DevicData-P-XXXXXXXX 勒索病毒以其恶意加密文件的手段引起了广泛关注。本文将介绍该病毒的特点、数据恢复方法以及如何预防遭受其攻击。 如不幸感染这个勒索病毒&…

workflow源码解析:ThreadTask

1、使用程序&#xff0c;一个简单的加法运算程序 #include <iostream> #include <workflow/WFTaskFactory.h> #include <errno.h>// 直接定义thread_task三要素 // 一个典型的后端程序由三个部分组成&#xff0c;并且完全独立开发。即&#xff1a;程序协议算…

【机器学习入门】机器学习基础概念与原理

*&#xff08;本篇文章旨在帮助新手了解机器学习的基础概念和原理&#xff0c;不深入讨论算法及核心公式&#xff09; 目录 一、机器学习概念 1、什么是机器学习&#xff1f; 2、常见机器学习算法和模型 3、使用Python编程语言进行机器学习实践 4、机器学习的应用领域 二…

Dokerfile

阅读目录 什么是dockerfile?Dockerfile的基本结构Dockerfile文件说明 什么是dockerfile? Dockerfile是一个包含用于组合映像的命令的文本文档。可以使用在命令行中调用任何命令。 Docker通过读取Dockerfile中的指令自动生成映像。 docker build命令用于从Dockerfile构建映…

Android studio RecyclerView 应用设计

一、创建empty activity项目: 二、打开activity_main.xml布局文件: 添加RecyclerView控件 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/…

Spring Boot - Application Events 同步 VS 异步 发布订阅事件实战

文章目录 PreCode基础工程启动类切入口事件 发布事件同步 Listener异步Listener增加EnableAsync增加 Async 测试 Pre Spring Boot - Application Events 的发布顺序_ApplicationStartingEvent Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEv…

手机与电脑更改IP地址怎么使用代理IP?

在现代互联网时代&#xff0c;代理IP已成为许多人日常生活和工作中不可或缺的一部分。通过代理IP&#xff0c;用户可以隐藏自己的真实IP地址&#xff0c;并获得更好的网络体验。本文将详细介绍如何在手机和电脑上更改IP地址并使用代理IP。 一、手机使用代理IP 1. 打开手机设置&…

目标检测DETR:End-to-End Object Detection with Transformers

NMS 对一个目标生成了多个检测窗口&#xff0c;但是事实上这些窗口中大部分内容都是重复的&#xff0c;找到目标检测最优的窗口 选取多个检测窗口中分数最高的窗口&#xff0c;剔除掉其他同类型的窗口 anchor generator 首先在该点生成scale512, aspect ratio{1:2&#xff…

第10章 通信业务

文章目录 10.1.1 通信行业1、通信行业的界定2、通信行业的特点 10.1.2 通信企业10.1.3 通信终端1、通信终端的分类2、终端发展趋势 10.2.1 通信业务的定义及分类10.2.2 基础电信业务1、第一类基础电信业务A11 固定通信业务A12 蜂窝移动通信业务A13 第一类卫星通信业务A14 第一类…

uni-app购物车页面详细代码

效果图&#xff1a; 这里的购物车加减用的是uni-app中的sku插件 代码附下&#xff08;全&#xff09;&#xff1a; <script setup lang"ts"> import {reqMemberCartList,reqMemberdelentCart,reqMemberPutCart,putMemberCartSelectedAPI, } from /services/…

go语言初探(一)

package mainimport ("fmt""time" )func main() {fmt.Print("hello go!")time.Sleep(1 * time.Second)}运行后&#xff0c;结果如下&#xff1a; 1、golang表达式中&#xff0c;加&#xff1b;和不加&#xff1b;都可以 2、函数的{和函数名一…

解决com.alibaba.fastjson.JSONException: default constructor not found的问题

1.问题描述 在进行JSON和对象互转时&#xff0c;发现有个报错&#xff1a; com.alibaba.fastjson.JSONException: default constructor not found. class com.hellobike.ph.match.service.taxi.model.message.DelayAddSkuMsg 2.原因和解决方案 通过其提示可以看出在利用fastJ…

【RTOS】快速体验FreeRTOS所有常用API(7)任务通知

目录 七、任务通知7.1 基本概念7.2 发出通知7.3 等待通知7.4 实例 七、任务通知 该部分在上份代码基础上修改得来&#xff0c;代码下载链接&#xff1a; https://wwzr.lanzout.com/i4Efu1la39wh 密码:cbvx 该代码尽量做到最简&#xff0c;不添加多余的、不规范的代码。 内容主要…

白码ERP快速实现库存不足时自动生成采购单功能

创建生产订单时&#xff0c;系统自动根据产品所需物料库存是否充足&#xff0c;如有物料库存不足&#xff0c;自动生成对应的采购订单&#xff1b; 前期准备&#xff1a; 需创建产品、物料、BOM、生产订单、生产订单明细、需求物料、采购订单、采购订单明细数据表&#xff0c…

如何查看centos7中dataease的安装位置

在 CentOS 7 中查找 DataEase 的安装位置&#xff0c;可以通过以下步骤进行&#xff1a; 检查服务状态&#xff1a; 如果 DataEase 作为服务运行&#xff0c;您可以使用 systemctl 命令来查看服务的状态&#xff0c;这通常会显示相关的路径信息。例如&#xff1a; systemctl st…

开发企业微信中的内嵌h5时如何开发与调试

前言&#xff1a; 在我们的项目中&#xff0c;开发企业微信内部的项目的话&#xff0c;分为两种&#xff0c;1种是直接开发企业微信的小程序&#xff0c;另一种则是企业微信内嵌我们的H5界面&#xff0c;我们这里讲一讲企业微信内嵌h5的方法与注意点。 1、开发h5项目 这点没有…

文件操作一(非常重要)

文件操作一&#xff08;非常重要&#xff09; 一、为什么使用文件&#xff1f;二、什么是文件&#xff1f;三、文件名(简单理解)四、二进制文件和文本文件&#xff08;重要&#xff09;五、流的概念&#xff08;非常重要&#xff09;六、文件的打开和关闭七、文件的顺序读写函数…

C++I/O流——(2)预定义格式的输入/输出(第二节)

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 含泪播种的人一定能含笑收获&#xff…