当前位置: 首页 > ops >正文

通过SpringCloud Gateway实现API接口镜像请求(陪跑)网关功能

前言

在某些业务场景中,为实现新老接口的顺利切换,需要设置新接口陪同老接口同时运行(即接口陪跑)、比较新老接口的执行结果,待比较验证通过后,再正式切换到新接口。

我们可将这种在调用老接口的同时调用新接口进行陪跑的功能,称为”接口镜像“。并且可以通过 Spring Cloud Gateway实现接口镜像。

Spring Cloud Gateway概述

目前Spring Cloud的 API网关,主要基于Spring 6、Spring Boot 3,可提供API路由及安全、监控统计和可扩展性等网关基本功能。其中有两个核心概念:Server 和Proxy exchange代理交换机,可兼容WebFlux 和 MVC两种模式。

网关提供了很多内置的Gateway Filter工厂类,提供了多种路由过滤器,用于针对特定路由进行修改HTTP请求或响应。同时,我们可以自定义GatewayFilterFactory,从而实现更强大的定制化功能。

详细介绍,参见:Spring Cloud Gateway 官网。

自定义FilterFactory实现接口镜像

Gateway在处理http请求时,因为其遵循了Reactive Streams(响应式流规范),数据流只能被消费一次,即在通过 request.getBody() 获取的请求体,只能读取一次,无法对其进行直接编辑或复制。这有点类似于 Rust 语言风格,可以大幅提升内存效率。

那如果想多次处理请求体,则需要通过缓存请求体,并包装新的请求对象的方式来解决。

import com.mydemo.project.services.ApiLogger;
import com.mydemo.project.services.ServiceInvoker;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;/*** 镜像请求过滤器工厂类*/
@Slf4j
@Component
public class MirrorRequestFilterFactory extends AbstractGatewayFilterFactory<MirrorRequestFilterFactory.Config> {@Autowiredprivate ApiLogger apiLogger;@Autowiredprivate ServiceInvoker serviceInvoker;// 注入 Spring 自动配置的编解码器配置类,用于获取 HttpMessageReaderprivate final ServerCodecConfigurer codecConfigurer;// 构造器注入 ServerCodecConfigurerpublic MirrorRequestFilterFactory(ServerCodecConfigurer codecConfigurer) {super(Config.class);this.codecConfigurer = codecConfigurer;log.info("MirrorRequestFilter init");}@Overridepublic GatewayFilter apply(Config config) {log.info("request filtered, in apply");return (exchange, chain) -> {ServerHttpRequest request = exchange.getRequest();// 仅处理 POST 请求if (request.getMethod() != HttpMethod.POST) {return chain.filter(exchange);}// 读取并缓存原始请求体return DataBufferUtils.join(request.getBody()).flatMap(dataBuffer -> {// 复制请求体数据byte[] bytes = new byte[dataBuffer.readableByteCount()];dataBuffer.read(bytes);DataBufferUtils.release(dataBuffer); // 释放原始缓冲区// 创建新的DataBuffer用于原始请求继续使用Flux<DataBuffer> cachedFlux = Flux.defer(() -> {DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);DataBufferUtils.retain(buffer);return Mono.just(buffer);});// 创建装饰器请求,确保原始请求能继续使用请求体ServerHttpRequest decoratedRequest = new ServerHttpRequestDecorator(request) {@Overridepublic Flux<DataBuffer> getBody() {return cachedFlux;}};if (config.isMirrorEnabled()) {log.info("starting mirror request");// 使用复制的请求体发起新请求String copiedBody = new String(bytes, StandardCharsets.UTF_8);// 异步发起镜像请求(不阻塞主流程)serviceInvoker.invokeAsync(config.getMirrorUrl(), request, copiedBody);}// 将新的请求传入下一个 Filter 链,继续处理原始请求ServerWebExchange nextExchange = exchange.mutate().request(decoratedRequest).build();return chain.filter(nextExchange).doOnSuccess(response -> {apiLogger.log("primary", "success", request, response);}).doOnError(e -> {apiLogger.log("primary", "failure", request, e.getMessage());});});};}/*** 配置类*/@Datapublic static class Config {private String mirrorUrl; // 镜像 URLprivate boolean mirrorEnabled = true; // 是否启用镜像}
}

