前言
在某些业务场景中,为实现新老接口的顺利切换,需要设置新接口陪同老接口同时运行(即接口陪跑)、比较新老接口的执行结果,待比较验证通过后,再正式切换到新接口。
我们可将这种在调用老接口的同时调用新接口进行陪跑的功能,称为”接口镜像“。并且可以通过 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> {
@Autowired
private ApiLogger apiLogger;
@Autowired
private ServiceInvoker serviceInvoker;
// 注入 Spring 自动配置的编解码器配置类,用于获取 HttpMessageReader
private final ServerCodecConfigurer codecConfigurer;
// 构造器注入 ServerCodecConfigurer
public MirrorRequestFilterFactory(ServerCodecConfigurer codecConfigurer) {
super(Config.class);
this.codecConfigurer = codecConfigurer;
log.info("MirrorRequestFilter init");
}
@Override
public 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) {
@Override
public 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());
});
});
};
}
/**
* 配置类
*/
@Data
public static class Config {
private String mirrorUrl; // 镜像 URL
private boolean mirrorEnabled = true; // 是否启用镜像
}
}
镜像功能的配置和启用
修改 application.yml,添加如下配置:
spring:
cloud:
gateway:
routes:
- id: route1
uri: http://localhost:8088 # 模拟主API地址
predicates:
- Path=/api/req
filters:
- 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
最后,为实现接口陪跑,还需要实现异步发起镜像接口调用、主接口与镜像接口的交互日志记录、接口返回数据比对等功能,因与本次讨论主题无关,故略过。