导读:本期聚焦于向日葵创作的《Nginx日志写入遭遇NUMA性能瓶颈?手把手教你做日志亲和性优化》,敬请观看详情。在高并发Web服务场景下,Nginx日志写入看似只是简单的磁盘IO操作,但在多路服务器上却经常成为隐藏的性能杀手。同一台机器内多个CPU节点之间的跨NUMA内存访问,会让日志缓冲区的读写延迟成倍增加,worker进程没有绑定到合适的CPU节点时,日志写入吞吐量可能下降百分之三十以上。本文从NUMA架构的基本原理讲起,分析Nginx日志写入链路中内存分配与CPU调度的关系,介绍如何通过numactl绑定、worker_cpu_affinity配置以及日志盘挂载优化等手段,让日志写入尽量发生在本地NUMA节点,从而降低延迟、提升整体吞吐。文章附有完整的配置示例与验证方法,适合正在做高并发场景调优的运维和开发人员参考。

Nginx作为最流行的开源Web服务器之一,其日志模块承担着访问记录、错误追踪、流量分析等重要职责。在中小规模场景下,日志写入的开销几乎可以忽略,但当你把Nginx部署到一台几十核甚至上百核的多路服务器上时,事情就变得复杂了。这类服务器普遍采用NUMA(非统一内存访问)架构,CPU被划分为多个节点,每个节点拥有自己的本地内存。如果Nginx的worker进程在节点0上运行,而日志缓冲区却分配在节点1的内存上,每次写日志都要跨节点访问内存,延迟会显著增加。这就是所谓的NUMA远端访问问题,也是很多高并发场景下日志写入吞吐上不去的隐形原因。本文将系统讲解如何针对Nginx日志写入做NUMA亲和性优化。

Nginx日志写入遭遇NUMA性能瓶颈?手把手教你做日志亲和性优化

一、理解NUMA架构与日志写入的关联

NUMA架构的核心思想是让每个CPU插槽(Socket)管理一块本地内存,CPU访问本地内存的速度远快于访问其他节点上的远端内存。以一台双路服务器为例,节点0包含CPU 0-31和一部分内存,节点1包含CPU 32-63和另一部分内存。CPU访问本节点内存大约需要100纳秒左右,而跨节点访问则可能达到200纳秒以上,延迟翻倍还会占用跨节点互连总线带宽。

Nginx的日志写入链路涉及多个内存操作:首先worker进程在处理请求时会将日志内容格式化到用户态缓冲区,然后通过write系统调用写入文件。内核侧的Page Cache页分配、文件缓冲块的分配,都会遵循当前进程所在NUMA节点的内存策略。如果进程频繁在节点之间迁移,缓冲区会散落在不同节点上,造成大量远端内存访问。此外,如果日志文件所在的磁盘控制器挂在某一个节点的PCIe通道上,跨节点提交IO也会带来额外开销。

你可以通过以下命令查看系统的NUMA拓扑,确认节点数量以及每个节点包含的CPU:

# 查看NUMA节点信息
numactl --hardware
# 查看每个节点的内存使用情况
numastat -m
# 查看Nginx进程分布在哪些节点上
numastat -p $(pidof nginx | awk '{print $1}')

如果发现worker进程的内存大部分标记为其他节点的远端访问,就说明存在明显的NUMA不亲和问题,值得做针对性优化。

二、通过进程绑定实现CPU与内存亲和

最直接的优化手段是让Nginx的worker进程固定运行在特定的NUMA节点上,这样内存分配器会优先从本地节点拿内存。Nginx原生提供了worker_cpu_affinity指令,可以将worker与CPU核心一一绑定。假设服务器有2个NUMA节点、每个节点16个核心,开了8个worker,可以这样配置:

worker_processes  8;
# 将前4个worker绑定到节点0的CPU,后4个绑定到节点1
worker_cpu_affinity 00000000000000000000000000000001 00000000000000000000000000000010
                    00000000000000000000000000000100 00000000000000000000000000001000
                    00000000000000010000000000000000 00000000000000000000000000100000
                    00000000000000001000000000000000 00000000000000010000000000000000;

