关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

DPDK 换来的不是吞吐:轮询模式把什么换掉了,又让你失去了什么

发布时间:2026-10-21

把 DPDK 说成"让网卡变快的库",是最常见、也最容易把人带偏的一句理解。网卡的物理端口速率不会因为你装了 DPDK 就提高一条,链路上能跑多少字节,依然是网卡能力、交换机端口和上游带宽共同决定的。DPDK 真正动的是另一件事:它把数据包从网卡到应用之间那条路径换掉了。原本由网卡中断触发、交给内核协议栈逐层处理的流程,改成了由用户态程序主动去轮询网卡的收发队列,直接在用户态把包取走。

这次替换带来的是三件好事:路径变短、每个包的固定开销下降、延迟尾部更可控。它同时带来一件必须接受的事——原先内核替你做掉的那些工作,现在要么你在应用层自己实现,要么你在架构上想办法绕开。所以内核旁路从来不是一次"性能优化",而是一次责任转移。判断要不要上,本质上是在回答一个问题:这些责任,你的团队和系统接不接得住。

先把"快"拆开:吞吐、每秒包数与延迟尾部不是一回事

讨论要不要上内核旁路之前,得先说清楚想解决的到底是哪种"慢"。很多人把性能问题笼统地说成"网络不够快",但这三个指标背后的瓶颈完全不同,解法也不一样。

吞吐关心的是每秒能搬多少字节。大包、长连接、批量传输的场景里,瓶颈多半落在链路带宽、DMA 效率、内存带宽和总线宽度上。这一层上,内核路径的每包开销被摊薄在大量字节里,占比很小,换掉内核路径能拿到的收益也就有限。

每秒包数关心的是每秒能处理多少个包,尤其是 64 字节小包。同样跑满一条链路,小包的数量要比大包多出一大截,而每个包都要走一遍完整的接收路径:中断、软中断、协议栈逐层解析、内存分配、拷贝到用户态。这部分开销和包里装了多少字节基本无关,是固定成本。包越小、速率越高,固定成本占总成本的比例就越高,CPU 就越早被打满。这就是为什么小包场景才是内核旁路的主战场。

延迟尾部关心的是最慢的那批请求。平均值看着正常,但总有一小部分请求的响应时间明显更长,这些长尾往往来自中断合并带来的等待、队列排队、调度延迟、锁竞争和缓存失效。内核路径里这些来源比较分散,也很难由应用控制;轮询模式下处理时机由应用自己决定,抖动来源少了很多,尾部更可控。需要注意的是,追求更低延迟和追求更高吞吐在某些配置上是互相拉扯的,比如中断合并这个机制本身就是用延迟换吞吐。

所以,如果一个业务的问题出在字节吞吐上,内核旁路多半不是答案;如果问题出在小包速率或延迟尾部上,才值得往下谈。这个拆分不做,后面所有的讨论都会跑偏。

中断模式在小包场景下把 CPU 花在哪

要看懂轮询省掉了什么,得先看中断模式下那些开销具体长什么样。一个包从网卡到达应用,在传统的内核路径上大致要付这几笔账。

第一笔是硬件中断。包到达网卡后触发中断请求,CPU 必须停下手上的活,保存当前执行现场,跳转到中断处理函数。中断的优先级高于普通线程,它会直接抢占正在跑的业务代码。包速率低的时候这不是问题,包速率一上来,中断频率跟着上升,业务线程被频繁打断,真正用于计算的时间被切碎,缓存和流水线也反复失效。

第二笔是软中断。中断处理函数被刻意做得极短,把绝大部分工作推到软中断里完成,收包对应的就是 NET_RX 这一类软中断。软中断仍然要占用 CPU,而且在高包速率下可能长时间占据一个核,让这个核上的业务线程迟迟得不到执行机会,严重时会出现"收包把 CPU 占满、应用反而没有进度"的活锁现象。系统把软中断转移到专门的线程上处理之后,又会引入一轮调度开销。

