本文分享自天翼雲開發者社區《nginx解決進程內存佔用翻倍》.作者:z****n

1.問題

某天,線上生成環境發現一個問題:

某一台機器線上的nginx進程佔用的內存是其他機器的2倍 ,嘗試對nginx進行reload後,並沒有恢復

內存佔用翻倍機器:

nginx解決進程內存佔用翻倍_中間件

正常機器:

nginx解決進程內存佔用翻倍_中間件_02

2.分析

1.每次reload或者啓動時worker進程從master進程fork出來,所以reload後worker進程的內存和master進程內存大小應該保持一致。

2.由於master進程reload時,是先用一個全新的結構體解析配置後,再free釋放到原有配置的結構體。所以master進程再reload過程,會短暫的保持2個結構體(內存2倍),再變為1個全新配置結構體(內存正常)。所以猜測2倍的原因和這個原理有關,很可能是內存泄漏,沒有釋放原有結構體。

3.內存佔用2倍的機器上對nginx進行不斷的reload,發現內存保持不變,依然保持2倍。所以推翻第2步的猜測,若是nginx內存泄漏,那每次reload都會造成內存不斷增長,而不是一直保持2倍狀態

4.由於nginx中使用的是glibc的malloc/free, 即在程序中調用malloc 函數開闢一段空間後,再次調用free函數,程序會把開啓的空間還給glibc,但是glibc不一定會把內存還給操作系統。除非調用malloc_trim(0)。所以懷疑大概率要加上一個malloc_trim(0)


3.復現

1.準備

在本地機器上準備一個簡單的nginx,nginx中包含眾多的server{} 配置(server數量多時候比較好復現)

nginx.conf 配置如下

worker_processes  1;
error_log  logs/error.log  debug;
events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    server_names_hash_max_size 8192;
    server_names_hash_bucket_size 256;
    server {
        listen 80;
        location / {
            root html;
        }
    }
    include server/*.conf;
}

server 目錄如下中是一堆server{}塊,如下

server {
        listen 80;
        server_name www.a.com1;
        location / {
                return 200 okokokokok;
        }
 }
server {
        listen 80;
        server_name www.a.com2;
        location / {
                return 200 okokokokok;
        }
 }
####大量不同server_name 的server{}

2.測試

啓動nginx,觀察當前內存

nginx解決進程內存佔用翻倍_中間件_03

進行reload 操作,觀察當前內存,發現內存翻了一倍

nginx解決進程內存佔用翻倍_中間件_04

4.解決

在 src/os/unix/ngx_process_cycle.c 中 解析完新配置釋放舊的配置的結構體後,增加一行 malloc_trim(0)後,重新編譯。再次啓動nginx, reload後,觀察內存不會翻倍

nginx解決進程內存佔用翻倍_nginx_05

有的nginx中使用了jemalloc中替換了glibc也可能遇到這個問題,需要把malloc_trim函數 替換為mallctl函數,同時加上3個jemalloc的頭文件,差別如下

12,15d11
< #include <jemalloc/jemalloc.h>
< #include <jemalloc/internal/jemalloc_internal_defs.h>
< #include <jemalloc/internal/util.h>
< 
235c231
< 	    mallctl("arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".purge", NULL, NULL, NULL, 0);
---
> 	    malloc_trim(0);