8. DPDK:多队列与流分类

1 为什么单个CPU核处理不过来了?

想象一下,你让一个单线程CPU去处理每秒 10Gbps 的网络流量------ 这就像让一个人单手接住从天上掉下的一万颗玻璃珠。

结果显而易见:CPU中断风暴、缓存失效、丢包频发。

于是,多核CPU的崛起带来了新的思路: "如果网卡能把不同的流分给不同的CPU核来处理,就能实现并行处理。"

这就是 网卡多队列技术(Multi-Queue NIC) 的诞生。


1.1 硬件视角:Intel 82580 的多队列架构

每个队列(Queue) 就像是一条独立的"数据高速公路", 拥有自己的DMA通道、描述符环(Descriptor Ring)和中断号

当数据包到达时,网卡会根据一定的规则(哈希或流分类),决定它应该进入哪个接收队列(Rx Queue)。

这些队列随后可以绑定到不同CPU核上实现并行处理。

💡 类比理解:

如果说传统单队列网卡是一条高速路所有车辆都挤在一个收费口, 那么多队列网卡则是"多收费口并行放行",极大缓解了瓶颈。


1.2 Linux 内核对多队列的利用

Linux 内核为多队列网卡提供了完善的支持,包括:

接收侧(Rx)
  • 每个接收队列可对应独立中断,通过 IRQ Affinity 绑定到特定核。
  • 可使用 RPS(Receive Packet Steering) 模拟硬件多队列效果,将不同流的软中断分散到多核。
发送侧(Tx)
  • 使用 XPS(Transmit Packet Steering) 机制,为每个CPU指定发送队列。
  • 减少锁竞争,提升cache命中率。

代码片段:

ini 复制代码
int dev_queue_xmit(struct sk_buff *skb) {
    struct net_device *dev = skb->dev;
    txq = dev_pick_tx(dev, skb); // 选择合适的发送队列
    spin_lock_prefetch(&txq->lock);
    ...
}

Linux的策略很灵活,但仍有锁与上下文切换开销


1.3 DPDK的革命性改进:核-队列一对一绑定

DPDK抛弃内核协议栈后,采用用户态直接访问网卡队列的模型:

  • 每个核(lcore)只负责一个接收队列 + 一个发送队列。
  • 收发包都在该核上完成,无需锁。
  • 数据缓存在本地HugePage内存池中,cache命中率极高。

代码(摘自l3fwd):

ini 复制代码
ret = rte_eth_dev_configure(portid, nb_rx_queue, nb_tx_queue, &port_conf);
ret = rte_eth_tx_queue_setup(portid, queueid, nb_txd, socketid, txconf);
qconf->tx_queue_id[portid] = queueid;
...
rte_eth_tx_burst(port, queueid, m_table, n);

小结:DPDK 的多队列模型 = 零锁 + 零拷贝 + 高亲和。


1.4 队列分配策略:RSS 与 Flow Director

网卡分流的核心是Queue Select逻辑:

  1. RSS(Receive Side Scaling) :基于哈希(四元组)平均分配流量,负载均衡。
  2. Flow Director(Intel FD) :基于流表匹配,将特定流导向特定队列(如特定核或VM)。

现代网卡(如 Intel XL710)同时支持多种策略,可实现更精细的流量控制。

2. 为什么网卡要学会"分类"?

在传统的单队列网卡中,所有流量都混在一条流水线中。

这就像所有车辆都挤在一个收费站口------无论是紧急车辆还是普通车辆,都得排队。

而现代的智能网卡(SmartNIC)则能在硬件层面进行"流分类(Flow Classification)":

它能识别出不同类型的流量,并将它们导向不同的处理队列。

这就是今天要讲的主角 ------ 流分类(Flow Classification)


2.1 网卡眼中的"包类型":识别是第一步

要做分类,首先得看得懂包

以 Intel XL710 为例,它能直接识别出以下包类型:

层级 示例
L2 以太网、VLAN
L3 IPv4、IPv6
L4 TCP、UDP
隧道 VXLAN、NVGRE

网卡会把这些信息写进接收描述符(Rx Descriptor),DPDK应用程序就能直接从 rte_mbuf.packet_type 字段中读取到:

arduino 复制代码
struct rte_mbuf {
    union {
        uint32_t packet_type; /**< L2/L3/L4/tunnel 信息 */
        struct {
            uint32_t l2_type:4;
            uint32_t l3_type:4;
            uint32_t l4_type:4;
            uint32_t tun_type:4;
            ...
        };
    };
};

