Nginx

一次线上 Nginx `malloc failed (12: Cannot allocate memory)` 故障排查与修复

By karp 124 Views 13 MIN READ 0 Comments
环境:Nginx 反向代理(10.0.12.13)→ 后端 API 服务(10.0.4.10:8901)
现象:接口大面积报错,错误日志刷屏

一、故障现象

某天下午,监控告警显示交易所后台的资产类接口(/ApiInt/Assets/depositWithdrawList、/Contract/openPositionList 等)大量返回 502,查看 Nginx error.log 发现日志被同一类错误刷屏:

2026/07/22 17:40:59 [emerg] 4257#0: *6286680928 malloc(1048576) failed
(12: Cannot allocate memory) while reading response header from upstream,
client: 10.0.2.19, server: localhost,
request: "POST /ApiInt/Assets/depositWithdrawList HTTP/1.1",
upstream: "http://10.0.4.10:8901/ApiInt/Assets/depositWithdrawList",
host: "10.0.12.13:8901"

几个关键信息值得注意:

  1. 日志级别是 [emerg],这是 Nginx 最高级别的错误,说明问题已经影响到进程正常工作;
  2. malloc(1048576) failed,即 Nginx 向操作系统申请 1MB(1048576 字节) 内存被拒绝,errno 12 = ENOMEM;
  3. while reading response header from upstream,发生在读取后端响应头的阶段——这个 1MB 正是 proxy_buffer_size 对应的缓冲区;
  4. 连接编号已经到了 *6286680928(62 亿+),说明这个 worker 进程运行时间很长、承载的请求量巨大。

二、原因分析

2.1 这 1MB 是谁申请的?

Nginx 作为反向代理,每收到一个上游响应,都会先分配一块 proxy_buffer_size 大小的缓冲区来存放响应头。检查配置后果然发现:

proxy_buffer_size 1m;
proxy_buffers 8 1m;
proxy_busy_buffers_size 2m;

proxy_buffer_size 1m 意味着每一个活跃的代理连接都要先 malloc 1MB。做个简单的算术:

10,000 并发连接 × 1MB = 约 10GB 内存(仅响应头缓冲)
再叠加 proxy_buffers 8×1m,峰值时单连接理论上限可达 9MB

而这类接口返回的是 JSON,响应头通常只有几百字节到几 KB,1MB 的头部缓冲纯属浪费,却在高并发下把内存活活吃光。

2.2 系统层面的验证

登录机器确认内存状态:

# 查看整体内存,available 接近 0 即为耗尽
free -h

# 按内存占用排序,看是谁吃掉了内存
ps aux --sort=-rss | head -20

# 查看是否触发过 OOM Killer
dmesg -T | grep -i -E "oom|out of memory"

# 查看 nginx worker 的资源限制(排除 ulimit -v 限制)
cat /proc/$(pgrep -f "nginx: worker" | head -1)/limits | grep -i "address\|memory"

典型的结论有两种:

  • 系统内存真的耗尽:available 趋近于 0,可能还伴随 OOM Killer 杀进程的记录;
  • 内存没满但分配被拒:多半是进程被 ulimit -v(虚拟内存上限)、cgroup memory limit(容器场景)或 vm.overcommit_memory=2 的严格模式限制住了。

本例属于前者:过大的 proxy 缓冲配置 × 高并发,把物理内存打满,且机器没有配置 swap,malloc 直接失败。

三、修复方案

3.1 紧急止血

# 1. 临时释放页缓存,缓解压力(治标)
sync && echo 1 > /proc/sys/vm/drop_caches

# 2. 若无 swap,先加一块应急 swap 兜底,避免 malloc 直接失败
fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
swap 只是安全网,不是解药。真正的问题在配置上。

3.2 根治:调整 Nginx 缓冲区配置

对于返回 JSON 的 API 网关,合理的配置量级是 KB 而不是 MB:

# 响应头缓冲:64k 足以覆盖绝大多数带大 Cookie/Token 的响应头
proxy_buffer_size        64k;

# 响应体缓冲:8 块 × 64k = 512k,超出部分自动落盘临时文件
proxy_buffers            8 64k;
proxy_busy_buffers_size  128k;

# 限制落盘临时文件大小,防止磁盘被打爆
proxy_max_temp_file_size 512m;

修改后验证并平滑重载:

nginx -t && nginx -s reload

调整后单连接的头部缓冲从 1MB 降到 64KB,内存占用直接下降约 16 倍,同样 10,000 并发只需要约 640MB。

如果确实有个别接口响应头超大(例如网关透传了巨型 Set-Cookie),应该单独为该 location 提高 proxy_buffer_size,而不是全局放大。收到 upstream sent too big header 报错时再针对性调整即可。

3.3 系统与内核层面加固

# 1. 确认 overcommit 策略(默认 0 即可,谨慎使用 2)
sysctl vm.overcommit_memory

# 2. 容器/systemd 场景:确认没有过低的内存限制
systemctl show nginx | grep -i memory

# 3. 适当调低 swappiness,让 swap 只在真正紧张时使用
sysctl -w vm.swappiness=10

3.4 建立监控防复发

  • 对 available memory、swap 使用率设置阈值告警(如 available < 10% 告警);
  • 对 Nginx error.log 中的 [emerg]、[alert]、[crit] 关键字做日志告警;
  • 定期压测验证:并发数 × 单连接缓冲上限,估算内存水位是否在安全范围内。

四、验证结果

配置修改并 reload 后:

  1. free -h 显示 available 内存恢复到正常水位;
  2. error.log 不再出现 malloc failed;
  3. 502 告警消除,depositWithdrawList 等接口恢复正常响应。

五、总结与经验

  1. malloc(N) failed 中的 N 往往能直接定位到配置项。本例的 1048576 = 1m,一眼就能对应到 proxy_buffer_size 1m;
  2. 缓冲区配置要按"单位连接成本 × 峰值并发"来估算,不能拍脑袋给个大数字图省事;
  3. API 类反向代理的 proxy_buffer_size 用 16k~64k 就足够,MB 级配置几乎都是误用;
  4. 没有 swap 的机器,内存耗尽时是"硬着陆"——malloc 直接失败、OOM Killer 随机杀进程。加一块小 swap 作为缓冲能给你争取到告警和处理的时间;
  5. [emerg] 级别日志一旦出现就应该立刻告警,而不是等业务方反馈 502。

本文由 karp 原创

采用 CC BY-NC-SA 4.0 协议进行许可

转载请注明出处:https://ikarp.top/index.php/archives/865.html

标签: nginx

0 评论