Skip to content

性能观测与调优 ​

OVS 的性能问题几乎都能归结到一句话:

慢路径处理了太多包,或者内核缓存的粒度太细。

这一章讲怎么测出来、怎么定位、怎么改。

先看四个数 ​

bash
ovs-dpctl show
text
system@ovs-system:
  lookups: hit:227 missed:164 lost:0
  flows: 2
  masks: hit:319 total:1 hit/pkt:0.82
  cache: hit:203 hit-rate:51.92%
指标含义健康危险
lookups.lostupcall 队列溢出丢掉的包0非零就是告警
lookups.missed / hit走慢路径的包占比missed 是少数missed 持续占大头
flows当前缓存条数远低于 limit接近 limit
masks.hit/pkt平均每个包要试多少种掩码接近 1大于 5

这四个数能覆盖绝大多数性能问题的定位。

lost 非零 ​

upcall 队列满了,包被直接丢弃。这是最严重的信号 —— 业务已经在丢包了。

短期缓解:

bash
ovs-vsctl set Open_vSwitch . other_config:n-handler-threads=4

但根因通常是 upcall 太多,见下面。

missed 占比高 ​

说明大量包走慢路径。区分两种情况:

  • 短时飙高:正常。流表刚改过,或者突发了大量新连接
  • 持续很高:缓存装不下或者失效太快,需要查流表

flows 接近上限 ​

bash
ovs-appctl upcall/show
text
system@ovs-system:
  flows : (current 0) (avg 0) (max 0) (limit 10000)

current 逼近 limit 时,OVS 会开始激进回收缓存,导致命中率进一步下降 —— 恶性循环。

limit 是动态调整的。看它被调整过多少次:

bash
ovs-appctl coverage/show | grep upcall_flow_limit
text
upcall_flow_limit_grew     total: 1
upcall_flow_limit_shrunk   total: 0

shrunk 持续增长说明 revalidator 处理不过来。

masks hit/pkt 大 ​

内核查缓存时要按掩码逐个尝试。掩码种类越多,每个包的查找成本越高。

text
masks: hit:319 total:1 hit/pkt:0.82     ← 健康,只有 1 种掩码
masks: hit:2048 total:47 hit/pkt:8.3    ← 有问题,47 种掩码

total 就是不同掩码的种类数。它直接由流表匹配了多少种字段组合决定。

根因几乎总是流表太细 ​

实验 4 用实测证明过这条因果链:

text
   流表匹配的字段越多
        ↓
   megaflow 掩码里精确匹配的字段越多
        ↓
   一条缓存能覆盖的包越少 → 缓存条数越多、掩码种类越多
        ↓
   命中率下降 + 查找成本上升 → 更多包走慢路径
        ↓
   ovs-vswitchd CPU 上升,吞吐下降

实验里的真实对比:加一条 tcp,tp_dst=9999 的规则(转发结果完全没变),缓存就从一条 proto=0/0 通吃,裂成了 ICMP 和 TCP 各一条,且 TCP 那条的端口号变成精确匹配。

怎么确认是这个问题 ​

bash
# 1. 流表规模
ovs-ofctl dump-flows br0 | wc -l

# 2. 看缓存里精确匹配了哪些字段
ovs-appctl dpctl/dump-flows -m | head -5

-m 会显示掩码。看到 tp_dst=8080(没有 /)就是精确匹配,看到 src=0.0.0.0/0.0.0.0 就是通配。

精确匹配的字段越多,缓存粒度越细。

优化手段 ​

一、能用 NORMAL 就别手写 ​

NORMAL 在 ovs-vswitchd 里是高度优化的 C 实现,而且它只看 MAC,megaflow 会把 IP 和端口全部通配掉,一条缓存能覆盖两台主机之间的所有流量。

典型写法是特殊流量走自定义规则,其余交给 NORMAL:

text
priority=200, <特殊条件>, actions=<自定义处理>
priority=0,               actions=NORMAL

二、匹配尽量粗 ​

