1. 简介
在微服务架构与高并发场景下,api接口的响应速度直接影响用户体验与系统稳定性。随着业务复杂度提升,接口性能问题逐渐成为系统瓶颈,例如数据库查询延迟、第三方服务调用超时等场景,均可能导致接口耗时激增。传统的手动埋点统计方式(如在每个接口方法中记录开始与结束时间)存在代码侵入性强、维护成本高的问题,难以满足大规模接口的监控需求。
本篇文章将介绍 spring boot 中记录 api 请求耗时的 6 种实用方案,涵盖从基础到进阶的多种实现方式,帮助开发者根据业务场景选择最适合的监控手段。
2.实战案例
2.1 手动记录
我们可以通过spring内置的stopwatch工具进行记录方法执行耗时情况:
@getmapping("/query") public responseentity<?> query() throws exception { stopwatch stopwatch = new stopwatch(); stopwatch.start(); // 业务逻辑 timeunit.milliseconds.sleep(new random().nextlong(2000)) ; stopwatch.stop(); system.out.printf("方法耗时:%dms%n", stopwatch.gettotaltimemillis()) ; return responseentity.ok("api query...") ; }
运行结果
方法耗时:1096ms
针对手动记录这里总结2点缺点:
- 代码侵入性强:每次需要记录请求耗时的时候都需要在具体的业务逻辑中插入相应的计时代码(如使用system.currenttimemillis()或stopwatch)。这种方式会增加代码的复杂度,并且使得业务逻辑与性能监控代码耦合在一起,降低了代码的可读性和维护性。
- 重复工作:如果多个地方都需要进行类似的耗时统计,则可能需要在每个地方都添加相同的代码,这导致了代码的重复。违反了dry(don't repeat yourself)原则。
2.2 自定义aop记录
通过自定义注解结合spring aop切面编程,可实现无侵入式的方法执行耗时统计与记录。
@aspect @component public class performanceaspect { private static final logger logger = loggerfactory.getlogger("api.timed") ; @around("@annotation(org.springframework.web.bind.annotation.requestmapping) || " + "@annotation(org.springframework.web.bind.annotation.getmapping) || " + "@annotation(org.springframework.web.bind.annotation.postmapping) || " + "@annotation(org.springframework.web.bind.annotation.putmapping) || " + "@annotation(org.springframework.web.bind.annotation.deletemapping) || " + "@annotation(org.springframework.web.bind.annotation.patchmapping)") public object recordexecutiontime(proceedingjoinpoint pjp) throws throwable { stopwatch sw = new stopwatch(); sw.start(); object result = pjp.proceed(); sw.stop(); logger.info("方法【{}】耗时: {}ms", pjp.getsignature(), sw.gettotaltimemillis()) ; return result; } }
在该示例,我们并没有自定义注解,而是直接拦截了定义controller接口使用的注解。
运行结果
line:29 - 方法【responseentity apicontroller.query()】耗时: 487ms
总结:aop实现耗时记录非侵入、统一管理、减少重复代码,适合全局监控。对非spring管理的方法无效,增加切面复杂度,可能影响性能。
2.3 拦截器技术
通过拦截器不用修改任何业务代码就能非常方便的记录方法执行耗时情况。
public class timedinterceptor implements handlerinterceptor { @override public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) { request.setattribute("starttime", system.currenttimemillis()); return true; } @override public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler, exception ex) { long starttime = (long) request.getattribute("starttime"); long cost = system.currenttimemillis() - starttime; system.out.printf("请求【%s】耗时: %dms%n", request.getrequesturi(), cost) ; } }
注册拦截器
@component public class interceptorconfig implements webmvcconfigurer { @override public void addinterceptors(interceptorregistry registry) { registry.addinterceptor(new timedinterceptor()) .addpathpatterns("/api/**") ; } }
运行结果
请求【/api/query】耗时: 47ms
总结:拦截器可集中管理请求耗时记录,减少代码侵入性,适用于controller层统一监控。仅对web请求(controller)生效,无法捕获非http接口或内部方法调用耗时,粒度较粗。
2.4 使用filter
filter 是 servlet 规范中的过滤器,用于在请求到达目标资源前后进行拦截处理,可用于日志记录、权限校验等通用逻辑。
@component @order(ordered.highest_precedence) // 确保最先执行 public class requesttimingfilter implements filter { private static final pathpatternparser parser = new pathpatternparser(); private static final logger logger = loggerfactory.getlogger(requesttimingfilter.class); // 从配置文件中读取排除路径 @value("${timing.filter.exclude-paths}") private string[] excludepaths; // 路径匹配器缓存 private list<pathpattern> excludedpatterns = collections.emptylist(); @override public void init(filterconfig filterconfig) { // 初始化时编译排除路径的正则表达式 excludedpatterns = arrays.stream(excludepaths).map(path -> parser.parse(path)).tolist() ; logger.info("记录请求耗时,不记录的uri: {}", arrays.tostring(excludepaths)); } @override public void dofilter(servletrequest request, servletresponse response, filterchain chain) throws ioexception, servletexception { httpservletrequest httprequest = (httpservletrequest) request; string requesturi = httprequest.getrequesturi(); // 检查是否在排除路径中 if (shouldexclude(requesturi)) { chain.dofilter(request, response); return; } long starttime = system.nanotime(); try { // 执行后续过滤器链和实际请求处理 chain.dofilter(request, response); } finally { long endtime = system.nanotime(); long durationnanos = endtime - starttime; long durationmillis = timeunit.nanoseconds.tomillis(durationnanos); // 记录日志(包含请求方法和状态码) if (response instanceof httpservletresponse httpresponse) { int status = httpresponse.getstatus(); logger.info("[{}] {} - {}ms (status: {})", httprequest.getmethod(), requesturi, durationmillis, status); } else { logger.info("[{}] {} - {}ms", httprequest.getmethod(), requesturi, durationmillis); } } } private boolean shouldexclude(string requesturi) { return excludedpatterns.stream() .anymatch(pattern -> pattern.matches(pathcontainer.parsepath(requesturi))) ; } }
运行结果
requesttimingfilter line:77 - [get] /api/query - 379ms (status: 200)
总结:filter 实现耗时记录非侵入,适用于全局请求监控,配置简单;仅能记录整个请求的处理时间,无法精确到具体方法或业务逻辑,粒度较粗。
2.5 通过事件监听
在spring mvc底层内部,当一个请求处理完成以后会发布servletrequesthandledevent 事件,通过监听该事件就能获取请求的详细信息。
@component public class timedlistener { @eventlistener(servletrequesthandledevent.class) public void recordtimed(servletrequesthandledevent event) { system.err.println(event) ; } }
运行结果
servletrequesthandledevent: url=[/api/query]; client=[0:0:0:0:0:0:0:1]; method=[get]; status=[200]; servlet=[dispatcherservlet]; session=[null]; user=[null]; time=[696ms]
详细的输出了当前请求的信息。
总结:非侵入式获取请求处理耗时,适用于全局监控且无需修改业务代码;仅能获取整个请求的耗时信息,无法定位具体方法或模块性能问题,粒度较粗。
2.6 micrometer + prometheus
micrometer 是一个指标度量工具,支持多种监控系统,如 prometheus。通过 @timed 注解可轻松记录方法耗时;prometheus 是开源监控系统,擅长拉取和聚合指标,常与 micrometer 集成实现可视化监控。两者结合适合微服务性能观测。
@timed(value = "api.query", description = "查询业务接口") @getmapping("/query") public responseentity<?> query() throws exception { timeunit.milliseconds.sleep(new random().nextlong(2000)) ; return responseentity.ok("api query...") ; }
在需要监控的接口上添加 @timed 注解,同时你还需要进行如下的配置:
management: observations: annotations: enabled: true
要结合prometheus,那么我们还需要引入如下依赖
<dependency> <groupid>io.micrometer</groupid> <artifactid>micrometer-registry-prometheus</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-actuator</artifactid> </dependency>
配置actuator暴露prometheus端点
management: endpoints: web: exposure: include: '*' base-path: /ac
最后,我们还需要在prometheus中进行配置
- job_name: "testtag" metrics_path: "/ac/prometheus" static_configs: - targets: ["localhost:8080"]
总结:非侵入、集成简单,支持细粒度方法级监控,与spring boot天然兼容,数据可持久化并可视化;需引入监控组件,增加系统复杂度。
2.7 使用arthas
arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
首先,在如下地址下载最新的arthas
接下来,通过如下命令启动arthas
java -jar arthas-boot.jar
通过前面的数字选择哪个进程。
连接到具体的进程后,我们可以通过如下的命令来跟踪记录方法执行耗时情况
trace com.pack.timed.controller.apicontroller query
2.8 使用skywalking
skywalking 是一个开源的 apm(应用性能监控)系统,支持分布式链路追踪、服务网格观测、度量聚合与可视化,适用于微服务、云原生和 service mesh 架构,帮助开发者实现全栈性能监控与故障诊断。
我们无需在代码中写任何代码或是引入依赖,因为skywalking将使用agent技术进行跟踪系统。
首先,我们需要在如下地址下载skywalking已经java对应的agent。
web ui 运行在8080端口
最后,我们在运行程序时,需要加入如下jvm参数。
-javaagent:d:\all\opensource\skywalking\skywalking-agent\skywalking-agent.jar -dskywalking.agent.service_name=pack-api 1.2.
总结:skywalking 自动完成链路追踪与耗时监控,支持分布式系统,无需修改代码,可视化强,性能影响小。在分布式系统中非常推荐。
到此这篇关于springboot监控api请求耗时的6中解决解决方案的文章就介绍到这了,更多相关springboot监控api请求耗时内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论