< 返回版块

locustbaby 发表于 2026-08-13 23:33

Tags:QUIC, Tunnel

🚀 DuoTunnel:基于 QUIC 的边缘转发网关,探索新一代高性能架构

大家好,近期我开源了一个基于 QUIC 协议的高并发边缘网络转发中间件 —— DuoTunnel

最初做这个项目的起因是,在重度使用传统的反向连接网关工具时,发现在极高并发或弱网环境下,基于 TCP + Yamux 多路复用的架构存在一些难以突破的瓶颈(如 TCP 队头阻塞、建连延迟极高)。

既然 Rust 的异步生态和 QUIC 协议栈(quinn)已经如此成熟,我就思考能不能彻底抛弃传统的 TCP 方案,造一个开源、高性能、控制面分离的新一代双向数据通道?借这个机会,想和大家分享一下 DuoTunnel 的架构设计与核心技术实现。


💡 核心优势:为什么选择 QUIC 与控制面分离?

DuoTunnel 的设计初衷不仅仅是“又一个网络转发工具”,更是为了解决复杂网络和高并发场景下的底层架构痛点:

  1. 彻底解决队头阻塞与 0-RTT 建连 相比传统 TCP,QUIC 原生支持独立的 Stream 多路复用。服务端在转发请求时直接调用 open_bi(),数据伴随 Stream 的创建瞬间到达客户端(0-RTT),彻底淘汰了传统架构中臃肿的“预建立连接池”。
  2. 控制面与数据面彻底解耦 DuoTunnel 采用控制面(负责规则)与数据面(负责转发)彻底分离的架构。数据节点完全无状态,可以随时水平扩容,路由规则变更毫秒级全网下发,实现真正的无感知热更新。
  3. 连接迁移(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-swapctld 推送的新路由表会在后台原子替换,而请求的热路径(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、上下文切换等核心指标)

Benchmark 资源监控大屏


🌟 欢迎 Star & 交流

如果你平时有极客式的底层网络开发需求,或者单纯对 Rust 高性能网络架构感兴趣,欢迎来 GitHub 交流并点个 ⭐️ Star 支持一下!

目前核心功能已相对稳定,也非常期待各位 Rust 开发者能够提 Issue、交 PR,共同探索 QUIC 在网络底层的更多可能性!

🔗 GitHub 传送门: https://github.com/locustbaby/duotunnel

欢迎大家在评论区探讨架构细节和 Rust 性能优化相关的话题!🦀

评论区

写评论
作者 locustbaby 2026-08-17 11:52

是的,有考虑过做打洞+p2p,不过项目会有点重了,我在考虑往轻量里做,更适配一些edge mesh的场景

--
👇
shenjinti: 可以用rustrtc 实现p2p

shenjinti 2026-08-15 20:04

可以用rustrtc 实现p2p

1 共 2 条评论, 1 页