软件环境:springboot2.1 + spring-data-jpa+ openjdk8
问题现象
重启服务后,在用户第一次访问时,部分系统响应接口响应缓慢,有时候需要20多秒才返回,但第二次调度同一个接口则返回很快
网络分析
网络上有该问题的三个解释说法:
|
|
方案一:dispatcherServlet 懒加载修改
在springboot的appcation.yml 中添加如下参数即可解决
|
|
方案二:数据库链接是懒加载修改(未验证)
在springboot的appcation.yml 中添加如下参数即可解决
|
|
或者在系统启动后进行预热:在@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启动时增加如下参数即可
|
|
或者直接去修改$/jre/lib/security/java.security
文件也可
|
|
以上处理方案执行 (当前预热方案还未执行)后,我的系统并未有改善。
监测分析
基于该问题,我创建了一个AOP监测controller
的执行耗时,监测结果是controller
的返回几乎都在几十毫秒,与实际不符,
我然后又创建了一个拦截器监测从接到请求开始到返回的执行耗时,此处的监测结果与实际耗时比较接近,使用debug分析日志,看到有如下两种情况
-
- 部分接口从拦截器接受到请求开始间隔了很长时间才交接给
controller
进行处理
- 部分接口从拦截器接受到请求开始间隔了很长时间才交接给
-
- 部分接口
controller
返回后,间隔了很长时间(后台还运行了很多sql查询)才返回给拦截器
- 部分接口
情况一:查找不存在的类耗时
从linux服务器中监测的日志截取如下
|
|
? 看到第3行到第4行的时间差了吗,足足耗费了7.6s
ClassNotFound的几个Class肯定是在类定义中不存在的;
查看几个查找类的命名都存在*Customizer
后缀,排除这个后缀后,可以找到对应的类;
com.yzsoft.synergy.reception.bean.param.ReceptionOfficialParam
是controller
中接口方法的参数,该参数bean的继承关系如下:
|
|
分析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
问题,这个问题是老生常谈了。
主要解决途径如下:
- 通过DO-VO来转换封装取消懒加载属性
- 在返回前置空懒加载属性
- 通过json序列化过滤懒加载属性
需要重写接口的返回,否则你的restapi只能返回json字符串了
- 通过jpa2.1中的新特性@NamedEntityGraph来提升懒加载查询效率(将
N+1 sql
变成1+1 sql
)
spring-data-jpa的默认实现并不能很好的支持第四种(需要重写dao的接口注解)
JMCP分析
针对为什么在linux中WebappClassLoaderBase的findClass会中断那么长时间问题,我在jmc中开启飞行记录器l来跟踪下性能耗时
然后我发现,在受阻线程主要的时间跳跃点在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中有以下两种思路:
- 打包成可执行的jar,不走tomcat的
org.apache.catalina.loader.WebappClassLoaderBase
- 更换sevlet容器
我使用思路一试验了下,我的问题消失了,证明思路是对的,我卡卡…
我分析了4天,终于搞定了,神清气爽…
解决方案可以说就是改了一个字母,将pom.xml中 <packaging>war</packaging>
修改为 <packaging>jar</packaging>