# 【NO.28】【涨知识】腾讯、京东、爱奇艺都在用DPDK，看看用它做了什么？

## 1. 腾讯 F-Stack

### 1.1F-Stack 开发背景

随着网卡性能的飞速发展，10GE 网卡已经大规模普及，25GE/40GE/100GE 网卡也在逐步推广，linux 内核在网络数据包处理上的瓶颈也越发明显，在传统的内核协议栈中，网卡通过硬件中断通知协议栈有新的数据包到达，内核的网卡驱动程序负责处理这个硬件中断，将数据包从网卡队列拷贝到内核开辟的缓冲区中（DMA），然后数据包经过一系列的协议处理流程，最后送到用户程序指定的缓冲区中。在这个过程中中断处理、内存拷贝、系统调用（锁、软中断、上下文切换）等严重影响了网络数据包的处理能力。操作系统的对应用程序和数据包处理的调度可能跨 CPU 调度，局部性失效进一步影响网络性能。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212122048224695382.png)

而互联网的快速发展亟需高性能的网络处理能力，kernel bypass 方案也越来被人所接受，市场上也出现了多种类似技术，如 DPDK、NETMAP、PF_RING 等，其核心思想就是内核只用来处理控制流，所有数据流相关操作都在用户态进行处理，从而规避内核的包拷贝、线程调度、系统调用、中断等性能瓶颈，并辅以各种性能调优手段，从而达到更高的性能。其中 DPDK 因为更彻底的脱离内核调度以及活跃的社区支持从而得到了更广泛的使用。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204856_34609.jpg)

### **1.2 F-Stack 开发框架**

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

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

### **1.3 F-Stack 架构**

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204857_84534.jpg)

F-Stack 总体架构如上图所示，具有以下特点:

- 使用多进程无共享架构。
- 各进程绑定独立的网卡队列和 CPU，请求通过设置网卡 RSS 散落到各进程进行处理。
- 各进程拥有独立的协议栈、PCB 表等资源。
- 每个 NUMA 节点使用独立的内存池。
- 进程间通信通过无锁环形队列（rte_ring）进行。
- 使用 DPDK 作为网络 I/O 模块，将数据包从网卡直接接收到用户态。
- 移植 FreeBSD Release 11.0.1 协议栈到用户态并与 DPDK 对接。

## 2. 京东 - SKYLB

随着京东业务的高速增长，作为应用入口的负载均衡，大流量大并发带来的挑战越来越严峻。本文主要介绍了京东商城设计和实践的一套高可靠，高性能的负载均衡器，名为SKYLB。是一个使用intel DPDK报文转发库，实现运行在通用X86服务器上自研的分布式负载均衡服务。配合网络路由器的多重等价路由协议（OSPF）或者边际网关协议（BGP），组成承担京东数据中心核心四层负载均衡的集群。最大限度的发挥普通X86服务器硬件资源的性能，实现一套适合于京东商城业务的低成本，分布式，高性能，可扩展的智能负载均衡系统。

负载均衡器一般介于网络上的路由器与后端服务器之间，负责将每个数据包通过一定的服务匹配，将其转发到后端服务器节点。充分考虑到京东商城数据中心全容器及全三层BGP组网的模型。以及基于DPDK的几乎达到网卡限速的性能，我们在设计负载均衡时，仅考虑实现了FULLNAT模式，即出向和入向的流量均通过负载均衡器，基本数据流程图如下图1所示：

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204857_48899.jpg)

一般根据业务及流量的规模的不同阶段来选择使用不同的负载均衡，通常我们在负载均衡的选择上大致有以下两个方向：

- 硬件负载均衡，如F5。CitrixNetscaler等；
- 软件负载均衡，如基于LVS，Haproxy，Nginx等开源软件来实现的负载均衡。

### **2.1 对于上述两种负载均衡的选择，各有优缺点，如下：**

1） 硬件负载均衡

可扩展性受限，无法跟上业务流量增长的需求。以及如618、双十一大促等瞬间流量高峰。

虽然可以成对部署避免单点故障，但是一般只能提供1+1冗余。

缺乏互联网快速迭代的灵活性，升级成本昂贵。

