第四章:操作系统内核优化

操作系统内核优化,说白了就是给交易系统铺一条高速公路。我见过太多团队,算法写得再漂亮,结果被操作系统调度拖了后腿。嗯,今天我们就来聊聊怎么把Linux内核调教成高频交易的利器。

4.1 Linux内核参数调优

内核参数调优,我习惯从三个维度入手:网络栈、内存管理、进程调度。这三个地方是交易系统的命门。

4.1.1 网络栈优化

高频交易最怕什么?网络延迟抖动。我记得有一次线上排查,发现交易订单偶尔会卡几十微秒,最后定位到是网络缓冲区满了。

先看几个关键参数:

# 网络缓冲区调大
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728

# 关闭Nagle算法,减少延迟
net.ipv4.tcp_nodelay = 1

# 减少TIME_WAIT连接数
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1

这里有个坑。我曾经把缓冲区设得特别大,结果内存占用飙升,反而影响了性能。你想想看,缓冲区不是越大越好,够用就行。

4.1.2 内存管理优化

内存管理这块,我重点关注两个参数:

# 减少swap使用
vm.swappiness = 1
vm.vfs_cache_pressure = 50

# 大页内存相关
vm.nr_hugepages = 1024

为什么要把swappiness设成1?说白了就是告诉内核:不到万不得已,别用swap。交易系统对延迟敏感,swap一触发,延迟直接飙升到毫秒级。

4.1.3 进程调度优化

进程调度,我建议把交易进程设为实时优先级:

# 设置实时优先级
kernel.sched_rt_runtime_us = 950000
kernel.sched_rt_period_us = 1000000

注意:实时优先级设置不当会导致系统锁死。我曾经在测试环境把rt_runtime_us设成1000000,结果系统直接卡死,只能硬重启。建议留5%的时间给其他进程。

4.2 CPU隔离与绑核

CPU隔离,说白了就是把某些CPU核心专门留给交易进程用。我习惯用isolcpus内核参数来实现。

4.2.1 内核启动参数

在GRUB配置里加上:

isolcpus=2,3,4,5 nohz_full=2,3,4,5 rcu_nocbs=2,3,4,5

这几个参数什么意思?

  • isolcpus:把CPU 2-5从普通调度中隔离出来
  • nohz_full:关闭这些CPU的时钟中断
  • rcu_nocbs:把RCU回调移到其他CPU

嗯,这里要注意。隔离CPU后,系统进程就不能用这些核心了。我建议至少留2个核心给操作系统用。

4.2.2 绑核实战

绑核有两种方式:taskset和cpuset。我个人更推荐cpuset,因为它更灵活。

# 用taskset绑核
taskset -c 2,3 ./trading_engine

# 用cpuset绑核(更推荐)
mkdir /sys/fs/cgroup/cpuset/trading
echo 2-3 > /sys/fs/cgroup/cpuset/trading/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/trading/cpuset.mems
echo $$ > /sys/fs/cgroup/cpuset/trading/tasks

小技巧:绑核后记得用perf stat检查一下,确认进程确实跑在指定核心上。我遇到过绑核没生效的情况,排查了半天才发现是cgroup配置有问题。

4.3 大页内存与NUMA优化

4.3.1 大页内存

大页内存,说白了就是减少TLB miss。标准页是4KB,大页可以到2MB甚至1GB。你想想看,同样大小的内存,大页需要的页表项少得多。

配置大页内存:

# 预留1024个2MB大页
echo 1024 > /proc/sys/vm/nr_hugepages

# 挂载hugetlbfs
mount -t hugetlbfs hugetlbfs /dev/hugepages

代码里怎么用?

#include <sys/mman.h>

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

重要:大页内存是稀缺资源,分配后不能换出。我建议只在关键数据结构上用大页,比如订单簿、行情缓存。别一股脑全用大页,内存浪费很严重。

4.3.2 NUMA优化

NUMA架构下,访问本地内存和远端内存的延迟差很多。我见过一个案例,交易进程跑在CPU 0上,但内存分配到了CPU 1的节点,延迟多了30%。

NUMA优化的核心原则:进程绑在哪个CPU,内存就分配在哪个节点

# 查看NUMA拓扑
numactl --hardware

# 绑定CPU和内存节点
numactl --cpunodebind=0 --membind=0 ./trading_engine

还有个技巧:用libnuma库在代码里控制内存分配:

#include <numa.h>

// 只在节点0上分配内存
struct bitmask *mask = numa_allocate_nodemask();
numa_bitmask_setbit(mask, 0);
numa_set_membind(mask);
numa_free_nodemask(mask);
优化项 延迟降低 适用场景
大页内存 5-10% 订单簿、行情缓存
NUMA绑核 10-30% 所有交易进程
CPU隔离 15-25% 核心交易线程

避坑指南:我曾经在NUMA节点间频繁分配内存,结果导致大量跨节点访问。后来改用内存池,提前在本地节点分配好,延迟直接降了一半。

4.4 实战经验总结

说了这么多,我总结几条实战经验:

  1. 先测量,后优化。用perf stat、numastat这些工具看看瓶颈在哪。别一上来就调参数。
  2. 一次只改一个参数。我见过有人一次改10个参数,出问题了都不知道是哪个引起的。
  3. 做好回退方案。每次优化前,记下原始配置。万一出问题,能快速回滚。
  4. 持续监控。内核参数调优不是一劳永逸的。系统负载变了,参数也得跟着调。

嗯,内核优化这块就聊到这。说白了,就是让操作系统别添乱,把硬件资源尽可能多地交给交易进程。下一章我们聊聊网络协议栈的优化,那才是真正考验功力的地方。