环境: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"几个关键信息值得注意:
- 日志级别是
[emerg],这是 Nginx 最高级别的错误,说明问题已经影响到进程正常工作; malloc(1048576) failed,即 Nginx 向操作系统申请 1MB(1048576 字节) 内存被拒绝,errno 12 =ENOMEM;while reading response header from upstream,发生在读取后端响应头的阶段——这个 1MB 正是proxy_buffer_size对应的缓冲区;- 连接编号已经到了
*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/fstabswap 只是安全网,不是解药。真正的问题在配置上。
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=103.4 建立监控防复发
- 对
available memory、swap 使用率设置阈值告警(如 available < 10% 告警); - 对 Nginx error.log 中的
[emerg]、[alert]、[crit]关键字做日志告警; - 定期压测验证:并发数 × 单连接缓冲上限,估算内存水位是否在安全范围内。
四、验证结果
配置修改并 reload 后:
free -h显示 available 内存恢复到正常水位;- error.log 不再出现
malloc failed; - 502 告警消除,
depositWithdrawList等接口恢复正常响应。
五、总结与经验
malloc(N) failed中的 N 往往能直接定位到配置项。本例的 1048576 = 1m,一眼就能对应到proxy_buffer_size 1m;- 缓冲区配置要按"单位连接成本 × 峰值并发"来估算,不能拍脑袋给个大数字图省事;
- API 类反向代理的 proxy_buffer_size 用 16k~64k 就足够,MB 级配置几乎都是误用;
- 没有 swap 的机器,内存耗尽时是"硬着陆"——malloc 直接失败、OOM Killer 随机杀进程。加一块小 swap 作为缓冲能给你争取到告警和处理的时间;
[emerg]级别日志一旦出现就应该立刻告警,而不是等业务方反馈 502。