Springboot 第一次访问慢的问题研究&解决方案

软件环境:springboot2.1 + spring-data-jpa+ openjdk8

问题现象

重启服务后,在用户第一次访问时,部分系统响应接口响应缓慢,有时候需要20多秒才返回,但第二次调度同一个接口则返回很快

网络分析

网络上有该问题的三个解释说法:

1、dispatcherServlet 是懒加载的
2、数据库链接是懒加载的
3、linux 下真随机数生成器

方案一:dispatcherServlet 懒加载修改

在springboot的appcation.yml 中添加如下参数即可解决

spring.mvc.servlet.load-on-startup=1

方案二:数据库链接是懒加载修改(未验证)

在springboot的appcation.yml 中添加如下参数即可解决

#最小空闲连接数量(有待考证是否会初始化10个连接)
spring.datasource.hikari.minimum-idle: 10

或者在系统启动后进行预热:在@PostConstruct方案中执行一次空查询

此种方案意图是初始化一次查询以填充缓存(orm session、mysql、redis),加快下次同样请求返回结果的速度

我使用的是jpa,并且开启了spring.jpa.open-in-view=true,orm session是每次请求重置的;也没有开启redis,解决方案对我有限

方案三:linux 下真随机数生成器修改

熵池问题:https://blog.csdn.net/wysnxzm/article/details/98482143

在java启动时增加如下参数即可

-Djava.security.egd=file:/dev/./urandom 

或者直接去修改$/jre/lib/security/java.security文件也可

securerandom.source=file:/dev./urandom

以上处理方案执行 (当前预热方案还未执行)后,我的系统并未有改善。

监测分析

基于该问题,我创建了一个AOP监测controller的执行耗时,监测结果是controller的返回几乎都在几十毫秒,与实际不符,

我然后又创建了一个拦截器监测从接到请求开始到返回的执行耗时,此处的监测结果与实际耗时比较接近,使用debug分析日志,看到有如下两种情况

    1. 部分接口从拦截器接受到请求开始间隔了很长时间才交接给controller进行处理
    1. 部分接口controller返回后,间隔了很长时间(后台还运行了很多sql查询)才返回给拦截器

情况一:查找不存在的类耗时

从linux服务器中监测的日志截取如下

14:16:58.844 INFO  c.y.s.monitor.interceptor.PerformanceInterceptor#20 : Interceptor performance: ip '192.168.1.109' request '/reception/receptionOfficial/page' start 
14:16:58.844 DEBUG o.s.o.j.support.OpenEntityManagerInViewInterceptor#86 : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor 
14:16:58.850 DEBUG org.apache.catalina.loader.WebappClassLoaderBase#173 :     findClass(java.lang.ObjectCustomizer) 
14:17:06.412 DEBUG org.apache.catalina.loader.WebappClassLoaderBase#173 :     --> Returning ClassNotFoundException 
14:17:06.416 DEBUG org.apache.catalina.loader.WebappClassLoaderBase#173 :     findClass(com.yzsoft.synergy.common.bean.BaseParamCustomizer) 
14:17:07.923 DEBUG org.apache.catalina.loader.WebappClassLoaderBase#173 :     --> Returning ClassNotFoundException 
14:17:07.926 DEBUG org.apache.catalina.loader.WebappClassLoaderBase#173 :     findClass(com.yzsoft.synergy.reception.bean.param.ReceptionActionParamCustomizer) 
14:17:07.928 DEBUG org.apache.catalina.loader.WebappClassLoaderBase#173 :     --> Returning ClassNotFoundException 
14:17:07.931 DEBUG org.apache.catalina.loader.WebappClassLoaderBase#173 :     findClass(com.yzsoft.synergy.reception.bean.param.ReceptionOfficialParamCustomizer) 
14:17:07.933 DEBUG org.apache.catalina.loader.WebappClassLoaderBase#173 :     --> Returning ClassNotFoundException 
14:17:07.935 INFO  c.yzsoft.synergy.monitor.aspect.PerformanceAspect#58 : controller performance: Start 

看到第3行到第4行的时间差了吗,足足耗费了7.6s

ClassNotFound的几个Class肯定是在类定义中不存在的;

查看几个查找类的命名都存在*Customizer后缀,排除这个后缀后,可以找到对应的类;

com.yzsoft.synergy.reception.bean.param.ReceptionOfficialParamcontroller中接口方法的参数,该参数bean的继承关系如下:

`com.yzsoft.synergy.reception.bean.param.ReceptionOfficialParam`继承自
`com.yzsoft.synergy.reception.bean.param.ReceptionActionParam`继承自
`com.yzsoft.synergy.common.bean.BaseParam`继承自
`java.lang.Object`

分析ClassNotFound的几个Class,恰好是这几个类名+*Customizer后缀

在测试过程中,使用了springboot 的不同版本(2.1x、2.2x、2.3x),openJDK的不同版本(8、11)均存在这个findClass过程

在我本机(windows10+openjdk8)环境下,这个过程在几十毫秒内完成,不影响请求速度,在服务器(ubuntu18.4+openjdk8 )环境下,部分接口在这个过程中会花费几秒(如上日志)

目前该问题仍在进一步分析中:

1、为什么会查找*Customizer

2、为什么在linux中WebappClassLoaderBase的findClass会中断那么长时间

情况二:hibernate懒加载的N+1 SQL耗时

​ 这实际上是hibernate懒加载的N+1 SQL问题,这个问题是老生常谈了。

​ 主要解决途径如下:

  1. 通过DO-VO来转换封装取消懒加载属性

  2. 在返回前置空懒加载属性

  3. 通过json序列化过滤懒加载属性

需要重写接口的返回,否则你的restapi只能返回json字符串了

  1. 通过jpa2.1中的新特性@NamedEntityGraph来提升懒加载查询效率(将N+1 sql 变成 1+1 sql

spring-data-jpa的默认实现并不能很好的支持第四种(需要重写dao的接口注解)

JMCP分析

针对为什么在linux中WebappClassLoaderBase的findClass会中断那么长时间问题,我在jmc中开启飞行记录器l来跟踪下性能耗时
Springboot 第一次访问慢的问题研究&解决方案_第1张图片

然后我发现,在受阻线程主要的时间跳跃点在java.beans.Introspector.getBeanInfo(Class, Class, int)的调用

基于这个类在网络上搜索到:

https://www.jb51.cc/java/437748.html

以及他的调度类org.springframework.beans.CachedIntrospectionResults.getBeanInfo(Class)的文章

https://blog.csdn.net/dennis_zane/article/details/83161898

分析下来,这个好像是tomcat的classLoader加载jar的老坑问题…

解决方案

那么换思路:我是否可以换一个classLoader呢

在springboot中有以下两种思路:

  1. 打包成可执行的jar,不走tomcat的org.apache.catalina.loader.WebappClassLoaderBase

  2. 更换sevlet容器

我使用思路一试验了下,我的问题消失了,证明思路是对的,我卡卡…

我分析了4天,终于搞定了,神清气爽…

解决方案可以说就是改了一个字母,将pom.xml中war修改为jar

? 还有个问题 为什么会查找*Customizer 下次再搞( 黑脸)

你可能感兴趣的:(java,spring)