在前一篇文章中,当控制面更新一条 ACL 规则时,如何更新给数据面- 掘金
介绍了dpdk中 基于trie tree的acl的实现,更新一条acl配置时,要重新构建trie tree。但是重新构建应该不是在控制平面,而是control-dp。
可以参考控制平面和数据平面各自角色功能 - 掘金
通常由 control-dp 线程负责 ACL 规则重建
1. 为什么是 control-dp?
在典型的 DPDK 多线程架构中:
+-----------------------------+
| Main Lcore (control-dp) |
| - 接收控制面消息 |
| - 解析规则、重建 ACL Trie |
| - 管理定时器、统计上报 |
+-----------------------------+
|
v
+-----------------------------+ +-----------------------------+
| Worker Lcore 0 (data plane) | | Worker Lcore N (data plane) |
| - 收包、ACL 匹配、转发 | | - 收包、ACL 匹配、转发 |
| - 使用共享的 acl_ctx | | - 使用共享的 acl_ctx |
+-----------------------------+ +-----------------------------+
-
control-dp(通常运行在 main lcore)是数据面中唯一与控制面通信的入口; -
它负责:
- 接收控制面通过 IPC/共享内存发来的规则更新;
- 验证、解析规则;
- 调用
rte_acl_build()重建整个 Trie 结构; - 原子发布新
acl_ctx给所有 worker;
-
所以:✅ 是的,Trie Tree 的重建通常在
control-dp线程中完成。
✅ 二、control-dp 是否要“存储所有规则”?
分两个层面看:
1. 是否需要“规则原始数据”?✅ 是的
-
control-dp必须持有当前生效的所有 ACL 规则的副本(如struct acl_rule rules[]); -
原因:
- 每次更新(增/删/改)时,需要基于完整规则集重建
rte_acl_ctx; - DPDK
rte_acl不支持“只添加一条规则”,必须传入全部规则数组; - 所以
control-dp必须维护一个规则数据库(rule DB) ,用于下次重建。
- 每次更新(增/删/改)时,需要基于完整规则集重建
🔹 类比:就像编译器需要源代码才能重新编译可执行文件。
2. 是否需要“运行时匹配结构”(Trie)?✅ 是的
-
control-dp本身不参与报文转发,但它需要:- 调用
rte_acl_build(acl_ctx, rules, n)—— 这个过程会在内存中构建 Trie; - 构建完成后,将
acl_ctx发布给 worker 线程;
- 调用
-
所以:
control-dp在重建期间会临时持有 Trie 结构,但构建完成后,它只保留规则数据,而 Trie 主要由 worker 使用。
✅ 三、内存布局示意图
text
深色版本
+--------------------------------------------------+
| Shared Memory Region |
| |
| +---------------------+ |
| | Rule DB (Raw Rules) | ← control-dp 维护 |
| | [rule1, rule2, ...] | |
| +---------------------+ |
| |
| +---------------------+ |
| | Current acl_ctx | ← 所有 worker 共享使用 |
| | (Trie structure) | |
| +---------------------+ |
| |
| +---------------------+ |
| | Pending acl_ctx | ← control-dp 构建中 |
| | (new Trie) | (双缓冲) |
| +---------------------+ |
| |
+--------------------------------------------------+
-
control-dp:- 读写 Rule DB
- 创建 Pending acl_ctx
- 构建完成后,原子替换
Current acl_ctx
-
worker:- 只读
Current acl_ctx,用于rte_acl_classify()
- 只读
✅ 四、为什么不把“规则重建”放在控制面?
你可能会问:既然 control-dp 要存规则、要重建 Trie,为什么不干脆让控制面来做?
答案:性能与解耦的权衡
| 方案 | 优点 | 缺点 |
|---|---|---|
| 控制面重建 Trie,下发二进制结构 | worker 无需重建,更新快 | 控制面压力大;二进制兼容性问题;跨进程指针无效 |
control-dp 重建 Trie | ✅ 数据面自主;避免跨进程复杂性;可利用 SIMD 优化 | control-dp 需要存规则和重建能力 |
✅ 主流选择是后者:让 control-dp 负责重建,原因:
- 避免控制面成为性能瓶颈;
- 避免传输巨大的二进制结构(如 10 万条规则的 Trie 占几十 MB);
- 允许数据面根据本地 CPU 特性(如 AVX-512)优化构建过程。
✅ 五、如何优化 control-dp 的资源开销?
虽然 control-dp 需要存储规则和重建 Trie,但可以通过以下方式优化:
1. 双缓冲(Double Buffering)
- 维护两个
acl_ctx:current和pending; control-dp在pending上构建;- 构建完成后,原子切换指针;
- 旧
current延迟释放(等待 worker 不再引用);
2. 异步重建
control-dp收到规则更新后,不立即重建;- 标记“规则已变更”,在低负载时重建;
- 避免影响转发性能。
3. 增量分类器替代 rte_acl
- 使用支持增量更新的分类器(如 VPP classify、BPF map);
control-dp只需插入单条规则,无需重建整个 Trie;- 内存和 CPU 开销大幅降低。
4. 规则压缩
- 合并等价规则;
- 使用 prefix sharing 减少 Trie 节点数;
- 降低内存占用。
✅ 六、总结:回答你的问题
“重建 Trie Tree 是不是在
control-dp中?”
✅ 是的。control-dp 是数据面中唯一适合做这件事的线程,它负责接收规则、重建 Trie、发布新上下文。
“那它岂不是也要存储所有的规则?”
✅ 是的,但它必须这么做:
- 它需要规则原始数据来调用
rte_acl_build(); - 它需要临时构建 Trie,但不参与转发;
- 这是 “功能完整性” 与 “性能隔离” 之间的合理权衡。
🔁 类比:就像一个“工厂的质检员”,他不直接生产产品(转发),但需要图纸(规则)和检测设备(Trie)来确保生产线(worker)使用的工具是正确的。
💡 最佳实践建议
| 目标 | 建议 |
|---|---|
减少 control-dp 开销 | 使用支持增量更新的分类器(如 VPP classify、BPF) |
| 提高更新实时性 | 双缓冲 + 原子切换 |
| 降低内存占用 | 规则压缩、共享前缀 |
| 避免阻塞转发 | 异步重建、低优先级任务队列 |
这种设计是现代高性能网络系统的标准模式,理解它有助于构建更健壮的数据面架构。