初步认识 F-Stack

近期计划系统地学习下 F-Stack,所以还是希望能够以文档的形式记录下来,作为学习总结吧。

背景与需求

  • 随着网卡性能的飞速发展,10G 网卡已经大规模普及,25G/40G/100G 网卡也在逐步推广(实际上还是业务流量的推动)。
  • Linux 内核在网络数据包处理上的瓶颈也越发明显,问题在于 Linux 本质上是一个通用的操作系统。
  • 互联网的快速发展急需高性能的网络处理能力,kernel bypass 方案也越来越被人所接受,市场上出现了多种类似的技术,如 DPDK、NETMAP、PF_RING 等,其核心思想是所有数据流相关操作都在用户态进行处理,并辅以各种性能优化手段,从而达到更高的性能。其中,DPDK 因为更彻底的脱离内核调度以及活跃的社区支持从而得到了更广泛的使用。
  • 传统上 DPDK 大多用于 SDN、NFV、DNS、LB 等简单的应用场景下,对于复杂的 TCP 协议栈上的七层应用很少。当时市面上也出现了部分用户态协议栈,如 mTCP、Mirage、IwIP、NUSE 等,也有用户态的编程框架,如 SeaStar 等,但统一的缺点是应用程序接入门槛较高,不易使用。
  • 2017 年 F-Stack 诞生,它使用纯 C 实现,充当了胶水粘合了 DPDK、FreeBSD 用户态协议栈、Posix API、微线程框架和上层应用(Nginx、Redis),使绝大部分的网络应用可以通过直接修改配置或替换系统的网络接口即可接入 F-Stack,从而获得更高的网络性能。

传统内核协议栈的问题

先来看下传统内核协议栈带来的性能瓶颈问题。

Linux 内核在网络数据包处理上的瓶颈也越发明显,在传统的内核协议栈中,网卡通过硬中断通知协议栈有新的数据包到达,内核的网卡驱动程序负责处理这个硬件中断,将数据包从网卡队列拷贝至内核开辟的缓冲区,然后数据包经过一系列的协议栈处理流程,最后送到用户程序指定的缓冲区中。

在这个过程中,中断处理、内存拷贝、系统调用、锁、软中断、上下文切换等严重影响了网络数据包的处理能力。另外,操作系统对应用程序和数据包处理,可能跨 CPU 调度,局部性失效进一步影响网络性能。

  • 局部性失效。一个数据包可能中断在 cpu0,内核处理在 cpu1,用户态处理在 cpu2,这样跨越多个核心,造成局部性失效,cpu 缓存失效,同时可能存在跨 numa 访问内存,性能受到很大影响。
  • 中断处理。当网络中数据量很大时,大量的数据包产生频繁的硬件中断请求,这些硬件中断可以打断之前较低优先级的软中断或者系统调用的执行过程,如果这种打断频繁进行的话,将产生较高的性能开销。用户态内核态的上下文切换和软中断都增加了额外的开销。
  • 内存拷贝。网络数据包从网卡到应用程序需要经过如下的过程:数据包从网卡通过 DMA 等方式传到内核开辟的缓冲区;数据包从内核空间复制到用户空间。在 Linux 内核协议栈中,这个耗时甚至占到了数据包整个处理流程的一半。
  • 系统调用。频繁的硬件中断或软中断都可能随时抢占系统调用的运行,这也将产生大量的上下文切换开销。内核中一些资源如 PCB 表等需要加锁处理,大量的并发操作造成很大的性能浪费,特别是大量短连接的创建。

可能有人会比较疑惑,Linux 竟然有这么多缺点,高性能网络处理不是它的强项么?个人理解,Linux 首先是一个多任务的,通用的操作系统,而我们平时追求的高性能目标一般是落地到某个具体的应用程序。Linux 设计时要考虑安全、多任务等因素,所以 Linux 不是一个专用的操作系统。试想一个追求各方面平衡的操作系统,是不会把系统资源都分给一个应用程序来使用,如何能够做到专用设备的高性能呢?

DPDK 介绍

DPDK 全称是 Data Plane Development Kit,数据平面开发工具集,提供高性能收发包框架,专注于网络应用中数据包的高性能处理。它是 Intel 公司开发与开源的项目,目前很多大厂基本都有在使用。

DPDK 是将各种常见的性能优化的技术手段综合了起来,形成了优秀的网络收发包框架,本质上并没有引入特别多新技术。所以,DPDK 体系包含了非常多的值得学习的知识点和技术,由于涉及内容范围广泛,学习的话需要选择重点才能更高效的达到预期的目标。

