4、中断优化:中断合并、亲和性与CPU隔离
中断,说白了就是硬件在喊“CPU,快来看我!”
但问题是——如果每来一个包就喊一次,CPU光响应中断就忙不过来了。我见过不少系统,吞吐量上不去,一查,CPU全在跑中断处理程序,业务逻辑反而没时间跑。这就是典型的“中断风暴”。
4.1 中断合并:让CPU少跑几趟
中断合并(Coalescing)的思路很简单:别来一个包就中断一次,攒一批再通知CPU。这样CPU每次处理能批量干活,效率自然高。
我个人习惯用 ethtool 来查看和调整合并参数:
# 查看当前中断合并设置
ethtool -c eth0
# 设置合并参数(示例)
ethtool -C eth0 rx-usecs 50 rx-frames 8
这里 rx-usecs 是延迟多少微秒再中断,rx-frames 是攒够多少个帧再中断。两个参数配合使用,效果更好。
我在项目中遇到过一个问题:某台机器网卡合并设了100微秒,结果业务延迟从1ms飙到了5ms。后来发现是合并值太大,包在网卡里排队等中断。调回20微秒,延迟立刻降下来。
4.2 中断亲和性:把中断绑定到指定CPU
默认情况下,中断可以在任意CPU上处理。这会导致一个问题:CPU缓存频繁失效。因为中断在不同CPU间跳动,缓存里的数据得反复加载。
中断亲和性(IRQ Affinity)就是把某个中断固定到某个或某几个CPU上。这样缓存命中率提高,性能自然提升。
具体做法:
# 查看中断号(以eth0为例)
cat /proc/interrupts | grep eth0
# 假设中断号是 78,把它绑定到 CPU0 和 CPU1
echo "3" > /proc/irq/78/smp_affinity
这里的“3”是二进制掩码:0b11 表示 CPU0 和 CPU1。如果是四核CPU,想绑定到 CPU2 和 CPU3,掩码就是 0b1100,即十进制12。
我曾经踩过一个坑:把两个网卡的中断都绑到了CPU0,结果CPU0负载100%,CPU1-3几乎空闲。后来改成每个网卡绑不同的CPU,吞吐量直接翻倍。
4.3 CPU隔离:给关键任务留出专用CPU
CPU隔离(Isolation)就是把某些CPU从系统的调度中“摘”出来,不让普通进程和中断打扰它们。这些CPU专门跑关键业务。
Linux内核启动参数里可以设置:
isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3
这个配置把 CPU2 和 CPU3 隔离出来。普通进程不会跑到这两个CPU上,中断也不会打到它们。
但光隔离还不够。你得手动把关键进程绑定到这些CPU上:
# 用 taskset 绑定进程到 CPU2
taskset -c 2 ./my_critical_app
我记得有一次帮客户优化数据库,他们总是抱怨延迟抖动大。一查,系统里各种后台任务在抢CPU。用了CPU隔离后,数据库的P99延迟从50ms降到了2ms。效果立竿见影。
4.4 三者如何配合?
中断合并、亲和性、CPU隔离,这三者不是孤立的。它们需要配合使用:
- 中断合并:控制中断频率,减少CPU打扰
- 中断亲和性:把中断固定到特定CPU,提高缓存效率
- CPU隔离:给关键任务留出专用CPU,避免争抢
我一般这样搭配:
- 先确定哪些CPU跑业务,哪些CPU处理中断
- 把网卡中断绑到处理中断的CPU上
- 隔离业务CPU,不让中断打扰
- 调整中断合并参数,找到延迟和吞吐的平衡点
下面这张图展示了整个流程:
嗯,中断优化这块,说白了就是让CPU少被打扰、被谁打扰、在哪被打扰。三个维度都照顾到了,延迟和吞吐才能两全。
公众号:蓝海资料掘金营,微信deep3321