🚀 DuoTunnel:基于 QUIC 的边缘转发网关,探索新一代高性能架构
大家好,近期我开源了一个基于 QUIC 协议的高并发边缘网络转发中间件 —— DuoTunnel。
最初做这个项目的起因是,在重度使用传统的反向连接网关工具时,发现在极高并发或弱网环境下,基于 TCP + Yamux 多路复用的架构存在一些难以突破的瓶颈(如 TCP 队头阻塞、建连延迟极高)。
既然 Rust 的异步生态和 QUIC 协议栈(quinn)已经如此成熟,我就思考能不能彻底抛弃传统的 TCP 方案,造一个开源、高性能、控制面分离的新一代双向数据通道?借这个机会,想和大家分享一下 DuoTunnel 的架构设计与核心技术实现。
💡 核心优势:为什么选择 QUIC 与控制面分离?
DuoTunnel 的设计初衷不仅仅是“又一个网络转发工具”,更是为了解决复杂网络和高并发场景下的底层架构痛点:
- 彻底解决队头阻塞与 0-RTT 建连
相比传统 TCP,QUIC 原生支持独立的 Stream 多路复用。服务端在转发请求时直接调用
open_bi(),数据伴随 Stream 的创建瞬间到达客户端(0-RTT),彻底淘汰了传统架构中臃肿的“预建立连接池”。 - 控制面与数据面彻底解耦 DuoTunnel 采用控制面(负责规则)与数据面(负责转发)彻底分离的架构。数据节点完全无状态,可以随时水平扩容,路由规则变更毫秒级全网下发,实现真正的无感知热更新。
- 连接迁移(Connection Migration) 当边缘节点的网络环境发生频繁切换(比如基站切换),基于 Connection ID 的 QUIC 协议可以保持底层连接不断开,上层业务完全无感知。
🏗️ 架构设计与核心组件
DuoTunnel 在拓扑上由三个核心组件构成:
duotunnel-ctld(控制面大脑):负责集中管理路由规则、虚拟主机匹配、鉴权凭证和证书。它将静态 YAML 与 SQLite 动态规则合并,生成全局路由快照,并通过 gRPC/长连接实时下发给所有数据节点。duotunnel-server(云端数据面节点):部署在云环境中,作为无状态的纯数据面服务。它监听ctld下发的路由快照,负责接收来自外部的 HTTP/TCP/TLS 流量,并通过 QUIC 加密链路将其精准路由到对应的边缘节点。duotunnel-client(边缘节点):部署在私有云或边缘计算节点中。它主动向云端 Server 发起连接建立 QUIC 链路。不仅负责将云端流量安全引入本地微服务环境,也支持本地应用通过该链路安全地进行外部服务调用。
系统部署拓扑图
graph TD
subgraph cp ["控制面 (Control Plane)"]
CTLD["duotunnel-ctld <br> 集中管控"] -->|读取/写入| DB[("SQLite 动态规则")]
CTLD -->|解析| YAML["静态基础配置"]
end
subgraph edge ["本地侧 (私有云 / 边缘机房)"]
CLIENT["duotunnel-client"]
LOCAL["被调微服务 (Web/gRPC)"]
LOCAL_APP["主动发起应用"]
end
subgraph dp ["云端侧 (公有云 / 数据面)"]
SERVER1["duotunnel-server 节点 1"]
SERVER2["duotunnel-server 节点 2"]
end
CTLD -.->|"Watch Stream 实时推送配置"| SERVER1
CTLD -.->|"Watch Stream 实时推送配置"| SERVER2
CLIENT ===>|"1. 发起连接建立 QUIC 双向数据链路"| SERVER1
%% Ingress 链路
EXT(("外部请求 / 用户")) -.->|"2. 入口流量接入"| SERVER1
SERVER1 -.->|"复用链路下发"| CLIENT
CLIENT -.->|"路由转发"| LOCAL
%% Egress 链路
LOCAL_APP -.->|"3. 出口流量调度"| CLIENT
CLIENT -.->|"复用链路上传"| SERVER1
SERVER1 -.->|"安全网关访问"| EXT_API(("外部公网 API"))
0-RTT 新流建立流程图
sequenceDiagram
participant Ext as 外部请求_External
participant Srv as duotunnel-server
participant Cli as duotunnel-client
participant Loc as 本地服务_Local
Ext->>Srv: 1. 发起 HTTP 请求 (Host: app.example.com)
Srv->>Srv: 2. 无锁匹配虚拟路由,命中 Client 节点
Note over Srv,Cli: QUIC 链路已连接,无需新建 TCP/Yamux 握手
Srv->>Cli: 3. 直接 open_bi() 开启新 Stream 并写入数据
Cli->>Loc: 4. Client 转发请求到本地服务
Loc-->>Cli: 5. 本地服务返回 Response
Cli-->>Srv: 6. Response 顺着 QUIC Stream 返回
Srv-->>Ext: 7. 返回给外部请求
💪 核心技术力:Rust 极致的性能优化
作为底层网络中间件,我们在并发模型和内存管理上做了一些深度的优化设计:
1. 基于 Actor 模型的并发调度
在管理复杂的数据传输生命周期、连接状态以及健康检查时,单纯的 async/await + Mutex 容易导致死锁或调度延迟。我们在 Server 的节点注册表和 Client 的连接管理中,广泛采用了 Actor 模型(通过 mpsc channel 传递消息)。这使得状态机更新完全独立且无阻塞,极大提升了单核情况下的高并发吞吐能力。
2. 无锁配置热更新 (Lock-free Hot-swap)
数据面在处理每秒数万个请求的虚拟路由匹配时,任何读锁都会成为严重的性能瓶颈。我们使用了 arc-swap:ctld 推送的新路由表会在后台原子替换,而请求的热路径(Hot Path)上是 100% 无锁读取的,实现了真正的零开销热更新。
3. 砍掉动态分配,消除伪共享
- Zero-allocation:在 HTTP/TCP 等多协议分发的热路径上,我们使用静态的枚举结构来替代
Box<dyn Trait>的动态派发,清零了堆内存分配的开销。 - False Sharing (伪共享) 防御:并发突增时,原子 Metrics 累加容易导致 CPU 缓存行失效。我们将核心的统计计数器做了 64 字节对齐,防止多核高并发写入时引发 Cache Miss 风暴。
4. 内核级特化调优
为了对抗高延迟网络,底层默认拉满了 QUIC 的 receive/send windows(4MB/32MB)并启用了 BBR 拥塞控制。如果在 Linux 5.4+ 的内核下运行,还会自动开启 UDP 的 GRO/GSO 批量读写机制,使 Syscall 的开销大幅下降。
📊 压测数据与 Benchmark
为了保障工程质量,项目集成了一套严谨的自动化测试流水线。每次代码合并,都会自动触发从单测到最高 8000 RPS 的 HTTP 极限压测,并生成公开的资源消耗大屏。
在压测中,DuoTunnel 在单机环境能稳定输出 8000 RPS 以上的吞吐量,同时保持极低的 CPU 占用和平稳的内存曲线。
压测数据概览 (Benchmark Dashboard) (系统自动采集网络吞吐、CPU、上下文切换等核心指标)

🌟 欢迎 Star & 交流
如果你平时有极客式的底层网络开发需求,或者单纯对 Rust 高性能网络架构感兴趣,欢迎来 GitHub 交流并点个 ⭐️ Star 支持一下!
目前核心功能已相对稳定,也非常期待各位 Rust 开发者能够提 Issue、交 PR,共同探索 QUIC 在网络底层的更多可能性!
🔗 GitHub 传送门: https://github.com/locustbaby/duotunnel
欢迎大家在评论区探讨架构细节和 Rust 性能优化相关的话题!🦀
评论区
写评论是的,有考虑过做打洞+p2p,不过项目会有点重了,我在考虑往轻量里做,更适配一些edge mesh的场景
--
👇
shenjinti: 可以用rustrtc 实现p2p
可以用rustrtc 实现p2p