一般只能根据网络的情况来设定负载均衡，无关乎实际系统和硬件的情况。

成本较高。

2） 基于开源软件的负载均衡

可以根据实际系统和应用的状态来合理的负载，迭代、扩容和部署相对方便。

单台负载均衡性能相对较差，需要用集群来支撑负载均衡的整体性能。

性价比较低。

### **2.2 项目目标**

1. 设计实现一套高可靠、高性能、易维护及性价比高的L4负载均衡系统,。
2. 基于通用X86_64服务器框架，以及支持DPDK网卡硬件，易开发和移植。
3. 方便部署、删除和维护，集成到京东软件定义数据中心（JDOS2.0）系统，作为京东下一代软件定义数据中心的基础组件。
4. 负载均衡下的服务器基于系统应用负载流量均摊，负载均衡器提供N+1 冗余，借助OSPF/BGP控制负载均衡器的流量负载。
5. 基于系统应用级别的探活，自动故障检测及流量快速恢复。

SKYLB一种基于DPDK平台实现的快速可靠的软件网络负载均衡系统。不仅可以快速的横向扩展，还可以最大限度的提升负载均衡单个NIC的处理转发速度，来实现L4的负载均衡。借助DPDK的优势，如便利的多核编程框架、巨页内存管理、无锁队列、无中断poll-mode 网卡驱动、CPU亲和性等等来实现快速的网卡收发及处理报文，后续考虑TCP/IP 用户态协议实现和优化，进而实现L7负载均衡。

### **2.3 系统概览**

- 工作场景

SKYLB部署在京东容器集群JDOS的前端，对于一个应用集群，发布一个或多个VIP到SKYLB服务上，当客户端需要访问应用节资源URL，首先通过域名访问JD智能分布式DNS服务（SKYDNS详见[https://github.com/ipdcode/skydns](https://link.zhihu.com/?target=https%3A//github.com/ipdcode/skydns)）, SkyDns会智能返回当前最近且状态正常且负载正常的VIP服务的IP,客户端就会向VIP去请求连接。

SKYLB节点上运行了一个路由发布服务agent，我们使用该agent与开启OSPF/BGP的路由器做路由交互，当SKYLB的上层路由器接收到请求VIP的数据包时，路由器通过（OSPF/BGP）的等价多路径转发协议选择一个可以使用的发布该VIP的SKYLB节点，将报文转发给一个SKYLB节点。通过这种策略来实现VIP的发布和横向容量负载能力扩展。

当报文通过上述步骤到达SKYLB负载均衡后，通过常用的负载均衡策略(round robin，一致性hash ，最小连接数)，将数据包送到相应的后端容器服务。

- 系统架构

JingdongDatacenter Operating System(JDOS) 是基于JDOS提供物理机/虚拟机/容器的统一管理系统、配备灵活的网络互连、可靠的分布式共享存储，实现软件定义数据中心的计算资源统一管理和集群管理。通过使用JDOS，可以直接迅速得到任意需要的计算、存储、网络、安全等方面的资源和能力。SKYLB作为整个JDOS系统的一个重要组成部分，负责提供集群的L4负载均衡能力，通过restful API等接口与JDOS系统交互。用户可以通过统一调度管理平台便捷的创建、删除、迁移负载均衡系统。同时多个应用服务进行流量分发的负载均衡服务，从而扩展应用系统对外的服务能力，并且通过消除单点故障提高应用系统的可用性。

系统的基本架构如下图2所示，每一个集群配备一组可容灾的负载均衡控制中心,主要通过restful api接口负责与JDOS调度中心交互，接收vip的配置请求。同时我们在每一个SKYLB的节点上运行一个代理进程，该代理进程通过gRPC与控制中心连接。接收控制中心下达的创建及删除vip,后端server endpoint服务等一系列指令，通过load balancer 提供的命令行执行相应的指令。接收load balancer 关于流量及报文的监控信息，对于流量及监控进行告警，并且通知控制中心和调度中心进行扩容等操作。

代理进程同时还负责后端服务 server endpoint基于服务可用性的健康检查，及时根据后端服务的状态通过命令行进行添加和删除的操作。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204858_18522.jpg)

