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的占用量增高。

總結

一開始認真的點看的話,直接清理就完事了。就不用花這么多時間,不過重溫一下知識點也好。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM