6.1810 / 6.1810 · 第 16 课 · nz
高性能网络 (Shenango)
现代数据中心网络服务器(如 memcached)需要低延迟和高 CPU 效率。Shenango (NSDI 2019) 通过内核旁路(kernel bypass)和 I/O 内核(I/O kernel)的细粒度调度,在低尾延迟和高利用率之间取得平衡。
Linux 网络软件结构
接收路径:
NIC 将收到的包 DMA 到 RX Ring → NIC 中断内核 → 内核处理包(TCP/UDP)
→ 复制到目的 socket 队列 → 唤醒应用的 read
发送路径:
应用发送包 → 插入 TX Ring → NIC 发送包
高性能网络服务器
以 memcached(内存键值存储服务器)为例:
- 高请求率
- 短请求/响应(set/get 约 1μs)
- 大量客户端,大量潜在并行
性能指标
- 低延迟(低负载和高负载下)
- 尾延迟(Tail Latency)
尾延迟
来自 Google 著名论文 The Tail at Scale:
- Google 有许多高负载机器,典型响应 10ms
- 但 1/100 的请求需要 1 秒(99% 尾延迟是 1s)
- 延迟来源:CPU 核争用、后台进程、包的突发排队等
- 用户请求可能扇出到许多机器——如果每个请求扇出到 100 台机器,许多请求会遭受 1s 延迟
延迟组成
| 低负载 | 高负载 |
| 光速 + 交换机往返时间 + 中断 + 队列操作 + sleep/wakeup + 系统调用 + 核间数据移动 + RAM 读取 |
延迟主要由等待时间(排队)决定 |
高负载下延迟难以分析:突发到达、突发服务时间、负载不平衡、无人服务队列等结构性问题都会增加排队时间。
相关硬件限制
| 资源 | 限制 |
| 10 Gbps 以太网 | ~1000 万 100 字节包/秒 |
| 1 个 2.4 GHz 核心 | 每包 240 周期 |
| 8 核心 | 每包 1920 周期 |
| 系统调用 | 每秒几百万次 |
| 中断 | 每秒 100 万次 |
结论:太慢了!
解决方案:内核旁路(Kernel Bypass)
Linux 内核给 Shenango NIC 队列和专用核心,然后 Linux 不太参与。
Shenango 用户态库(Figure 2)
- 直接访问 NIC 包环
- 实现 TCP/IP 栈
- 在专用核心上调度多个线程
- 本质上是一个实现为库的小型操作系统
内核旁路网络
- NIC 有许多包环/队列
- 用页表将环映射到应用地址空间
- 编程 NIC 将包引导到特定队列:
hash<src ip, dst ip, src port, dst port> → queue
- "流一致哈希"或"接收端扩展"(RSS)
- 每个连接由一个核心处理——无锁争用、无核间数据移动
轮询
应用有专用核心,持续检查 NIC 环是否有新输入。如果总是可能有输入等待,中断是多余的。
好处:低尾延迟
专用资源给应用,无共享资源争用。每个连接一个核心,无锁、无数据移动。
评估
Figure 3
- 16 个队列,8 核心(2 个超线程),6 台机器每台 200 连接生成负载
- Linux 中低负载尾延迟 ~300-400μs,800K req/s 时无法跟上
- Shenango 和 ZygOS 表现更好:低负载和高负载下尾延迟都低
利用率问题
| 负载 | Linux | ZygOS | Shenango |
| 高负载 | 核心忙于网络包 | 核心忙于网络包 | 核心忙于网络包 |
| 低负载 | 时间分享核心,运行其他应用 | 核心预留,不运行其他应用 | I/O 内核允许其他应用突发! |
数据中心通常以低利用率运行以实现低尾延迟。
Shenango 的解决方案:I/O 内核
I/O 内核是一个有 root 权限的 Linux 进程:
- 分配剩余核心给 Shenango 应用
- 应用预留最少核心数
- I/O 内核允许其他应用在"预留核心"上突发(如果未使用)
- 如果负载上升,抢占突发应用并归还核心
挑战:避免"计算拥塞"
工作因应用没有足够核心而延迟几微秒。I/O 内核必须快速决策。
I/O 内核的位置与职责
- 处理所有网络 I/O:扫描输入队列,将包放到应用核心队列;扫描输出队列发送包
- 每 5μs 监控网络队列和线程队列
- 如果检测到计算拥塞,分配核心给应用,并引导输入包到该核心
- I/O 内核引导(而非 NIC)因为重新编程 NIC 昂贵(数百微秒)
- 也允许核心从其他核心窃取包,有助于负载均衡
检测计算拥塞(Algorithm 1)
- 如果包在下次队列扫描时仍在输入队列中
- 如果 uthread 仍在运行时的 uthread 队列中
核心分配(Algorithm 2)
- 优先选择应用已有超线程的核心
- 优先选择应用最近 yield 的核心
- 选择任何空闲核心(如果有)
- 抢占突发应用的随机核心
Shenango 实现
- 复杂,因为使用 Linux 而不修改它
- 核心是 pthread 线程(Linux 用内核线程实现)——Shenango "钉住" pthread 到核心
- I/O 内核与应用进程间共享内存队列
- I/O 内核使用 DPDK 与 NIC 高效交互
- Runtime 在用户态实现操作系统(如自己的 TCP 实现)
响应负载变化
Figure 3 显示 Shenango 表现良好。Figure 5 直接回答:Shenango 能多快响应负载变化?
总结
高性能网络是困难的!
- 低延迟和高利用率难以同时实现
- 论文本质上提出了一个新操作系统
- 巧妙地实现在 Linux 之上
- Shenango libOS 不如 Linux 通用
测验
Q1:内核旁路(kernel bypass)的主要好处是什么?
Q2:为什么尾延迟(tail latency)对大规模服务很重要?
Q3:Shenango 的 I/O 内核的主要创新是什么?
Q4:为什么 Shenango 使用轮询而非中断?
Q5:为什么 I/O 内核自己引导包(而非重新编程 NIC)?
要点总结
尾延迟是核心指标
扇出使高尾延迟影响大量请求。低负载和高负载下都需保持低尾延迟。
内核旁路
专用核心 + 用户态 NIC 访问 + 轮询,消除内核开销和锁争用。
I/O 内核
细粒度核心分配,每 5μs 检测计算拥塞,动态调整。
低延迟 vs 高利用率
通过允许其他应用在空闲核心突发,兼顾两者。
延伸阅读