答案是日志锁竞争导致高并发下性能下降,表现为worker进程i/o等待高、日志写入延迟大、文件句柄被大量worker持有且未释放,根源常为logrotate触发的usr1信号失效、缓冲未启用、权限不足或文件描述符限制。

直接看日志写入链路是否卡在文件句柄层——高并发下性能下降若由日志锁竞争引发,往往不是 CPU 或内存打满,而是 worker 进程在写日志时频繁阻塞、响应延迟跳升、access.log 写入速率明显低于请求量。
确认是否真为日志锁竞争
先排除其他瓶颈,再聚焦日志层:
- 查 worker 进程 I/O 等待:用 top -p $(pgrep nginx | head -n1),观察 %wa(I/O wait)是否持续 >20%,高则说明进程在等磁盘或文件系统操作;
- 看日志写入延迟:对比 request_time 和 upstream_response_time,若 request_time 显著大于后者(比如差 50ms+),且集中在 access.log 轮转前后,大概率是日志缓冲 flush 或 reopen 卡住;
- 检查内核文件锁状态:运行 lsof -n | grep 'nginx.*log' | awk '{print $2,$9}' | sort | uniq -c | sort -nr,若同一日志文件被数十个 worker 同时持有 fd,且长时间未释放,说明 reopen 未完成或存在竞争残留。
重点排查 USR1 信号执行可靠性
logrotate 触发的 kill -USR1 是最常见锁源,高并发时极易失效:
- 验证信号是否真正落地:发完 USR1 后,轮询检查 lsof -p $MASTER_PID | grep access.log | wc -l,应从 2(旧+新)→ 1(仅新);若卡在 2 且持续数秒,说明部分 worker 未响应;
- 查 master 是否卡在通知阶段:用 strace -p $(cat /var/log/nginx/nginx.pid) -e trace=kill,write,观察是否反复向某 worker PID 发送 kill 但无 write 日志 reopen 成功日志;
- 确认 logrotate 是否并发执行:检查 /var/log/nginx/ 下是否有 access.log.1~access.log.3 等多个临时轮转文件,或 lsof +L1 | grep nginx 输出含多个 deleted 日志,表明多次轮转叠加导致 fd 混乱。
检查日志配置与系统级资源约束
缓冲、权限、句柄数三者共同决定锁竞争烈度:
- 确认 access_log 是否启用 buffer+flush:若仍为 access_log /var/log/nginx/access.log;(无 buffer 参数),高并发下每请求落盘一次,I/O 放大数倍,极易触发锁争抢;
- 检查日志目录权限与 worker 用户一致:运行 ps aux | grep nginx 看 worker 进程用户(如 www-data),再执行 ls -ld /var/log/nginx,确保该用户有写权限,否则写入失败会静默重试并加剧锁等待;
- 核实文件描述符上限:cat /proc/$(pgrep nginx | head -n1)/limits | grep "Max open files",若 soft limit ≤ 4096,worker 在万级连接时极易因 fd 不足被迫串行化日志写入,表现即为锁竞争假象。
快速缓解与长期规避
线上优先止损,再固化防护:
- 立即缓解:临时停用 logrotate,改用 systemd logrotate 的 copytruncate 模式(无需 reopen,fd 不变),或手动执行 kill -USR1 后加 sleep 1; sync 强制刷盘;
- 脚本加固:所有日志切割脚本必须包裹 flock -x /tmp/nginx-logrotate.lock,并在 postrotate 中加入 stat -c '%s' access.log && [ $size -gt 0 ] || exit 0 跳过空日志;
- 配置升级:将 access_log 改为 buffer=128k flush=2s,error_log 启用 buffer=64k flush=5s,同时在 nginx.conf 中设 worker_rlimit_nofile 100000; 并同步调大系统 limits。











