Nginx | 磁盘IO层面性能优化秘诀:error 日志内存环形缓冲区及小文件 sendfile 零拷贝技术

知识是人生的灯塔,只有不断学习,才能照亮前行的道路

📢 大家好,我是 WeiyiGeek,一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(DevSecOps),自动化运维工具开发与实践,企业网络安全防护,欢迎各位道友一起学习交流、一起进步 🚀,若此文对你有帮助,一定记得点个关注⭐与小红星❤️或加入到作者知识星球『 全栈工程师修炼指南』,转发收藏学习不迷路 😋 。

温馨提示:若文章代码块中存在乱码或不能复制,请联系作者,也可通过文末的阅读原文链接,加入知识星球中阅读,原文链接:https://articles.zsxq.com/id_713d6lwet0zc.html

针对 error.log 使用内存环形缓冲区及提高日志级别

在 Nginx 中,默认情况下 error.log 日志级别为 warn,这意味着只有警告及以上级别的日志会被记录,实际上在大多数场景中都是足够的。但如果需要记录更详细的错误信息,比如调试或监控目的,可以将日志级别设置为 info 或更低debug(仅限于测试),另外对于 error.log 日志为了提高性能和减少磁盘IO,减少无意义静态资源访问的错误日志以及使用内存环形缓冲区(buffer)来存储日志信息,若于留存的需要请一定设置日志轮转避免日志文件过大,在容器中通常会输出到控制台上,大家可根据实际场景选择合适的日志级别和配置。

指令参数:

  • error_log 指令用于指定错误日志的路径和级别。默认情况下,Nginx 会将错误信息写入到 logs/error.log 文件中,并且记录警告及以上级别的日志,另外,对于 error_log /path/to/log debug; 级别的日志需要在 Nginx 构建时使用 --with-debug,可通过 nginx -V 命令查看是否已经加入此参数。
go 复制代码
Syntax:	error_log file [level];
Default: error_log logs/error.log error;
Context: main, http, mail, stream, server, location

# 日志级别(从低到高,从详细到精简)
debug, info, notice, warn, error, crit, alert, or emerg

# 示例演示
# 将日志级别设置为 warn,并将日志输出到 /var/log/nginx/vhost/www.log 文件。
error_log /var/log/nginx/vhost/www.log warn;

# 将 info 日志级别的错误日志,通过 syslog 方式输出到远程日志服务器。
error_log syslog:server=192.168.1.100:514,facility=local7,tag=nginx_error info;
# 将错误日志发送到容器内的 /dev/log (通常由 journald 监听)
error_log syslog:server=unix:/dev/log,nohostname;

# 使用内存环形缓冲区,例如:32MB 大小。
error_log memory:32m debug;
  • log_not_found 指令用于控制是否记录找不到文件的错误。默认情况下,Nginx 会将这类错误写入到 error.log 日志文件中。如果不需要这些日志信息,可以将其关闭以减少磁盘 I/O 压力和日志文件的大小。
go 复制代码
Syntax:	log_not_found on | off;
Default: log_not_found on;
Context: http, server, location

# 使用示例
# 匹配 favicon.ico 请求时不记录日志,减少磁盘 I/O 压力。
location ~* \.(ico)$ {
# 1. 禁用访问日志记录
  access_log off;
# 2. 禁用错误日志记录(防止找不到文件时报错)
  log_not_found off;
# 4. 设置长期的客户端缓存,减少浏览器重复请求频率
  expires 30d;
}
  • debug_connection 指令用于在调试模式下,仅记录来自指定 IP 地址/段/unix的连接的相关日志。这对于排查特定客户端或服务器的网络问题非常有用。默认情况下,此功能是关闭的。
go 复制代码
Syntax:	debug_connection address | CIDR | unix:;
Default:	---
Context:	events

# 示例演示
events {
  debug_connection 127.0.0.1;
  debug_connection localhost;
  debug_connection 192.0.2.0/24;
  debug_connection ::1;
  debug_connection 2001:0db8::/32;
  debug_connection unix:;
  ...
}
error_log /path/to/log;
  • debug_points 指令用于在调试模式下检测内部错误,例如在重新启动工作进程时出现套接字泄漏,启用调试点会导致生成核心文件(中止)或停止进程(停止),以便使用系统调试器进行进一步分析。
