4. 操作系统调优:内核参数调优、中断亲和性与CPU隔离

操作系统调优,说白了就是给交易系统铺一条“高速公路”。

我见过太多团队,代码写得漂亮,硬件也舍得砸钱,结果系统跑起来就是卡顿。查到最后,问题往往出在操作系统层面——内核参数没调、中断乱跑、CPU被抢。嗯,今天我们就来把这几个坑填上。

4.1 内核参数调优:让网络栈“飞”起来

内核参数调优,是操作系统调优里最立竿见影的部分。我个人习惯,拿到一台新机器,第一件事就是改这些参数。

4.1.1 net.core.rmem_default 与 net.core.rmem_max

这两个参数控制的是UDP接收缓冲区的大小。为什么重要?因为高频交易里,UDP是主流——组播行情、订单回报,全走UDP。

默认值是多少?我记得在CentOS 7上,rmem_default是212992字节,也就是208KB。对于普通应用够了,但对于高频交易,这太小了。

我在项目中遇到过,行情数据量稍微一爆发,UDP缓冲区就满了,内核直接丢包。那感觉,就像你在高速上开车,前面突然出现一个坑,你只能眼睁睁看着轮胎碾过去。

推荐配置:

# 设置接收缓冲区默认值和最大值(单位:字节)
net.core.rmem_default = 16777216   # 16MB
net.core.rmem_max = 33554432       # 32MB

# 发送缓冲区同理
net.core.wmem_default = 16777216
net.core.wmem_max = 33554432

避坑指南:我曾经把rmem_max设成128MB,结果内存占用飙升,反而影响了性能。16MB到32MB是个安全区间,够用又不浪费。

4.1.2 kernel.sched_migration_cost_ns

这个参数控制的是进程迁移的“成本估算”。内核用它来判断,把一个进程从CPU A迁移到CPU B,是否划算。

默认值是500000纳秒,也就是0.5毫秒。对于高频交易来说,这个值太大了。你想想看,一个微秒级的交易决策,内核却花了0.5毫秒去考虑要不要迁移进程——这不是添乱吗?

我个人习惯,直接把这个值设成0:

kernel.sched_migration_cost_ns = 0

这样内核就不会因为“迁移成本”而犹豫,该迁移就迁移。当然,这也有代价——进程可能会更频繁地迁移,但高频交易场景下,我们更关心的是确定性,而不是省那点迁移开销。

注意:设成0后,如果系统负载很高,可能会增加上下文切换。建议配合CPU隔离使用,效果更好。

4.2 中断亲和性(IRQ affinity):把中断“钉”在指定CPU上

中断亲和性,说白了就是告诉内核:这个中断,只能由某个CPU处理。

为什么要这么做?因为默认情况下,中断可以在任意CPU上处理。这会导致一个问题——你的交易线程在CPU 0上跑得好好的,突然网卡中断来了,CPU 0被迫去处理中断,交易线程就被打断了。

我在项目中遇到过,某次回测时发现延迟抖动特别大,查了半天,发现是网卡中断和交易线程抢CPU。后来把中断绑定到单独的CPU上,抖动立刻消失了。

4.2.1 查看当前中断亲和性

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

# 查看某个中断的亲和性(假设中断号是 78)
cat /proc/irq/78/smp_affinity

smp_affinity是一个十六进制掩码。比如00000001表示只能由CPU 0处理,00000002表示只能由CPU 1处理,00000003表示CPU 0和CPU 1都可以处理。

4.2.2 设置中断亲和性

# 将中断78绑定到CPU 2(掩码为00000004)
echo 00000004 > /proc/irq/78/smp_affinity

最佳实践:

  • 把网卡中断绑定到CPU 0或CPU 1
  • 把交易线程绑定到CPU 2、3、4等
  • 确保中断CPU和交易CPU不重叠

4.3 CPU隔离(isolcpus、cpuset):给交易线程“包场”

CPU隔离,是操作系统调优的终极手段。它的目标很简单:让某些CPU只跑你的交易线程,其他任何东西都不准上去。

4.3.1 isolcpus 内核参数

在GRUB启动参数里加上isolcpus=2,3,4,5,内核就不会把普通进程调度到这些CPU上。但注意,中断和内核线程还是可能跑上去的。

我刚开始用isolcpus时,以为设了就万事大吉。结果发现,网卡中断还是时不时跑到隔离的CPU上。后来才明白,isolcpus只隔离用户态进程,不隔离中断。

解决方案:配合中断亲和性一起用。把中断绑定到非隔离的CPU上,隔离的CPU就彻底干净了。

4.3.2 cpuset:更精细的隔离

cpuset是cgroup的一个子系统,可以更精细地控制进程的CPU亲和性。相比isolcpus,cpuset更灵活——你可以在系统运行时动态调整。

# 创建cpuset
mkdir /sys/fs/cgroup/cpuset/trading

# 设置CPU范围
echo 2-5 > /sys/fs/cgroup/cpuset/trading/cpuset.cpus

# 设置内存节点(通常为0)
echo 0 > /sys/fs/cgroup/cpuset/trading/cpuset.mems

# 把交易进程加入cpuset
echo 12345 > /sys/fs/cgroup/cpuset/trading/cgroup.procs

这样,PID为12345的交易进程就只能跑在CPU 2-5上。

我的经验:

  • isolcpus适合“一锤子买卖”——启动时就定好
  • cpuset适合“动态调整”——比如行情线程和交易线程分开管理
  • 两者可以结合使用:isolcpus做粗粒度隔离,cpuset做细粒度控制

4.4 知识体系总览

下面这张图,是我自己总结的操作系统调优核心逻辑。你看一眼,就能明白这三块内容是怎么配合的。

操作系统调优核心逻辑 内核参数调优 中断亲和性 CPU隔离 rmem_default / rmem_max sched_migration_cost_ns smp_affinity 掩码 中断绑定到指定CPU isolcpus 内核参数 cpuset 动态控制 目标:确定性 + 低延迟 三者缺一不可,配合使用效果最佳 内核参数 → 中断亲和性 → CPU隔离 → 确定性低延迟

4.5 实战检查清单

每次部署新机器,我都会按这个清单检查一遍:

检查项 命令/方法 期望值
UDP接收缓冲区 sysctl net.core.rmem_default 16777216
进程迁移成本 sysctl kernel.sched_migration_cost_ns 0
中断亲和性 cat /proc/irq/*/smp_affinity 中断CPU与交易CPU不重叠
CPU隔离 cat /proc/cmdline | grep isolcpus 隔离了指定CPU
cpuset配置 cat /sys/fs/cgroup/cpuset/trading/cpuset.cpus 交易进程在隔离CPU上

最后提醒一句:调完参数一定要重启验证。我曾经在测试环境调好了,上线时忘了同步配置,结果生产环境还是默认值。嗯,那天的延迟数据,我都不想看第二眼。

专注资料整理