第四章:操作系统内核优化
操作系统内核优化,说白了就是给交易系统铺一条高速公路。我见过太多团队,算法写得再漂亮,结果被操作系统调度拖了后腿。嗯,今天我们就来聊聊怎么把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 实战经验总结
说了这么多,我总结几条实战经验:
- 先测量,后优化。用perf stat、numastat这些工具看看瓶颈在哪。别一上来就调参数。
- 一次只改一个参数。我见过有人一次改10个参数,出问题了都不知道是哪个引起的。
- 做好回退方案。每次优化前,记下原始配置。万一出问题,能快速回滚。
- 持续监控。内核参数调优不是一劳永逸的。系统负载变了,参数也得跟着调。
嗯,内核优化这块就聊到这。说白了,就是让操作系统别添乱,把硬件资源尽可能多地交给交易进程。下一章我们聊聊网络协议栈的优化,那才是真正考验功力的地方。