(一)背景

  • 网卡技术的飞速发展,而 CPU 运行频率停留在了 10 年前,严重阻碍了网络处理速度的提升;
  • 由于历史关系,网卡以中断方式运行,随着 IO 超越 CPU 运行速率,用轮询来处理高速端口开始成为了必然;

(二)能干什么

  • DPDK 是一个开发工具集,由一系列的相关工具库组成;
  • 用于加速在各 CPU 架构上数据包的处理;
  • 不是一个协议栈,可以基于 DPDK 开发更快的抓包工具,类似 tcpdump
  • 收包和发包所消耗的 CPU 时钟周期非常短;

(三)应用场景

DPDK 具备高性能、高吞吐、低延时等优点,具备如下的应用场景

  • 嵌入式
  • 金融交易
  • 电信领域
  • 网络设备,路由器、交换机、网关等;
  • LB、DNS
  • 软件定义网络(SDN)
  • 网络功能虚拟化(NFV)

缺点

  • 没有协议栈支持;
  • 传统网络工具无法使用;
  • 相对于普通程序开发,有较大的难度;

(四)如何实现高性能

  • 轮询
  • 用户态驱动,避免数据包拷贝;
  • 亲和性,减少 cpu 上下文切换;
  • Share Nothing、per-cpu、Local 化
    • 避免了锁和竞争,减少了 cpu 切换
  • 网卡队列与 cpu 绑定
  • 批处理收发包;
  • 高效的内存分配机制;
  • 大页内存
  • 系统调用 & Lib 库
  • 无锁消息队列;
  • Cache 预取、分支预测等;

DPDK 实际上涉及的技术知识点非常广,基本上涵盖了操作系统的方方面面,很难用简单语言来描绘出它的轮廓来,等我们逐步学习来领会它的强大和奥妙吧。

初识 F-Stack

终于要说到了我们的主角 F-Stack 了,是腾讯公司于 2017 年开源的一个项目,引起了很多人的关注和重视。下边从 F-Stack 的发展历程来简单认识它吧。

  • 2012 年,DNSPod 团队调研下一代 DNS 方案,为应对日益严重的 DDoS 攻击;
  • 2013 年,上线 DPDK + UDP 版本权威 DNS 解析程序 DKDNS,单机 10G 网卡性能达到 1100 万 QPS;
  • 2014 年,实现用户态简易 TCP/IP 协议栈,支持 TCP DNS,性能达到 60 万 TPS;
  • 2015 年,上线 DPDK + TCP/IP + 通用网关应用;
  • 2016 年,完善协议栈和应用层接口的开发框架 FlashStack,在腾讯云对象存储、HTTPDNS 等业务中上线;
  • 2017 年,移植 FreeBSD 协议栈替换自研协议栈,全新 F-Stack 开源;
  • 附上 Github 主页 | 官方网站

可以看到 f-stack 并不是一蹴而就的,经过了很多方案的迭代和尝试,最终形成了 f-stack 系统。

F-Stack 是一款兼顾高性能、易用性、通用性的网络开发框架,传统上的 DPDK 大多用于 SDN、NFV、DNS 等简单的应用场景下,对于复杂的 TCP 协议栈上的七层应用很少,市面上已经出现了部分用户态协议栈,如 mTCP、Mirage、IwIP、NUSE 等,也有用户态的编程框架,如 SeaStar 等,但统一的特点是应用程序接入门槛较高,不易于使用。

F-Stack 使用纯 C 实现,充当胶水粘合了 DPDK、FreeBSD 用户态协议栈、Posix API、微线程框架和上层应用(Nginx、Redis),使绝大部分网络应用可以通过直接修改配置或替换系统的网络接口即可接入 F-Stack,从而获得更高的网络性能;

关于 FreeBSD 协议栈

(一)为什么采用 FreeBSD 协议栈

  • 关于 SeaStar 协议栈存在的问题
    • 在 WAN 中使用存在一系列的问题;
    • 与已有的应用程序的不兼容性;
  • 关于 mTCP 用户态协议栈的问题
    • 只支持 TCP;
    • 用于生产环境的协议栈,太简单了;
    • vlan / vxlan / tunnel / bonding / Network Tools 等不支持;
  • 为什么不用 Linux?
    • 复杂的逻辑;
    • GPL;
  • 采用 FreeBSD 几个原因
    • 考虑到协议栈的稳定性、完整性、社区活跃性、生态完善程度等因素,适合生产环境,并可以快速跟进社区对协议栈的支持;
    • 支持多种网络工具,包括:sysctl、ifconfig、route、netstat、top 等;
    • 协议栈代码实现简单、清晰、易懂;