第三笔是上下文切换与特权态切换。中断与软中断都涉及内核态与用户态之间的来回,数据最终还要从内核缓冲区拷贝到用户缓冲区。每次切换都要保存和恢复寄存器状态,可能触发页表切换,带来 TLB 与缓存的污染。这些开销单看都很小,但乘以每秒的包数之后就很可观。

第四笔是每包的内核路径处理。一个包要依次经过驱动层、链路层、网络层的路由查找、传输层的协议处理、socket 层,才最终交给应用。路径上还挂着 netfilter 的钩子点和连接跟踪表查询,防火墙规则越多、连接状态越复杂,这段就越长。这部分是内核提供的能力,也是它的成本。

第五笔是每包内存分配。内核为收到的包分配 sk_buff 结构体,处理完再释放。在多核并发、高并发包速率的环境下, slab 分配器的竞争和缓存行失效会成为实实在在的开销。再加上拷贝、校验和计算、分片重组等动作,一个包所消耗的资源远不止"把它搬到内存里"这么简单。

这五笔账有个共同特征:它们和包的大小基本无关,只和包的数量有关。这就是为什么在 64 字节小包占主导的流量里,CPU 会比带宽更早到达上限。

轮询模式省掉的是哪几笔开销

DPDK 的用户态轮询模式驱动做了一件很直接的事:让网卡把包通过 DMA 直接放进预先分配好的用户态内存池,然后由应用线程主动、持续地去检查队列描述符,有包就取走处理,没包就继续检查。整个过程中,没有中断,没有进入内核,也没有那一次从内核缓冲区到用户缓冲区的拷贝。

具体来说,省掉的第一笔是中断本身,包括硬件中断的抢占和软中断的排队。这意味着不会出现中断风暴,也不会因为收包抢占而导致业务线程饿死。第二笔是内核态与用户态之间的切换和数据拷贝,包从网卡 DMA 进来的位置就在用户态可直接访问的内存里,应用就地处理。第三笔是每包内存分配,缓冲区来自启动时一次性分配好的内存池,用 mbuf 这样的固定结构来表示,处理完归还池子,不再走通用分配器,也就没有对应的锁竞争与缓存颠簸。

省掉的第四笔藏在细节里:批处理与缓存局部性。轮询不是一次取一个包,而是一次取回一批描述符,批量处理可以摊销循环与函数调用的成本,也能让数据预取更有效、缓存命中率更高。再配合大页配置,减少地址转换时的 TLB 缺失——用户态网络程序访问的内存区域通常很大且访问模式偏随机,这一点对性能的现实意义不小,所以大页常被列为配置前提而不是可选项。

延迟方面,因为不再依赖中断通知,也就不再受中断合并窗口的影响,处理时机由应用决定,队列排队和调度等待都大幅减少,延迟尾部因此更稳定。这也是很多对延迟有硬性要求的业务选择这条路的原因。

但必须把边界说清楚:轮询省的是每包的固定开销,不是每字节的开销。一个 1500 字节的包经过内核路径,固定开销被摊到更多字节上,占比很小;换成轮询能拿到的绝对收益自然也小。当瓶颈是 DMA 带宽、内存带宽或链路带宽时,换掉内核路径解决不了问题。另外,轮询需要网卡有对应的 PMD 支持,网卡型号是否在支持范围内,是动手前第一件要确认的事。

代价一:独占核与 CPU 占用接近满格是设计使然

轮询的本质是忙等:一个核上跑着一个循环,不断检查队列描述符,没包也检查。结果就是这个核的 CPU 使用率长期接近满格。这不是配置错误,也不是程序死循环,而是轮询模式的设计预期。理解了这一点,才能理解它带来的一连串连锁反应。

第一个反应在监控上。CPU 使用率这个指标对轮询核失去了意义——它永远是满的,无法反映负载高低,也就不能再作为容量告警的判据。你需要换成别的指标:队列有没有积压、网卡的丢包与错包计数有没有增长、每个核的处理量是多少、延迟分布是否变化、内存池的余量是否充足。整套观测口径都要重建,否则上线之后你会发现自己看不出系统是不是快撑不住了。

第二个反应在核的分配上。轮询核必须独占,不能被调度器安排别的线程上去,否则一次抢占就可能造成一批包的延迟尖刺。常见做法是在启动阶段就把这些核从调度域里隔离出来,再把轮询线程绑定到上面。核数因此成为一项硬性资源:要用几个核轮询,就得实打实地留几个核出来,业务线程、管理线程、日志采集、监控代理都要为它们让路并且排开。

第三个反应在 NUMA 上。双路服务器有两颗物理 CPU,也对应两个 NUMA 节点,网卡插在哪个 PCIe 槽位,就归属于哪个节点。轮询核应当绑在与网卡相同的 NUMA 节点上,内存池也应当在同一节点上分配。跨节点访问内存和设备寄存器会引入额外延迟并占用节点间互联带宽,而这类损耗在小包高频访问的场景下会被放大。很多"上了 DPDK 但没达到预期"的情况,根源就在这里:核绑错了节点,或者内存池从另一个节点上分配了。

第四个反应在成本上。中断模式下,机器空闲时收包核的占用可以很低;轮询模式下,即使一个包都不来,那几个核依然满负荷运转。也就是说,你用固定的 CPU 资源换来了高负载时的处理能力,代价是低负载时也照付。如果业务的流量峰谷差距很大、低谷时间很长,这笔账就要认真算一遍。这一层的费用判断,需要结合机型、核数与带宽形态来看,价格主要受 CPU、内存、存储、带宽、IP 与线路形态影响,具体以官网实时信息或咨询确认为准。

代价二:内核协议栈的能力不再自动拥有

这是最容易被低估的一项代价。绕过内核意味着内核协议栈提供的那整套能力,不再自动生效。它们不会报错,也不会提醒你,只是静悄悄地不起作用了。

最明显的是 TCP 协议栈本身。连接的建立与关闭、超时重传、拥塞控制、滑动窗口、乱序重组、保活探测,这些原本由内核完成的事情,现在要么由应用自己实现,要么引入用户态协议栈来承担。对一个只需要做无状态转发的业务来说,这不算负担;但对需要完整连接语义的业务来说,这是一整个子系统的工作量。

其次是安全与策略能力。连接跟踪、iptables 与 nftables 的规则、内核队列规则与限流,都工作在内核路径上。流量一旦被旁路,这些规则就管不到它了。如果你所在的环境里,访问控制策略是建立在 iptables 之上的,那么旁路过来的这部分流量实际上是"裸奔"的,需要在边界处重新建立等效的策略能力。

再次是接口与工具链。现有基于 socket 编写的程序没法直接跑在旁路上,需要改用 DPDK 提供的接口重写数据面,这是应用侧的改造成本。排障层面变化更大:常用的抓包工具抓不到旁路后的流量,或者抓到的位置与原先的语义不同;ss、netstat 这类看连接统计的命令不再反映真实情况;网卡统计也可能只反映入口而不反映应用实际处理了多少。运维团队熟悉的那套定位手法需要推倒重建,这个隐性成本经常被漏算。

实务上,绝大多数部署并不会把所有流量都旁路掉。更常见的是分层处理:管理面、控制面、低速率的辅助流量继续走内核,只有高包速率的数据面流量走旁路,在边界设备上做流分类,把需要内核处理的流回注到内核路径。这样既能拿到小包场景的收益,又不必把整套安全与运维体系推翻。需要注意的是,"旁路了所以更安全"这种说法是不成立的,恰恰相反,安全能力需要重新建设,在本来由内核承担的位置上换成人去承担。

代价三:多队列、RSS 与 NUMA 的绑定关系要自己算

