实战:Java web应用性能分析之【为什么springboot的restful接口第一次访问很慢,第二次之后就快很多】

概叙

实战:Java web应用性能分析之【Arthas性能分析trace监控后端性能】-CSDN博客

科普文:Java基础系列之后端性能优化汇总_java bex接口性能-CSDN博客

实战:Java基础系列性能优化之API接口超时排查【二】_java 日志中如何判断接口超时中断-CSDN博客

Java web应用性能分析之【java进程问题分析工具】_java性能分析-CSDN博客

Java web应用性能分析之【java进程问题分析概叙】_java进程性能监控web页面-CSDN博客

科普文:Java基础系列之后端性能优化汇总_java bex接口性能-CSDN博客

如题:为什么springboot的restful接口第一次访问很慢,第二次之后就快很多?

第一次访问
2025-04-23 20:06:10.072 |834f571028a64bb3b4f474b27b8b2329 |XNIO-1 task-1 | com.zxx.study.boot.web.config.ControllerAspect#logTime:39 | RestController方法 runAsync1 执行耗时: 329ms
2025-04-23 20:06:10.227 |834f571028a64bb3b4f474b27b8b2329 |XNIO-1 task-1 | com.zxx.study.common.web.interceptor.LoggingInterceptor#afterCompletion:126 | [/Ecommerce/api/v1/lock/runAsync1-响应] : GET: application/json:  status: 200:  costTime: 528ms
2025-04-23 20:06:10.387 |834f571028a64bb3b4f474b27b8b2329 |XNIO-1 task-1 | com.zxx.study.common.web.filter.TraceIdFilter#doFilter:111 | [0:0:0:0:0:0:0:1:/Ecommerce/api/v1/lock/runAsync1] : GET: null:  status: 200:  costTime: 763ms

第二次访问
2025-04-23 20:08:10.078 |438aa3bb4a434644b0b49b151ca684be |XNIO-1 task-1 | com.zxx.study.boot.web.config.ControllerAspect#logTime:35 | RestController方法 runAsync1 执行耗时: 6ms
2025-04-23 20:08:10.082 |438aa3bb4a434644b0b49b151ca684be |XNIO-1 task-1 | com.zxx.study.common.web.interceptor.LoggingInterceptor#afterCompletion:122 | [/Ecommerce/api/v1/lock/runAsync1-响应] : GET: application/json:  status: 200:  costTime: 11ms
2025-04-23 20:08:10.133 |438aa3bb4a434644b0b49b151ca684be |XNIO-1 task-1 | com.zxx.study.common.web.filter.TraceIdFilter#doFilter:111 | [0:0:0:0:0:0:0:1:/Ecommerce/api/v1/lock/runAsync1] : GET: null:  status: 200:  costTime: 70ms

如上日志所示:

第一次访问耗时【763ms【528ms【329ms】】】

第二次访问耗时【70ms【11ms【6ms】】】

访问controller,第一次和第二次访问相差10-50倍,是哪里出了问题?

当前环境:springboot2.6.8 + JDK11.0.2  同时用undertow替换了内置Tomcat。

这里面的原因很多,第一次访问慢通常涉及JVM的类加载和JIT编译、数据库连接池初始化、DispatcherServlet的懒加载、安全随机数生成器的问题,以及资源懒加载和缓存机制等等。

例如:

  1. ‌Filter层耗时‌:TraceIdFilter本身可能处理了一些逻辑,比如生成Trace ID、记录请求开始时间等。如果Filter中有复杂的逻辑或阻塞操作,可能会导致耗时增加。需要检查Filter的实现,是否有耗时的代码‌。
  2. ‌序列化与反序列化‌:Controller处理的是POST请求,数据格式为application/json。如果请求体较大,JSON的解析和序列化可能消耗较多时间,尤其是在处理复杂或大数据量的情况下。可以通过优化DTO结构或使用更高效的序列化库(如Fastjson或Jackson的优化配置)来减少时间‌。
  3. ‌连接池初始化与资源获取‌:应用可能在处理请求时需要从数据库连接池获取连接,如果连接池初始化较慢或连接获取有延迟,会导致额外耗时。预热连接池可以避免首次获取连接的延迟,这在之前的回答中也有提到‌。
  4. ‌事务管理‌:如果Service层使用了声明式事务(如@Transactional),事务的提交或回滚可能会在切面之外消耗时间。特别是如果事务范围较大,包含了一些非数据库操作,会导致事务提交时间较长。需要检查事务的边界和配置,确保事务范围最小化‌。
  5. 数据库连接池未预热‌:首次请求需要建立连接,之后复用。
  6. ‌JVM JIT编译‌:首次执行时方法被解释执行,之后编译优化。
  7. ‌缓存未预热‌:首次查询未命中缓存,后续命中。
  8. ‌类加载延迟‌:首次加载类需要时间。
  9. ‌网络与IO延迟‌:虽然请求是本地测试(127.0.0.1),但如果有代理、网关或负载均衡器,也可能引入延迟。此外,日志记录、监控上报等异步操作如果同步执行,也会增加耗时。需要确认是否有网络层面的中间件或同步日志操作‌。
  10. ‌第三方拦截器或组件‌:除了用户提到的切面,可能还有其他全局拦截器、安全框架(如Spring Security)、权限校验等组件在处理请求,这些组件的处理时间可能未被切面捕获,从而导致总耗时增加。需要审查所有全局配置和依赖库的拦截逻辑‌。

