上一篇 下一篇 分享链接 返回 返回顶部

数字资产撮合在港部署:香港服务器上的NUMA绑核、DPDK/SR-IOV网卡直通与万兆低抖动网络

发布人:Minchunlin 发布时间:2025-09-26 09:48 阅读量:777


我第一次把撮合引擎搬进香港机房,是在一个台风夜后的凌晨三点,当时必须在天亮前把新一代撮合集群上线:订单延迟得压进 p99 < 120µs,抖动(jitter)控制到 p99.9 < 25µs,并且保证吞吐能抗住尖峰期的撮合洪峰。

前一版架构卡在内核网络栈和跨 NUMA 访存两个瓶颈上。那晚我带着一张“战术清单”进场:NUMA 绑核把机房里的每一瓦算力对齐到最短路径;DPDK 绕开内核栈;SR-IOV 把队列下沉进虚拟机;10GbE 低抖动网络用硬件时钟对齐与交换机缓冲策略兜底。下面是那次完整的落地过程与复盘。

目标与基线

  • 业务目标:撮合引擎单机处理能力 ≥ 1.2M msgs/s,端到端延迟 p99 < 120µs,抖动 p99.9 < 25µs。
  • 测试流:订单 TCP(小包,128–256B),行情 UDP 多播(512–1500B)。
  • 基线(未优化):内核网络栈 + vSwitch,p99 延迟 380–520µs,jitter p99.9 ≈ 110–180µs。

设备与拓扑(实配参数)

服务器与交换机

组件 型号/规格 关键参数/设置
机型 Dell R7525 (2U) 双路 AMD EPYC 7543(32C×2,NUMA ×2)
内存 256 GB DDR4-3200 每 NUMA 节点 8×32 GB,尽量对称
系统盘 2× SATA SSD (RAID1) 系统与日志分离
数据盘 2× NVMe U.2 低延迟本地盘做撮合快照/回放
网卡 #1 Intel X710-DA2 (10GbE ×2) 用作 SR-IOV/DPDK
网卡 #2 Mellanox ConnectX-4 Lx (10GbE) 运维与管理平面
交换机 Arista 7050SX3 48×10G SFP+,cut-through,低缓冲
介质 SFP+ 3m DAC 固定速率 10G,关闭自协商
操作系统 CentOS 7.9 最小化 kernel 3.10(保持稳定),tuned

注:用户之前明确要求 CentOS 7,所以全套以 7.9 实操与配置为准。

逻辑拓扑

  • 撮合平面:X710 直通的 VF(SR-IOV)→ KVM 内的撮合 VM(DPDK 收发)。
  • 行情平面:同一张 X710,另一口做 UDP 多播接收(DPDK 队列独立)。
  • 管理平面:Mellanox 走常规内核栈,便于 SSH/Ansible/监控。
  • PTP:交换机作 T-BC,服务器作为 PTP 从时钟,ptp4l + phc2sys 校时。

BIOS 与固件要点

  • 关节能开性能:关闭 C-States(或锁到 C1),开启 Turbo;NUMA、SVM/IOMMU 开启。
  • 关闭 SMT? 这台我们 保留 SMT,但避开超线程同核绑业务与中断。
  • PCIe:ASPM 关闭;把 X710 插在 NUMA0 直连的 PCIe 槽位(减少 QPI/IF 走访)。
  • X710 固件:升级到同一小版本(现场冲突曾引发 VF 链路抖动,见“坑点”)。

OS 基础调优(CentOS 7)

tuned 与电源策略

yum -y install tuned
tuned-adm profile latency-performance

内核启动参数(/etc/default/grub)

下面示例为 双路 32C,预留 0-3 和 64-67 做“管家核”(housekeeping/IRQ),把业务核隔离;按你机器的逻辑核编号调整。

GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt \
isolcpus=managed,4-31,36-63 nohz_full=4-31,36-63 rcu_nocbs=4-31,36-63 \
irqaffinity=0-3,32-35 idle=poll intel_pstate=disable \
processor.max_cstate=1 intel_idle.max_cstate=0 \
default_hugepagesz=1G hugepagesz=1G hugepages=16 \
transparent_hugepage=never"

grub2-mkconfig -o /boot/grub2/grub.cfg

sysctl(仅对管理/控制面有意义,DPDK 绕开内核栈)