text
ip,nw_dst=10.0.0.0/8            比    ip,nw_dst=10.1.2.3   好
tcp                             比    tcp,tp_dst=8080      好

能用网段就别用单个 IP,能不看端口就别看端口。

三、把细粒度规则关进独立的表 ​

用多级流表,让细规则只对需要的流量生效:

text
table=0  粗粒度分流:绝大多数流量直接 NORMAL
         只有特定子网 goto_table:10
table=10 细粒度策略:按端口、按五元组

这样大部分流量的 megaflow 依然很粗。

四、慎用会强制走慢路径的动作 ​

这些动作每个包都要上用户态,无法缓存:

动作影响
CONTROLLER还要过一趟网络到控制器
learn每个包都要动态生成规则
sFlow / IPFIX 采样按采样率复制包

它们只该用在少量流量上(比如只把 ARP 送控制器)。

五、注意 ct() 的 recirculation 代价 ​

实验 16 讲过 ct() 会让包重新进入流水线,缓存按 recirc_id 拆开,条数至少翻倍。

优化:在 table 0 就把不需要跟踪的流量分流掉。

text
table=0, priority=200, arp,        actions=NORMAL          ← 不进 ct
table=0, priority=200, <已知安全>,  actions=NORMAL          ← 不进 ct
table=0, priority=100, ip,         actions=ct(table=1)     ← 只有需要的才进

六、调缓存老化时间 ​

bash
ovs-vsctl set Open_vSwitch . other_config:max-idle=10000    # 毫秒,默认 10 秒
  • 调大:缓存留得久,命中率高,但占内存、条数多
  • 调小:条数少,但短连接场景会反复 upcall

短连接密集的场景(比如大量 HTTP 短请求)适当调大有帮助。

七、加 handler 线程 ​

bash
ovs-vsctl set Open_vSwitch . other_config:n-handler-threads=4
ovs-vsctl set Open_vSwitch . other_config:n-revalidator-threads=2

这是缓解不是根治。 upcall 太多的根因还是流表。

观测 CPU ​

bash
top -H -p $(pidof ovs-vswitchd)

看线程名:

线程干什么CPU 高说明
handler*处理 upcall慢路径流量太多
revalidator*回收缓存、汇总统计缓存条数太多
pmd*用户态转发轮询正常(设计如此)
urcu*内存回收一般不高

handler 和 revalidator 同时高 = 典型的缓存爆炸。

端口级统计 ​

bash
ovs-ofctl dump-ports br0
text
port 1: rx pkts=1000, bytes=98000, drop=0, errs=0
        tx pkts=980, bytes=96040, drop=20, errs=0

drop 和 errs 非零要查:

  • rx drop:接收缓冲不够,或 CPU 处理不过来
  • tx drop:发送队列满,通常是限速或对端拥塞

也可以从数据库看,字段更全:

bash
ovs-vsctl --columns=name,statistics list Interface

里面有 upcall_packets 和 upcall_errors,能定位是哪个端口在制造 upcall。

coverage:内部计数器 ​

bash
ovs-appctl coverage/show

几百个计数器。常用的过滤:

bash
# upcall 相关
ovs-appctl coverage/show | grep -i upcall

# 缓存回收
ovs-appctl coverage/show | grep -i revalidate

# 流表操作
ovs-appctl coverage/show | grep -i flow_mod

输出形如:

text
revalidate_missing_dp_flow    0.0/sec  0.083/sec  0.0014/sec  total: 7

三个速率分别是最近 5 秒、最近 1 分钟、最近 1 小时的平均值。看趋势比看总量有用。

用户态 datapath 的指标 ​

跑 netdev datapath 时指标不同(实验 19):

bash
ovs-appctl dpif-netdev/pmd-stats-show
text
  packets received: 27
  avg. datapath passes per packet: 1.00
  emc hits: 0
  smc hits: 0
  megaflow hits: 16
