最近在客户的要求下,对系统进行了压测。自己压测的时候一切指标都挺正常。但是客户却反馈说压测的时候有时候会碰到性能衰减很厉害,不稳定。于是有了下面这次的排查过程。客户反馈说问题是发生在多次连续压测之后碰到的,所以进行了一系列的测试。
先说一下测试环境的一些信息:测试主机是阿里云的ECS,配置是2C + 16G。为了方便,数据库采用的是Docker搭建的,版本是5.6.X。另外,这台机器上还安装了Zookeeper 3.4.X 和Kafka 1.0。系统本身是一个标准的Spring + CXF的应用,提供给客户使用的服务是标准的Restful接口。
1. 重现问题
因为客户反馈是多次压测之后碰到的,因此首先尝试连续压测多次看看能不能重现问题。为了可能存在的数据库性能问题,临时申请了RDS作为数据库;另外为了尽快发现问题,将程序占用内存缩小到1G。这次测试测出了客户所说的问题,通过jconsole监控也发现了问题原因,应该是因为GC导致的。
看图表可以发现,压测5~6分钟之后,Java的GC占用了大量的CPU时间(左上图中的蓝色线),而且内存占用一直居高不下(右上图)。
2. 调整内存使用量
把内存占用调整到2G,发现问题还是同样存在,只是出现问题的时间晚了,压测开始15分钟之后才会出现问题。也就是说问题是同样存在的,只是内存大了之后,把出现问题的时间拖后了而已。
3. 问题根源
通过上面的测试发现了问题是GC导致的,也就是系统中有些内存无法给GC回收,当内存达到Java虚拟机可以申请的上限的时候,会出现频繁的GC操作,从而导致GC操作占用大量CPU时间,必然导致系统本身的TPS下降。
通过分析dump文件,以及上网搜索,最终确认问题可能出现在Session超时时间设置上: 在Java Web开发中,Session为我们提供了很多方便,Session是由浏览器和服务器之间维护的。Session超时理解为:浏览器和服务器之间创建了一个Session,由于客户端长时间(休眠时间)没有与服务器交互,服务器将此Session销毁,客户端再一次与服务器交互时之前的Session就不存在了。
这个Session长短是可以设置的。默认情况下,什么也不设置的话Session超时时间是30分钟。设置方法是修改项目中的web.xml,如下面的配置是设置session时间为1分钟:
1 <session-config>
2 <session-timeout>1</session-timeout>
3 </session-config>
之前项目中都没有对session时间进行设置,因此会有大量的session信息被缓存,不到时间是无法GC的。而项目本身的服务是不依赖于session的,完全可以将session时间设置的更短一些。
4. 调整session时间为1分钟
这一轮测试基本信息如下:
- 数据库使用RDS
- 内存占用使用2G
- Session设置为1分钟 压测结果如下:
这次总共压测了30分钟,GC占用的CPU时间也非常少,内存也表现良好,可以正常GC回收。
性能表现一直稳定。
5. 数据库切换回Docker
上一轮测试结果给了很大的信心,因此决定把数据库切换为本机的Docker中的MySQL,其他的不变:
GC表现正常,内存也回收良好。
TPS降低了不少,之前是1700+,这次只有1250+了。这个一方面是所有服务运行在一台主机上带来的性能损耗,另一方面应该是Docker本身带来了一些损耗。
6. 调整为1G内存
再把内存调整为1G,再测试一次。
GC占用时间略有上升,但是总体还是很稳定。
7. 结论
如果你的系统本身不依赖于Web的Session机制,请把Session时间调小一些,这样在高负载的情况下,GC才能够正常工作。