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

发布于:2025-09-04 ⋅ 阅读:(18) ⋅ 点赞:(0)

前言

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

我们可将这种在调用老接口的同时调用新接口进行陪跑的功能,称为”接口镜像“。并且可以通过 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

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


网站公告

今日签到

点亮在社区的每一天
去签到