第四章:操作系统内核调优——让Linux为交易而生

暗池交易系统跑在Linux上,但默认的Linux内核参数是为通用服务器设计的。说白了,它根本不知道你是在做纳秒级的交易。我刚开始调优时,以为改几个参数就完事了,结果延迟反而更高了。嗯,这里面的门道,咱们得一个一个说清楚。

核心观点:内核调优不是玄学,是实实在在的工程。每改一个参数,你都得知道它为什么能降低延迟。

4.1 网络缓冲区:net.core.rmem_default 与 wmem_default

先聊聊网络缓冲区。暗池交易系统最怕什么?丢包?不对,是缓冲区溢出导致的丢包。我见过一个团队,他们发现交易数据偶尔会丢失几个字节,查了三天,最后发现是内核的接收缓冲区太小了。

net.core.rmem_defaultnet.core.wmem_default 这两个参数,控制的是每个TCP socket的默认接收和发送缓冲区大小。默认值通常是 212992 字节(约208KB),对于高频交易来说,这远远不够。

为什么会这样?你想想看,暗池的订单流是突发性的。平时可能很安静,但一旦有大单进来,数据量瞬间暴涨。缓冲区太小,内核就会丢包,或者触发TCP的背压机制,导致延迟飙升。

我个人习惯这样设置:

# /etc/sysctl.conf 或 /etc/sysctl.d/99-trading.conf

# 接收缓冲区,默认设为 4MB,最大 16MB
net.core.rmem_default = 4194304
net.core.rmem_max = 16777216

# 发送缓冲区,默认设为 2MB,最大 8MB
net.core.wmem_default = 2097152
net.core.wmem_max = 8388608

# 别忘了应用
sysctl -p

注意,这里有个坑。缓冲区不是越大越好。我在项目中遇到过,有人把缓冲区设到 64MB,结果内存占用暴涨,反而因为缺页中断导致延迟抖动。建议你根据实际流量做压测,找到那个平衡点。

参数 默认值 推荐值(交易场景) 说明
rmem_default 212992 4194304 接收缓冲区默认大小
rmem_max 212992 16777216 接收缓冲区最大值
wmem_default 212992 2097152 发送缓冲区默认大小
wmem_max 212992 8388608 发送缓冲区最大值

小技巧:如果你用的是DPDK或RDMA,这些参数其实影响不大。但大多数暗池交易系统还是走传统TCP/IP栈,所以别跳过这一步。

4.2 内存交换:vm.swappiness 的取舍

接下来是 vm.swappiness。这个参数控制内核倾向于使用物理内存还是交换分区。默认值是 60,意味着内核在内存使用率达到 60% 时就开始把不活跃的页面换到磁盘上。

对于低延迟交易系统来说,这简直是灾难。磁盘 I/O 的速度和内存差了几个数量级,一旦触发 swap,延迟直接飙升到毫秒级。你想想看,一个订单在微秒级完成匹配,结果因为一次 swap 卡了几毫秒,这单子还怎么做?

我建议直接设为 0 或 1:

# 关闭主动交换
vm.swappiness = 1

# 或者更激进一点
vm.swappiness = 0

设为 0 意味着除非内存完全耗尽,否则绝不使用 swap。设为 1 则是允许在极端情况下使用,但优先级极低。我个人习惯用 1,因为万一内存泄漏了,系统还能撑一下,不至于直接 OOM。

警告:我曾经在一个生产环境上把 swappiness 设为 0,结果某个进程内存泄漏,系统直接 OOM killer 把交易进程干掉了。嗯,从那以后我学乖了——要么加内存监控,要么留一点 swap 空间做缓冲。

4.3 中断亲和性:把中断绑在正确的CPU上

网络中断来了,CPU 0 在处理,CPU 1 在闲着。结果呢?CPU 0 忙得要死,中断处理延迟越来越高。这就是没有设置中断亲和性的后果。

