记一次服务器内存占用过高问题

1,849 阅读7分钟

首发

自然博客:hsslive.cn/article/96

背景

服务器是腾讯云服务器,配置一开始是是2核4g内存,后面免费升级成了4核4g,系统是centos8。

一开始服务器的作用只是托管博客以及博客的node后端、以及mysql、nginx。而且比较经常的重新构建项目,因此即使项目中存在内存泄漏,也很难看出内存占用过高的情况,或者即使超过内存限制了,也被pm2自动重启了。

起因

大概几个月前,想着把自己的项目都统一的部署一遍,手动部署很明显比较重复浪费时间,因此使用了jenkins进行统一的构建发布,因为部署的项目大多数是客户端渲染的,因此其实还是托管在nginx上就好了,不会占用太多额外的内存。

但是每次在构建的时候,由于是在服务器进行构建,因此构建的时候cpu和内存都会飙升,如果内存或者cpu一直占满,很容易就导致构建的时候卡死服务器,但是当时一直不清除是那个环境出的问题,因此就在腾讯云控制台加了cpu和内存占用过高的预警,然后基本就隔三四天,就报警提示内存占用超过90%,因此趁着放假排查一下问题。

排查

重启服务器,然后将所有项目(包括mysql、redis、nginx等等这些服务)以重启一遍,也就是看看常态情况下,cpu和内存占用的情况,重启服务器后将所有项目启动后使用top命令:

image.png

可以看出,最占内存的是java(jenkins需要)和mysqld(mysql需要)这两个服务,其次是node服务,这里在用pm2 list命令看看每个项目分包都使用了多少内存:

image.png

这里可以看出来其实好像每个项目都没有占用很多的内存,而且从top命令来看,正常情况下还有1.3g左右内存可以使用,为什么会隔三差五的报内存超90%???因为并不是初始状态就内存占用很高,随着时间的推移,内存一点点的上去的,最终到了90%就报警。因此首当其冲的就先对博客做个压力测试看看先,看看是不是因为博客项目是服务端渲染,看看是不是因为博客的代码问题,导致访问的人多了,一些定时器堆积或者socket的没有关闭等等导致的内存泄漏。

压力测试

需要使用ab命令,先看看服务器能不能用ab命令

ab -V

image.png

如果没有打印,则先安装一下http-tools

yum -y install httpd-tools

安装完成之后,使用ab命令:

ab -c 100 -n 1000 http://localhost:3000/

ab命令最基本的参数是-n和-c: -n 执行的请求数量 -c 并发请求个数,最后面的链接一定要带/,不然的话会报错。上面这条命令的意思是一共请求1000次,每次并发100次。即同一时刻有100个人访问,然后测试1000个时刻。

注意:请不要对线上的服务器进行压力测试。

一些实用的命令

获取占用CPU资源最多的10个进程

ps aux|head -1;ps aux|grep -v PID|sort -rn -k +3|head

image.png

获取占用内存资源最多的10个进程

ps aux|head -1;ps aux|grep -v PID|sort -rn -k +4|head

image.png

根据pid持续监控内存

使用top命令(没有历史记录):

top -p 要查看的pid

image.png

或者使用pidstat命令,更直观(有历史记录),需要安装sysstat

yum -y install sysstat

pidstat 示例 pidstat 的用法:

pidstat [ 选项 ] [ <时间间隔> ] [ <次数> ] 

如:

pidstat -r -p 要查看的pid 1 100

-r:显示各个进程的内存使用统计

-p:指定进程号

即表示每隔1秒打印pid使用的内存信息,一共打印100次

image.png

测试过程

定位问题

最直观的测试应该是在服务器测试,因为服务器一般很少外在因素干扰(一般情况尽量在本地测试,缺点就是干扰因此比较多)

  1. 首先在服务器跑top命令
  2. 在另外单独开一个ssh连接,使用ab命令ab -c 1000 -n 10000 http://localhost:3000/,然后看top信息,等可用内存还剩两三百m的时候,取消ab命令(避免卡死服务器)
  3. 使用ps aux|head -1;ps aux|grep -v PID|sort -rn -k +4|head命令,找到占用最高的pid,我这里测试后,发现root 248807 0.4 2.9 981460 114192 ? Sl 17:24 0:22 node /node/nuxt-blog-client/node_modules/.bin/nuxt start 这个进程的也就是nuxt的进程,占用内存会持续上升,并且,这个内存不会降下去,因此感觉大概率就是nuxt也就是博客项目的问题,然后就开始了在博客项目里面找问题。

博客项目review

这里省略了review过程,直接说结论:

  1. 首先是在nuxt.config.js里面使用了analyze:true,这样的话构建成功后会起一个服务显示各个包大小,其实生产环境没用,因此去掉这个配置。
  2. 大部分的setTimeout和setInterval没有清除,而且,在组件的mouted钩子之前使用定时器,这些定时器如果不清除,会一直堆积,如果在mounted钩子之后使用定时器,不清除的话最多就把客户端卡死了,但是如果在mouted之前使用定时器而且不清除,这些定时器会一直堆积在服务器的node进程里面,随着堆积的数量增多,会把服务器给卡死了!可以分别在created和mounted里面for循环新建1000个定时器,然后用ab -c 1000 -n 10000 http://localhost:3000/命令测试,然后观察node进程,会发现:
    1. 定时器在mounted里面的话,node进程一直都在,而且内存上涨其实只会涨个一两百兆就停住了,然后过一会就会降下去。
    2. 定时器在created里面的话,内存会涨到三四百兆然后还会继续涨,直到达到内存限制(我猜的。)被杀掉进程,然后pm2重启这个进程。

结果

最终排查到最大的问题是在created里面的定时器如果不清除,会造成堆积导致服务器卡死!要么及时清除created的定时器,要么就不要再created里面使用定时器或者闭包,否则它里面使用的内存会堆积!

发现问题后,将定时器进行清除以及将created的逻辑放到mounted里面实现,最后再进行压力测试,结果就正常了,内存上去后,过一会就会自动降下来。

结论

在nuxt里面,或者说服务端渲染对比客户端渲染,mounted这个钩子是分水岭,mounted之后的钩子,它里面都是的操作都是在客户端执行,但是mounted之前的钩子,比如created,它不仅仅会在客户端执行,同时它也会在服务端执行,而且是客户端访问一次就执行一次,客户端访问几万次,mounted之前的操作就会执行几万次,因此,一定要注意性能问题,及时清除定时器以及清空无用的变量等,否则会导致服务器端内存泄漏等问题!

优化

使用云数据库

数据库可以使用云数据库,这样服务器就不需要mysql了,可以减少压力,缺点是要钱。

优化代码

优化项目的代码,尤其是服务端渲染以及后端的项目。全局搜定时器以及监听事件,及时的清除这些东西!

清除buff/cache

随着时间的推移,top命令查看buff/cache,其实他会一直增长,可以在适当的时机清除buff/cache。

# 清除pagecache。
echo 1 > /proc/sys/vm/drop_caches
# 清除回收slab分配器中的对象(包括目录项缓存和inode缓存)。slab分配器是内核中管理内存的一种机制,其中很多缓存数据实现都是用的pagecache。
echo 2 > /proc/sys/vm/drop_caches
# 清除pagecache和slab分配器中的缓存对象。
echo 3 > /proc/sys/vm/drop_caches

扩展

使用类似Fail2ban的工具防止恶意主机爆破服务、站点简单防cc等