go 复制代码
Syntax:	debug_points abort | stop;
Default:	---
Context:	main

另外,对于 debug 级别的日志,若需要在生产环境中做异常定位,不建议直接输出到文件可能导致磁盘IO激增,引发性能问题,所以建议使用内存环形缓冲区(循环写)规避高负载,下面作者将演示如何配置及使用内存环形缓冲区。

首先,在编译 Nginx 时需要加上 --with-debug 参数,如下所示。

go 复制代码
cd /usr/local/src/nginx-1.29.0/

# 编译安装 Nginx,并开启 debug 模式
./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --sbin-path=/usr/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --pid-path=/usr/local/nginx/nginx.pid --error-log-path=/var/log/nginx/logs/error.log --http-log-path=/var/log/nginx/logs/access.log --lock-path=/var/run/nginx.lock --modules-path=/usr/local/nginx/modules --with-http_stub_status_module --with-http_realip_module --with-http_v2_module --with-http_ssl_module --with-http_slice_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt=-O2 --with-compat --with-debug
make -j$(nproc) && make install

# 验证
nginx -V 2>&1 | grep -- "--with-debug"

其次,在测试的 test.conf 配置文件中,将 error_log 日志级别设置为 debug 并使用内存环形缓冲区(例如:32MB),意味者只能访问最近的 32MB 日志信息,如下所示。