中断亲和性(IRQ affinity)允许你把特定的中断绑定到指定的 CPU 核心上。对于暗池交易系统,我们通常把网卡中断绑定到隔离出来的 CPU 核心上,避免被其他进程干扰。

具体操作分三步:

  1. 找到网卡中断号
# 查看网卡中断
cat /proc/interrupts | grep eth0
# 或者用更现代的方式
cat /proc/interrupts | grep enp3s0
  1. 设置亲和性
# 假设中断号是 78,绑定到 CPU 2 和 CPU 3
echo "0c" > /proc/irq/78/smp_affinity

# 0c 是十六进制,表示 CPU 2 和 CPU 3(bit 2 和 bit 3 置位)
# CPU 0 = 0x01, CPU 1 = 0x02, CPU 2 = 0x04, CPU 3 = 0x08
  1. 持久化配置
# 使用 irqbalance 或手动写入 /etc/rc.local
# 推荐用 irqbalance 的 --banirq 参数排除关键中断

这里有个细节:不要把所有中断都绑到一个核心上。我见过有人把 8 个网卡队列的中断全绑到 CPU 0,结果 CPU 0 负载 100%,其他核心在围观。正确的做法是分散到多个隔离核心上,每个核心处理 1-2 个队列。

4.4 CPU隔离:isolcpus 的妙用

CPU 隔离是低延迟系统的终极武器。它的原理很简单:告诉内核,某些 CPU 核心你别碰,留给我的交易进程专用。

在 GRUB 启动参数里加上 isolcpus

# /etc/default/grub
GRUB_CMDLINE_LINUX="isolcpus=2,3,4,5 nohz_full=2,3,4,5 rcu_nocbs=2,3,4,5"

# 更新 GRUB
update-grub

解释一下:

  • isolcpus=2,3,4,5:把 CPU 2-5 从内核调度器中隔离出来
  • nohz_full=2,3,4,5:关闭这些核心的时钟中断(减少抖动)
  • rcu_nocbs=2,3,4,5:把 RCU 回调也移走

隔离之后,你需要用 tasksetcset 把交易进程绑定到这些核心上:

# 绑定到 CPU 2 和 3
taskset -c 2,3 ./trading_engine

# 或者用 cset 创建专属 cpuset
cset set --cpu=2-5 --mem=0 --set=trading_set
cset proc --move --pid=1234 --toset=trading_set

经验之谈:隔离核心的数量取决于你的交易引擎是单线程还是多线程。单线程引擎隔离 1-2 个核心就够了,多线程的可以隔离 4-8 个。别贪多,系统还需要核心处理中断和系统调用。

4.5 知识体系总览

说了这么多,咱们用一张图把整个脉络理清楚。这张图展示了内核调优的四个维度,以及它们如何协同工作:

Linux内核调优知识体系 暗池交易系统内核调优 网络缓冲区 内存交换控制 中断亲和性 CPU隔离 rmem_default / wmem_default rmem_max / wmem_max 避免缓冲区溢出 vm.swappiness = 0/1 禁用主动交换 避免磁盘I/O抖动 绑定网卡中断到隔离核心 smp_affinity 设置 分散中断到多个核心 isolcpus 启动参数 nohz_full 关闭时钟中断 taskset / cset 绑定进程 目标:减少中断抖动、避免内存交换、消除调度延迟 最终实现微秒级以下的确定性延迟

这张图把四个维度的关系说清楚了。网络缓冲区解决的是数据接收的稳定性,内存交换控制解决的是内存访问的确定性,中断亲和性解决的是中断处理的效率,CPU隔离解决的是进程调度的干扰。四者缺一不可。

最后说一句:内核调优不是一劳永逸的。每次硬件升级、内核版本更新、交易策略调整,都可能需要重新评估参数。我建议你把调优参数写成脚本,每次变更后做一次完整的延迟对比测试。嗯,这才是工程化的做法。

专注资料整理