另外有个小问题需要注意,很多发行版可能会自带一个 irqbalance 的守护进程(),会将手动中断均衡的设置给覆盖掉。这个程序做的核心事情就是把上面手动设置文件的操作放到程序里,有兴趣可以去看下它的代码(https://github.com/Irqbalance/irqbalance/blob/master/activate.c),也是把这个文件打开,写对应的数字进去就可以了。
内核-数据处理
最后是数据处理部分了。当数据到达网卡,进入队列内存后,就需要内核从队列内存中将数据拉出来。如果机器的 PPS 达到了十万甚至百万,而 CPU 只处理网络数据的话,那其他基本的业务逻辑也就不用干了,因此不能让数据包的处理独占整个 CPU,而核心点是怎么去做限制。
针对上述问题主要有两方面的限制:整体的限制和单次的限制
while (!list_empty(&sd->poll_list)){ struct napi_struct *n; int work,weight; /* If softirq window is exhausted then punt. * Allow this to run for 2 jiffies since which will allow * an average latency of 1.5/HZ. */ if (unlikely(budget <= 0 || time_after_eq(jiffies, time_limit))) goto softnet_break;整体限制很好理解,就是一个 CPU 对应一个队列。如果 CPU 的数量比队列数量少,那么一个 CPU 可能需要处理多个队列。
weight = n->weight; work = 0; if (test_bit(NAPI_STATE_SCHED, &n->state)) { work = n->poll(n,weight); trace_napi_poll(n); } WARN_ON_ONCE(work > weight); budget -= work;单次限制则是限制一个队列在一轮里处理包的数量。达到限制之后就停下来,等待下一轮的处理。
softnet_break: sd->time_squeeze++; _raise_softirq_irqoff(NET_RX_SOFTIRQ); goto out;而停下来就是很关键的节点,幸运的是有对应的指标记录,有 time-squeeze 这样中断的计数,拿到这个信息就可以判断出机器的网络处理是否有瓶颈,被迫中断的频率高低。
上图是监控 CPU 指标的数据,格式很简单,每行对应一个 CPU,数值之间用空格分割,输出格式为 16 进制。那么每一列数值又代表什么呢?很不幸,这个没有文档,只能通过检查使用的内核版本,然后去看对应的代码。
seq_printf(seq, "%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", sd->processed, sd->dropped, sd->time_squeeze, 0, 0, 0, 0, 0, /* was fastroute */ sd->cpu_collision, sd->received_rps, flow_limit_count);下面说明了文件中每个字段都是怎么来的,实际情况可能会有所不同,因为随着内核版本的迭代,字段的数量以及字段的顺序都有可能发生变化,其中与网络数据处理被中断次数相关的就是 squeeze 字段:
sd->processed 处理的包数量(多网卡 bond 模式可能多于实际的收包数量)
sd->dropped 丢包数量,因为队列满了
sd->time_spueeze 软中断处理 net_rx_action 被迫打断的次数
sd->cpu_collision 发送数据时获取设备锁冲突,比如多个 CPU 同时发送数据
sd->received_rps 当前 CPU 被唤醒的次数(通过处理器间中断)
sd->flow_limit_count 触发 flow limit 的次数
下图是业务中遇到相关问题的案例,最后排查到 CPU 层面。图一是 TOP 命令的输出,显示了每个 CPU 的使用量,其中红框标出的 CPU4 的使用率存在着异常,尤其是倒数第二列的 SI 占用达到了 89%。SI 是 softirq 的缩写,表示 CPU 花在软中断处理上的时间占比,而图中 CPU4 在时间占比上明显过高。图二则是对应图一的输出结果,CPU4 对应的是第五行,其中第三列数值明显高于其他 CPU,表明它在处理网络数据的时被频繁的打断。
针对上面的问题推断 CPU4 存在一定的性能衰退,也许是质量不过关或其他的原因。为了验证是否是性能衰退,写了一个简单的 python 脚本,一个一直去累加的死循环。每次运行时,把这段脚本绑定到某个 CPU 上,然后观察不同 CPU 耗时的对比。最后对比结果也显示 CPU4 的耗时比其他的 CPU 高了几倍,也验证了之前的推断。之后协调运维更换了 CPU,意向指标也就恢复正常了。
总结以上所有操作都只是在数据包从网卡到了内核层,还没到常见的协议,只是完成了万里长征第一步,后面还有一系列的步骤,例如数据包的压缩(GRO)、网卡多队列软件(RPS)还有 RFS 在负载均衡的基础上考虑流的特征,就是 IP 端口四元组的特征,最后才是把数据递交到 IP 层,以及到熟悉的 TCP 层。