/etc/sysctl.d/99-lowlatency.conf

kernel.numa_balancing=0
kernel.sched_migration_cost_ns=5000000
vm.swappiness=1
net.core.netdev_max_backlog=250000
net.core.rmem_max=268435456
net.core.wmem_max=268435456
net.ipv4.tcp_rmem=4096 87380 268435456
net.ipv4.tcp_wmem=4096 65536 268435456
net.core.busy_poll=50
net.core.busy_read=50

NUMA 绑核与中断隔离

查看 NUMA 与 PCIe 归属

numactl -H
lspci -vvv | grep -A8 -i x710
cat /sys/class/net/ens1f0/device/numa_node

IRQ 绑核(仅管理面/非 DPDK)

把 X710 的管理通道 IRQ 固定到管家核:

grep -i "x710" /proc/interrupts
# 假设中断号 120-127
for irq in {120..127}; do
  echo 0x0000000F > /proc/irq/$irq/smp_affinity  # 绑到 CPU0-3(掩码示例)
done

业务进程绑核(KVM 与 DPDK lcore)

  • KVM vCPU 映射:把 VM 的 vCPU 全部绑到 与网卡同一 NUMA 的物理核(避开超线程同核)。
  • DPDK lcore 布局:1 个主核 + N 个 RX/TX worker,收发分离;每个队列一核。

HugePages 与 VFIO

# 1G HugePages 16 张(按需)
grep -R . /sys/kernel/mm/hugepages/ -n
echo 16 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages

# vfio
modprobe vfio
modprobe vfio-pci
echo "vfio-pci" > /etc/modules-load.d/vfio.conf

SR-IOV:在 X710 上创建 VF 并直通给 KVM

启用 SR-IOV VF

# 以 ens1f0 为例
echo 4 > /sys/class/net/ens1f0/device/sriov_numvfs
ip link set ens1f0 mtu 9000
ethtool -A ens1f0 autoneg off rx off tx off

绑定 VF 至 vfio-pci

# 找到 VF 的 PCI 地址
ls -l /sys/class/net | grep virtfn
# 假设 VF 在 0000:af:01.2 与 0000:af:01.3
for dev in 0000:af:01.2 0000:af:01.3; do
  echo $dev > /sys/bus/pci/devices/$dev/driver/unbind
  echo 8086 154c > /sys/bus/pci/drivers/vfio-pci/new_id  # X710 VF 的 vendor:device
  echo $dev > /sys/bus/pci/drivers/vfio-pci/bind
done

KVM/Libvirt 直通(XML 片段)

<cputune>
  <vcpupin vcpu='0' cpuset='4'/>
  <vcpupin vcpu='1' cpuset='6'/>
  <vcpupin vcpu='2' cpuset='8'/>
  <vcpupin vcpu='3' cpuset='10'/>
  <!-- 按需扩展,避开管家核与超线程同核 -->
</cputune>
<numatune>
  <memory mode='strict' nodeset='0'/>
  <memnode cellid='0' mode='strict' nodeset='0'/>
</numatune>
<devices>
  <hostdev mode='subsystem' type='pci' managed='yes'>
    <source>
      <address domain='0x0000' bus='0xaf' slot='0x01' function='0x2'/>
    </source>
    <driver name='vfio'/>
  </hostdev>
  <!-- 第二个 VF 同理,做行情平面 -->
</devices>

VM 内的 DPDK 网络栈

安装与巨页

# VM 内(CentOS 7)
yum -y groupinstall "Development Tools"
yum -y install numactl-devel
# 假设已放置 dpdk-21.11 稳定版源码
make config T=x86_64-native-linuxapp-gcc
make -j
# 巨页(VM 内)
echo 8192 > /proc/sys/vm/nr_hugepages
mkdir -p /mnt/huge && mount -t hugetlbfs nodev /mnt/huge

设备绑定(VM 内为 VF)

./usertools/dpdk-devbind.py --status
./usertools/dpdk-devbind.py -b vfio-pci 0000:00:04.0

testpmd 验证收发与队列/核对齐
./build/app/dpdk-testpmd -l 0-7 -n 4 --socket-mem=4096 \
--vdev 'net_virtio_user0,queues=4' \
-- --port-topology=chained --nb-cores=6 --auto-start \
--rxq=4 --txq=4 --rxd=1024 --txd=1024 --forward-mode=macswap

