接入层 Nginx 部分 worker 进程死锁解决方案

背景

部分 worker 进程 CPU 使用率达到100%整个 worker 不可用。

原因

诱因

上游长连接服务上线,由于服务发现服务生效时间长,导致 Nginx 连接上游失败,在释放连接时 Nginx worker 进程可能在持有读锁时出现问题,从而导致读写锁的状态和实际情况不一致,最终读锁永远没法释放,而写锁会死锁。

主因

通过火焰图分析,发现整个进程的耗时都在 ngx_rwlock_wlock 这个函数上。

通 GDB 查看该进程的堆栈信息,执行下面的命令(121606 是对应 CPU 打满进程的进程号 ),查看进程调用栈。

1
2
3
4
5
6
# 先执行下面命令进入 GDB
/opt/compiler/gcc-4.8.2/bin/gdb -nx /proc/121606/exe 121606


# 执行 bt
> bt

上述命令执行的结果如下图,可以看出 worker 进程在释放连接时出现问题,最终死锁在 ngx_rwlock_wlock 函数上。

查看 Nginx 源码,可以发现在 ngx_http_upstream_free_round_robin_peer 函数中 调用了 ngx_http_upstream_rr_peers_rlock 函数,该函数最终调用了 ngx_rwlock_wlock。

从上面的代码可以看出当宏定义 NGX_HTTP_UPSTREAM_ZONE 为1时 ngx_http_upstream_rr_peer_lock 有可能加锁,当 NGX_HTTP_UPSTREAM_ZONE 不为1时,不会加锁。当 Nginx 编译时不将http_upstream_zone_module 模块去掉(添加 –without-http_upstream_zone_module 参数)时 NGX_HTTP_UPSTREAM_ZONE 为1,即使在 Nginx 中没有使用 zone 指令。

解决方案

由于我们没有使用 zone 指令,即没有使用 http_upstream_zone_module 模块,只需重新编译 Nginx 二进制,将 http_upstream_zone_module 模块去掉(添加 –without-http_upstream_zone_module 参数)即可。