(二)对 FreeBSD 的修改

  • 通过外加文件、宏控制、以及 hook 相关实现进行的移植,对 FreeBSD 源代码的修改不到 100 行,对后续的跟进社区,升级版本非常友好;
  • 内存分配相关函数重新 hook 实现,当前使用 mmap 和 malloc,后续会替换成 rte_mempoolrte_malloc
  • 定时器使用 rte_timer 驱动,ticks 定时更新,timecounter 定时执行;
  • 调度:对 FreeBSD Network Stack 的内核线程、中断线程、定时器线程、sched、sleep 等进行了去除;
  • 锁:对 FreeBSD Network Stack 的锁进行了去除,包括 mtx、rw、rm、sx、cond 等;
  • 内存相关:phymem、uma_page_slab_hash、uma、kmem_malloc、malloc 等实现;
  • 全局变量:pcpu、curthread、proc0、thread0 等初始化;
  • 环境变量:setenv、getenv 实现;
  • SYS_INIT:mi_startup;
  • 时钟:timecounter、ticks、hz、定时器等实现;
  • 其他:Linux 和 FreeBSD 的 errno 转换、胶水代码、移除了不需要的功能模块;
  • 其他 glue 代码;

F-Stack 架构

F-Stack 充当胶水粘合了 DPDK、FreeBSD 用户态协议栈、Posix API 等,提供了一个全用户态的开发套件。

  • DPDK 是用来快速收发报文;
  • FreeBSD 提供协议栈能力;
  • 类 Posix-API 接口为应用程序提供调用协议栈的能力;

在这里插入图片描述

F-Stack 是多进程架构模型,每个进程独享协议栈,架构简图如下:

在这里插入图片描述

  • 总体架构特点
    • 使用多进程无共享架构;
    • 各进程绑定独立的网卡队列和 CPU;
    • 请求报文通过设置网卡的 RSS 散落到各进程进行处理;
    • 各进程拥有独立的协议栈,PCB 表等资源,消除了协议处理过程中的资源竞争;
    • 每个 NUMA 节点使用独立的内存池;
    • 进程间通信通过无锁环形队列;
    • 使用 DPDK 轮询模式,排除中断处理造成的性能影响;
    • 数据包从网卡直接接收到用户态;
    • 移植 FreeBSD Release 11.0.1 协议栈到用户态并与 DPDK 对接;
  • 全用户态
    • 无上下文切换;
    • 零拷贝;
    • 无硬中断和软中断;
  • Share-Nothing 架构
    • 线性扩展;
    • 无锁;
    • 减少本地 Cache Miss;
  • 提供了 Posix-Like API
    • ff_initff_run
    • ff_socketff_readff_writeff_keventff_epoll_ctlff_epoll_wait
    • 底层 fstack/lib
  • Coroutine(协程)
    • 微线程框架,具有同步编程、异步执行的特点,无需处理复杂的异步逻辑;
  • 工具(Tools)
    • sysctl、ifconfig、route、netstat、top
    • etc …
  • 优化 & 最佳实践
    • 引入 轮询 + 中断
    • Nginx Reload;
    • 使用性能高的多核 CPU;
    • 使用 10G、25G、40G 多队列网卡,支持硬件卸载,支持 RSS 队列数越多越好;
    • 尽可能配置多的 Hugepage;
    • 配置 config.ini 关闭抓包;

Roadmap

补加官方社区提供的 Roadmap:

  • 继续移植各种网络工具,方便 fstack 在各种网络环境下的部署和使用;
  • 移植 SPDK 用户态驱动至 fstack,提升磁盘 IO 性能;
  • 增加对数据流的 HOOK 点和镜像,方便对数据包进行自定义处理;
  • 提供协议栈的相关优化模块,如 TCP 加速、防护等;
  • 类 Posix 接口提供 LD_PRELOAD 方式,简化已有应用的接入方式;
  • 提供 PHP、Python 等语言的接口封装,方便相关 web 服务的快速接入;

结束语:本文作为 F-Stack 学习总结文章的开篇,简单介绍了 F-Stack 基本样貌,后续会逐层次来分析、总结相关的内容。