You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot代理API耗时异常,如何分步测量各环节耗时?

Spring Boot代理API耗时排查方案

一、手动埋点(最直接定位)

在代理的关键环节插入时间记录,计算各步骤耗时并打印日志,核心监控节点包括:

  • 请求接收起始点
  • 请求体读取完成后
  • 转发请求前
  • 目标API响应接收完成后
  • 响应返回给客户端前

示例代码:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/proxy")
public class ProxyController {
    private static final Logger log = LoggerFactory.getLogger(ProxyController.class);
    private final RestTemplate restTemplate;
    private static final String TARGET_API_URL = "http://target-api.example.com";

    public ProxyController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @PostMapping("/**")
    public ResponseEntity<String> proxyRequest(@RequestBody String payload, HttpServletRequest request) {
        // 1. 请求接收起始点
        long totalStart = System.currentTimeMillis();
        String requestUri = request.getRequestURI();
        log.info("开始处理请求:{},请求方法:{}", requestUri, request.getMethod());

        // 2. 读取请求体完成后
        long afterReadPayload = System.currentTimeMillis();
        int payloadSize = payload.getBytes().length;
        log.info("读取请求体耗时:{}ms,请求体大小:{}字节", afterReadPayload - totalStart, payloadSize);

        // 3. 转发请求前
        long beforeForward = System.currentTimeMillis();
        String targetUrl = TARGET_API_URL + requestUri.replace("/proxy", "");
        log.info("准备转发至目标API:{}", targetUrl);

        // 4. 调用目标API并接收响应
        ResponseEntity<String> targetResponse = restTemplate.postForEntity(targetUrl, payload, String.class);
        long afterForward = System.currentTimeMillis();
        log.info("目标API响应耗时:{}ms,响应状态码:{}", afterForward - beforeForward, targetResponse.getStatusCodeValue());

        // 5. 返回响应前
        long beforeResponse = System.currentTimeMillis();
        log.info("处理响应准备返回耗时:{}ms", beforeResponse - afterForward);

        // 总耗时
        log.info("请求{}处理完成,总耗时:{}ms", requestUri, System.currentTimeMillis() - totalStart);
        return targetResponse;
    }
}

二、Spring拦截器统一监控

通过自定义拦截器统一拦截所有请求,批量记录各阶段耗时,同时借助ContentCachingRequestWrapper读取请求体大小,避免重复埋点:

1. 自定义计时拦截器

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TimingInterceptor implements HandlerInterceptor {
    private static final Logger log = LoggerFactory.getLogger(TimingInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        log.info("请求进入:{} {},客户端IP:{}", request.getMethod(), request.getRequestURI(), request.getRemoteAddr());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        if (request instanceof ContentCachingRequestWrapper) {
            ContentCachingRequestWrapper wrappedRequest = (ContentCachingRequestWrapper) request;
            long startTime = (Long) request.getAttribute("startTime");
            long currentTime = System.currentTimeMillis();
            int payloadSize = wrappedRequest.getContentAsByteArray().length;
            log.info("读取请求体完成,耗时:{}ms,请求体大小:{}字节", currentTime - startTime, payloadSize);
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        long startTime = (Long) request.getAttribute("startTime");
        long totalTime = System.currentTimeMillis() - startTime;
        String errorMsg = ex != null ? ",异常:" + ex.getMessage() : "";
        log.info("请求结束:{} {},响应状态:{},总耗时:{}ms{}", 
                 request.getMethod(), request.getRequestURI(), 
                 response.getStatus(), totalTime, errorMsg);
    }
}

2. 注册拦截器和请求体缓存过滤器

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.ContentCachingRequestFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TimingInterceptor())
                .addPathPatterns("/**");
    }

    @Bean
    public FilterRegistrationBean<ContentCachingRequestFilter> contentCachingRequestFilter() {
        FilterRegistrationBean<ContentCachingRequestFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new ContentCachingRequestFilter());
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

三、利用Micrometer做精细化监控

Spring Boot默认集成Micrometer,可通过它对转发请求、请求体读取等环节做指标监控,方便后续做可视化分析:

1. 给RestTemplate添加拦截器记录转发耗时

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(MeterRegistry meterRegistry) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getInterceptors().add((request, body, execution) -> {
            Timer.Sample sample = Timer.start(meterRegistry);
            try {
                ClientHttpResponse response = execution.execute(request, body);
                sample.stop(Timer.builder("proxy.forward.request.duration")
                        .tag("target_url", request.getURI().toString())
                        .tag("status_code", String.valueOf(response.getStatusCode().value()))
                        .register(meterRegistry));
                return response;
            } catch (IOException e) {
                sample.stop(Timer.builder("proxy.forward.request.duration")
                        .tag("target_url", request.getURI().toString())
                        .tag("error", e.getClass().getSimpleName())
                        .register(meterRegistry));
                throw e;
            }
        });
        return restTemplate;
    }
}

2. 自定义计时器记录其他环节

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/proxy")
public class ProxyController {
    private final RestTemplate restTemplate;
    private final MeterRegistry meterRegistry;
    private static final String TARGET_API_URL = "http://target-api.example.com";

    public ProxyController(RestTemplate restTemplate, MeterRegistry meterRegistry) {
        this.restTemplate = restTemplate;
        this.meterRegistry = meterRegistry;
    }

    @PostMapping("/**")
    public ResponseEntity<String> proxyRequest(@RequestBody String payload, HttpServletRequest request) {
        // 记录读取请求体耗时
        Timer.Sample readSample = Timer.start(meterRegistry);
        int payloadSize = payload.getBytes().length;
        readSample.stop(Timer.builder("proxy.read.request.body.duration")
                .tag("request_uri", request.getRequestURI())
                .tag("payload_size", String.valueOf(payloadSize))
                .register(meterRegistry));

        // 转发请求(已通过RestTemplate拦截器记录耗时)
        String targetUrl = TARGET_API_URL + request.getRequestURI().replace("/proxy", "");
        return restTemplate.postForEntity(targetUrl, payload, String.class);
    }
}

四、额外排查点

  1. 检查连接池配置:若使用默认RestTemplate客户端,可能存在连接复用不足导致请求等待。可切换到HttpComponentsClientHttpRequestFactory配置连接池:
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(200); // 最大总连接数
        connectionManager.setDefaultMaxPerRoute(50); // 每个目标地址的最大连接数

        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
                HttpClients.custom().setConnectionManager(connectionManager).build());
        factory.setConnectTimeout(3000); // 连接超时时间
        factory.setReadTimeout(5000); // 读取超时时间

        return new RestTemplate(factory);
    }
}
  1. 关联耗时与请求体大小:在日志中同时记录请求体大小和各环节耗时,对比耗时久的请求是否payload明显更大,验证你的猜测。

内容的提问来源于stack exchange,提问作者Himanshu Bhatt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 03:14:54