这意味着:

  • 应用层无需再自己解析包头
  • 可以直接判断包类型,从而决定处理逻辑;
  • 还能结合流分类机制,决定该包属于哪个队列。

2.2 RSS:最经典的负载均衡算法

RSS(Receive-Side Scaling) 是最常见、最基础的流分类技术。

它的核心思想非常简单:

"用哈希算法把流均匀分配到不同的队列。"

具体过程如下:

  1. 从包中抽取关键字(Key),如 源IP、目的IP、源端口、目的端口
  2. 用哈希函数(通常是 Toeplitz Hash)计算出哈希值;
  3. 用哈希值映射到某个队列编号;
  4. 数据包DMA到对应的接收队列。

以IPv4 UDP 为例,关键字是四元组:

ini 复制代码
Key = SrcIP + DstIP + SrcPort + DstPort

Intel XL710 网卡支持多种哈希算法(包括对称哈希),这样双向流(A→B 与 B→A)可以被分配到同一个队列上,非常适合防火墙、负载均衡等场景。

🧠 类比:RSS 就像一台"高速公路匝道分流器",

它不会管车是什么类型,只负责"均匀分车道"。

DPDK中可通过 rte_eth_dev_configure()rte_eth_dev_rss_hash_update() 等API配置RSS。


2.3 Flow Director:让网卡"定向分流"

Flow Director(FDIR) 是RSS的"进阶版"。

RSS是哈希随机分流 ,而Flow Director是规则匹配分流

其原理:

  • 网卡中维护一张 Flow Table
  • 驱动或应用可以动态添加规则;
  • 当网卡收到数据包时,会根据关键字段匹配表项;
  • 匹配成功后执行对应动作(如:导入特定队列、丢弃)。

示意:

css 复制代码
[Packet] --> [Flow Director Table] --> [Action: Queue 3 / Drop / Forward]

例如,你可以设置:

所有 S-IP=10.0.0.1, D-Port=80 的TCP包 → 队列1

所有 S-IP=10.0.0.2, D-Port=443 的TCP包 → 队列2

这样,特定业务流量(如HTTP、HTTPS)可以绑定到不同核处理,提高cache命中率、减少锁争用。

RSS vs Flow Director 对比表

特性 RSS 流量导向器
分流规则 哈希(平均分配) 精确匹配
动态可配置
典型场景 负载均衡 精确流控、QoS、NFV
实现复杂度 高(需硬件支持)

Flow Director 的哲学: "不平均,但精确。"


2.4 QoS:网卡级的服务质量调度

在大型数据中心或NFV环境中,不同业务流量有不同的优先级,QoS(Quality of Service)就是要在网卡层面实现优先级调度

Intel 82599 网卡支持 DCB(Data Center Bridging)模型:

发送方向(Tx)
  • 通过 VLAN Tag 中的 UP(User Priority)字段 确定业务类型;
  • 不同 UP → 不同 TC(Traffic Class);
  • 每个 TC 拥有独立队列和 buffer;
  • 网卡使用 加权严格优先级(WSP) 调度算法决定先发哪个队列。
接收方向(Rx)
  • 根据 UP 决定TC;
  • TC 内部再通过RSS或Flow Director细分。

类比:QoS 就像机场的登机口分区,

头等舱、商务舱、经济舱虽然都上飞机,但排队顺序不一样。


2.5 虚拟化场景下的多队列调度

在SR-IOV或VMDQ场景中,每个虚拟机(VF)或虚拟池(POOL) 都对应一组独立的硬件队列。

例如:

javascript 复制代码
PF(物理功能)
 ├── VF0 → Queue Set 0
 ├── VF1 → Queue Set 1
 └── VF2 → Queue Set 2

这使得虚拟机能直接接收属于自己的流量,避免VM之间的干扰。

DPDK中可通过 rte_eth_dev_set_vf_rx_queue_assignment() 等API管理VF队列。


2.6 流过滤(Flow Filtering):最后一道防线

在所有分类动作之前,网卡会先进行合法性过滤:

  1. MAC 地址过滤(L2Filter)
  2. VLAN 标签过滤
  3. 管理控制帧过滤(如ARP、LLDP)

Intel 网卡还提供:

  • Src MAC/VLAN Antispoofing(防欺骗)
  • N-Tuple Filter(自定义五元组匹配)
  • Cloud Filter(VXLAN/NVGRE等隧道过滤)

总结:过滤 ≈ "保安岗";分类 ≈ "分流岗"。

一前一后,保障安全与性能并重。

3. 控制流与数据流的分治难题