- 优势

1）扩展性

支持动态添加和删除后端服务的容器，实现无缝的伸缩；在伸，缩过程中，对相关调用和访问者无影响。

2）高可用性

提供多活负载均衡，有多个VIP，它们对应一个域名，自研DNS服务SKYDNS会根据请求的客户端IP智能解析可用的VIP，返回给用户，从而实现更高的可用性；即使一个VIP不可用，也不会影响业务系统对外提供服务。同时借助OSPF/BGP等协议实现负载均衡的横向扩充

3）服务能力自动可调

SKYLB根据VIP实际接收流量的负载需要调整负载均衡的服务能力，比如流量、连接数的控制等指标。

- 功能特点

1）协议支持

负载均衡支持包含TCP、UDP协议的四层负载均衡，配备健全的链路跟踪机制，以及多种调度策略，用户可以根据服务的需要创建合适自己的负载均衡。

2）高可用性

支持容器的健康检查，除传统的IP+Port，并加入对URL检查，保证应用可用性： 健康检查频率可自定义；一旦探测到异常，则不会将流量再分配到这些异常实例，保证应用可用性。

3）集群部署，多层次容错机制： 负载均衡采用集群部署，支持热升级，机器故障和集群维护对用户完全透明，结合DNS使用还可支持全局负载均衡。

4）灵活性

支持多种流量调度算法，使得流量分配更均匀： 负载均衡支持加权轮询和最小连接数这两种调度算法，可根据自身需求选择相应的算法来分配用户访问流量，并支持设置后端容器权重，使得流量调度更均匀，提升负载均衡能力。

支持会话保持，满足用户个性化需求： 负载均衡通过IP地址实现会话保持，可将一定时间内来自同一用户的访问请求，转发到同一个后端容器上进行处理，从而实现用户访问的连续性。

5）易用性

提供多种管理途径，轻松操纵负载均衡： 用户可通过控制台轻松实现负载均衡器的配置、释放等功能。后续会开放标准的API或SDK提供给用户，从而自己开发对负载均衡的控制管理。

## 3. 爱奇艺 - DPVS

DPVS 是 iQiYi 采用 DPDK 技术开发的的高性能四层负载均衡器。与 Linux 内核的 LVS （Linux Virtual Server）相比，DPVS 具有如下特点：

- 更高的性能：DPVS 的包处理速度，1 个工作线程可以达到 2.3 Mpps，6 个工作线程可以达到万兆网卡小包的转发线速（约 12Mpps)。这是主要因为 DPVS 绕过了内核复杂的协议栈，并采用轮询的方式收发数据包，避免了锁、内核中断、上下文切换、内核态和用户态数据拷贝产生的性能开销。
- 更完善的功能：从转发转发模式看，DPVS 支持 Direct Routing（DR）、NAT、Tunnel、Full-NAT、SNAT 五种转发模式，可以灵活适配各种网络应用场景；从协议支持上看，DPVS 支持 IPv4 和 IPv6 协议、且最新版本增加了 NAT64 的转发功能，实现了用户从 IPv6 网络访问 IPv4 服务。
- 更好的维护性：DPVS 是一个用户态程序，与内核功能相比，功能开发周期更短、调试更方便、问题修复更及时。

### **3.1 DPVS 基本原理**

DPVS 的总体架构如下图所示，下面对相关的几个点着重解释说明一下。

- **Master/Worker 模型**

DPVS采用经典的 Master/Worker 模型。Master 处理控制平面，比如参数配置、统计获取等；Worker 实现核心负载均衡、调度、数据转发功能。另外，DPVS 使用多线程模型，每个线程绑定到一个 CPU 物理核心上，并且禁止这些 CPU 被调度。这些 CPU 只运行 DPVS 的 Master 或者某个 Worker，以此避免上下文切换，别的进程不会被调度到这些 CPU，Worker 也不会迁移到其他 CPU 造成缓存失效。

- **网卡队列 /CPU 绑定**

现代的网卡支持多个队列，队列可以和 CPU 绑定，让不同的 CPU 处理不同的网卡队列的流量，分摊工作量，实现并行处理和线性扩展。DPVS是由各个 Worker 使用 DPDK 的 API 处理不同的网卡队列，每个 Worker 处理某网卡的一个接收队列，一个发送队列，实现了处理能力随CPU核心、网卡队列数的增加而线性增长。

