直播平台如何在香港服务器的Ubuntu环境中启用SR-IOV网卡直通,降低推流延迟?

香港葵涌的机房的凌晨两点,直播前端运营在钉钉里不断催促:“深圳主播的推流,起码有 180~220ms 的接入抖动”。业务侧的诉求很明确:把接入延迟稳稳地压到 80ms 内,同时保持 CPU 较低占用。我们已经把应用侧该做的都做了(TCP 拥塞、RTMP/SRT 缓冲、线程绑核),最后还是指向了虚拟化网络的开销——当时接入节点跑在 KVM 上,走的是 vSwitch(OVS)+ virtio。SR-IOV 直通成了当晚唯一能“快刀斩乱麻”的选项。
机房与硬件环境(真实参数)
| 项 | 规格 |
|---|---|
| 机型 | Dell PowerEdge R650 (双路) |
| CPU | 2 × Intel Xeon Gold 6338(32C/64T @ 2.0GHz),NUMA=2 |
| 内存 | 256GB DDR4-3200 |
| 系统 | Ubuntu Server 22.04.4 LTS(内核 5.15) |
| 虚拟化 | KVM + QEMU 6.2,libvirt 8.x |
| 网卡(PF) | 2 × Intel X710-DA2(每张 2×10GbE,驱动 i40e),固件 9.x |
| 交换机 | Arista 7050 系列,ToR 侧聚合上联至 HKT/CMI |
| 直播接入 | RTMP + SRT(SRT 作为低延时优先) |
| VM 数量 | 每台宿主跑 6 |
选择 X710 的原因:SR-IOV 成熟,单 PF 可开较多 VF,且驱动栈在 Ubuntu 上比较省心。Mellanox/ConnectX-5 也很好,但这批机器到港时标配是 X710。
SR-IOV 的价值(对直播推流的直观意义)
- 绕开 vSwitch:VF 由硬件在 PF 上“切出来”,VM 直连 VF,少了 vhost/virtio/OVS 的上下文切换和复制,P99 延迟显著收敛。
- 更低 CPU 抖动:中断路径更短、软中断压力下降,应用线程的时间片更稳定。
- 带宽与 QoS 可控:PF 对 VF 有原生的 VLAN、速率限制、信任模式,可按 VM/业务线做隔离。
开工前 Checklist(5 分钟自检)
- BIOS:开启 Intel VT-d / SR-IOV / Above 4G Decoding;如果有 PCIe ACS 选项也开(便于 IOMMU 分组)。
- GRUB:intel_iommu=on iommu=pt(AMD 平台则 amd_iommu=on iommu=pt),更新 update-grub 后重启。
驱动版本:确认 i40e 正常、固件不落后太多:
ethtool -i ens2f0
ethtool -i ens2f1
NUMA 对齐:网卡和将要绑核的 CPU 在同一 NUMA 节点:
numactl -H
lspci -tv | grep -i ether
cat /sys/class/net/ens2f0/device/numa_node
步骤一:启用 SR-IOV 并创建 VF
方式 A:sysfs 即时生效(X710 推荐)
# 每个 PF 开 8 个 VF(视容量而定,别一上来就 64)
echo 8 | sudo tee /sys/class/net/ens2f0/device/sriov_numvfs
echo 8 | sudo tee /sys/class/net/ens2f1/device/sriov_numvfs
# 验证
lspci | grep -i virtual
ip link show ens2f0 | grep VF
⚠️ 如果报 Device or resource busy,说明已经创建过 VF,要先 echo 0 再重新设置:
echo 0 | sudo tee /sys/class/net/ens2f0/device/sriov_numvfs
echo 8 | sudo tee /sys/class/net/ens2f0/device/sriov_numvfs
方式 B:开机持久化(systemd)
sudo tee /etc/systemd/system/sriov-config.service <<'EOF'
[Unit]
Description=Configure SR-IOV VFs
After=network-pre.target
Before=network.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo 8 > /sys/class/net/ens2f0/device/sriov_numvfs'
ExecStart=/bin/sh -c 'echo 8 > /sys/class/net/ens2f1/device/sriov_numvfs'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now sriov-config.service
步骤二:为 VF 配置 MAC/VLAN/QoS(在宿主机 PF 上操作)
给每个 VF 绑定固定 MAC 和 VLAN,并设置信任模式(做 SRT/RTMP 多连接时更稳):
# 以 ens2f0 为例,为 VF 0~3 配置
ip link set ens2f0 vf 0 mac 52:54:00:10:00:01 vlan 120 spoofchk on trust on
ip link set ens2f0 vf 1 mac 52:54:00:10:00:02 vlan 120 spoofchk on trust on
ip link set ens2f0 vf 2 mac 52:54:00:10:00:03 vlan 120 spoofchk on trust on
ip link set ens2f0 vf 3 mac 52:54:00:10:00:04 vlan 121 spoofchk on trust on
# 适当的速率限制,单位 Mbps,防止某个 VM 打满上行
ip link set ens2f0 vf 0 rate 3000
交换机侧把 ToR 端口打成 trunk,承载业务 VLAN(如 120/121)。如果你用 ip link set PF vf N vlan X,VF 出来的就是打好标签的“接入二层”,无需再让 VM 自己打 VLAN,路径更短更稳定。
步骤三:把 VF 直通到 KVM 虚机(VFIO)
找到 VF 的 PCI 地址:
# 典型地,PF 是 0000:af:00.0 / 00.1,VF 会是 0000:af:00.2/… 之类
lspci -nn | grep -i ether
绑定到 vfio-pci(libvirt 也可自动做,这里演示手工):
VF=0000:af:00.4
echo $VF | sudo tee /sys/bus/pci/devices/$VF/driver/unbind
echo 8086 154c | sudo tee /sys/bus/pci/drivers/vfio-pci/new_id # 8086:154c 是 X710 VF 的常见 ID
echo $VF | sudo tee /sys/bus/pci/drivers/vfio-pci/bind
在 libvirt 添加 hostdev(节选):
<hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0xaf' slot='0x00' function='0x4'/>
</source>
</hostdev>
NUMA 与大页对齐(把 vCPU/内存/hugepages 固定在与网卡同 NUMA):
<cpu placement='static'>
<numa>
<cell id='0' cpus='0-15' memory='131072' unit='KiB'/>
</numa>
</cpu>
<memoryBacking>
<hugepages/>
</memoryBacking>
<cputune>
<vcpupin vcpu='0' cpuset='0'/>
<emulatorpin cpuset='0-1'/>
</cputune>
经验:直通 VF 的虚机无法无缝热迁移(除非用厂商特性配合),生产要保留一块 virtio 辅助管理口,以便在 VF 故障时可以回切。
步骤四:虚机内的网卡与网络栈调优(Ubuntu 22.04)
1)确认 VF 在来宾机内加载
lspci -nn | grep -i ether # 能看到 Intel X710 VF
ip a # 通常叫 ens3/ens4
ethtool -k ens3 # 看 offload 能力
2)Netplan 配置(静态 IP 示例)
# /etc/netplan/01-sriov.yaml
network:
version: 2
ethernets:
ens3:
dhcp4: no
addresses: [203.0.113.10/27]
gateway4: 203.0.113.1
nameservers:
addresses: [8.8.8.8, 1.1.1.1]
mtu: 1500
sudo netplan apply
3)关闭会拉高延迟的聚合(在来宾机 VF 上)
对直播推流(小包多、实时性高),GRO/LRO/TSO/GSO 可按需关闭,通常能换来 P99 的稳定:
sudo ethtool -K ens3 gro off lro off gso off tso off
4)TCP 内核参数(来宾机)
sudo tee /etc/sysctl.d/99-streaming-tcp.conf <<'EOF'
net.core.netdev_max_backlog = 250000
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_rmem = 4096 131072 8388608
net.ipv4.tcp_wmem = 4096 131072 8388608
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
EOF
sudo sysctl --system
5)中断亲和与绑核(宿主机)
SR-IOV 的中断最好绑在与 VF 同 NUMA 的几个核上,减少跨 NUMA 抖动:
grep -R . /proc/irq/*/affinity_hint | grep i40e
# 然后写入 smp_affinity_list:
echo 0-3 | sudo tee /proc/irq/XX/smp_affinity_list # 仅示例
步骤五:接入服务(RTMP/SRT)的侧向优化
RTMP(nginx-rtmp 简例)
# /etc/nginx/nginx.conf 片段
rtmp {
server {
listen 1935;
chunk_size 4096;
publish_time_fix off;
application live {
live on;
gop_cache on;
}
}
}
- Chunk 与缓冲:小一些的 chunk_size、关闭多余缓冲,减少队头阻塞。
- 进程亲和:让 nginx worker 落在与 VF 同 NUMA 的 CPU 上(taskset 或 systemd CPUAffinity)。
SRT(srt-live-server 或应用直集成)
- latency(ms)设定为链路 RTT 的 2~3 倍;linger、peerlatency 配合压抑抖动。
- 如果跨境推流,FEC 视链路质量开启,宁可轻 FEC 也不要过大缓冲。
关键配置一览表(便于回放)
| 类别 | 参数 | 建议值/示例 | 备注 |
|---|---|---|---|
| GRUB | intel_iommu |
on |
IOMMU 开启 |
| GRUB | iommu |
pt |
旁路模式,降低开销 |
| PF→VF | sriov_numvfs |
8/PF |
分批创建,先小后大 |
| VF 安全 | spoofchk |
on |
防欺骗 |
| VF 信任 | trust |
on |
需要多 MAC/队列时 |
| VF VLAN | vlan |
120/121 |
由 PF 打标签 |
| VF 速率 | rate |
3000 Mbps |
防单 VM 滥用 |
| Offload | gro/lro/gso/tso |
off |
降 P99 延迟 |
| TCP | rmem/wmem |
上面 sysctl | 吞吐/延迟折中 |
| IRQ | smp_affinity |
同 NUMA | 降跨 NUMA 抖动 |
| MTU | ens3 |
1500 | 低延迟优先 |
实测数据(一次切换前后对比)
测试方法:深圳办公室(专线+公网回落)→ 香港接入节点;推 RTMP(OBS)与 SRT(srt-live-transmit),每种 5 分钟,采 10 次。
| 指标 | 变更前(virtio+OVS) | 变更后(SR-IOV VF 直通) |
|---|---|---|
| RTMP 端到端 p50 | 135 ms | 78 ms |
| RTMP 端到端 p99 | 225 ms | 118 ms |
| SRT 端到端 p50 | 92 ms | 54 ms |
| SRT 端到端 p99 | 160 ms | 85 ms |
| 单 VM CPU(接入进程) | 120%(2核) | 78%(2核) |
| 宿主软中断 si% | 12% | 5% |
| 丢包(应用统计) | 0.35% | 0.09% |
注意:关闭 GRO/LRO 后 p50 明显下降,p99 更稳;吞吐没有明显损失(直播以小包为主)。
那些年我踩过的坑(当晚也复现了几例)
- PF 重置导致 VF 抖动:X710 固件落后时,i40e 会偶发 PF reset,VM 网络瞬断。解法:升级固件/驱动;分批创建 VF,避免一次性 32/64 个。
- 无法减少/增设 VF:echo N > sriov_numvfs 报 busy。解法:先 echo 0 清空再设新值;确保相关 VF 没有分配给 VM。
- VLAN 不通:ToR 没配 trunk 或本地 PF 给 VF 打了 VLAN 而 VM 里又打了一次(双重打标签)。解法:规范化——要么 PF 打、VM 原生二层,要么 ToR trunk + VM 自打,二选一。
- NUMA 不一致:VF 在 NUMA1,VM vCPU 在 NUMA0,时延抖动大。解法:核对 /sys/class/net/<PF>/device/numa_node 与 libvirt 绑核设置。
- MTU 不一致:VM 1500,ToR/上游 9000 或反之,导致分片和奇怪的重传。解法:统一 1500(低延时优先)。
- 信任模式:不开 trust on 有时 ARP/多连接被限。解法:直播接入 VF 建议默认 trust on + spoofchk on。
- 运维折中:有了 VF 直通,热迁移受限。解法:VM 保留一块 virtio 管理口;ZK/Consul 健康探针能把流量平滑摘/挂。
回滚与灰度策略
每台宿主先改 1~2 个 VM,观察 24 小时,确认 P99 与丢包数据达标再扩大。
VM 同时保留 virtio 管理网口,Nginx/SRT 服务通过 VIP/Anycast 暂停引流后切换 VF。
预置 systemd 回滚脚本:
- 关闭 VF:echo 0 > sriov_numvfs
- libvirt 切回 virtio 网卡并重启 VM。
完整小抄(能复用就别死记)
# 1) BIOS 开 VT-d/SR-IOV/Above 4G;GRUB:
sudo sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt /' /etc/default/grub
sudo update-grub && sudo reboot
# 2) 创建 VF(以 ens2f0 / ens2f1 为例)
echo 8 | sudo tee /sys/class/net/ens2f0/device/sriov_numvfs
echo 8 | sudo tee /sys/class/net/ens2f1/device/sriov_numvfs
# 3) 为 VF 配置 MAC/VLAN/QoS
ip link set ens2f0 vf 0 mac 52:54:00:10:00:01 vlan 120 spoofchk on trust on
ip link set ens2f0 vf 0 rate 3000
# 4) 绑定 VF 到 vfio-pci 并直通给 VM(以 0000:af:00.4 为例)
echo 0000:af:00.4 | sudo tee /sys/bus/pci/devices/0000:af:00.4/driver/unbind
echo 8086 154c | sudo tee /sys/bus/pci/drivers/vfio-pci/new_id
echo 0000:af:00.4 | sudo tee /sys/bus/pci/drivers/vfio-pci/bind
# 5) 虚机内调优
sudo ethtool -K ens3 gro off lro off gso off tso off
sudo sysctl --system # 先写好 /etc/sysctl.d/99-streaming-tcp.conf
凌晨四点半,我把最后一台宿主的两块 PF 都开了 8 个 VF,接入 VM 逐个切过去。OBS 那头的同事在群里丢来第一张截图:SRT p50 稳在 50ms 左右,p99 不超过 90ms。机房外面天微亮,港岛对面的灯还没完全灭。我知道,这一晚没有“银弹”,只有把每一个细节抠到位:BIOS、IOMMU、PF/VF、VLAN、IRQ、NUMA、Offload、TCP,以及回滚策略。
SR-IOV 不是魔法,它只是把该让硬件做的事情交还给了硬件。直播这种对时延极致敏感的业务,直通的价值就体现在每一个不被浪费的微秒里。等到下一次需求再来,我会重复同样的流程,但会先看一眼风向——如果机器上的是 ConnectX-5,我会把 NUM_OF_VFS、Trust、TC 规则和 UPT 模式再玩一遍;如果是边缘节点,我可能用更激进的 IRQ 绑核策略。
那天清晨,我合上了机柜门,拍了拍门上的那张黄色小贴纸:“SR-IOV 已启用”。心是热的,风还是冷的,但推流的延迟,确实降下来了