指标关注点
emc hits 占比最关键。低说明流数超过了 EMC 容量(默认 8192)
avg. datapath passes大于 1 说明有 recirculation
megaflow hits落到这一级说明前面几级都没命中

也要看 PMD 是不是空转:

bash
ovs-appctl dpif-netdev/pmd-perf-show

基准测试怎么做 ​

带宽 ​

bash
# 服务端
iperf3 -s
# 客户端
iperf3 -c <对端> -t 30 -f m

多流测试更接近真实:

bash
iperf3 -c <对端> -P 8 -t 30

小包转发能力(pps) ​

带宽测不出 OVS 的真实瓶颈 —— 大包时瓶颈在网卡,小包时才在转发路径。

bash
iperf3 -c <对端> -u -b 0 -l 64 -t 30

或者用专业工具:pktgen、trex、moongen。

延迟 ​

bash
ping -c 100 -i 0.01 <对端>

关注 max 和 mdev,不是 avg。抖动大通常意味着慢路径参与得多。

测试前先预热

实验 1 证明过第一个包明显更慢。测试前先打一轮流量把缓存建起来,否则测的是慢路径。

一份排查流程 ​

text
症状:吞吐低 / 延迟抖动 / CPU 高
   │
   ▼
① ovs-dpctl show
   lost 非零?        → 队列溢出,看 ③
   missed 占比高?    → 缓存命中率差,看 ②
   masks hit/pkt 大? → 掩码太多,看 ②
   │
   ▼
② ovs-appctl dpctl/dump-flows -m | head
   看缓存里哪些字段是精确匹配的
   → 回到流表,找出是哪条规则要求匹配这些字段
   → 能不能写粗一点?能不能挪到独立的 table?
   │
   ▼
③ top -H -p $(pidof ovs-vswitchd)
   handler 高     → upcall 太多,回到 ②
   revalidator 高 → 缓存条数太多,回到 ②
   │
   ▼
④ ovs-ofctl dump-ports br0
   drop / errs 非零 → 队列或限速问题

几乎所有路径最后都指向"流表写得太细"。 这不是巧合 —— 它就是 OVS 性能问题的主要来源。

小结 ​

  • 四个核心指标:lost(必须为 0)、missed/hit 比例、flows 对 limit、masks hit/pkt(应接近 1)
  • 根因几乎总是流表太细 —— 匹配字段越多,megaflow 粒度越细,缓存条数和掩码种类越多
  • 用 dpctl/dump-flows -m 看缓存里哪些字段是精确匹配的,反推是哪条规则造成的
  • 优化优先级:能用 NORMAL 就别手写 > 匹配写粗 > 细规则关进独立 table > 慎用 CONTROLLER/learn > 减少 ct() 范围
  • 加 handler/revalidator 线程是缓解不是根治
  • top -H 看线程:handler 高 = upcall 多,revalidator 高 = 缓存多,两者都指向流表
  • 用户态 datapath 看 emc hits 占比
  • 测试前先预热,第一个包永远走慢路径
  • 小包 pps 才能测出转发瓶颈,大包测的是网卡

教程到这里就结束了 ​

从第一个网桥的三条命令,到 Overlay、有状态防火墙、控制器和性能调优 —— 十九个实验,每一条命令都在真实环境里跑过。

如果你把 verify.sh 全部跑绿过一遍,现在应该可以:

  • 看懂任意一台 OVS 交换机的配置,知道每个字段在哪张表
  • 用流表实现任意转发策略,而不只是靠 NORMAL
  • 搭出一个跨主机的多租户 Overlay 网络
  • 遇到"包不通"时有确定的排查顺序,而不是重启大法

接下来可以往这些方向走:

  • OVN —— 如果你要做多租户虚拟网络
  • OVS 官方文档 —— docs.openvswitch.org,ovs-fields(7) 和 ovs-actions(7) 这两个 man page 是流表语法的权威参考
  • 读源码 —— ofproto/ofproto-dpif-xlate.c 是流表翻译的核心,看懂它就真的懂 OVS 了