主题
性能观测与调优
OVS 的性能问题几乎都能归结到一句话:
慢路径处理了太多包,或者内核缓存的粒度太细。
这一章讲怎么测出来、怎么定位、怎么改。
先看四个数
bash
ovs-dpctl showtext
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.lost | upcall 队列溢出丢掉的包 | 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/showtext
system@ovs-system:
flows : (current 0) (avg 0) (max 0) (limit 10000)current 逼近 limit 时,OVS 会开始激进回收缓存,导致命中率进一步下降 —— 恶性循环。
limit 是动态调整的。看它被调整过多少次:
bash
ovs-appctl coverage/show | grep upcall_flow_limittext
upcall_flow_limit_grew total: 1
upcall_flow_limit_shrunk total: 0shrunk 持续增长说明 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 br0text
port 1: rx pkts=1000, bytes=98000, drop=0, errs=0
tx pkts=980, bytes=96040, drop=20, errs=0drop 和 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-showtext
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 了