非RESTful SpringBoot应用性能测试:如何获取各服务耗时拆解数据?
嘿,这个问题我之前帮不少做消息队列驱动的Spring Boot项目解决过——非REST架构确实没法直接用常规的Web请求链路追踪,但还是有不少简便的方案能拿到各服务的耗时拆解,给你整理几个实用的:
这是最贴合Spring Boot生态的方案,不需要引入太多额外依赖(Micrometer是Spring Boot Actuator的默认依赖),能轻松实现服务方法的耗时统计,还能对接Prometheus+Grafana做可视化监控。
步骤大概是这样:
- 自定义一个用于标记需要统计耗时的注解,比如
@ServiceTimed - 写一个AOP切面,环绕被标记的方法,记录方法执行的开始/结束时间,将耗时数据上报到Micrometer的
MeterRegistry - 通过Actuator的
/metrics端点查看数据,或者集成监控工具展示
示例代码:
// 自定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ServiceTimed {} // AOP切面 @Aspect @Component public class ServiceTimingAspect { private final MeterRegistry meterRegistry; public ServiceTimingAspect(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } @Around("@annotation(com.yourpackage.ServiceTimed)") public Object recordServiceTime(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { long durationMs = System.currentTimeMillis() - startTime; String serviceMethod = joinPoint.getSignature().toShortString(); // 注册计时器并记录耗时 Timer.builder("service.processing.duration") .tag("method", serviceMethod) .register(meterRegistry) .record(durationMs, TimeUnit.MILLISECONDS); } } }
之后只需要在你想统计的服务方法上加上@ServiceTimed注解,启动项目后访问/actuator/metrics/service.processing.duration就能看到各方法的耗时分布(平均值、百分位数等)。
如果你的项目以后可能扩展成分布式系统,或者想要更直观的链路可视化,用OpenTelemetry+Jaeger是个不错的选择——它能从消息消费的入口开始,追踪每个服务方法的调用链路,清晰展示每个步骤的耗时。
核心思路是:
- 在消息消费方法中创建一个根Span(代表整个消息处理链路)
- 在每个服务方法中创建子Span,标记该步骤的执行
- 配置OpenTelemetry将追踪数据导出到Jaeger,在Jaeger UI中查看完整链路的耗时拆解
示例代码:
@Service public class QueueMessageConsumer { private final Tracer tracer; private final OrderProcessingService orderService; public QueueMessageConsumer(Tracer tracer, OrderProcessingService orderService) { this.tracer = tracer; this.orderService = orderService; } @JmsListener(destination = "order.queue") public void consumeOrderMessage(Message message) throws JMSException { String messageId = message.getJMSMessageID(); // 创建根Span,标记消息消费入口 Span rootSpan = tracer.spanBuilder("message.consume.order") .setAttribute("message.id", messageId) .startSpan(); try (Scope scope = tracer.withSpan(rootSpan)) { // 调用各个服务步骤 orderService.validateOrder(message.getBody(String.class)); orderService.processPayment(); orderService.notifyUser(); } finally { rootSpan.end(); } } } // 服务方法中创建子Span @Service public class OrderProcessingService { private final Tracer tracer; public OrderProcessingService(Tracer tracer) { this.tracer = tracer; } public void validateOrder(String orderJson) { Span span = tracer.spanBuilder("order.validate").startSpan(); try (Scope scope = tracer.withSpan(span)) { // 订单校验逻辑 } finally { span.end(); } } }
配置好OpenTelemetry和Jaeger后,你就能在Jaeger的UI里看到每个消息处理链路的完整时序图,每个服务步骤的耗时一目了然。
如果只是临时需要排查某个消息的处理耗时,不想引入额外依赖,直接用日志埋点最方便。可以结合SLF4J的MDC(映射诊断上下文)来关联同一个消息的所有日志,方便后续筛选分析。
示例代码:
@Service public class PaymentService { private static final Logger logger = LoggerFactory.getLogger(PaymentService.class); public void processPayment(String messageId) { MDC.put("messageId", messageId); long startTime = System.currentTimeMillis(); try { // 支付处理逻辑 logger.info("Payment processing started"); // ...业务代码 } finally { long durationMs = System.currentTimeMillis() - startTime; logger.info("Payment processing completed in {} ms", durationMs); MDC.remove("messageId"); } } }
在消息消费方法中把消息ID传入服务方法,这样所有日志都会带上messageId标记,你可以通过日志工具(比如ELK)筛选出同一个消息的所有日志,快速计算各步骤的耗时。
内容的提问来源于stack exchange,提问作者Snowman ROTMG

