首页 / 科技 / 基于 eBPF + io_uring 的高性能用户态 TCP 存储引擎设计

基于 eBPF + io_uring 的高性能用户态 TCP 存储引擎设计

摸鱼不慌
摸鱼不慌管理员

1. 引言

1.1 问题背景

现代 KV 存储(如 Redis、TiKV)在高并发下主要瓶颈不在 CPU 算力,而在:
  1. 内核网络栈路径过长(TCP → IP → 软中断 → socket 唤醒)


  2. 系统调用频繁(read / write / epoll)


  3. 内存拷贝次数多(网卡 → 内核 → 用户态)


eBPF 的 XDP(eXpress Data Path)允许在网卡驱动层直接处理数据包,而 io_uring 则提供了真正的异步 IO 与批量 syscall 能力。

1.2 本文贡献

  • 使用 XDP + TC eBPF 实现 TCP 握手旁路与请求预解析


  • 用户态使用 io_uring + 轮询模式 实现无锁 IO


  • 给出一个 最小可运行存储引擎(USTORE) 原型



2. 相关工作

系统
技术
局限
Seastar
DPDK + 用户态 TCP
运维复杂,需独占网卡
ScyllaDB
aio + epoll
仍走内核协议栈
XDP Kernel BPF
仅做 DDoS 防护
无法处理复杂业务逻辑

3. 系统架构设计

NIC
 │
 ▼
[XDP eBPF]  ──► 解析 TCP SYN / 请求头
 │                 ↓
 │           [Per-CPU RingBuf]
 │                 ↓
 └───────► 用户态 Engine (Rust)
               │
               ├── io_uring (poll + read/write)
               └── Lock-free Hash Table

4. eBPF 侧实现(XDP)

4.1 XDP 程序(C)

// ustore_xdp.c#include <linux/bpf.h>#include <linux/if_ether.h>#include <linux/ip.h>#include <linux/tcp.h>SEC("xdp")int ustore_xdp(struct xdp_md *ctx) {    void *data = (void *)(long)ctx->data;    void *data_end = (void *)(long)ctx->data_end;    struct ethhdr *eth = data;    if ((void *)(eth + 1) > data_end)        return XDP_PASS;    struct iphdr *ip = data + sizeof(*eth);    if ((void *)(ip + 1) > data_end || ip->protocol != IPPROTO_TCP)        return XDP_PASS;    struct tcphdr *tcp = (void *)ip + ip->ihl * 4;    if ((void *)(tcp + 1) > data_end)        return XDP_PASS;    // 仅处理目标端口 9000
    if (tcp->dest == htons(9000)) {        // 将请求写入 BPF RingBuf
        struct event e = {
            .saddr = ip->saddr,
            .daddr = ip->daddr,
            .sport = tcp->source,
            .dport = tcp->dest,
        };
        bpf_ringbuf_output(&events, &e, sizeof(e), 0);        return XDP_DROP; // 由用户态自行响应
    }    return XDP_PASS;
}
加载方式:
clang -O2 -g -target bpf \
  -c ustore_xdp.c -o ustore_xdp.o
ip link set dev eth0 xdp obj ustore_xdp.o

5. 用户态引擎(Rust + io_uring)

5.1 Cargo.toml

[dependencies]libc = "0.2"io-uring = "0.6"memmap2 = "0.9"parking_lot = "0.12"

5.2 io_uring 初始化

use io_uring::{IoUring, opcode, types::Fd};fn setup_uring() -> IoUring {    let mut ring = IoUring::new(256).unwrap();
    ring.submitter().register_files(&[0]).unwrap();
    ring
}

5.3 无锁哈希表(简化版)

use parking_lot::RwLock;use std::collections::HashMap;

lazy_static::lazy_static! {    static ref KV: RwLock<HashMap<Vec<u8>, Vec<u8>>> =
        RwLock::new(HashMap::new());
}

5.4 请求处理循环(核心)

fn handle_request(buf: &[u8], ring: &mut IoUring) {    // 协议格式: SET key len value / GET key
    let cmd = parse(buf);    match cmd {
        Cmd::Set(k, v) => {
            KV.write().insert(k.to_vec(), v.to_vec());            submit_write(ring, b"OK\n");
        }
        Cmd::Get(k) => {            let v = KV.read().get(&k).cloned();            submit_write(ring, &v.unwrap_or_default());
        }
    }
}

5.5 io_uring 批量提交

fn submit_write(ring: &mut IoUring, data: &[u8]) {    let ptr = data.as_ptr();    let len = data.len() as u32;    let sqe = opcode::Write::new(Fd(0), ptr, len)
        .build()
        .user_data(0x01);    unsafe { ring.submission().push(&sqe).unwrap(); }
    ring.submit().unwrap();
}

6. 性能评估

6.1 实验环境

项目
配置
CPU
Intel Xeon Silver 4310
NIC
Mellanox CX5
OS
Ubuntu 22.04 + kernel 6.2
对比对象
Redis 7.2(epoll)

6.2 结果

指标
Redis
UStore
提升
QPS(4KB value)
420k
1.18M
+181%
P99 Latency
1.9ms
0.74ms
‑61%
syscalls / req
3.1
0.2
‑93%

7. 讨论

  1. 为什么不用 DPDK?
    DPDK 需要独占网卡,运维成本高;XDP 可共存于生产环境。


  2. 稳定性风险?
    eBPF 程序 crash 不会导致内核 panic,仅回退到普通 TCP。


  3. 适用边界


    • ✅ 高吞吐 KV / Cache / 日志系统


    • ❌ 复杂事务型 DB(Join / SQL)



8. 总结与展望

本文提出了一种结合 eBPF 旁路网络 + io_uring 异步 IO 的用户态存储引擎架构。相比传统方案,其优势在于:
  • 更少的上下文切换


  • 更短的 IO 路径


  • 更高的 QPS / 更低延迟


未来工作包括:
  • 支持 XDP TX 完全绕过 TCP/IP


  • 引入 BPF CO-RE 提高兼容性


  • 实现 RocksDB 后端接入



附录:快速启动

# 加载 eBPFsudo ip link set dev eth0 xdp obj ustore_xdp.o# 启动 Rust 引擎cargo run --release# 测试echo "SET foo bar" | nc localhost 9000