镜像功能的配置和启用

修改 application.yml,添加如下配置:

spring:cloud:gateway:routes:- id: route1uri: http://localhost:8088  # 模拟主API地址predicates:- Path=/api/reqfilters:- RewritePath=/api/req, /primary/req  # 网关代理主API地址- name: MirrorRequestFilterFactory  # 接口镜像功能过滤器args:mirrorUrl: http://127.0.0.1:8081/mirror/req  # 模拟镜像API地址mirrorEnabled: true  # 启用镜像功能

此处使用了两个过滤器。RewritePath过滤器,实现接收外部请求,并转换为对模拟主API地址的请求;
MirrorRequestFilterFactory,接口镜像功能过滤器,实现对模拟主API地址访问的同时,启用镜像API功能,实现对模拟镜像API地址的访问。

工程依赖版本

Spring Cloud Gateway 不同版本变化较大,当前官网最新版本为4.3.0,但考虑到要兼容目前最新版的Nacos等组件,工程依赖版本情况如下:

groupIdartifactIdversion
org.springframework.cloudspring-cloud-dependencies2023.0.3.3
org.springframework.bootspring-boot-dependencies3.3.13
org.springframework.cloudspring-cloud-starter-gateway4.1.9
org.springframework.cloudspring-cloud-starter-loadbalancer4.1.6
org.springframework.bootspring-boot-configuration-processor3.3.13
org.projectlomboklombok1.18.20

采用的Java版本:JDK17

最后,为实现接口陪跑,还需要实现异步发起镜像接口调用、主接口与镜像接口的交互日志记录、接口返回数据比对等功能,因与本次讨论主题无关,故略过。

http://www.xdnf.cn/news/19751.html

相关文章:

  • 进攻是最好的防守 在人生哲学中的应用
  • 百度智能云「智能集锦」自动生成短剧解说,三步实现专业级素材生产
  • 以太坊网络
  • Spring Boot中MyBatis Plus的LambdaQueryWrapper查询异常排查与解决
  • 外网获取瀚高.NET驱动dll方法和使用案例
  • Axure文件上传高保真交互原型:实现Web端真实上传体验
  • NodeJS配置镜像仓局
  • k8s的SidecarSet配置和initContainers
  • 【明道云】[工作表控件4] 邮箱控件的输入校验与业务应用
  • RAG|| LangChain || LlamaIndex || RAGflow
  • HTML `<datalist>`:原生下拉搜索框,无需 JS 也能实现联想功能
  • 用 “走楼梯” 讲透动态规划!4 个前端场景 + 4 道 LeetCode 题手把手教
  • 戴尔笔记本电池健康度检测、无电池开机测试与更换电池全流程记录
  • 孩子玩手机都近视了,怎样限制小孩的手机使用时长?
  • 你只需输入一句话,MoneyPrinterTurbo直接给你输出一个视频
  • 小说、漫剧小程序系统开发:独立部署,源码交付
  • SpringBoot Web 入门指南:从零搭建第一个SpringBoot程序
  • 【leetcode】200. 岛屿数量
  • 有限元方法中的数值技术:预处理共轭梯度法 PCG (2)
  • 【Cursor-Gpt-5-high】StackCube-v1 任务训练结果不稳定性的分析
  • 关于linux网络编程——4
  • 醋酸铕:点亮现代生活的“隐形之光“
  • HTML元素周期表
  • 【C++】C++入门—(中)
  • ASP.NET Web Forms 实战:用 RadioButton 打造“性别/称谓选择”表单的最佳实践
  • 【数据结构】1绪论
  • 【Qt中信号槽连接connect有接收者和无接收者的区别】
  • 执行一条select语句期间发生了什么?
  • 常用符号 Emoji 对照表——Unicode UTF-8
  • CSS Sass Less 样式.xxx讲解