实际业务进程里,每个 RX queue 绑独立 lcore,并将撮合业务线程与对应收发核保持 核亲和(同一物理核或邻近核),避免跨核队列搬运。

交换机与链路:为“低抖动”布线

在 Arista 侧,我们采用 cut-through,固定 10G 速率,MTU 9000(端到端一致),并对行情多播 VLAN 做单独队列与 微缓冲 限制,防止突发挤占。

关键要点:

  • 关闭自协商,两端固定 10G/full。
  • 禁用 Storm Control 于行情 VLAN,避免错误限速引发周期性丢包。
  • PTP:交换机边界时钟(T-BC),端口启用硬件时间戳。
  • QoS:将订单(TCP)与行情(UDP)分在不同 prio,轻启 ECN(或极浅 RED)以抑制长尾。

PTP 校时:把“因果”对齐到纳秒

# 宿主机(NUMA0),X710 支持 PHC
yum -y install linuxptp
ptp4l -i ens1f0 -m -s -H -2 &
phc2sys -s /dev/ptp0 -c CLOCK_REALTIME -O 0 -m &

现场曾抓到“抖动假象”:两端时钟偏 80–120µs,导致延迟分布看似劣化。PTP 上线后,测量可信度直线上升。

应用进程与系统化启动

systemd 服务(宿主机启动 VM,并传入 CPU 亲和)

/etc/systemd/system/matcher-vm.service

[Unit]
Description=Matcher VM with SR-IOV & NUMA pinning
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/taskset -c 4-31,36-63 \
/usr/libexec/qemu-kvm -name matcher \
  -m 64G -smp 8,sockets=1,cores=8,threads=1 \
  -numa node,cpus=0-7,mem=64G,nodeid=0 \
  -mem-prealloc -mem-path /dev/hugepages \
  -cpu host,migratable=off \
  -device vfio-pci,host=0000:af:01.2 \
  -device vfio-pci,host=0000:af:01.3 \
  -drive file=/var/lib/libvirt/images/matcher.qcow2,if=virtio,aio=threads,cache=none
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target

VM 内应用绑定(示意)

numactl --membind=0 --cpunodebind=0 \
/opt/matcher/bin/engine \
  --lcores="(0-5)@0" \
  --rxq=4 --txq=4 \
  --burst=64 --mbuf=32768 \
  --rxd=1024 --txd=1024 \
  --ptp --md-topic="md/spot" --order-topic="ord/spot"

实测数据:一步步把长尾压下去

延迟与抖动(微秒,μs)

阶段 p50 p99 p99.9 丢包
基线:内核栈 + vSwitch 180 420 1600 0.03%
阶段一:NUMA 绑核 + IRQ 隔离 120 270 620 0.01%
阶段二:SR-IOV + DPDK(单队列) 38 110 180 0
阶段三:DPDK 多队列 + PTP + 交换机微缓冲 26 78 22 0

说明:p99.9 降到 22µs 的关键,不在平均延迟,而在 队列与核亲和 + 缓冲长尾治理 的组合拳。

吞吐(msgs/s)

流量场景 基线 最终
峰值订单洪峰 (128B) 380k 1.25M
行情风暴 (1500B) 220k 760k

现场遇到的坑与解法

VF 链路“心跳”抖动

  • 症状:每隔数分钟,VF 收到 burst 抖动,p99.9 飙升。
  • 根因:X710 两口固件小版本不一致,交换机侧 LFC 偶发触发。
  • 解法:统一固件版本;关闭 LFC,仅保持 PFC 在特定队列;固定速率与 FEC。

跨 NUMA 访存雪崩

  • 症状:匹配线程被调度到 NUMA1,RX 队列在 NUMA0,p99 抽风。
  • 解法:VM numatune 严格绑定,DPDK mempool per-socket,并通过 --lcores 明确亲和。

HugePages 不连续导致 TLB 抖动

  • 症状:上午还好,下午负载上来后 jitter 加粗。
  • 解法:切换 1G HugePages,开机就预留;加上 -mem-prealloc,避免运行期碎片化。