在DPDK的高速转发场景中,我们常常面临这样的设计矛盾:

  • 转发流量(Data Plane) 要求高吞吐、低延迟,需要多个核心并行;
  • 控制流量(Control Plane) 数量不大,但要求稳定、可靠,而且逻辑完全不同。

举个例子:

路由器需要高速转发数据包(多核并行), 但同时也要解析少量控制报文(如ARP、心跳等), 你不希望这点控制包影响主数据流的性能。

那么,如何在硬件层面实现"分流"

这正是DPDK结合RSS + Flow Director的经典应用场景👇


3.1 DPDK配置实战:让网卡学会智能分流

下面是一段完整的配置逻辑,我们以 Intel® 82599 为例。

(1)初始化网卡配置

这里需要同时打开 RSSFlow Director 两种分类机制:

ini 复制代码
static struct rte_eth_conf port_conf = {
    .rxmode = {
        .mq_mode = ETH_MQ_RX_RSS, // 启用多队列 + RSS
    },
    .rx_adv_conf = {
        .rss_conf = {
            .rss_key = NULL,
            .rss_hf = ETH_RSS_IP | ETH_RSS_UDP |
                      ETH_RSS_TCP | ETH_RSS_SCTP,
        },
    },
    .fdir_conf = {
        .mode = RTE_FDIR_MODE_PERFECT,      // 精确匹配模式
        .pballoc = RTE_FDIR_PBALLOC_64K,    // 表大小
        .status = RTE_FDIR_REPORT_STATUS,   // 报文上报
        .mask = {
            .ipv4_mask = {
                .src_ip = 0xFFFFFFFF,
                .dst_ip = 0xFFFFFFFF,
            },
            .src_port_mask = 0xFFFF,
            .dst_port_mask = 0xFFFF,
        },
        .drop_queue = 127, // 匹配失败的流默认丢弃
    },
};

rte_eth_dev_configure(port, 4, 4, &port_conf); // 启用4个收发队列

提示:RSS和FDIR都依赖硬件资源,配置时要注意网卡是否支持(如82599/XL710均支持)。


(2)配置收发队列
ini 复制代码
struct rte_mempool *mbuf_pool =
    rte_pktmbuf_pool_create("MBUF_POOL", 8192, 256, 0,
                            RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());

for (int q = 0; q < 4; q++) {
    rte_eth_rx_queue_setup(port, q, 128,
        rte_eth_dev_socket_id(port), NULL, mbuf_pool);
    rte_eth_tx_queue_setup(port, q, 128,
        rte_eth_dev_socket_id(port), NULL);
}

(3)启动设备
scss 复制代码
rte_eth_dev_start(port);

(4)添加Flow Director规则

将特定UDP报文(源IP=2.2.2.3,目的IP=2.2.2.5,端口=1024)

直接导入队列3:

ini 复制代码
struct rte_eth_fdir_filter filter = {
    .soft_id = 1,
    .input = {
        .flow_type = RTE_ETH_FLOW_NONFRAG_IPV4_UDP,
        .flow = {
            .udp4_flow = {
                .ip = {
                    .src_ip = RTE_IPV4(2,2,2,3),
                    .dst_ip = RTE_IPV4(2,2,2,5),
                },
                .src_port = rte_cpu_to_be_16(1024),
                .dst_port = rte_cpu_to_be_16(1024),
            },
        },
    },
    .action = {
        .rx_queue = 3,
        .behavior = RTE_ETH_FDIR_ACCEPT,
        .report_status = RTE_ETH_FDIR_REPORT_ID,
    },
};

rte_eth_dev_filter_ctrl(port, RTE_ETH_FILTER_FDIR,
                        RTE_ETH_FILTER_ADD, &filter);

这一行代码的意义:

Flow Director将特定UDP流量"精准派送"到队列3,由控制核单独处理。


(5)调整RSS分配表(RETA)

由于RSS默认会在0~3队列均衡分配,我们需要把队列3"剔除",

让它只用于控制流。

ini 复制代码
struct rte_eth_rss_reta_entry64 reta_conf[2];
int idx, i, q = 0;
for (idx = 0; idx < 2; idx++) {
    reta_conf[idx].mask = ~0ULL;
    for (i = 0; i < RTE_RETA_GROUP_SIZE; i++, q++) {
        if (q == 3) q = 0;  // 不分配到队列3
        reta_conf[idx].reta[i] = q;
    }
}
rte_eth_dev_rss_reta_update(port, reta_conf, 128);

这样:

  • 队列0~2 → RSS数据流;
  • 队列3 → Flow Director控制流。

