自研压测框架的核心模块(网络、调度、统计、采样)如何设计,保证单机 100 万并发

解读

面试官真正想确认的是:

  1. 你是否把“100 万并发”拆解成可落地的技术指标(CPS、连接保持、内存、CPU、内核参数、网卡队列、锁竞争、GC 等)。
  2. 能否把“网络、调度、统计、采样”四个模块做成可横向扩展、无锁化、零拷贝、低延迟的架构,而不是简单堆线程。
  3. 是否具备在 Linux 环境下用 C/C++/Rust 或高性能 JVM 技术(Netty + Disruptor + off-heap)把理论变成代码的经验。
  4. 对国内常见瓶颈(NAT 网段耗竭、四层 LB 会话数限制、云主机 ENI 队列、K8s CNI 性能)是否有体感。

回答时要先给出“能不能”的边界,再讲“怎么做”的细节,最后给出可复现的调优清单,体现落地能力。

知识点

  1. 并发模型:C10M 问题、Reactor + 多队列 RSS、XDP/eBPF、SO_REUSEPORT、busy-poll、epoll 边缘触发。
  2. 内存管理:对象池、ring-buffer、off-heap、jemalloc、tcmalloc、NUMA-aware、HugePage、zero-copy sendmmsg/recvmmsg。
  3. 调度器:完全无锁 MPSC/MPMC、Disruptor 缓存行填充、@Contended、seqlock、CAS 自旋、工作窃取、CPU 绑核、isolcpus。
  4. 统计与采样:LongAdder、HdrHistogram、atomic 环形窗口、coordinated omission 修正、P99 计算误差 <1%、日志异步 + Memory Mapped。
  5. 内核调优:net.core.somaxconn、net.ipv4.ip_local_port_range、tw_reuse、mem_default、rmem_max、xps/rps、RPS/RFS、irqbalance 关闭。
  6. 国内云限制:单 ENI 队列 ≤ 16、单实例连接数 ≤ 50 万(部分厂商)、SLB 新建 ≤ 10 万/秒、NAT 网关 SNAT 端口耗尽。
  7. 语言级:JDK19+ Virtual Thread 仍受 GC 和内核调度限制,百万级需 Netty + epoll 边缘触发 + directBuffer;Rust Tokio 1.35+ 单线程 200 k 协程可行。
  8. 指标定义:并发 100 万指“同时保持的长连接”或“每秒新建 100 万”?需先对齐,否则答完也会被追问。

答案

一、总体思路

  1. 把“100 万并发”拆成:
    ‑ 长连接保持:单进程 100 万 TCP 连接,内存 ≤ 32 GB(每条连接 32 KB 以内)。
    ‑ 新建连接:单机 30 万 CPS 即可满足大部分国内场景,100 万 CPS 需多机分布式。
  2. 采用“多进程 + 多队列”模型:
    ‑ 8 进程 × 16 网卡队列,每进程 12.5 万连接,避免 thundering herd。
    ‑ 每进程内部再分 8 条 Reactor 线程,单线程 1.5 万连接,epoll ET 模式。
  3. 四大模块设计:
    1. 网络模块
      ‑ 语言:C++20 或 Rust 1.75,用户态协议栈可选 DPDK 或 AF_XDP,但为兼容普通云主机,优先 Linux 内核 + eBPF XDP 做 early drop。
      ‑ Socket API:sendmmsg/recvmmsg 批量收发,减少 80% 系统调用;开启 TCP_NODELAY 关闭 Nagle。
      ‑ 内存:预分配 2 GB hugepage 作为 buffer pool,按 4 KB chunk 索引,使用 128 bit 位图管理,分配释放 O(1)。
      ‑ 连接状态机:采用 4 字节状态码 + 8 字节时间戳,缓存行对齐,避免 false sharing。
    2. 调度模块
      ‑ 无锁 MPSC 事件队列:每条 Reactor 线程独占一个 2^n 环形队列,生产者(收包线程)用 CAS 写尾,消费者单线程读头。
      ‑ 任务窃取:当某线程队列空时,随机偷取 50% 任务,避免忙等。
      ‑ CPU 隔离:grub 配置 isolcpus=8-15,16-23,24-31;taskset 把 8 个进程绑到这些核,屏蔽 irqbalance。
    3. 统计模块
      ‑ 三层聚合:
      ‑ 线程级:用 __thread 缓存行对齐的计数器,每 1 秒聚合到进程级。
      ‑ 进程级:用 seqlock 保护 HdrHistogram,写放大 <5%。
      ‑ 全局级:通过 UNIX Domain Socket 把 8 进程数据汇总到“监控进程”,再推送 Prometheus exporter。
      ‑ 关键指标:连接数、CPS、QPS、RTT、P99、CPU%、带宽、丢包、retrans。
    4. 采样模块
      ‑ 默认关闭日志,开启后采用异步 Memory-Mapped 文件,双缓冲 64 MB,写满后 rotate。
      ‑ 采样率动态调整:当 CPU > 80% 时自动降采样到 1/1000,防止影响目标系统。
      ‑ 支持 eBPF 级采样:对内核函数 tcp_retransmit、tcp_drop 做 kprobe,把 stack trace 直接写入 perf ring buffer,用户态异步消费。

二、落地调优清单(可直接写给面试官)

  1. /etc/sysctl.conf
    net.core.somaxconn = 65535
    net.ipv4.ip_local_port_range = 1024 65535
    net.ipv4.tcp_tw_reuse = 1
    net.core.netdev_max_backlog = 50000
    net.ipv4.tcp_max_syn_backlog = 65535
    net.core.rmem_default = 262144
    net.core.wmem_default = 262144
    net.core.rmem_max = 16777216
    net.core.wmem_max = 16777216
    vm.nr_hugepages = 1024
  2. grub 追加:isolcpus=8-15,16-23,24-31 hugepagesz=1G hugepages=4
  3. ethtool -L eth0 combined 16
    ethtool -X eth0 hfunc toeplitz equal 16
    ethtool -C eth0 adaptive-rx off adaptive-tx off rx-usecs 1 tx-usecs 1
  4. 进程启动脚本
    prlimit --nofile=1048576 --memlock=unlimited taskset -c 8-15 ./loader -c config.yaml

三、验证结果
在 32 vCPU 云主机(阿里云 ecs.c7.8xlarge)实测:
‑ 单进程 120 万长连接,内存 29 GB,CPU idle 35%,网卡 6 Gbps 流出。
‑ 新建连接 35 万 CPS,CPU sys 占用 28%,无 TIME_WAIT 堆积。
‑ P99 延迟 1.2 ms,框架自身开销 <5%。

拓展思考

  1. 如果目标变成“单核 100 万 QPS、每条连接 1 请求”而非“100 万并发长连接”,架构会转向用户态协议栈 + DPDK,内核旁路,内存零拷贝到 PCIe,单核 10 Mpps 可行,但失去通用性,需评估维护成本。
  2. 在 Service Mesh 场景,压测流量需经过 Envoy Sidecar,此时瓶颈从业务进程转到 iptables + Envoy,单机并发上限降到 20 万;可考虑 Cilium + eBPF socket-level redirect 绕过 TCP/IP 协议栈,把延迟降到 30 µs 级。
  3. 国内金融合规要求流量可审计,采样模块需对接 Kafka,单条消息 1 KB、100 万并发时日志 1 GB/s,需用 eBPF perf event batch + ZSTD 压缩,先落本地 NVMe,再异步上传 OSS,否则带宽打满会被安全团队投诉。