通过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等组件,工程依赖版本情况如下:
groupId | artifactId | version |
---|---|---|
org.springframework.cloud | spring-cloud-dependencies | 2023.0.3.3 |
org.springframework.boot | spring-boot-dependencies | 3.3.13 |
org.springframework.cloud | spring-cloud-starter-gateway | 4.1.9 |
org.springframework.cloud | spring-cloud-starter-loadbalancer | 4.1.6 |
org.springframework.boot | spring-boot-configuration-processor | 3.3.13 |
org.projectlombok | lombok | 1.18.20 |
采用的Java版本:JDK17
最后,为实现接口陪跑,还需要实现异步发起镜像接口调用、主接口与镜像接口的交互日志记录、接口返回数据比对等功能,因与本次讨论主题无关,故略过。