一、问题出现
之前在搞性能优化,发现有些通过Spring boot feign的RPC接口比较慢,达到100-200ms,按理RPC调用都是IP直连,不应该超过100ms的,然后使用阿里开源的arthas工具测试接口执行时间,结果发现同一个接口大概每10秒会出现一次慢100+ms,如图:
但实际上服务方的API层响应很快:
二、原因分析
1、负载均衡到其它开发机
疑点:由于个人开发机连接测试环境的注册地址,有可能测试接口会调用个人地址,但测试机ip为10.8,个人开发机为192.168,但若是分到个人机器应该不止100ms。
操作:使用单独的测试机依然重现
结论:排除此原因
2、httpclient配置出错
疑点: 1)怀疑线程keepalive太短,过期后重新开启线程,增加延迟,查看代码确实设置了keepalive=5,但开启线程不应该100ms那么多
2)怀疑配置出错导致负载均衡算法延迟
操作:使用arthas测试执行时间,如下图,可以看出最慢是读取流操作了
结论:排除此原因
3、服务方的tomcat配置问题
疑点: 既然不是调用者问题,那就从接收者身上找问题好了,从tomcat接收请求开始跟踪,果然发现执行时间长的位置
这是什么模块呢?简单来说就是加载jar中的静态资源,接口请求过来之后需要判断是否命中jar中的静态资源。查看源码发现每次加载完会缓存起来,但隔一段时间就重新加载,表明应该有地方清了缓存。搜索发现有个后台线程ContainerBackgroundProcessor执行gc方法,方法内会将缓存设null
// ContainerBackgroundProcessor部分代码
while(!ContainerBase.this.threadDone) {
try {
Thread.sleep((long)ContainerBase.this.backgroundProcessorDelay * 1000L);
} catch (InterruptedException var8) {
;
}
if (!ContainerBase.this.threadDone) {
// 用于重载standardContext、处理过期的session、清除jar中的静态资源缓存等等
this.processChildren(ContainerBase.this);
}
}
// AbstractArchiveResourceSet清除jar中的静态资源缓存
public void gc() {
Object var1 = this.archiveLock;
synchronized(this.archiveLock) {
if (this.archive != null && this.archiveUseCount == 0L) {
try {
this.archive.close();
} catch (IOException var4) {
;
}
this.archive = null;
this.archiveEntries = null;
}
}
}
由于backgroundProcessorDelay默认为10,所以每10秒清一次缓存,这也印证了现象中的情况!注意ContainerBackgroundProcessor只清除是jar中的静态资源,项目中的静态资源并不会缓存于此,也就不会有影响了。
通过debug发现,每次加载的静态资源出处是springfox-swagger-ui.jar(自动生成api文档),里面的确存在静态资源,于是将其依赖去掉,发现延迟100ms的情况也消失了!
结论:tomcat在启动时,通过StaticResourceConfigurer.addResourceJars将所有jar中的静态资源路径保存到list中,请求过来时先从此list中加载全部资源并检查是否命中,是则返回,若所有jar均没有静态资源则不会加载;为了增加性能,在加载完静态资源后会缓存下来,但设置了一个后台线程,定时清除缓存(个人认为:tomcat这样做应该是为了热部署,但完全没有配置控制不gc的确有点沙雕,虽然耗时只有100+ms)。
三、结论
最终结论:没事不要把静态资源扔到jar里!!!
最终结论:没事不要把静态资源扔到jar里!!!
最终结论:没事不要把静态资源扔到jar里!!!