绑定的核心原则是让每个节点的CPU上都有worker在跑,负载大致均衡,避免所有worker都挤在同一个节点上导致另一个节点的内存和IO资源闲置。需要注意的是,如果开启了Hyper-Threading,建议只绑定物理核心,逻辑核心留给软中断等内核任务。

除了配置文件内的绑定,还可以在启动层面用numactl指定内存策略。例如强制所有内存都从节点0分配:

# 使用numactl启动Nginx,内存优先从本地节点分配
numactl --cpunodebind=0 --membind=0 nginx
# 或者更推荐的方式:优先本地,不足时允许跨节点
numactl --preferred=0 nginx

--membind是严格绑定,本地内存不足时会直接分配失败;--preferred则是尽力而为,更稳妥。对于日志这种持续写入的场景,只要保证了进程在节点上的稳定运行,大部分内存自然落在本地,因此--preferred通常已经够用。优化后再次执行numastat观察,如果远端访问计数占比降到百分之十以下,说明亲和性已经比较理想。

三、日志盘IO路径与文件系统的NUMA优化

进程绑定了节点只是第一步,IO路径同样要考虑亲和性。每个物理设备的PCIe控制器挂载在特定NUMA节点下,跨节点提交磁盘IO会增加延迟并消耗互连带宽。可以用下面的命令确认日志盘挂在哪个节点:

# 查看磁盘的NUMA亲和节点
cat /sys/block/sdb/device/numa_node
# 如果输出-1,说明内核没有给出明确归属,可以手动设置
echo 0 > /sys/block/sdb/device/numa_node

拿到归属信息后,规划原则很朴素:尽量让写入日志盘的worker运行在与该盘同节点的CPU上。如果条件允许,可以为不同的NUMA节点各配一块日志盘,并通过Nginx多实例或者按节点划分日志文件的方式,让每个节点写自己本地的盘,彻底消除跨节点IO。

文件系统层面的优化同样重要。日志是典型的顺序追加写入,建议使用ext4或xfs并将挂载选项调整为适合日志的模式:

# xfs针对顺序大块写入的挂载参数
mount -t xfs -o noatime,nodiratime,logbsize=256k /dev/sdb1 /var/log/nginx

同时调整内核的脏页回写参数,让日志数据以较大的批量异步落盘,减少write系统调用的阻塞时间:

# 增大脏页比例和回写间隔,提升写入吞吐
sysctl -w vm.dirty_ratio=20
sysctl -w vm.dirty_background_ratio=10
sysctl -w vm.dirty_expire_centisecs=3000

另一个值得考虑的方案是缓冲日志。Nginx的access_log指令支持buffer参数,在用户态先攒够一批再写,配合flush参数控制最长落盘间隔:

# 64KB缓冲区,最多缓存30秒
access_log /var/log/nginx/access.log main buffer=64k flush=30s;

缓冲机制大幅减少了write系统调用的次数,也就减少了跨NUMA内存拷贝的发生频率,是性价比非常高的优化项。

四、效果验证与常见误区

优化完成后需要用数据说话。推荐使用sysbench或者wrk压测工具对比优化前后的表现,重点关注两个指标:一是numastat输出中的numa_missnuma_foreign计数是否明显下降,二是Nginx每秒处理的请求数与日志写入延迟的变化。一个简单的对比脚本如下:

# 压测前记录NUMA统计基线
numastat > /tmp/before.txt
# 执行压测
wrk -t8 -c1000 -d60s http://127.0.0.1/
# 压测后对比
numastat > /tmp/after.txt
diff /tmp/before.txt /tmp/after.txt

实践中常见的误区有几个需要提醒。第一,盲目开启numactl --interleave=all把内存均匀打散到所有节点,这对日志写入这种高频率内存操作是负优化,应优先保证本地分配。第二,过度绑定CPU反而伤害性能,比如把所有worker绑到同一个节点的少数核心上,会造成本地内存耗尽和其他节点资源浪费。第三,如果系统同时开启了irqbalance服务,网卡中断可能被调度到任意节点,最好手动将网卡中断亲和到与worker相同的节点,保证请求接收和日志写入都在本地完成。NUMA优化不是孤立操作,只有把进程、内存、IO三者统一规划到同一节点,日志写入的性能提升才能完全释放出来。

Nginx日志优化NUMA亲和性性能调优修改时间:2026-09-01 22:04:42

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。