背景
近期,有一个同事反馈一个技术问题,细节如下:
- 用户使用AWS MSK(Kafka)作为MQ,并使用AWS EC2作为消费者消费这个MQ里面的消息
- 用户在进行极限压测的时候,遇到了AWS EC2被打死,导致服务器无响应
- SSH 无法登陆AWS EC2
- SpringBoot程序作为MQ的消费者失去响应,一小时内都似乎没有恢复
- AWS CloudWatch面板上观察到AWS EC2 CPU其实利用率随着压测压力的释放,利用率不到20%
- 等待很长时间后发现AWS EC2即使利用率不高也没发恢复到SSH可登陆状态
所以综合来看,似乎是AWS EC2这边出现了某种资源不够导致的OS级别的问题
分析
一般而言,云服务商的服务器都是久经考验的,经过了无数客户在各种场景下的压力考验,出现这样的情况我们可以分析到可能是如下的几种情况
- CPU被打满:当CPU被打满后,服务器过载导致失去响应,但是如果压力下降,CPU利用率下降后,应该会自动恢复
- Memory被打满:当Memory被耗尽后,OS进程被挂起,包括SSH在内的一些进程出现memory资源抢占或者无法申请的问题。这时会导致这些程序无法正常的运行。但是如果memory被释放,有足够的memory后一般而言这些进程会恢复工作
- 网络被打满:当网络带宽被打满后,新的流量无法进入到服务器里面,导致发生大量的丢包等问题
结合上面的分析,我们可以看到这个同事的问题应该是Memory被打满的问题,但是要具体分析
排查
- 首先排除网络问题,AWS EC2的带宽在一众云服务商中是很有领先优势的,我们可以在network bandwidth看到具体机型的带宽上限,AWS EC2的带宽最小都是5G以上,也就是可以达到500MB/S,这是非常大的数字,所以网络部分问题不大,而且我们可以在AWS CloudWatch上看到网络是否被打满。当时的情况是网络并为被看到打满的情况
- 其次排除CPU的问题,AWS EC2的CPU客户使用的M5系列,CPU资源强劲,在AWS CloudWatch上我们看到压测压力释放后,CPU利用率下降了,但是AWS EC2依然没有响应。因此排除CPU资源不够的问题
- 最后定位Memory的问题,AWS EC2默认不监控Memory的利用率,这可能是考虑到客户的信息安全问题的设计,我们可以安装CloudWatch agent来监控memory,并把memory指标发送到CloudWatch上去,Monitor memory and disk metrics for Amazon EC2 Linux instances
当我们安装了CloudWatch agent后继续进行压测,发现Memory到利用率快速的上升,并在利用率达到92%后,复现上述的现象。这是我们发现AWS EC2失去了响应,SSH无法登陆,CloudWatch Agent停止了汇报内存利用率,估计这个agent这时也挂起了。
因此我们确认是内存资源导致的问题,那么多种监控手段都用了,但是AWS EC2已经出现了停止响应的问题,那么我们怎么继续定位呢,有下面的几种方式
- 使用其他方式尝试链接
- 使用AWS上自带的Serial Console尝试连接到AWS EC2上
- 使用AWS System Manager上的session manager连接
- 将压测停止,然后进行reboot,安装atop,尝试监控更多的信息
- 这些监控组件安装好后进行有梯度的缓慢的压力提升,尽量在问题边界定位问题
- 尝试获取Amazon Linux的日志,定位问题
- 检查服务器的swap是否打开,My EC2 Linux instance failed the instance status check due to over-utilization of its resources. How do I troubleshoot this?
当我们尝试前面2个方式都不太凑效的时候,那么就可以考虑检查swap,swap能在服务物理内存不够的时候,使用磁盘做Memory的虚拟化扩展,当物理内存不够用的时候,操作系统会从内存中取出一部分暂时不用的数据,放在交换分区中,从而为当前运行的程序腾出足够的内存空间,保证程序的正常运行,但是swap由于磁盘的IO性能限制,远不如实际的物理内存的性能,一般云服务商都没有打开swap
AWS EC2 默认没有打开SWAP,除了极少数低memory机型:
- AWS EC2 Instance store swap volumes
- Why don't EC2 ubuntu images have swap?
- How do I allocate memory to work as swap space in an Amazon EC2 instance by using a swap file?
Aliyun ECS默认没有打开SWAP:
当时客户在一台AWS EC2上运行了多个java Spring程序,EC2的物理内存为8G(实际可用内存不到8G),每个java程序配置的-xms为4096M;那么我们假设他运行了至少2个不同java程序A、B,表面上每个程序的内存消耗都不到物理内存的一半,但是在压测期间JVM会随着压力不断的尝试申请内存(每个最大4096M),那么JVM合计的内存消耗就大于EC2的实际内存了,这才是根本原因!!!
在压测时,出现的上述情况,在Java上体现的最明显
- JVM在管理内存的时候,其实它只会管理自己堆上的内存,其实实际的内存需求是 堆外内存+堆内存,当我们设置-xms 4096M时实际的内存消耗是大于4096M的
- 不同的java进程间时无感知的,合计的内存大于了实际的物理内存
- 如果你使用Java 8以及更老的版本,JVM并不会自动的在空闲时段将不用的内存释放给Linux OS;Java 11以及更新的版本,可以考虑使用ZGC或者Shenandoah GC释放内存给OS
解决
我们可以就这个问题,直接打开SWAP缓解物理内存不够的问题。没打开SWAP在JVM耗尽内存后,由于Linux没有额外的手段做非热点内存数据的腾挪,那么被卡死,当我们打开SWAP后,Linux把闲置内存数据临时放在SWAP,优先满足繁忙的应用程序的内存需求,这也就避免的上述的问题
注意:
- 开启SWAP只能保证Linux最大化的因为内存空间不足导致的卡死,不能无限的扩展内存大小
- 开启SWAP后,并不能降低JVM向Linux提交的内存需求的大小
- 开启SWAP后,会有一定程度的性能损失