关键在于:

  1. 检查并优化JVM配置:调整堆内存和垃圾回收策略,提升性能。
  2. 监控和分析类加载:通过日志和工具,了解哪些类在首次请求时被加载,优化类加载过程。
  3. 优化数据库连接池配置:确保连接池在应用启动时就初始化,减少首次请求时的连接创建延迟。
  4. 启用合理的缓存策略:利用方法级缓存、HTTP缓存等,减少重复计算和资源访问。
  5. 预加载关键组件和数据:在应用启动时,提前加载所需的组件和数据,避免首次请求时的延迟。

第一次请求延迟的主要原因分析和解决方案

1. ‌JVM 类加载与JIT编译预热

  • 首次请求‌:触发类加载(如控制器、过滤器、Spring Bean等),且字节码由解释器逐行执行,未触发JIT编译优化。
  • 后续请求‌:热点代码(如接口处理逻辑)被JIT编译为本地机器码,执行效率提升。

解决方案:

1.1 JVM 预热与参数优化

# 启用并行类加载  
-XX:+AlwaysPreTouch  
# 降低JIT编译阈值  
-XX:TieredStopAtLevel=1 -XX:CompileThreshold=500  

1.2 查看JVM类加载日志

 在JVM启动参数中添加-verbose:class,查看类加载的时间,识别是否有特定类加载导致延迟:java -verbose:class -jar yourapp.jar

1.3 代码预热

@Bean  
public CommandLineRunner preheat(WebApplicationContext context) {  
    return args -> {  
        new RestTemplate().getForObject("http://localhost:8080/api/v1/lock/runAsync1", String.class);  
    };  
}  

2. ‌数据库连接池和线程池初始化

  • 首次请求‌:若涉及数据库操作,连接池(如HikariCP)需首次创建物理连接(TCP握手、权限验证),耗时较高。
  • 后续请求‌:直接复用连接池缓存连接,无初始化开销。

解决方案

2.1 初始化线程池

2.2 初始化数据库连接池

2.3数据库预热

预热的日志

3. ‌Tomcat与DispatcherServlet延迟加载

  • 默认行为‌:Spring Boot内嵌Tomcat的DispatcherServlet可能设置为懒加载,首次请求需初始化Servlet及关联组件(如消息转换器、拦截器)。
  • 后续请求‌:组件已初始化完成,直接处理请求。

解决方案

3.1undertow替换tomcat

科普文:SpringBoot项目禁用Tomcat,并用Undertow替换_springboot tomcat 禁用jsp-CSDN博客

科普文:Java web应用性能分析之【源码解读SpringBoot如何默认支持这三款内嵌服务器:Tomcat 、 Jetty 、 Undertow 】_springboot undertow tomcat-CSDN博客

科普文:Java web应用性能分析之【Spring Boot内嵌服务器选型:Tomcat vs Jetty vs Undertow 】_内置undertow支持并发-CSDN博客

3.2 显式配置‌:实现 DispatcherServlet 的启动时加载

@Configuration
@Slf4j
public class SystemConfig {