(6)核心线程绑定与收发逻辑
scss 复制代码
// 每个核绑定一个接收队列
rte_eal_remote_launch(lcore_data_loop, (void*)0, 1);
rte_eal_remote_launch(lcore_data_loop, (void*)1, 2);
rte_eal_remote_launch(lcore_data_loop, (void*)2, 3);
rte_eal_remote_launch(lcore_ctrl_loop, (void*)3, 4);

lcore_ctrl_loop() 中单独处理UDP控制包,即可完成控制/数据分治。

4. 什么是 RMT(Reconfigurable Match Table)

RMT,全称 Reconfigurable Match Table(可重构匹配表) ,最早由斯坦福大学在 SDN(Software Defined Networking)架构中提出,用于抽象网络设备的数据平面可编程能力

4.1 传统网络处理方式

在传统 ASIC(固定逻辑)网络设备中,匹配 + 动作(Match + Action) 是固化的:

  • 只能匹配特定字段(如 MAC、IP、TCP 端口)。
  • 动作集有限(如转发、丢弃、修改头部)。
  • 一旦芯片逻辑定死,就无法修改。

因此,当我们要引入新协议(VXLAN、Geneve、SRv6)或新处理逻辑时,就必须等芯片厂商推出新硬件。


4.2 RMT 的核心思想

RMT 提出了一个通用抽象模型: 把数据包处理流程抽象为一系列可配置的「匹配+动作(Match-Action)」表组成的流水线(Pipeline)。

每一级表:

  • 对输入包的某些字段进行匹配;
  • 执行对应动作;
  • 将结果(可能修改后的包)交给下一级表继续处理。

硬件不再固定支持哪些协议或规则,而是提供「匹配表模板」,由软件定义匹配字段和动作逻辑。


4.3 在 DPDK 中的意义

虽然 DPDK 运行在软件层,但它与 RMT 思想天然契合。

例如:

  • rte_flow 定义的流表项(flow rule)→ 就是「Match + Action」模型;
  • DOCA Flow、Mellanox mlx5、Intel ice PMD 都是把硬件的 Match-Action 能力开放出来。

RMT 的提出,让我们可以从更抽象的层面理解:

不同的网卡流分类机制,本质上只是匹配字段和动作集不同而已。


4.4 可实践的代码示例:rte_flow 模拟 RMT

在软件层,你可以通过 DPDK 的 rte_flow 接口,构建类似 RMT 的多级匹配规则。

例如,匹配 IPv4 源地址并重定向:

ini 复制代码
struct rte_flow_attr attr = { .ingress = 1 };
struct rte_flow_item pattern[3];
struct rte_flow_action action[2];

// 匹配 IPv4 源地址
struct rte_flow_item_ipv4 ip_spec = { .hdr.src_addr = RTE_BE32(0xC0A80101) }; // 192.168.1.1
struct rte_flow_item_ipv4 ip_mask = { .hdr.src_addr = 0xFFFFFFFF };
pattern[0].type = RTE_FLOW_ITEM_TYPE_IPV4;
pattern[0].spec = &ip_spec;
pattern[0].mask = &ip_mask;
pattern[1].type = RTE_FLOW_ITEM_TYPE_END;

// 动作:重定向到队列1
struct rte_flow_action_queue queue = { .index = 1 };
action[0].type = RTE_FLOW_ACTION_TYPE_QUEUE;
action[0].conf = &queue;
action[1].type = RTE_FLOW_ACTION_TYPE_END;

struct rte_flow *flow = rte_flow_create(port_id, &attr, pattern, action, &error);

这个简单示例本质上就是一个最小化的「RMT 一级表」:

  1. 匹配 IPv4 地址 → 执行动作(重定向)。
  2. 多级匹配可以用多个 rte_flow pipeline 串联实现。
相关推荐
李广坤2 小时前
限流算法实现
后端
吴祖贤2 小时前
4.6 Docker Model Runner Chat
后端
用户221765927922 小时前
python有哪些方案可以处理多线程请求接口时结果的顺序问题?
后端
间彧2 小时前
💻 Windows服务器K8s学习与SpringBoot部署实战指南
后端
FreeCode2 小时前
LangChain1.0智能体开发:MCP
后端·langchain·agent
前端小张同学2 小时前
基础需求就用AI写代码,你会焦虑吗?
java·前端·后端
zyb_1234563 小时前
手把手带你入门 TypeORM —— 面向新手的实战指南
后端
爱吃程序猿的喵3 小时前
Spring Boot 常用注解全面解析:提升开发效率的利器
java·spring boot·后端
zyb_1234563 小时前
NestJS 集成 RabbitMQ(CloudAMQP)实战指南
后端