- **关键数据 per-cpu及无锁化**

内核性能问题的一大原因就是资源共享和锁。所以，被频繁访问的关键数据需要尽可能的实现无锁化，其中一个方法是将数据做到 per-cpu 化，每个 CPU 只处理自己本地的数据，不需要访问其他 CPU 的数据，这样就可以避免加锁。就 DPVS 而言，连接表，邻居表，路由表等，都是频繁修改或者频繁查找的数据，都做到了 per-cpu 化。
在具体 per-cpu 的实现上，连接表和邻居表、路由表并不相同。对于连接表，高并发的情况下，不光是查找，还会被频繁地添加、删除。我们让每个 CPU 维护的是不相同的连接表，不同的网络数据流（TCP/UDP/ICMP）按照 N 元组被定向到不同的 CPU，在此特定 CPU 上创建、查找、转发、销毁。同一个数据流的包，只会出现在某个 CPU 上，不会落到其他的 CPU 上。这样就可以做到不同的 CPU 只维护自己本地的表，无需加锁。另一方面，对于邻居和路由表，这种系统“全局”的数据，每个 CPU 都是要用到它们的。如果不采用”全局表+锁保护“的方式，而要做成 per-cpu，也需要让每个 CPU 有同样的视图，也就是每个 CPU 需要维护同样的表。对于这两个表，采用了跨 CPU 无锁同步的方式，虽然在具体实现上有小的差别，本质上都是通过跨 CPU 通信（路由是直接传递信息，邻居是克隆数数据并传递分组给别的 CPU），将表的变化同步到每个 CPU。不论用了什么方法，关键数据做到了 per-cpu 之后没有了锁的需求，性能也就能提升了。

- **用户态轻量级协议栈**

四层负载均衡并不需要完整的协议栈，但还是需要基本的网络组件，以便完成和周围设备的交互（ARP/NS/NA）、确定分组走向 （Route）、回应 Ping 请求、健全性检查（分组完整性，Checksum校验）、以及 IP 地址管理等基本工作。使用 DPDK 提高了收发包性能，但也绕过了内核协议栈，DPVS 依赖的协议栈需要自己实现。

- **跨 CPU 无锁消息**

之前已经提到过这点了。首先，虽然采用了关键数据 per-cpu等优化，但跨 CPU 还是需要通信的，比如:

- - Master 获取各个 Worker 的各种统计信息
  - Master 将路由、黑名单等配置同步到各个 Worker
  - Master 将来自 KNI 的数据发送到 Worker（只有 Worker 能操作 DPDK 接口发送数据）

- 既然需要通信，就不能存在互相影响、相互等待的情况，因为那会影响性能。为此，我们使用了 DPDK 提供的无锁 rte_ring 库，从底层保证通信是无锁的，并且我们在此之上封装一层消息机制来支持一对一，一对多，同步或异步的消息。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204859_92048.jpg)

下图给出了 DPVS 详细的功能模块，主要包含如下五大部分：

- 网络设备层
  负责数据包收发和设备管理，支持 vlan、bonding、tunnel 等设备，支持 KNI 通信、流量控制。

- 轻量级协议栈层
  轻量级的 IPv4 和 IPv6 三层协议栈，包括邻居、路由、地址管理等功能。

- IPVS 转发层
  五种数据转发模式的连接管理、业务管理、调度算法、转发处理等。特别地，Full-NAT 转发模式下支持了 IPv6-to-IPv4（NAT64） 转发、 SYN flood 攻击防御、 TCP/UDP 的源地址获取（toa/uoa）等功能。

- 基础模块
  包含定时器、CPU 消息、进程通信接口、配置文件等基础功能模块。

- 控制面和工具
  用于配置和管理 DPVS 服务的工具，包括 ipvsadm、keepalived、dpip，也支持使用进行 quagga 集群化部署。

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/12/20221212204859_33031.jpg)

原文链接：https://zhuanlan.zhihu.com/p/406806789

原文作者：零声Github整理库