3、操作系统内核调优:Linux内核参数调优、中断亲和性设置、CPU隔离与绑核、内存大页配置

低延迟交易系统,说白了就是跟时间赛跑。你想想看,一笔交易从发出到成交,可能就差那么几微秒。而操作系统内核,就是那个决定你能否跑赢时间的裁判。我做了这么多年金融IT,见过太多团队花大价钱买硬件,结果内核参数没调好,性能直接打对折。嗯,今天我们就来聊聊内核调优这回事。

3.1 Linux内核参数调优

内核参数调优,我个人习惯先从网络栈入手。交易系统最怕什么?网络延迟。而Linux内核默认的参数,其实是给通用场景设计的,对低延迟交易来说,太保守了。

3.1.1 网络栈参数

先看几个关键参数:

# 减少TCP连接建立时间
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2

# 减少TIME_WAIT状态影响
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 0  # 注意:新内核已废弃

# 增大TCP缓冲区
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728

# 减少NAPI轮询延迟
net.core.busy_poll = 50
net.core.busy_read = 50

我在项目中遇到过一个问题:某次上线后,交易系统偶尔出现几十毫秒的延迟抖动。查了半天,发现是tcp_tw_recycle在NAT环境下导致的问题。后来关掉它,配合tcp_tw_reuse,问题就解决了。这里要提醒你,新内核已经废弃了tcp_tw_recycle,千万别再用。

3.1.2 内存与调度参数

内存和CPU调度,也是调优重点:

# 减少内存分配延迟
vm.swappiness = 1
vm.dirty_ratio = 5
vm.dirty_background_ratio = 2

# 调度器优化
kernel.sched_min_granularity_ns = 10000000
kernel.sched_wakeup_granularity_ns = 15000000
kernel.sched_migration_cost_ns = 5000000

# 减少上下文切换
kernel.numa_balancing = 0
kernel.sched_autogroup_enabled = 0

为什么要把swappiness设成1?说白了,就是告诉内核:尽量别用交换分区。交易进程的内存必须常驻物理内存,一旦被换出,那延迟就不可控了。我曾经见过一个团队,swappiness设成默认的60,结果行情高峰期内存紧张,内核开始疯狂换页,交易延迟直接飙升到毫秒级。嗯,这种坑踩过一次就够了。

核心原则:低延迟系统追求的是确定性,而不是吞吐量。所有参数调整,都要围绕「减少抖动」这个目标。

3.2 中断亲和性设置

中断亲和性,说白了就是告诉内核:这个网卡的中断,交给哪个CPU核心去处理。默认情况下,中断可能分散到所有核心上,这对低延迟系统来说不是好事。

3.2.1 查看当前中断分布

# 查看网卡中断号
cat /proc/interrupts | grep eth0

# 查看当前中断亲和性
cat /proc/irq/<中断号>/smp_affinity

我建议的做法是:把网卡中断绑定到专用的CPU核心上,跟业务进程隔离开。比如你有16个核心,可以把核心0-1留给系统,核心2-3专门处理网卡中断,核心4-15跑交易进程。

3.2.2 设置中断亲和性

# 将中断绑定到核心2-3(二进制:1100)
echo "c" > /proc/irq/<中断号>/smp_affinity

# 或者使用irqbalance工具(但建议关闭)
systemctl stop irqbalance
systemctl disable irqbalance

这里有个坑:irqbalance这个服务,默认是开启的。它会动态调整中断亲和性,但对我们来说,这种动态调整反而会引入不确定性。我习惯直接关掉它,手动绑定。

注意:设置中断亲和性时,要确保目标核心没有被其他关键进程占用。否则中断处理可能会被其他任务打断,反而增加延迟。

3.3 CPU隔离与绑核

CPU隔离,就是把一部分核心从Linux调度器的管理范围中移除,专门留给你的交易进程用。这样,其他进程就不会跑到这些核心上,干扰你的交易逻辑。

3.3.1 内核启动参数

在GRUB配置中,添加isolcpus参数:

# 隔离核心4-15
isolcpus=4-15 nohz_full=4-15 rcu_nocbs=4-15
  • isolcpus:将指定核心从调度器中隔离
  • nohz_full:关闭隔离核心的时钟中断(减少抖动)
  • rcu_nocbs:将RCU回调从隔离核心上移除

我曾经帮一个客户调优,他们用的是40核的服务器。我建议隔离核心32-39给交易进程用,结果对方说「那系统进程怎么办?」其实不用担心,系统进程会跑在核心0-31上,完全不影响。

3.3.2 使用taskset绑核

# 启动交易进程并绑定到核心4-7
taskset -c 4-7 ./trade_engine

# 查看进程当前绑核情况
taskset -p <PID>

嗯,这里要注意:绑核不是绑得越死越好。如果你的交易进程是多线程的,可以考虑把每个线程绑定到不同的核心上,避免缓存竞争。我一般会配合numactl一起用,确保内存分配也在同一个NUMA节点上。

小技巧:可以用cset工具来管理CPU集合,比手动操作方便很多。比如cset shield --cpu 4-15 --kthread=on,可以一键隔离核心并迁移内核线程。

3.4 内存大页配置

内存大页,是低延迟系统的标配。为什么?因为TLB(页表缓存)的容量是有限的。默认的4KB小页,意味着你需要更多的页表项来映射大块内存,TLB命中率自然就低。而2MB甚至1GB的大页,可以显著减少TLB miss,降低内存访问延迟。

3.4.1 配置HugePages

# 分配1024个2MB大页(共2GB)
echo 1024 > /proc/sys/vm/nr_hugepages

# 查看大页使用情况
cat /proc/meminfo | grep Huge

我建议在系统启动时就分配好大页,避免运行时动态分配带来的延迟:

# 在GRUB中配置
default_hugepagesz=2M hugepagesz=2M hugepages=1024

3.4.2 透明大页(THP)的问题

Linux内核有个透明大页(THP)机制,会自动合并小页为大页。听起来不错,但实际用起来问题很多。THP的合并操作(khugepaged)会占用CPU,而且合并过程中可能触发内存拷贝,导致延迟抖动。

强烈建议:在低延迟交易系统中,关闭透明大页。手动分配和管理大页,虽然麻烦一点,但性能更可控。

# 关闭透明大页
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

3.4.3 在程序中使用大页

如果你的交易程序是用C/C++写的,可以通过mmap配合MAP_HUGETLB标志来使用大页:

#include <sys/mman.h>

void *buf = mmap(NULL, 2 * 1024 * 1024,
                 PROT_READ | PROT_WRITE,
                 MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
                 -1, 0);

Java程序的话,可以用-XX:+UseLargePages-XX:LargePageSizeInBytes=2m参数。我在一个高频交易项目中,把Java堆从4KB小页改成2MB大页后,GC暂停时间减少了约30%。

3.5 本章小结

内核调优这件事,说白了就是「减少不确定性」。中断亲和性、CPU隔离、大页配置,每一个手段都是为了让你交易进程的执行路径更短、更稳定。我见过太多团队,花几十万买FPGA加速卡,结果内核参数没调好,性能还不如人家调优过的纯软件方案。

嗯,最后说一句:调优不是一蹴而就的。每改一个参数,都要做A/B测试,用真实的交易数据去验证。我曾经花了两周时间,就为了调一个busy_poll的值,从20调到80,最终找到最优的50。这种耐心,是做低延迟系统必备的素质。

记住:内核调优没有银弹。每个系统都有自己的特点,你需要理解每个参数背后的原理,然后根据实际情况去调整。这才是资深工程师该有的态度。


公众号:蓝海资料掘金营,微信deep3321