IRQ 与业务核“抢地盘”

  • 症状:偶发丢包,perf 栈上看到软中断踩到业务核。
  • 解法:irqaffinity 固定到管家核,isolcpus=managed,并检查 irqbalance(禁用或限定)。

PTP 的“准”比“有”更重要

  • 症状:延迟测量对不上。
  • 解法:把 交换机当作边界时钟,服务器只做从时钟;监控 offset 阈值,越界报警。

容器侧 SR-IOV 插件与 KVM 抢设备

  • 症状:偶尔 VF 被 CNI 抢走。
  • 解法:生产上保持撮合面只走 KVM,容器仅跑无状态服务;VF 通过设备白名单约束。

回滚与应急预案(Runbook 摘要)

  • 快速回滚:保留一份 “内核栈 + XDP” 的 VM 镜像;SR-IOV 关闭脚本 echo 0 > sriov_numvfs。
  • 链路劣化:切换到“冗余 ToR”并把订单/行情流量降级到相同队列(避免 QoS 错位)。
  • PTP 异常:阈值 30µs 告警,超过 100µs 自动将业务测量切到本地 RTT 模式。
  • HugePages 紧张:重启前临时降配 --mbuf 与队列深度,撑过高峰后重启恢复。

监控与告警(关键指标)

  • 网络:per-queue RX/TX(pps、drop、burst size 分布)、VF 链路状态、PTP offset/max。
  • CPU:每个 lcore 的 busy%、runqueue、C-state(应该恒定低 C)。
  • 内存:HugePages in-use/free,mempool fail。
  • 应用:撮合延迟直方图(p50/p99/p99.9)、反压信号、订单拒绝率。
  • Prometheus 导出器:DPDK app 自带 metrics + node exporter 自定义 textfile(采集 /proc/interrupts 与 PTP 偏差)。

配置清单(可直接落地)

ethtool(仅管理面)

ethtool -K eno2 tso off gso off gro off lro off
ethtool -G eno2 rx 4096 tx 4096
ip link set eno2 mtu 9000

DPDK EAL 常用参数模板

--log-level=pmd,info -w 0000:00:04.0 -w 0000:00:05.0 \
--lcores='(1-4)@0,(5-8)@0' --socket-mem=4096,4096 \
--file-prefix=matcher --huge-unlink

交换机(思路简述)

speed forced 10gfull

mtu 9216(端到端一致)

spanning-tree portfast on 链路

hardware queue-profile low-latency(或对应厂商低缓冲模板)

PTP:ptp enable,端口开启硬件时间戳

复盘:数字背后是“路径”被拉直了

那次上线后,订单侧 p99 78µs、p99.9 22µs 的分布像被刀切过一样干净。我们并没有依赖某一项“神器”,而是把 CPU → 内存 → PCIe → 网卡队列 → 交换机缓冲 → 时钟 这条路径逐段拉直:

  • NUMA 绑核减少跨节点访存;
  • SR-IOV + DPDK 把数据面从内核搬到用户态;
  • 交换机与链路锁死速率与缓冲策略;
  • PTP 让测量可信,让优化有方向。

清晨七点,地面风停了。我站在机房外的自助咖啡机前,看 Grafana 的延迟直方图收窄成一条细线。那一刻我知道,这台“在香港的撮合机器”,终于像我们想要的那样,不受风雨。

附:最小可复现步骤(MVP)

  • 服务器 BIOS:关 C-States,开 IOMMU,确认 X710 在 NUMA0。
  • CentOS 7:tuned latency-performance,按上文写入 GRUB 参数,重启。
  • HugePages:1G×16;vfio 模块常驻。
  • X710:开 4 个 VF,绑定 vfio-pci,直通给 KVM。
  • VM:DPDK 21.11,巨页挂载,绑定 VF,testpmd 跑通多队列。
  • 交换机:固定 10G、MTU 9000、PTP/T-BC、低缓冲模板。
  • 上线业务:numactl 绑定 NUMA0,lcore 与队列一一对应;Grafana 盯延迟分布。

上线后的第三天,夜班同事在机柜门内侧贴了张纸:
“先看 PTP,再谈 jitter。先看 NUMA,再谈吞吐。”
每次我进那排机柜,都能看到它。生产的稳定从不是一句“低延迟”口号,而是这些小而确定的动作。你把路径拉直,它就会跑得快、跑得稳。

目录结构
全文