• 正文
  • 相关推荐
申请入驻 产业图谱

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

04/16 11:11
110
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

大家好,我是 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 命令查看是否已经加入此参数。

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 压力和日志文件的大小。

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的连接的相关日志。这对于排查特定客户端或服务器的网络问题非常有用。默认情况下,此功能是关闭的。

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 指令用于在调试模式下检测内部错误,例如在重新启动工作进程时出现套接字泄漏,启用调试点会导致生成核心文件(中止)或停止进程(停止),以便使用系统调试器进行进一步分析。

Syntax:	debug_points abort | stop;
Default:	—
Context:	main

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

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

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 日志信息,如下所示。

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 命令查看转存内存中日志,如下所示。

# 重启 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)。

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() 调用中可以传输的数据量,如果没有限制,一个快速连接可能会完全占用工作进程。

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 更适合大文件传输场景。

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 配置参数启用它。

指令参数:

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。

Syntax:	gunzip on | off;
Default:	
gunzip off;
Context:	http, server, location

gunzip_buffers:设置用于解压缩响应的缓冲区的数量和大小。默认情况下,缓冲区大小等于一个内存页,这是4K或8K,具体取决于平台。

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 系列文章,感谢大家支持。

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

相关推荐