Nginx 内存占用高排查
背景
线上两台 OpenResty 占用内存过高,8c32G 的机器用了 28G 内存,总觉得不正常,使用简单的重启大法,并没什么用处,今天刚好排查一下。
free
[root@iZ1w4igf11Z conf]# free -g
total used free shared buffers cached
Mem: 31 26 5 0 0 24
-/+ buffers/cache: 1 29
Swap: 0 0 0
top -M(按内存占用排序)
top - 11:19:40 up 137 days, 23:46, 1 user, load average: 0.03, 0.06, 0.07
Tasks: 180 total, 1 running, 179 sleeping, 0 stopped, 0 zombie
Cpu(s): 5.2%us, 0.9%sy, 0.0%ni, 93.1%id, 0.0%wa, 0.4%hi, 0.4%si, 0.0%st
Mem: 32877652k total, 27437576k used, 5440076k free, 343712k buffers
Swap: 0k total, 0k used, 0k free, 25292856k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
14706 root 20 0 124m 55m 3740 S 6.9 0.2 1:48.64 nginx
14704 root 20 0 124m 53m 3744 S 6.9 0.2 1:54.05 nginx
14711 root 20 0 119m 51m 3460 S 3.4 0.2 1:46.87 nginx
14705 root 20 0 119m 50m 3592 S 6.9 0.2 1:49.36 nginx
14709 root 20 0 119m 50m 3464 S 10.3 0.2 1:45.98 nginx
14707 root 20 0 119m 50m 3468 S 0.0 0.2 1:50.33 nginx
14710 root 20 0 119m 50m 3476 S 6.9 0.2 1:47.90 nginx
14708 root 20 0 119m 49m 3488 S 3.4 0.2 1:53.28 nginx
12494 root 20 0 93480 28m 2892 S 0.0 0.1 0:18.17 nginx
排查过程
strace
因为使用 OpenResty 安装了几个第三方模块,怀疑是不是这些模块导致了内存泄露,于是用 strace -o /tmp/nginx.txt -p pid
跟踪进程,并没有看到什么有用的信息。
epoll_wait(50, {{EPOLLOUT, {u32=2908154745, u64=140160575925113}}}, 512, 3087) = 1
write(64, "\224|4\347\355\313\202w\\-\332\v\314\220u\262\353\257<wB\352\212o|\325\33nUH\211="..., 12264) = 12264
write(64, "\27\3\3@\30T\332Yu\254D,\227M\207\256V\361z\7\232G\1\244\16\204\16\212\342Pp\236"..., 16413) = 16413
write(64, "\27\3\3@\30T\332Yu\254D,\230Vcs\326\340\247\276y\207\220\30\223\365\313f[\243\253q"..., 16413) = 16413
write(64, "\27\3\3@\30T\332Yu\254D,\231\257d\"*\330.yz,q\237#\350\344\271\323w~\215"..., 16413) = 3870
write(64, "\267C9\303o\33\20{\357v\3202\325I\215\256\237D\302\256u\310\275\35\214\223'f\177\211\313\232"..., 12543) = -1 EAGAIN (Resource temporarily unavailable)
epoll_wait(50, {{EPOLLIN, {u32=2908139864, u64=140160575910232}}}, 512, 3067) = 1
read(37, "\27\3\3\2\373\0\0\0\0\0\0\0\1\307\36\241\350)E\233H\374\27\26>\330C\333Q\361v\315"..., 33093) = 768
read(37, 0x30abc73, 33093) = -1 EAGAIN (Resource temporarily unavailable)
epoll_ctl(50, EPOLL_CTL_MOD, 37, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=2908139864, u64=140160575910232}}) = 0
socket(PF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_IP) = 31
ioctl(31, FIONBIO, [1]) = 0
fcntl(31, F_SETFD, FD_CLOEXEC) = 0
epoll_ctl(50, EPOLL_CTL_ADD, 31, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=2908175329, u64=140160575945697}}) = 0
connect(31, {sa_family=AF_INET, sin_port=htons(10000), sin_addr=inet_addr("192.168.209.150")}, 16) = -1 EINPROGRESS (Operation now in progress)
epoll_wait(50, {{EPOLLOUT, {u32=2908139864, u64=140160575910232}}}, 512, 3039) = 1
epoll_wait(50, {{EPOLLOUT, {u32=2908175329, u64=140160575945697}}}, 512, 3038) = 1
getsockopt(31, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
writev(31, [{"POST /push/v6/t/bind HTTP/1.0\r\nH"..., 446}, {"expire_time=1599494399244&key=Lf"..., 344}], 2) = 790
epoll_wait(50, {{EPOLLOUT, {u32=2908154745, u64=140160575925113}}}, 512, 3038) = 1
write(64, "\267C9\303o\33\20{\357v\3202\325I\215\256\237D\302\256u\310\275\35\214\223'f\177\211\313\232"..., 12543) = 12543
write(64, "\27\3\3@\30T\332Yu\254D,\232fr\374\266\245Y\217\250\333$\264J\231\331\340\252\307-\331"..., 16413) = 16413
write(64, "\27\3\3@\30T\332Yu\254D,\2337\333\240\276\372\307c\302\374)\351\317Y\370\216\335\257\v*"..., 16413) = 16413
write(64, "\27\3\3@\30T\332Yu\254D,\234\276\30\253\235\270L\203\330\307\231F\370\316\177\250\245\211\251y"..., 16413) = 14471
write(64, "\33\220\234*\323\5\177\0342H .\366\322\336\10X(6-a\371\305\204\327\356\315m\370T\31-"..., 1942) = -1 EAGAIN (Resource temporarily unavailable)
epoll_wait(50, {{EPOLLIN|EPOLLOUT, {u32=2908175329, u64=140160575945697}}}, 512, 3029) = 1
recvfrom(31, "HTTP/1.1 200 OK\r\nServer: Apache-"..., 262144, 0, NULL, NULL) = 199
epoll_wait(50, {{EPOLLIN|EPOLLOUT|EPOLLRDHUP, {u32=2908175329, u64=140160575945697}}}, 512, 3017) = 1
readv(31, [{"UAA4GNADCBiQKBgQCiWydoXPuXeTu3IL"..., 261945}], 1) = 0
close(31) = 0
message
查看 /var/log/messages
无异常
Nginx buffer 配置
查看目前的 buffer,尝试调低
# grep buffer nginx.conf
large_client_header_buffers 4 16k;
client_body_buffer_size 128k;
proxy_busy_buffers_size 256k;
client_header_buffer_size 256k;
proxy_buffer_size 256k;
proxy_buffers 64 128k;
access_log logs/access.log access buffer=32k;
gzip_buffers 16 64k;
修改为:
[root@iZ1rp1vunvZ conf]# grep buffer nginx.conf
large_client_header_buffers 4 16k;
client_body_buffer_size 64k;
proxy_busy_buffers_size 64k;
client_header_buffer_size 64k;
proxy_buffer_size 64k;
proxy_buffers 8 32k;
access_log logs/access.log access buffer=32k;
gzip_buffers 32 4k;
reload 后,无变化。
进一步查看meminfo
[root@iZ1rp1vunvZ conf]# cat /proc/meminfo
MemTotal: 32877652 kB
MemFree: 5626820 kB
Buffers: 477252 kB
Cached: 25028124 kB
SwapCached: 0 kB
Active: 18819564 kB
Inactive: 7086080 kB
Active(anon): 401336 kB
Inactive(anon): 856 kB
Active(file): 18418228 kB
Inactive(file): 7085224 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 45564 kB
Writeback: 0 kB
AnonPages: 400660 kB
Mapped: 23288 kB
Shmem: 1828 kB
Slab: 1118776 kB
SReclaimable: 1071452 kB
SUnreclaim: 47324 kB
KernelStack: 2504 kB
PageTables: 7608 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 16438824 kB
Committed_AS: 1204760 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 65224 kB
VmallocChunk: 34359637096 kB
HardwareCorrupted: 0 kB
AnonHugePages: 245760 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 6144 kB
DirectMap2M: 33548288 kB
发现 Slab 占用挺多的,其中的 Slab,查看相关资料:
通常的说法是:内核数据结构缓存的大小,可以减少申请和释放内存带来的消耗
这里的说法太笼统了
详细的说法如下:
在 linux 内核中会有许多小对象,这些对象构造销毁十分频繁,比如 i-node,dentry。这么这些对象如果每次构建的时候就向内存要一个页,而其实际大小可能只有几个字节,这样就非常浪费,为了解决这个问题就引入了一种新的机制来处理在同一页框中如何分配小存储器区,这个机制可以减少申请和释放内存带来的消耗,这些小存储器区的内存称为 Slab
df -i
查看 inode,占用并不大
[root@iZ1rp1vunvZ conf]# df -ih
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/xvda1 1.3M 150K 1.2M 12% /
tmpfs 4.0M 1 4.0M 1% /dev/shm
/dev/xvdb1 6.3M 101K 6.2M 2% /usr/local/openresty/nginx/html/image
陷入僵局
我这么多内存去哪了?
没办法了,只能去打扰大神了。
请教大神
首先查看进程到底占用了多少内存:
[root@iZ1rp1vunvZ conf]# ps aux | awk '{print $6/1024 " MB\t\t" $11"\t"$NF}' | sort -nr|head -10
50.8242 MB nginx: process
50.4219 MB nginx: process
50.3633 MB nginx: process
50.3477 MB nginx: process
50.2734 MB nginx: process
50.2305 MB nginx: process
50.2266 MB nginx: process
49.8516 MB nginx: process
28.7969 MB nginx: nginx
21.0898 MB ./node_exporter ./node_exporter
free -m发现了 cached
[root@iZ1rp1vunvZ conf]# free -g
total used free shared buffers cached
Mem: 31 25 5 0 0 23
-/+ buffers/cache: 1 29
Swap: 0 0 0
难道都变成 cached 了?尝试着清理
sync
echo 1 > /proc/sys/vm/drop_caches
echo 2 > /proc/sys/vm/drop_caches
echo 3 > /proc/sys/vm/drop_caches
我丢失的内存回来了。。。
原理
上面执行的清除原理如下:
- sync:将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件
- echo 1 > /proc/sys/vm/drop_caches:清除page cache
- echo 2 > /proc/sys/vm/drop_caches:清除回收 Slab分配器中的对象(包括目录项缓存和 inode 缓存)。Slab 分配器是内核中管理内存的一种机制,其中很多缓存数据实现都是用的 pagecache。
- echo 3 > /proc/sys/vm/drop_caches:清除 pagecache 和 Slab分配器中的缓存对象。
/proc/sys/vm/drop_caches 的值,默认为0
最后了解下两个概念 buff 和 cache
- buff(Buffer Cache)是一种 I/O 缓存,用于内存和硬盘的缓冲,是 io设备的读写缓冲区。根据磁盘的读写设计的,把分散的写操作集中进行,减少磁盘碎片和硬盘的反复寻道,从而提高系统性能。
- cache(Page Cache)是一种高速缓存,用于 CPU 和内存之间的缓冲 ,是文件系统的 cache。
把读取过的数据保存起来,重新读取时若命中(找到需要的数据)就不要去读硬盘了,若没有命中就读硬盘。其中的数据会根据读取频率进行组织,把最频繁读取的内容放在最容易找到的位置,把不再读的内容不断往后排,直至从中删除。
它们都是占用内存。两者都是RAM中的数据。简单来说,buff是即将要被写入磁盘的,而cache是被从磁盘中读出来的。
目前进程正在实际被使用的内存的计算方式为used-buff/cache,通过释放buff/cache内存后,我们还可以使用的内存量free+buff/cache。通常我们在频繁存取文件后,会导致buff/cache的占用量增高。
总结
一开始认真的点看的话,直接清理就完事了。就不用花这么多时间,不过重温一下知识点也好。