2

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

前端 0 1091 2
发表于: 2022-06-05 20:27:17

简介: 服务端渲染不可忽略的细节

背景

服务器是腾讯云服务器,配置一开始是是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命令

-n requests Number of requests to perform

要执行的请求数

-c concurrency Number of multiple requests to make at a time

并发一次发出的多个请求数

-t timelimit Seconds to max. to spend on benchmarking This implies -n 50000

在基准测试上花费的最大秒数这意味着-n 50000

-s timeout Seconds to max. wait for each response Default is 30 seconds

每次响应的最大等待时间为秒默认值为30秒

测试

ab -c 100 -n 1 http://localhost:3300

报错:ab: invalid URL Usage: ab [options] [http[s]://]hostname[:port]/path,ab认为你输入的url不符合规则。你在后面加个/即可。

ab -c 100 -n 1 http://localhost:3300/

报错:Cannot use concurrency level greater than total number of requests,不能使用大于请求总数的并发级别。即你要求一共请求1次,但是一次并发100,很明显你的一共请求次数一定得大于或等于100.

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

一次并发100,一共1000个请求,即测试10次。

测试结果:

This is ApacheBench, Version 2.3 <$Revision: 1901567 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        
Server Hostname:        127.0.0.1
Server Port:            3300

Document Path:          /article/test           #测试页面
Document Length:        50 bytes                #测试页面大小

Concurrency Level:      100                     #并发数
Time taken for tests:   22.586 seconds          #整个测试花费的时间
Complete requests:      1000                    #完成请求的总量
Failed requests:        0                       #失败的请求次数
Total transferred:      476000 bytes            #传输数据总大小
HTML transferred:       50000 bytes             #传输页面总大小  
Requests per second:    44.27 [#/sec] (mean)    #平均每秒请求数
Time per request:       2258.612 [ms] (mean)    #平均每次并发100个请求的处理时间
Time per request:       22.586 [ms] (mean, across all concurrent requests)    #平均每个请求处理时间,所有并发的请求加一起
Transfer rate:          20.58 [Kbytes/sec] received                           #平均每秒网络流量

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   1.1      0       6
Processing:  2008 2040  42.3   2018    2164
Waiting:     2007 2037  37.1   2017    2162
Total:       2008 2040  43.1   2018    2169
#花费在连接Connect,处理Processing,等待Waiting的时间的最小min,平均值mean,标准差[+/-sd],中值median,最大表max的一个表。

Percentage of the requests served within a certain time (ms)
  50%   2018   #50%请求的响应时间在2018ms内
  66%   2025   #66%请求的响应时间在2025ms内
  75%   2050   #75%请求的响应时间在2050ms内
  80%   2067   #80%请求的响应时间在2067ms内
  90%   2118   #以此类推
  95%   2150
  98%   2165
  99%   2168
 100%   2169 (longest request)
 

一些实用的命令

获取占用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等

vue Node
最近他们赞了该文章:

最后更新于:2023-01-14 23:52:15

欢迎评论留言~
0/200
支持markdown
Comments | 0 条留言
按时间
按热度
目前还没有人留言~