内核模式下,多队列网卡的队列分配、中断亲和、负载均衡,内核已经有一套默认机制在管。旁路之后,这些关系全部要由你自己规划。

起点是多队列。一张网卡提供多个收发队列,每个队列可以由一个独立的核去轮询,这是多核并行收包的前提。队列数量决定了并行度的上限:队列少于轮询核,多出来的核无事可做;队列多于轮询核,则有的队列没人及时处理,包会在队列里堆积甚至被丢弃。

接下来的关键是 RSS。网卡按照哈希把不同的流分发到不同的队列,分发的目标不只是负载均衡——更重要的是同一条流的所有包必须落到同一个队列。如果同一条流的包被拆到多个队列、由多个核并行处理,包与包之间的顺序就无法保证,接收端会看到乱序,传输层可能把它误判为丢包并触发重传,性能反而下降。哈希字段的选择(用完整五元组还是只用部分字段)会直接影响分流的均匀程度和对称性,双向流量是否落在同一个队列,也是需要确认的能力之一。对流特征明确的业务,还可以使用流分类把特定流定向到指定队列。

再往下是核与 NUMA 的对应。轮询核要与网卡所在的 NUMA 节点对齐,mbuf 内存池要在同一节点上分配,业务处理线程如果也要频繁访问这些报文,最好也在同一节点上。机器上有多张网卡时,每张卡的 NUMA 归属可能不同,规划时要逐个确认而不是想当然。至于是否启用超线程,需要谨慎:同一物理核上的两个逻辑核共享执行资源,一个跑忙轮询、另一个跑别的任务,会互相干扰,通常建议把轮询核安排在相互干扰最小的位置上。

还有一类需要做取舍的场景是混用:一部分队列交给内核,一部分交给 DPDK。这样可以在同一张网卡上同时保留内核路径与旁路路径,代价是配置更复杂,RSS 的哈希规则要精确地把流量分开,且要清楚哪些流走哪边,否则排查时会非常混乱。至于队列数、核数、NUMA 节点三者之间的对应关系,建议按"先定网卡 NUMA 归属,再定可用核,最后定队列与哈希策略"的顺序推一遍,而不是反过来。

什么业务才值得上内核旁路

判断的核心只有两条轴:一是流量里 64 字节小包的每秒包数是不是真正的瓶颈,二是业务对延迟尾部有没有硬性要求。两条里至少占一条,才值得认真评估;两条都不占,收益通常覆盖不了代价。

典型适合的形态包括:四层负载均衡与转发网关、DNS 这类以短小查询为主的服务、实时音视频的媒体转发、报文采集与流量镜像、软交换一类的转发平面。它们的共同点是包小、数量大、单包处理逻辑简单、对连接状态依赖弱——正好契合轮询模式省掉每包固定开销这个特长。以下为典型部署思路,并非特指某一真实客户。

典型不适合的形态包括:以大文件传输和批量同步为主的业务、依赖完整 TCP 语义与复杂连接状态的业务、安全策略强绑定内核防火墙框架的业务、以及团队不具备用户态网络维护能力的环境。这些场景下,瓶颈往往在带宽、磁盘、后端处理或者业务逻辑本身,换掉内核路径既解决不了问题,还会把运维复杂度抬上去。

中间地带其实很大。很多"网络性能不够"的问题,在内核侧就能缓解:启用并正确配置多队列、调整中断亲和性、开启网卡的卸载特性、调整环形缓冲区大小、合理设置中断合并参数、让收发包处理分散到更多核上。这些手段的改造成本远低于内核旁路,却常常能解决相当一部分问题。建议把它们作为前置步骤走一遍,确认瓶颈确实落在每包开销上,再考虑动内核路径。

