DPVS - 小米高性能负载均衡器
负载均衡简介
负载均衡是指通过一台负载均衡器将客户端请求分散到不同的服务器上形成集群模式。早期的负载均衡主要是针对某个域名利用 DNS 轮询解析出不同的 IP 地址,以此来实现最简单的轮询式负载均衡。而目前,负载均衡主要包含四层和七层的负载均衡,四层负载均衡多用于转发,七层负载均衡主要用于代理。
小米负载均衡现状及面临的问题
小米在初期选择了开源四层负载均衡软件 LVS,前期也对 LVS 的部署模式进行了一些探索,以下是几种模式的优缺点对比
在几种模式的权衡中,我们选择了 FULLNAT + OSPF-ECMP(一致性 hash)模式部署,这种模式适合大规模的集群化部署,也能够实现单机 LVS 故障时的平滑迁移。而随着公司业务的进一步发展,LVS 集群需要频繁的进行扩容操作,服务器数量从几十台增长到了几百台,日常运维的痛点也随之而来:
- 硬件成本居高不下
- 集群管理难度越来越大
- LVS 本身不再维护,故障排查困难
在这种背景下,我们自研了高性能的 DPVS 来替代现有的 LVS。
高性能 DPVS
1
什么是dpdk
DPDK 全称是数据平面开发套件 (Data Plane Development Kit),由 6WIND,Intel 等多家公司开发,主要基于 Linux 系统运行,用于快速数据包处理的函数库与驱动集合,可以极大提高数据处理性能和吞吐量,提高数据平面应用程序的工作效率。
2
dpvs特性
支持 SYNPROXY
运行在 FULLNAT 模式下且支持 TCP/UDP
支持 OSPF、ICMP 协议
支持 PERSISTENT
DPVS 如何实现高性能的四层 LB
1
避免中断
linux 系统使用中断方式来通知 CPU 来处理数据包,在大流量的场景下,中断会占据大量的时间,影响数据包的转发效率,在 DPVS 中使用了 DPDK 提供的 PMD 驱动通过轮询方式收发包,避免了在大流量场景下的中断开销。同时由于 PMD 驱动的存在,使得数据包不再经过内核协议栈,这对于提高包转发效率也至关重要。
2
Memory Pool
Memory pool 即为内存池,也被称为动态内存分配,内存池能够有效的避免内存碎片,并且 mempool 提供了一些其他可选服务,例如核本地缓存和内存对齐辅助器,内存对齐辅助器可确保对象被均匀地填充到所有 DRAM 或 DDR3 通道上(对象被均匀的放在通道上,会提高存取速度)。
3
无锁化
无锁化队列
DPDK 实现了 ring library 来提供一个无锁的、有限大小的环形 FIFO 队列,它支持多消费者或单消费者出队和多生产者或单生产者入队,同时也支持批量、爆发 (burst) 出、入队。相比于链表,每次的入队或出队只需比较 void * 大小的数据并且只需执行一次 CAS(Compare-And-Swap)操作且该操作是原子的。
session 表无锁化
对于多队列网卡,RSS 能够根据数据报的元信息将数据包分散到不同的网卡队列上,每个网卡对应的 CPU 会从网卡队列中读取数据包进行处理。但对于 FULLNAT 模式而言,其入向流量和出向流量在经过 RSS hash 后可能会分布在不同 CPU 绑定的网卡队列上,例如:入向数据包携带的元信息为(cip1,cport0,vip1,vport1),此时 RSS(cip1,cport0,vip1,vport1)= hash_value1 → cpu0;出向数据报携带的元信息为(rip1,rport1,lip1,lport1),此时 RSS(rip1,rport1,lip1,lport1)= hash_value2 → cpu1。在不同的 CPU 上处理意味着 session 表需要加锁(可能引发并发操作)。为了无锁化,需要使入向和会向数据包经过同一网卡队列即同一 CPU(同一 CPU 将串行处理两个方向的数据包,session 表无需加锁)。DPVS 的实现是利用 INTEL 网卡的对称 RSS,选择合适的 RSS_KEY 保证 RSS(rip1,rport1,lip1,lport1)→ cpu0,此时入向数据包和出向数据包都经过 cpu0 处理,而无需加锁。
4
cpu亲和
在 NUMA 系统下,CPU 访问它自己的本地存储器比非本地存储器更快。在分配内存时,使用 DPDK 提供的 API 可以分配某个 CPU 所在 NUMA 的内存,在初始化网卡收发队列时,同样可以控制网卡和 CPU 和亲和性。
5
巨页
操作系统会对物理内存分成固定大小的页,按照页来进行分配和释放,编程时只使用虚拟内存进行编程,不用关系物理内存的映射,而处理器在寄存器收到虚拟地址之后,需要根据页表把虚拟地址转换成真正的物理地址。TLB(translation lookaside buffer)是一种小、专用、快速的硬件缓冲,它只包括页表中的一小部分条目,利用虚拟地址查找物理地址时,先根据页号在 TLB 查找,如果命中则得到页内地址,访问内存;否则则在内存中的页表中得到页内地址,将其存入 TLB,访问内存。从虚拟地址到物理地址的转换逻辑我们知道:如果一个程序使用了 512 个内容页也就是 2MB(512*4KB=2048KB=2MB)大小,那么需要 512 个页表表项才能保证不会出现 TLB 不命中的情况。
通过上面的介绍,TLB 是有限的,随着程序的变大或者程序使用内存的增加,势必会增加 TLB 的使用项,最后导致 TLB 出现不命中的情况。因此,大页的优势就显现出来了。如果采用 2MB 作为分页的基本单位,那么只需要一个表项(2MB/2MB=1)就可以保证不出现 TLB 不命中的情况;对于消耗内存以 GB 为单位的大型程序,可以采用 1GB 为单位作为分页的基本单位,减少 TLB 不命中的情况。
集群化部署
图1 多运营商单一部署
图2 多运营商混合部署
对于 DPVS 部署方案我们的选型是 OSPF+ECMP(一致性 hash)。ECMP 能够将不同流的数据报转发到集群的不同节点上,同时通过 OSPF 协议的心跳包保证了在某台服务器异常时能够动态的将其剔除出集群(如图 1)。但是这种模式还存在一定的优化空间:单台服务器只能承担单个运营商的流量,而不同运营商的流量可能是不均的,也就是说有一部分服务器资源被浪费了,为了提高服务器的使用率,我们利用 linux vlan 以及 PBR 在单台服务器上起多个 OSPF 进程,每个 OSPF 进程对应一个运营商线路(如图 2),这样我们能够保证每台服务器上的流量都均分了每个运营商的流量。
性能测试
1
测试环境
CPU:Intel(R) Xeon(R) CPU E5-2620 v2 @ 2.10GHz,24 个逻辑核
内存:128G
网卡:Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection
操作系统:Ubuntu 16.04.1
内核版本:4.4.0-92-generic
dpvs 版本:v0.4
2
拓扑
三台测试机,dpdk03 模拟 lvs 服务器,运行 dpvs;dpdk01 模拟客户端,运行 dpdk-pktgen,往 lvs 服务器发送数据包;dpdk02 模拟 real server,运行 dpdk-pktgen,从 lvs 服务器接收数据包。所有的网卡都接在同一台交换机上。
dpvs 上配置 1 个 vip,10 个 real server IP,10 个 local address,LVS 连接数为 1000。
3
性能指标
丢包率:
负载为 10000Mbit/s 的情况下,60s 内没有转发的包占收到的包的百分比。
吞吐量:
不丢包的情况下,每秒转发包的数量(单位 Mpps)。
4
测试方法
测试包大小为 64、128、256、512、1024 字节
测试包为 UDP 包
5
DPVS配置
使用 16 个逻辑核作为 worker,对应 16 个网卡队列,2 个逻辑核作为发送 IO 核,3 个逻辑核作为接收 IO 核,根据 vip、local address、real server 数量及连接数,dpvs 配置如下:
内存池大小:
- mbuf 2560000
- svc 1024
- rs 1024
- laddr 1024
- conn 2048
哈希表大小:
- conn 2^11
- svc 2^8
- rs 2^4
- worker CPU 数量:16
- tx CPU 数量:2
- rx CPU 数量:3
- ring_sizes:1024, 1024, 1024, 1024
- burst_sizes:(144,144),(144,144),(144,144)
6
测试流程
1)在 dpdk03 上启动 dpvs,按照上述配置要求,准备测试环境
2)在 dpdk02 上启动 dpdk-pktgen,监控网卡,准备收包
3)在 client 上启动 dpdk-pktgen,按要求设置,准备发包
4)开始发包,收集测试结果
7
测试结果
丢包率:发包速率为 10000Mbit/s 时,60s 内发送包数量、数据量与接受包数量、数据量:
发包速率为 10000Mbit/s 时,丢包率与包大小的关系:
吞吐量:万兆网卡上,不同大小的数据包吞吐量的理论值与实际值
总结
本文介绍了负载均衡的相关知识,以及小米对于高性能 4 层负载均衡的实践 ---DPVS。DPVS 开发利用了 DPDK,充分发挥和利用 DPDK 的优势,并且结合一些内部生产环境需求进行了一些优化和调整。并且介绍了一种 DPVS 的部署方式,希望能够对大家有所帮助。