go 复制代码
tee test.conf <<'EOF'
http {

# 为了调试方便,我将worker 进程数量设置为 1
worker_processes 1;
# 关键点:使用内存环形缓冲区
error_log memory:32m debug; 

...

# 虚拟主机配置,反向代理到上游服务器组
server {
  listen 80;
  server_name test.weiyigeek.top;
  charset utf-8;
  default_type text/html;

# 启用 HTTP/2 支持
  http2 on;

# 日志文件
  access_log /var/log/nginx/test.log main;

  location / {
    proxy_pass http://backend_server;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 启用缓存,指定缓存区域为 data01
    proxy_cache data01;
# 自定义缓存KEY
    proxy_cache_key $scheme$request_method$host$uri;
# 响应码为200的响应数据进行缓存有效期为5分钟
    proxy_cache_valid 200 5m;
# 自定义响应头,用于验证上游缓存状态
    add_header X-Cache-Status $upstream_cache_status;
  }
}
EOF

之后,重启 Nginx 并使用 gdb 命令查看转存内存中日志,如下所示。

go 复制代码
# 重启 Nginx 服务
nginx -s reload

# 安装 gdb 调试工具
dnf install gdb -y
tee nginx-memory-log.gdb <<'EOF'
# 显式强制转换,防止偏移计算错误
set$log = (ngx_log_t *) ngx_cycle->log
set$found = 0

while$log != 0
# 检查 writer 是否匹配内存写入器
if$log->writer == &ngx_log_memory_writer
set$found = 1
        loop_break
    end
set$log = $log->next
end

if$found == 1
set$buf = (ngx_log_memory_buf_t *) $log->wdata
printf"找到内存日志: Start=%p, End=%p, Size=%d 字节\n", $buf->start, $buf->end, $buf->end - $buf->start
    dump binary memory debug_log.txt $buf->start $buf->end
else
printf"错误: 在当前 Nginx 运行实例中未发现 memory 类型的 error_log。\n"
printf"请检查 nginx.conf 是否包含: error_log memory:32m debug;\n"
end
EOF

# 查看 Nginx Worker 进程的 PID
$ ps -ef | grep "nginx"
root     3089982       1  0 16:57 ?        00:00:00 nginx: master process nginx
nginx    3090687 3089982  0 17:01 ?        00:00:00 nginx: worker process
root     3090709 3071798  0 17:01 pts/0    00:00:00 grep --color=auto nginx

# 使用 gdb 调试 Nginx,并查看内存中的日志
gdb -p 3090687 -ex "source nginx-memory-log.gdb" --batch
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib64/libthread_db.so.1".
0x00007ff0bf72b517 in epoll_wait () from /usr/lib64/libc.so.6
找到内存日志: Start=0x7ff0ba3fe010, End=0x7ff0bc3fe010, Size=33554432 字节
[Inferior 1 (process 3090687) detached]

# 成功后将生成 debug_log.txt 文件,即为内存环形缓冲区中的日志内容。
$ ls -alh debug_log.txt
-rw-r--r-- 1 root root 32M Mar 12 17:03 debug_log.txt


weiyigeek.top-使用gdb输出nginx内存环形缓冲区图

使用 sendfile 零拷贝技术提升性能

什么是 sendfile 零拷贝技术呢?

简单来说,就是在文件传输过程中,避免用户空间与内核空间的频繁数据拷贝,以及减少进程间切换操作,从而减少CPU的负担和提升性能,在 Linux 系统中,sendfile 系统调用是实现这一技术的关键。

例如,左图中应用程序(如 NGINX)作为静态资源服务,需从磁盘读取文件并发送至客户端,其流程是程序调用 read() 将磁盘内容读入应用程序即文件经磁盘高速缓冲区应用程序内存,然后调用 write() 在 TCP socket 上发送数据即 内核 Socket 缓冲区 → TCP 滑动窗口机制 → 网卡客户端

而右图使用了 sendfile 零拷贝技术,它首先会调用 sendfile(),指定源文件、起始偏移、字节数、目标 socket 缓冲区(文件句柄),即内核直接通过 DMA 将磁盘数据读入内核页缓存,并直接送入 socket 缓冲区(跳过用户态)。其拷贝次数减至两次(磁盘→页缓存→socket缓冲区),避免用户态内存拷贝,减少上下文切换次数,在此过程应用程序无需参与数据搬运。


weiyigeek.top-sendfile 零拷贝技术图

指令参数:

  • sendfile: 开启或关闭 sendfile 功能, 默认情况下,sendfile 是禁用的(off)。
go 复制代码
Syntax:	sendfile on | off;
Default: sendfile off;
Context:http, server, location, ifin location

# 示例演示
location /video/ {
  sendfile       on;
  tcp_nopush     on;
  aio            on;
}
  • sendfile_max_chunk: 限制单个 sendfile() 调用中可以传输的数据量,如果没有限制,一个快速连接可能会完全占用工作进程。
go 复制代码
Syntax:	sendfile_max_chunk size;
Default: sendfile_max_chunk 2m;
Context: http, server, location

温馨提示: 从 nginx 0.8.12 和 FreeBSD 5.2.1 开始,aio 可用于预加载 sendfile() 的数据:

温馨提示:在启用 O_DIRECT(direct IO)时,sendfile 将会被自动被禁用,因为当文件过大时,sendfile 可能会导致内核缓冲区溢出,所以 directio 更适合大文件传输场景。

go 复制代码
location /video/ {
  sendfile     on;
  aio          on;
  directio     8m;
}

另外,还有一个问题当我们使用 sendfile 时,若需对响应体(response body)进行 gzip 压缩,则必须将原始文件从磁盘读入用户态内存处理(依赖 output_filter 阶段),此时 sendfile 零拷贝失效, 为此设计专用模块gzip_static 模块。

ngx_http_gzip_static_module 模块允许发送扩展名为 ".gz" 的预压缩文件,而不是常规文件;模块为 content 阶段前置模块,比较考前,它会先对静态文件预先执行压缩命令,在原目录下生成同名 .gz 文件,若 .gz 文件存在且功能已开启,则直接返回该压缩文件内容,而非原始文件,并自动添加 Content-Encoding: gzip 及相应 Content-Type 等头部。默认情况下,此模块不是内置的,应使用--with-http_gzip_static_module 配置参数启用它。

指令参数:

go 复制代码
Syntax:	gzip_static on | off | always;
Default: gzip_static off;
Context: http, server, location

# 参数
on : 检查客户端 Accept-Encoding 请求头;仅当客户端声明支持 gzip 时才返回 .gz 文件;否则返回原始文件。
always : 无论客户端是否声明支持 gzip,都返回 .gz 文件。

# 示例
gzip_static  on;
gzip_proxied expired no-cache no-store private auth;

温馨提示:若仅存在 .gz 文件而无原始文件,always 模式仍可正常返回。

此外,若客户端不支持 gzip 压缩,但服务器端存在 .gz 文件,此时应如何处理?我们可使用 ngx_http_gunzip_module 模块,它是一个过滤器,它能够在客户端不支持 gzip 时自动解压 .gz 文件并返回原始内容,当需要存储压缩数据以节省空间和降低I/O成本时,该模块将非常有用。同样该模块默认不是内置的,需要通过--with-http_gunzip_module 配置参数启用。

指令参数:

  • gunzip : 为缺乏gzip支持的客户端启用或禁用gzip压缩响应的解压缩。如果启用,在确定客户端是否支持 gzip 时,还会考虑以下指令:gzip_http_version、gzip_proxied和gzip_disable。
go 复制代码
Syntax:	gunzip on | off;
Default:	
gunzip off;
Context:	http, server, location
  • gunzip_buffers:设置用于解压缩响应的缓冲区的数量和大小。默认情况下,缓冲区大小等于一个内存页,这是4K或8K,具体取决于平台。
go 复制代码
Syntax:	gunzip_buffers number size;
Default:	gunzip_buffers 32 4k|16 8k;
Context:	http, server, location

本小节中讲解了在小文件静态资源网站时,启用 sendfile 零拷贝显著降低性能消耗,另外,针对于大文件传输以及资源压缩时 sendfile 将会被禁用,此时还需考虑启用 directio 直写磁盘,以及在需要压缩时,如何选择合适的模块(gzip_static 或 gunzip)来优化性能,


0x03 总结回顾

本篇文章,围绕前述四大软件优化方向(CPU、内存、磁盘IO、网络带宽)展开具体策略详解、实践,通过介绍主要的内核参数以及关键的 nginx 指令,帮助道友们在实际的运维工作中,能够应对不同场景下的优化需求,进而系统性地提升 Nginx 的性能。另外,也有助于道友们对 Nginx 的工作原理(机制)有更深的理解,从而在实际工作中能够更加灵活地应对各种问题。

最后有需要新手朋友们,可加入到作者知识星球中,完整系统的学习 Nginx 高性能Web服务器从入门到生产时间,后续也将持续更新 OpenResty 系列文章,感谢大家支持。

Nginx \| 核心知识150讲之基础介绍与进程信号管理学习笔记\](https://articles.zsxq.com/id_1b7rrwwqo422.html) \[Nginx \| 核心知识150讲之事件驱动框架笔记\](https://articles.zsxq.com/id_w52o5cd47zwi.html) \[Nginx \| 核心知识150讲之静态、动态模块编译使用笔记\](https://articles.zsxq.com/id_dertjh0239h3.html) \[Nginx \| 核心知识150讲之连接池与内存池介绍笔记\](https://articles.zsxq.com/id_wxgg5glpimm9.html) \[Nginx \| 核心知识150讲之常用容器类型介绍笔记\](https://articles.zsxq.com/id_xnch5n50hm9j.html) \[Nginx \| 核心知识150讲之HTTP模块指令处理流程介绍笔记\](https://articles.zsxq.com/id_4wfw412wei48.html) \[Nginx \| 核心知识150讲之处理请求的11个阶段与对应模块介绍笔记(上篇)\](https://articles.zsxq.com/id_70wxcdwid00g.html) \[Nginx \| 核心知识150讲之处理请求的11个阶段与对应模块介绍笔记(下篇)\](https://articles.zsxq.com/id_3dcvx6w81906.html) \[Nginx \| 核心知识150讲之变量介绍与实践应用笔记\](https://articles.zsxq.com/id_rx229kjix356.html) \[Nginx \| 核心知识150讲之反向代理流程与负载均衡实践笔记\](https://articles.zsxq.com/id_a2l6pacq0flr.html) \[Nginx \| 核心知识150讲之HTTP协议中反向代理学习实践笔记\](https://articles.zsxq.com/id_fkims9wlo8ww.html) \[Nginx \| 核心知识150讲之SSL证书签发与HTTPS加密传输学习实践笔记\](https://articles.zsxq.com/id_wwnjk3rmwroa.html) \[Nginx \| 核心知识150讲之前端静态缓存、反向代理动态缓存学习实践笔记\](https://articles.zsxq.com/id_6hpaidhf5l5s.html) \[Nginx \| 核心知识150讲之其它HTTP框架下反向代理学习实践笔记\](https://articles.zsxq.com/id_7wc0af7viwqr.html) \[Nginx \| 核心知识150讲之利用 open_file_cache 缓存文件句柄提升性能\](https://articles.zsxq.com/id_19oatlbj0w64.html) \[Nginx \| 核心知识150讲之介绍使用 HTTP/2.0 协议实现资源主动推送\](https://articles.zsxq.com/id_fib851xw86fq.html) \[Nginx \| 核心知识150讲之四层反向代理的七个阶段及对应模块浅析实践(上)\](https://articles.zsxq.com/id_wtv12vsjglt0.html) \[Nginx \| 核心知识150讲之四层反向代理的七个阶段及对应模块浅析实践(下)\](https://articles.zsxq.com/id_t5z28zcqv7us.html) \[Nginx \| 核心知识150讲之CPU层面性能优化指南\](https://articles.zsxq.com/id_c5gt26jx4zcp.html) \[Nginx \| 核心知识150讲之内存层面性能优化指南\](https://articles.zsxq.com/id_i1qqni3rce3f.html) \[Nginx \| 核心知识150讲之网络层面性能优化指南\](https://articles.zsxq.com/id_q8wr9cv6ke27.html) \[Nginx \| 核心知识150讲之磁盘IO层面性能优化指南\](https://articles.zsxq.com/id_713d6lwet0zc.html) \[Nginx \| 核心知识150讲之利用 stub_status 实时监控网络连接情况

(https://articles.zsxq.com/id_h0irm4e7kezu.html)

END

加入:作者【全栈工程师修炼指南】知识星球

『 全栈工程师修炼指南』星球,主要涉及全栈工程师(Full Stack Development)实践文章,持续更新包括但不限于企业SecDevOps和网络安全等保合规、安全渗透测试、编程开发、云原生(Cloud Native)、物联网工业控制(IOT)、人工智能Ai,从业书籍笔记,人生职场认识等方面资料或文章。

Q: 加入作者【全栈工程师修炼指南】星球后有啥好处?

✅ 将获得作者最新工作学习实践文章以及网盘资源。

✅ 将获得作者珍藏多年的全栈学习笔记(需连续两年及以上老星球友,也可单次购买)

✅ 将获得作者专门答疑学习交流群,解决在工作学习中的问题。

✅ 将获得作者远程支持(在作者能力范围内且合规)。

获取:作者工作学习全栈笔记

作者整理了10年的工作学习笔记(涉及网络、安全、运维、开发),需要学习实践笔记的看友,可添加作者微信或者回复【工作学习实践笔记】,当前价格¥299,除了获得从业笔记的同时还可进行问题答疑以及每月远程技术支持,希望大家多多支持,收获定大于付出!

知识推荐 往期文章

若文章对你有帮助,请将它转发给更多的看友,若有疑问的小伙伴,可在评论区留言你想法哟 💬!

相关推荐
@LuckY BoY2 小时前
Linux Mint 上开启 VNC 远程桌面
linux·运维·服务器
ywgcsxue2 小时前
在麒麟V10上源码编译安装Nginx
linux·运维·服务器·nginx
Victoria.a2 小时前
网络基础知识
网络
科技风向标go2 小时前
2026监控摄像头TOP10权威发布;格行视精灵、小米、萤石、海康、360怎么选?TOP10品牌优缺点一句话总结
网络·安全·监控·户外安防
Warren983 小时前
Windows本地部署n8n完整教程(基于Docker,新手友好)
运维·windows·python·测试工具·docker·容器·可用性测试
somi73 小时前
ARM-05-Platform + DTS + GPIO子系统 + 中断 + 等待队列 + 错误处理
linux·运维·arm开发
MU在掘金916953 小时前
在Perfetto里框选一段卡顿,AI直接告诉你哪行代码有问题
性能优化
徒 花3 小时前
HCIP学习16 RIP 与 OSPF 路由重分布综合实验
网络·学习·智能路由器·hcip·ensp
T0uken3 小时前
【Linux】耗时任务执行并邮件通知
linux·运维·服务器