场景 流量特征与包速率关注点 是否值得上内核旁路 更该优先做的事
四层负载均衡与转发网关 小包占比高,关注每秒包数与转发路径长度 值得,旁路收益通常最直接 先确认网卡多队列与 RSS 能力,规划轮询核与 NUMA 归属
DNS 与短查询类服务 报文极小、查询次数密集,关注突发包速率 值得,但需评估是否可用内核侧调优替代 先做多队列与中断亲和调整,再评估旁路范围
实时音视频媒体转发 包小且持续,关注延迟尾部与抖动 值得,尾部收益往往比吞吐收益更有价值 先量化抖动来源,确认是否来自收包路径
报文采集与流量镜像 包速率高、处理逻辑简单、通常无连接状态 值得,且改造面相对可控 确认采集口 NUMA 归属与内存池容量,避免丢包
大文件下载与批量同步 大包为主,瓶颈在字节吞吐与磁盘 IO 不值得,收益有限 排查带宽、磁盘 IO、卸载特性与发送缓冲区配置
长连接 API 网关与鉴权服务 连接状态复杂,需要完整传输层语义 一般不值得,协议栈自建成本过高 优化业务处理耗时与连接复用,必要时做水平扩容
数据库与缓存服务 请求包不大但整体瓶颈常在存储与锁竞争 一般不值得 优先排查慢查询、锁、内存与持久化策略
一般 Web 与应用服务器 混合流量,峰值多来自业务逻辑而非收包路径 不值得,除非有明确的小包瓶颈证据 做应用层性能剖析与水平扩容,内核侧参数调优

一万网络深耕 IDC 行业 19 年(成立于 2007 年),在华南、华东、华北及中国香港、海外多地整合自营节点与 BGP 多线资源;高包速率业务的网卡型号与核绑定方式,建议结合自有流量模型向服务商确认后再定。高包速率业务的网卡型号与核绑定方式,建议结合自有流量模型向服务商确认后再定。

上之前先做的四项验证

决定要动之前,有四项验证值得按顺序做完。它们的作用不是证明方案可行,而是把"到底该不该做"这个问题回答清楚,同时把上线后的风险面收敛到可控范围。

第一项:确认瓶颈确实在收包路径上。先看清系统当前的开销分布:软中断的收包计数集中在哪几个核上、网卡有没有丢包和错包计数增长、中断在各核之间是否分布不均、应用线程是否被抢占。只有当证据指向"每个包的固定开销吃掉了 CPU",内核旁路才是合理的下一步。如果证据指向的是业务处理耗时、磁盘等待或者后端依赖,那么动了也是白动。

第二项:确认网卡与驱动的适配性。网卡型号是否有对应的用户态轮询驱动支持,能开多少个收发队列,RSS 支持哪些哈希字段与对称哈希,固件版本是否需要升级,原先依赖的卸载特性在旁路后是否仍然可用。这一项不通过,后面都不必谈。同一张卡在不同固件版本下的行为也可能不一样,需要按实际版本核对。

第三项:把核与内存的账算清楚。先看清机器的 CPU 拓扑和 NUMA 结构,确认网卡挂在哪个节点上,再规划轮询核、业务核、管理核分别用哪些核,彼此不重叠、不跨节点访问。预留余量,不要把所有核都排满,否则一旦有突发流量就没有缓冲。同时把大页配置好,内存池按峰值包速率估算容量,避免池子耗尽导致的丢包。

第四项:评估应用改造与回退路径。明确传输层语义由谁承担、安全策略如何在边界重建、监控指标换成哪些、抓包与排障怎么做、哪些流量必须回注内核。改造不要一次铺开:先选一条非关键、可隔离的业务流做灰度,保留内核路径作为随时可切的回退方案,确认延迟、丢包计数、资源占用都符合预期之后,再逐步扩大范围。回退方案不是备选,是必须项。

关于 DPDK,几个常被问错的问题

装了 DPDK,网卡会变快吗?不会。端口速率是硬件属性,不会因为换了驱动就提高。变的是包从网卡到应用之间的路径长度和每包开销,所以收益体现在小包处理能力和延迟稳定性上,而不是"带宽变大了"。

