故障现象
周巡检,发现生产nginx挂掉,oom。由于部署两台nginx及keepalived构成高可用,因此没有发生服务不可用故障,流量切换到另一台服务器。
主机监控,发现内存水位很高,主机内存64GB,并发生跳变。
排查过程及解决方案
第一层
使用下面这个命令,可以看到有很多shutting down进程,卡死进程数147,正常进程数56。
ps -C nginx -o pid,rss,vsz,cmd
root@idc-alb-nginx-p2:~# ps -C nginx -o pid,rss,vsz,cmd
PID RSS VSZ CMD
315602 15320 48316 nginx: master process /usr/local/openresty/nginx/sbin/nginx -g daemon on; master_process on;
2621335 296392 319752 nginx: worker process is shutting down
2621337 296524 319624 nginx: worker process is shutting down
2621338 296540 319624 nginx: worker process is shutting down
2621341 296260 319624 nginx: worker process is shutting down
2621343 296608 319752 nginx: worker process is shutting down
……
2873866 303692 331324 nginx: worker process
2873867 303668 331324 nginx: worker process
2873868 303820 331324 nginx: worker process
2873869 303816 331324 nginx: worker process
……
单个worker线程300MB内存,147个,差不多就是40G+,基本可以锁定问题就出在这里。
第二层
问下gpt worker进程卡死原因。推测以下答案靠谱:
频繁执行 nginx -s reload,旧 worker 收不到退出信号或处理慢,导致大量僵尸 worker 堆积。
公司自动化运维,可以通过CICD来更新nginx配置(需要devops审批),在周末发生比较频繁。
reload会批量杀死并创建线程,也符合内存异常跳变现象。
查询CICD记录和监控内存跳变时刻基本对得上,至此可以判断出来是更新nginx配置,引起线程卡死。
临时解决方案
- kill掉所有卡死进程
ps -C nginx -o pid,state,cmd | grep 'shutting down' | head -5 | awk '{print $1}' | while read pid; do
echo "Killing nginx worker PID $pid"
kill -9 $pid
done
内存确实减少到15G左右,不用担心再次OOM。
- 比较粗暴永久解决方案,在nginx.conf增加配置
worker_shutdown_timeout 60s;
worker_shutdown_timeout 用来控制 worker 进程在接收到退出信号后,最多等待多少时间完成关闭,超过这个时间会被强制杀掉。
默认是 0,表示无限等待,worker 不会超时强制退出,可能导致 shutting down 状态卡住。
第三层
下一步定位下,为什么reload导致线程卡死。 通过以下命令获取nginx进程,并通过FD取反查网络连接。
strace -p [pid]
lsof -n -p 2828695 |grep -E '172|182'
2883是ob端口,ip也符合ob的ip地址。至此,可以断定ob阻止了线程释放。
这组nginx是L4和L7混用的,而配置经常重载遇上数据库长连接保持,阻止了worker释放,因为不是所有ob连接集中在某几个worker,可能在任意worker。最终,导致卡死。
根本解决方案
- 申请专门数据库nginx组成lb,与业务域名nginx相互隔离
其他收获
用到其他命令:
# 查看内存占用高的进程
ps aux --sort=-rss | head -20
---
root@idc-alb-nginx-p2:~# cat /proc/sys/net/ipv4/tcp_rmem
16384 1048576 12582912
root@idc-alb-nginx-p2:~# cat /proc/sys/net/ipv4/tcp_wmem
16384 1048576 12582912
TCP 内核缓冲参数:
tcp_rmem: 最小 16 KB,默认 1 MB,最大 12 MB
tcp_wmem: 最小 16 KB,默认 1 MB,最大 12 MB
---
root@idc-alb-nginx-p2:~# ss -s
Total: 6460
TCP: 5767 (estab 5669, closed 83, orphaned 6, timewait 83)
| 参数 | 用途 | 影响范围 | 内存影响 |
|---|---|---|---|
| proxy_buffer_size | 响应头部缓冲区 | 每个连接一次性分配 | 较小 |
| proxy_buffers | 响应主体缓冲区 | 每个连接按需分配(可能多个) | 较大,影响显著 |
root@idc-alb-nginx-p2:~# ss -s
Total: 6460
TCP: 5767 (estab 5669, closed 83, orphaned 6, timewait 83)
Transport Total IP IPv6
RAW 2 2 0
UDP 292 291 1
TCP 5684 5681 3
INET 5978 5974 4
FRAG 0 0 0
| 行 | 说明 |
|---|---|
| RAW | 原始套接字(Raw sockets),常用于网络工具、抓包等直接操作协议层数据的情况。IPv4有2个,IPv6无。 |
| UDP | UDP协议套接字,传输无连接的报文。IPv4有291个,IPv6有1个。 |
| TCP | TCP协议套接字,面向连接的传输协议。IPv4有5681个,IPv6有3个。 |
| INET | 表示IPv4和IPv6的套接字总和,5974(IPv4)+4(IPv6)=5978总数。 |
| FRAG | IP分片套接字,用于处理IP数据包的分片和重组。这里为0。 |