    /**
     *
     * 在 Undertow 中需通过 ‌编程显式配置‌ 实现 DispatcherServlet 的启动时加载,
     * 而 spring.mvc.servlet.load-on-startup=1 仅适用于 Tomcat 环境。
     *
     *  若需在 Undertow 中实现 ‌启动时初始化 DispatcherServlet‌,需通过编程方式显式配置:
     *  自定义 ServletRegistrationBean
     * */
    /**
     * 显式注册 DispatcherServlet,并强制关联 WebApplicationContext
     */
    @Bean
    public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
            WebApplicationContext webApplicationContext) {
        // 手动创建 DispatcherServlet 并注入 WebApplicationContext
        DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext);

        ServletRegistrationBean<DispatcherServlet> registration =
                new ServletRegistrationBean<>(dispatcherServlet, "/");

        // 关键:设置 Bean 名称与默认值一致,避免冲突
        registration.setName("dispatcherServlet");
        // 强制启动时初始化
        registration.setLoadOnStartup(1);
        if (log.isDebugEnabled()) {
            log.debug("Undertow:强制启动时加载 DispatcherServlet.");
        }
        return registration;
    }

    /**
     * 显式提供 DispatcherServletPath Bean,解决 ErrorMvcAutoConfiguration 依赖问题
     */
    @Bean
    public DispatcherServletPath dispatcherServletPath() {
        return () -> "/";  // 路径需与 ServletRegistrationBean 的映射路径一致
    }
}

4. ‌资源懒加载与缓存机制

  • 首次请求‌:
    • ORM框架(如Hibernate)加载实体元数据并生成SQL模板。
    • MyBatis解析Mapper XML文件或注解。
  • 后续请求‌:资源已缓存,直接复用。
    @GetMapping("/data")
    @Cacheable(value = "responseDataCache", key = "#id")
    public ResponseEntity<?> getData(@PathVariable Long id) {
        // 具体实现
    }

5. ‌打印日志的影响

实战:Java web应用性能分析之【spring boot 高性能日志:UDP+异步日志log4j2+logstash】-CSDN博客

实战:Java web应用性能分析之【Arthas性能分析trace监控后端性能】-CSDN博客

6.过滤器、拦截器、AOP切面的影响

在Spring Boot应用程序中,使用过滤器(Filters)、拦截器(Interceptors)和AOP(Aspect-Oriented Programming)是常见的实践,用于处理横切关注点,如日志记录、安全检查、事务管理等。这些技术确实可以增强应用的功能性和可维护性,但同时也可能会对性能产生影响,尤其是在高负载或高频请求的场景下。

下面我们来分析这些技术对RESTful接口性能的可能影响:

1. 过滤器(Filters)

性能影响:

  • 正向过滤器:通常在请求到达控制器之前执行,例如用于身份验证、请求日志记录等。如果过滤器执行的计算密集型操作或阻塞操作(如远程调用),则可能会显著增加延迟。

  • 反向过滤器:在响应发送给客户端之前执行,主要用于修改响应内容或添加额外的响应头。这类操作通常对性能影响较小。

建议:

  • 确保过滤器执行的操作尽可能轻量级。

  • 避免在过滤器中进行复杂的业务逻辑处理或耗时的操作。

  • 使用异步处理或非阻塞IO操作来优化性能。

2. 拦截器(Interceptors)

性能影响:

  • 拦截器通常在控制器方法执行前后执行,用于拦截请求和响应。与过滤器类似,如果拦截器中包含耗时的操作,将直接增加处理时间。

建议:

  • 保持拦截器逻辑的简洁性,避免在其中执行复杂的业务逻辑。

  • 对于需要耗时处理的操作,考虑异步处理或使用其他机制(如消息队列)。

3. AOP(面向切面编程)

性能影响:

  • AOP通过在方法执行前后加入额外的逻辑(如日志记录、事务管理等)来实现横切关注点的统一处理。如果AOP的切面过于复杂或在关键路径上执行,会影响性能。

建议:

  • 优化AOP切面的使用,避免在关键路径上使用AOP进行耗时操作。

  • 使用@Async注解进行异步处理,减少对主线程的影响。

  • 考虑使用更细粒度的控制,例如使用@Around注解时,精确控制哪些方法需要被增强。

4.优化策略:

  1. 异步处理:对于可能阻塞的长时间操作,使用@Async注解实现异步处理。

  2. 缓存:对于重复计算或不常变化的数据,使用缓存减少计算和数据库访问。

  3. 最小化切面范围:仅对必要的类和方法应用AOP增强,避免过度使用。

  4. 性能测试:定期进行性能测试,评估不同配置下的响应时间和吞吐量,找出瓶颈并进行优化。

  5. 监控和分析:使用工具如Spring Boot Actuator、Micrometer等进行监控和分析,了解哪些部分是性能瓶颈。

通过上述策略,可以在保证应用功能的同时,有效控制这些技术对RESTful接口性能的影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

01Byte空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值