第三章:操作系统内核调优——把Linux调教成量化交易的最佳拍档
做市系统跑在Linux上,就像赛车手坐在驾驶舱里。操作系统就是那个方向盘和油门,调不好,再牛的算法也白搭。今天咱们聊聊内核调优,说白了就是让Linux为低延迟交易服务。
我个人习惯把内核调优分成四大块:网络、内存、调度,还有中断和CPU隔离。每一块都有坑,也有捷径。咱们一个一个来。
3.1 网络参数调优:别让网卡成为瓶颈
做市系统对网络延迟极其敏感。你想想看,一个订单从发出到成交,中间经过网卡、协议栈、内核缓冲区……任何一个环节卡壳,延迟就上去了。
我遇到过最典型的问题:某次实盘时,系统突然出现毫秒级的延迟抖动。查了半天,发现是net.core.rmem_default设得太小,导致UDP包在接收队列里排队。嗯,从那以后我养成了一个习惯——先调网络缓冲区。
3.1.1 网络缓冲区调大
核心参数就这几个:
# 接收缓冲区默认值和最大值(字节)
net.core.rmem_default = 262144
net.core.rmem_max = 16777216
# 发送缓冲区默认值和最大值
net.core.wmem_default = 262144
net.core.wmem_max = 16777216
# TCP读写缓冲区(最小值、默认值、最大值)
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
这里有个技巧:rmem_max和wmem_max建议设到16MB以上。为什么?因为做市系统经常要处理突发流量,缓冲区小了,丢包就是灾难。
rmem_default设得太大,否则每个连接都占16MB内存,连接数一多反而浪费。我一般设256KB作为默认值,让系统按需增长。
3.1.2 减少TCP延迟
做市系统里,TCP连接通常用于管理指令或行情订阅。延迟敏感的场景,我建议关闭Nagle算法:
# 关闭Nagle算法
net.ipv4.tcp_nodelay = 1
# 减少TIME_WAIT连接数
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
注意:tcp_tw_reuse只适用于客户端,服务端别乱开。我曾经在服务端开了这个参数,结果连接复用导致数据错乱,排查了一整天。
3.1.3 网卡队列与RPS/RFS
现代网卡支持多队列,每个队列可以绑定到不同CPU核心。但有些老网卡不支持,这时候可以用RPS(Receive Packet Steering)来模拟:
# 开启RPS,将网卡中断分散到多个CPU
echo "f" > /sys/class/net/eth0/queues/rx-0/rps_cpus
我个人建议:如果网卡支持RSS(Receive Side Scaling),优先用硬件队列。RPS是软件方案,有额外开销。
3.2 内存参数调优:大页内存是神器
做市系统里,内存访问延迟直接影响性能。尤其是行情处理和订单簿重建,频繁的内存分配和释放会带来不可预测的延迟。
大页内存(HugePages)就是解决这个问题的。它把默认的4KB页换成2MB甚至1GB的页,减少了TLB miss,说白了就是让CPU少跑几趟内存。
3.2.1 配置HugePages
先看系统支持多大页:
grep Hugepagesize /proc/meminfo
# 输出:Hugepagesize: 2048 kB
然后分配大页数量。假设系统有64GB内存,我一般分32GB给大页:
# 分配16384个2MB大页(共32GB)
echo 16384 > /proc/sys/vm/nr_hugepages
永久生效需要改/etc/sysctl.conf:
vm.nr_hugepages = 16384
3.2.2 透明大页(THP)关掉
Linux有个透明大页功能,自动把普通页合并成大页。听起来不错,但实际坑很多。它会在运行时触发内存规整(compaction),导致延迟抖动。
我建议直接关掉:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
永久生效:
# /etc/default/grub 中添加
transparent_hugepage=never
3.2.3 内存锁定与mlockall
做市进程的内存页如果被换出到swap,延迟会飙升。我习惯在代码里调用mlockall()锁定所有内存页:
# 在C/C++代码中
#include <sys/mman.h>
mlockall(MCL_CURRENT | MCL_FUTURE);
同时关闭swap:
# 关闭swap
swapoff -a
# 永久关闭:注释掉/etc/fstab中的swap行
3.3 调度与CPU隔离:让核心只干一件事
做市系统的核心逻辑,比如订单簿更新、策略计算,需要独占CPU核心。如果被其他进程打断,延迟就不可控了。
CPU隔离(isolcpus)就是干这个的。它告诉内核:这些核心别跑普通进程,只跑我指定的任务。
3.3.1 配置isolcpus
在GRUB启动参数里添加:
isolcpus=2,3,4,5
这样核心2-5就被隔离了。系统进程和中断不会跑到这些核心上。
然后通过taskset或cset把做市进程绑定到隔离核心:
# 绑定进程到核心2-5
taskset -c 2-5 ./market_maker
3.3.2 中断亲和性设置
网卡中断如果落在隔离核心上,会打断做市进程。所以要把中断绑定到非隔离核心。
查看中断号:
cat /proc/interrupts | grep eth0
假设中断号是78,绑定到核心0:
echo "1" > /proc/irq/78/smp_affinity
这里1是位掩码,表示核心0。核心1是2,核心2是4,以此类推。
我建议写个脚本,在系统启动时自动设置:
#!/bin/bash
# 将所有网卡中断绑定到核心0-1
for irq in $(grep eth0 /proc/interrupts | awk '{print $1}' | sed 's/://'); do
echo "3" > /proc/irq/$irq/smp_affinity
done
3.3.3 调度策略:SCHED_FIFO
做市进程的调度策略,我推荐用SCHED_FIFO(实时优先级)。这样内核会优先调度它,不会被普通进程抢占。
# 设置实时优先级(范围1-99,越大越优先)
chrt -f 99 ./market_maker
注意:实时优先级太高可能导致系统卡死。我一般设80-90之间,留点余量给关键系统进程。
3.4 知识体系总览
下面这张图总结了内核调优的核心逻辑:
3.5 实战检查清单
每次部署新系统,我都会按这个清单检查一遍:
| 类别 | 检查项 | 推荐值 | 验证命令 |
|---|---|---|---|
| 网络 | rmem_max | 16777216 | sysctl net.core.rmem_max |
| 网络 | tcp_nodelay | 1 | sysctl net.ipv4.tcp_nodelay |
| 内存 | HugePages数量 | ≥16384 | grep HugePages /proc/meminfo |
| 内存 | THP状态 | never | cat /sys/kernel/mm/transparent_hugepage/enabled |
| CPU | isolcpus | 隔离核心列表 | cat /proc/cmdline |
| CPU | 中断亲和性 | 非隔离核心 | cat /proc/irq/*/smp_affinity |
| 调度 | 进程优先级 | SCHED_FIFO 80-99 | chrt -p <pid> |
核心原则: 调优不是一次性的。每次系统升级、内核更新、硬件变更后,都要重新验证一遍。我见过有人升级内核后忘了重新配置大页,结果延迟从10微秒飙到100微秒。
好了,内核调优这块就聊到这儿。记住一句话:低延迟不是调出来的,是设计出来的。参数只是辅助,架构和代码才是根本。
公众号:蓝海资料掘金营,微信deep3321