操作系统与内核调优:Linux内核参数调优、CPU隔离与绑核、内存大页配置、网络协议栈优化
各位同学,咱们今天聊点硬核的。券商交易系统对延迟有多敏感,不用我多说。你想想看,一笔订单从客户端发出,到交易所撮合,中间经过的每一微秒都是真金白银。而操作系统,恰恰是那个最容易拖后腿的环节。
我个人习惯,在部署交易系统之前,第一件事就是先把操作系统“扒一层皮”。把那些无关的服务关掉,把内核参数调到最适合低延迟的场景。说白了,就是让操作系统别来打扰我们的交易进程。
核心思路: 交易系统追求的是“确定性低延迟”。我们不只要快,更要每次都快,不能有抖动。内核调优的所有动作,都是围绕这个目标展开的。
一、Linux内核参数调优:让系统“听话”
内核参数调优,说白了就是修改 /etc/sysctl.conf 里的那些值。但改什么、怎么改,这里头门道不少。
1. 网络相关参数
交易系统最怕网络延迟抖动。我建议重点关注这几个:
net.core.rmem_max和net.core.wmem_max:增大 socket 缓冲区上限。我一般设到 16MB 以上。net.ipv4.tcp_rmem和net.ipv4.tcp_wmem:TCP 读写缓冲区。建议设为 “4096 87380 16777216” 这种三段式。net.ipv4.tcp_fastopen:开启 TCP 快速打开。能减少一次握手延迟,对高频交易很有用。net.core.netdev_budget:网卡轮询预算。适当调大,比如 600,能减少丢包。
# 我的常用配置片段
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_fastopen = 3
net.core.netdev_budget = 600
net.core.netdev_budget_usecs = 4000
我的经验: 有一次线上交易延迟突然飙升,查了半天发现是 net.core.somaxconn 设得太小,导致连接队列溢出。从那以后,我习惯把这个值设到 65535。
2. 内存与调度参数
交易进程最怕被换出到 swap。所以:
vm.swappiness:设为 0 或 1。尽量别用 swap。vm.overcommit_memory:设为 1。允许内存过量使用,避免某些应用因内存申请失败而崩溃。kernel.sched_migration_cost_ns:设为 5000000。减少进程迁移频率。
vm.swappiness = 0
vm.overcommit_memory = 1
kernel.sched_migration_cost_ns = 5000000
注意: vm.overcommit_memory = 1 虽然能避免 OOM,但也要配合 vm.overcommit_ratio 控制比例。否则内存泄漏时系统会直接挂掉。我曾经吃过这个亏。
二、CPU隔离与绑核:把核心“私有化”
为什么要做 CPU 隔离?你想想看,Linux 默认的调度器会把进程在各个核心之间迁移。每次迁移,缓存就失效了,TLB 也刷新了。这对交易系统来说,简直是灾难。
1. 使用 isolcpus 内核参数
在 GRUB 启动参数里加上 isolcpus=2,3,4,5,把核心 2-5 从内核调度器中隔离出来。这样普通进程就不会跑到这些核心上去了。
# 修改 /etc/default/grub
GRUB_CMDLINE_LINUX="... isolcpus=2,3,4,5 nohz_full=2,3,4,5 rcu_nocbs=2,3,4,5"
# 然后更新 grub
grub2-mkconfig -o /boot/grub2/grub.cfg
2. 使用 taskset 绑核
隔离完核心,还得把交易进程绑上去。我习惯用 taskset 或者 numactl:
# 把交易进程绑定到核心 2 和 3
taskset -c 2,3 ./trade_engine
# 或者用 numactl 更精细地控制
numactl --physcpubind=2,3 --membind=0 ./trade_engine
避坑指南: 我曾经把交易进程和网卡中断绑在同一个核心上,结果延迟反而更高了。后来才意识到,网卡中断处理会抢占交易进程的 CPU 时间。正确的做法是:网卡中断绑在核心 0-1,交易进程绑在核心 2-5,彻底隔离。
3. 使用 cgroups 做更细粒度的控制
如果系统里跑着多个交易进程,用 cgroups 可以更灵活地分配 CPU 资源:
# 创建 cgroup
mkdir /sys/fs/cgroup/cpuset/trade
echo "2-3" > /sys/fs/cgroup/cpuset/trade/cpuset.cpus
echo "0" > /sys/fs/cgroup/cpuset/trade/cpuset.mems
# 把进程加入 cgroup
echo 12345 > /sys/fs/cgroup/cpuset/trade/cgroup.procs
三、内存大页配置:减少 TLB 缺失
现代 CPU 的 TLB(页表缓存)条目有限。默认 4KB 的页大小,意味着要管理大量页表项。而 2MB 甚至 1GB 的大页,能显著减少 TLB 缺失。
1. 配置 HugePages
我建议为交易系统的共享内存区域配置大页:
# 预留 1024 个 2MB 大页
echo 1024 > /proc/sys/vm/nr_hugepages
# 或者写入 sysctl.conf 持久化
vm.nr_hugepages = 1024
2. 使用透明大页(THP)?我劝你慎重
透明大页虽然方便,但有个问题:它会在后台做内存规整,这个过程可能导致延迟抖动。我在项目中遇到过,开启 THP 后,交易延迟偶尔会飙高几十微秒。
我的建议: 对于交易系统,关掉透明大页,手动配置 HugePages。虽然麻烦点,但胜在稳定。
# 关闭透明大页
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
3. 应用层使用大页
如果你的交易程序用到了共享内存(比如 shmget),可以加上 SHM_HUGETLB 标志:
#include <sys/shm.h>
int shmid = shmget(IPC_PRIVATE, 2*1024*1024, SHM_HUGETLB | IPC_CREAT | 0666);
四、网络协议栈优化:从网卡到应用
网络延迟是交易系统的命门。我见过太多团队,应用层优化做得很好,但网络协议栈的配置一塌糊涂。
1. 网卡多队列与 RSS
现代网卡支持多队列,每个队列可以绑定到不同的 CPU 核心。这样网卡中断就能分散处理,避免单核瓶颈。
# 查看网卡队列数
ethtool -l eth0
# 设置队列数(假设支持)
ethtool -L eth0 combined 4
# 查看当前 RSS 配置
ethtool -x eth0
2. 中断亲和性设置
把网卡中断绑定到隔离核心之外的 CPU 上:
# 查看中断号
cat /proc/interrupts | grep eth0
# 设置亲和性(假设中断号 78,绑定到 CPU 0-1)
echo 3 > /proc/irq/78/smp_affinity
3. 使用 DPDK 或 AF_XDP
如果延迟要求极其苛刻(比如微秒级),可以考虑绕过内核协议栈。DPDK 和 AF_XDP 是两种主流方案。
# DPDK 绑定网卡
dpdk-devbind.py --bind=igb_uio 0000:02:00.0
# AF_XDP 示例(伪代码)
struct xsk_socket *xsk;
xsk_socket__create(&xsk, "eth0", 0, &umem, &rxq, &txq, &xsk_cfg);
我的经验: DPDK 虽然性能好,但开发成本高。如果你们的交易系统用的是标准 TCP,AF_XDP 是个不错的折中方案。我在一个期货项目中用过,延迟从 50 微秒降到了 10 微秒左右。
知识体系总览
下面这张图,是我对本章知识体系的总结。你可以把它当作一个检查清单:
嗯,到这里,操作系统与内核调优的核心内容就讲完了。记住,调优不是一蹴而就的。我每次部署新系统,都会先跑一遍基准测试,然后根据实际数据再微调参数。毕竟,每个交易系统的负载特征都不一样,没有银弹。