轮询核的 CPU 长期接近满格,是不是出问题了?不是。忙轮询就是要不停地检查队列,这是设计预期。真正该看的指标是队列是否积压、有没有丢包、延迟分布是否变化。如果把这个核的使用率当成告警条件,告警会一直响。

旁路之后,原来的防火墙规则还管得住吗?管不住。被旁路的流量不经过内核的钩子点,连接跟踪和 iptables、nftables 的规则对它无效。要么在边界设备或应用侧重建等效策略,要么在边界就把这部分流量回注内核再处理。这一点在安全要求较高的环境里必须事先说清楚。

是不是所有流量都要走 DPDK?不需要,通常也不应该。更常见的是只把高包速率的数据面流量旁路,管理面、控制面和辅助流量继续走内核。这样既拿到收益,又保留了内核的协议栈、策略和运维工具链。

配好大页就可以了吗?大页只是前提之一。它缓解的是地址转换开销,但队列与核的对应关系、NUMA 归属、RSS 哈希策略、内存池容量、批处理大小,每一项都会影响最终结果。只做其中一项通常看不到预期效果。

上了 DPDK 就不用管内核参数了吗?仍然要管。非旁路的流量还在内核路径上,中断亲和、环形缓冲区、卸载特性、连接跟踪表大小这些参数依然影响那部分流量的表现。旁路只是接管了其中一段。

包速率没达到预期,是不是方案不行?更常见的原因是配置层面的:队列数与核数不匹配、核绑到了另一个 NUMA 节点、内存池从远端节点分配、同一条流被拆到多个队列造成乱序、或者批处理与burst 大小设置不合理。这些都属于规划问题,而不是方案本身的问题。

决定放弃内核旁路的几条判断线

有明确的入口条件,就该有明确的退出条件。出现下面这些情况时,继续推进的代价通常大于收益,及时收手是更专业的做法。

第一条:瓶颈在字节吞吐而不在包速率。链路带宽、DMA 或内存带宽先到上限的场景里,换掉内核路径拿不到多少收益,却要付全套改造成本。

第二条:业务需要完整的传输层语义与复杂连接状态,而团队不打算自建用户态协议栈。这种情况下改造量会迅速膨胀到一个独立子系统的规模,风险不可控。

第三条:安全策略强依赖内核的连接跟踪与防火墙框架,且无法在边界重建等效能力。绕过内核之后的策略空白,是比性能问题严重得多的问题。

第四条:核资源紧张,无法为轮询腾出独占核,或者无法做到轮询核与网卡的 NUMA 对齐。核数与拓扑对不上时,强行上线的结果往往是性能不如预期且难以诊断。

第五条:运维与排障体系无法同步重建。抓包看不到、连接统计失真、告警指标失效,会让日常运维进入到一种"系统跑着但看不见"的状态,这在小团队里尤其危险。

第六条:流量峰谷差距大、低谷时间占比高。忙轮询的固定成本在低负载时段照付不误,如果大部分时间机器都在低负载区间,这笔账算不过来。

第七条:内核侧调优的收益已经接近旁路方案的收益。既然成本更低的手段能达到相近效果,就不必再往上加复杂度。

放弃不等于判断失误。更实际的做法是把方案降级而不是推翻:把旁路范围缩小到一条具体的业务流、只在边界节点上旁路、或者保留双路径随时可切。这样既留下了收益的可能性,也留下了退路。内核旁路是一项很强的能力,但它换来的从来不是吞吐本身,而是每包开销与延迟可控性——代价是核、协议栈能力和运维体系。把这个交换关系想清楚,比急着动手更重要。


上一篇:OAuth 2.0、OIDC 和 SAML 到底分工在哪:按客户端形态选,而不是按"哪个更安全"

下一篇:毛里求斯节点的海缆什么时候会变:SAFE 退役窗口、T3 接替与 T4 的时间账