如何使用程序员四层负载均衡DPDK(模块数据内核地址开源)「dpdk 负载均衡」

使用DPDK加速四层负载均衡上文介绍了LVS的性能瓶颈及如何通过一些技术手段提高性能,下面以开源的DPVS项目为例(见链接[29]),看一看如何使用DPDK,在用户态下实现一个高性能的四层负载均衡器
我们先从大的视角看一下DPVS的目标和总体设计架构
有些设计要求是一开始就有的,如性能提升的要求、兼容LVS的要求,有些则是实现和应用过程中逐步演化和加入的,并非在项目开始时就有很明确的目标,如VLAN、SNAT、一致性哈希支持
这在软件设计中比较常见,很难一开始就设计出没有漏洞的完美系统
分阶段快速迭代也更符合快速变化的互联网环境
每年发布一个版本,可能是十几年前的软件开发模式了
因此,不论目标的先后,在此先把这些要求和目标统一描述一下,从大的方面来看一个基于DPDK的高性能负载均衡器需要完成怎样的事情
首要的设计目标是“高性能”,这样可以带来成本上的优势,从而减少机器的使用和运维方面的难度
为了实现高性能,会用到上文提到的大部分技术,如Kernel Bypass、Share-Nothing思想及关键数据无锁化、避免上下文切换、轮询、在无锁的情况下跨CPU通信等
四层负载均衡提供的高可用特性包括后端服务器的高可用和四层负载均衡器本身集群的高可用
其中,后端服务器的高可用保证是通过负载均衡设备对后端服务器进行健康检查实现的
和LVS一样,DPVS的健康检查使用开源软件Keepalived完成,如果健康检查失败,则Keepalived会实时在LB上将该后端服务器的权重设置为0
负载均衡器集群的高可用通过ECMP和Keepalived共同完成,其中ECMP可以通过OSPF/BGP等路由协议实现,负载均衡器将服务的虚拟IP(VIP)地址通过OSPF/BGP等通知上游交换机,交换机使用ECMP技术将不同的流量均衡到负载均衡器集群内的机器,如果检测出负载均衡器不可用或该VIP所有后端服务器不可用,则可以把VIP从负载均衡器中摘除,不再通过OSPF/BGP 等 路 由 协 议 对 其 进 行 通 知
ECMP 可 以 通 过 开 源 套 件 Quagga 的ospfd/bgpd程序来实现
我们知道,无论是Keepalived还是ospfd/bgpd,都是运行在Linux设备(真实设备或虚拟设备)、Linux协议栈及Socket系统调用上的
而DPDK这样的Kernel Bypass应用会完全接管网卡
导致无法直接使用Keepalived和ospfd/bgpd,解决的方案是使用DPDK的KNI接口,将Keepalived和ospfd/bgpd关 心 的 数 据 通 过 KNI 接 口 传 递 到 Linux 协 议 栈 , 然 后 递 交 给Keepalived/ospfd/bgpd,在发送数据时,可以通过KNI接口发送到DPVS,再从DPDK接管的网卡中发送出去
KNI接口同样适用于运行sshd来提供服务器的SSH访问
具体实现是将通过了路由查找(目标是多播、广播或本机IP地址)且DPVS又不需要的数据交给KNI接口
DPVS为了替换原来的LVS,必须实现其绝大部分功能,如FullNAT、DR等转发模式,RR、WRR、WLC等调度模式,Syn-proxy功能,Service、RS、Conn管理和TOA,连接模板,fast-xmit等
而DPVS特有的挑战是,因为使用DPDK绕开了内核协议栈,所以我们需要一个简单的协议栈功能,它不需要TCP/UDP和Socket的实现,但是需要基本的IPv4/IPv6、ARP/NDisc、路由、地址管理和基本的ICMPv4/ICMPv6的实现,而且需要保证性能不能成为瓶颈
为了适应复杂的IDC部署环境,还需要支持VLAN、Bonding、Tunnel接口等,这些本来在Linux上现成的功能,需要在DPVS中重新实现
7.4.1 高性能负载均衡器的架构接下来,我们从总体架构开始逐步分析DPVS的实现
DPVS的总体架构如图7-4所示
图7-4 DPVS的总体架构其中主要包括以下几点
1.Master/Worker模型DPVS采用经典的Master/Worker模型
Master处理控制面,如参数配置、统计数据的获取等;Worker实现核心负载均衡、调度、数据转发功能
另外,DPVS使用多线程模型,毕竟负载均衡逻辑并没有Nginx那么复杂,也不需要由Master管理、监控Worker进程是否正常运行,甚至支持热升级等,使用多线程可以满足要求
使用该模型后,将Master及各个Worker和CPU内核绑定,并且禁止这些CPU被调度
这些CPU内核只运行DPVS的Master或某个Worker,以此避免上下文切换,别的进程不会被调度到这些CPU中,Worker也不会迁移到其他CPU中造成缓存失效
2.网卡队列/CPU绑定现代的网卡支持多个队列,可以结合SMP架构的多个内核来使用以提高效率,让不同的内核处理不同的网卡队列的流量,分摊工作量,实现并行处理和线性扩展
在Linux中,我们通过中断亲和性的设置,将不同的队列中断设置到不同的内核
在基于DPDK的DPVS上,具体的绑定方法则是由各个Worker使用DPDK的API处理不同的网卡队列,每个Worker都会对网卡的一个接收队列和一个发送队列进行处理
3.关键数据Per-Core及无锁化内核性能问题的关键在于资源共享和锁
所以,被频繁访问的关键数据需要尽可能地实现无锁化,其中一个方法是将数据进行Per-Core化,不同的CPU内核只处理自己本地的数据
不需要访问其他CPU内核的数据,这样就可以避免加锁
对DPVS而言,connection表、邻居表、路由表等都是频繁修改或频繁查找的数据,需要进行Per-Core化
在Per-Core的具体实现上,connection表(连接表)和ARP/route表并不相同
对于connection表,在高并发的情况下,不仅会被频繁地查找,还会被频繁地添加、删除
我们让每个CPU维护的是不相同的连接表,不同的网络数据流(包含TCP和UDP两种协议的数据流)按照n元组被定向到不同的CPU内核,在此特定CPU内核上创建、查找、转发、销毁
同样的数据流(即n元组匹配)只会出现在某个CPU内核上,不会落到其他的CPU上
这样就可以做到不同的CPU只维护自己本地的表,无须加锁
当然,只看入口方向流量,凭借网卡的RSS(Receive Side Scaling)就可以做到将同一个数据流通过哈希算法映射到同一个队列,但要实现同一个数据流在出口和入口两个方向的数据都落在同一个CPU内核上就没那么容易了
后面会通过“返程数据亲和性”问题仔细讨论如何解决这个问题,避免connection查找失败无法转发
另外,对于邻居表和路由表,每个CPU都会用到系统的“全局”数据
如果不采用“全局表+锁保护”的方式,而采用Per-Core(每个CPU内核保存一份数据)的方式,那么需要让每个CPU有同样的视图,也就是需要维护同样的表
邻居信息、路由信息不像connection表那样会频繁变化,最多就是配置、收到ARP/NS/NA及表项超时时需要修改一下表
这两个表采用了跨CPU无锁同步的方式,虽然在具体实现上有一些小差别(路由表无锁实现使用DPVS消息机制,邻居表无锁实现使用数据复制和转发的方式),但本质上就是通过跨CPU通信将表的变化同步到每个CPU内核上的
不论使用什么方法,只要做到了Per-Core化之后没有了锁的需求,性能也就能提升了
4.用户态轻量级IP协议栈四层负载均衡并不需要完整的协议栈,但还是需要基本的网络组件,以便完成和周围设备的交互(ARP/NS/NA)、确定分组走向(route)、回应ping请求、进行健全性检查(checksum,检查分组完整性)及管理IP地址等基本工作
但是,采用DPDK实现Kernel Bypass后,这些需要自己实现
5.跨CPU无锁消息虽然采用了关键数据Per-Core化等优化措施,但在以下场景中还是需要跨内核通信的
● Master获取Worker的各种统计信息
● Master将路由、黑名单等配置同步到各个Worker
● Master将来自KNI接口的数据发送到Worker(只有Worker能操作DPDK接口发送数据)
既然需要进行跨内核通信,就不能存在互相影响、相互等待的情况,这样会影响性能
为此,我们使用了DPDK提供的无锁rte_ring库,从底层保证通信是无锁的
当然,我们在此之上要封装一层消息机制来支持一对一、一对多、同步或异步的消息
应该尽量使用异步消息来进行各个CPU内核的逻辑解耦,解除依赖,不要因为互相等待而影响性能
Master作为控制面可以短时间等待一下各个Worker回复,但是绝对不能使Worker一直等待回复而影响转发
7.4.2 高性能负载均衡器功能模块了解了DPVS的总体架构后,我们看一下DPVS的功能模块,如图7-5所示,从下至上包括
● 网络设备层(Network Device)
● 轻量级IP协议栈(Lite IP Stack)
● 负载均衡层(IPVS)
● 基础功能模块(Common Module)
● 控制面(Control Plane)
图7-5 DPVS的功能模块1.网络设备层(Network Device)网络设备层包括管理设备收发包的网络接口,也包括一些虚拟设备的支持
同时,流量控制和硬件地址管理也是在该层实现的
流量控制类似于Linux的Traffic Control/TC;硬件地址管理的目的是支持MAC多播
● 网络接口:netif模块
网络设备层的核心模块是netif,它可以处理接口的收发包TX/RX,而且集成了Worker的主循环
最初,主循环设计得比较简单,主要包含TX、RX、timer;接着,开始支持消息处理、可注册的“job”;后来,开始支持网卡绑定、ARP环形队列
随着时间的推移,netif模块变得越来越庞大,成为整个项目最大的源文件,包含的数据结构也越来越复杂,有一定的重构空间,至少可以把数据包分发逻辑和主循环从模块中独立出来
● 虚拟设备:VLAN、KNI、Bonding、Tunnel
除了netif模块,网络设备层还实现了众多虚拟设备:VLAN、KNI、Bonding、Tunnel(IP-in-IP和GRE隧道)
因为IDC的部署环境比较复杂,所以有时需要通过多网卡绑定解决交换机单点问题,通过多个VLAN接口接收带有不同VLAN标签的数据帧的情况(针对交换机透传VLAN标签、即没有设置为access-port的情况),通过隧道来支持特殊的应用(SNAT-GRE)场景,通过KNI来和Linux上运行的应用(如Keepalived、ospfd、sshd)进行交互
不过,DPVS并没有实现VxLAN,因为这需要UDP协议的支持,目前轻量级IP栈还不支持UDP协议
不过目前VxLAN环境IDC的endpoint是交换机,不需要DPVS来处理VxLAN
● 流量控制:TC模块
为了实现相对精确、灵活的流量控制,如对来自网段为192.168.1.0/24、目的地址为192.168.2.0/24的TCP端口80的流量限流10Mbps,DPVS实现了流量控制模块——TC模块
DPVS的TC模块的实现参考了Linux内核中TC模块的实现方法,是Linux内核的TC模块的一个“简化”版本
这里介绍以下两个问题
首先,因为TC模块支持灵活配置,无法预先限定5元组、ingress/egress接口,也无法预知和限定匹配规则的流量在哪个CPU,可能出现符合规则的流量出现在多个CPU的情况,所以该模块采用全局的match规则表,并使用锁保护来应对上述情况
其次,因为模块支持模糊的规则匹配,无法像<VIP:port>类型的“Service”那样设计Per-Core的match表,所以match配置链的实现只能是单链表
以上两点都会造成性能问题,第一点使用了全局表和锁,第二点在规则链比较长的情况下,每个分组都要逐一检查链上的规则
因此,TC灵活配置这个功能默认是关闭的
对于流量控制这个主题,有时业务的需求可能是比较明确的,如对某个VIP进行限制,或者在SNAT模式下对某个外网地址或某个内网主机网段进行限制
这些特定化的需求也许可以避免因为使用灵活的模糊规则匹配带来的性能问题,但同时也会让DPDP的TC代码过多关注业务应用策略,和业务需求有所耦合
这里存在一个取舍问题
2.轻量级IP协议栈层(Lite IP Stack)虽然四层负载均衡并不需要一个包含TCP/UDP Socket在内的完整的协议栈,但还是需要基本的IPv4/IPv6、路由、ARP/NS/NA和ICMPv4/ICMPv6的功能,以便完成和其他网络设备的交互,做一个“合格”的网络节点和Middlebox
这一层支持的模块有以下几个
● IPv4/IPv6模块
IPv4/IPv6模块主要包括数据包健全性(sanity)、校验和、输入/输出路由查找、L2/L3层分用(递交)、分片重装、IP头封装、TTL等功能,支持数据接收、发送和转发
同时,该模块还嵌入了类似Netfilter的Hook机制,提升了代码可扩展性和模块化
此外,该模块还提供了必要的API供其他模块注册回调和使用
IPv4/IPv6模块的实现参考了Linux相应的代码,因为IP协议是无状态的,不需要维护信息,所以IPv4/IPv6模块基本不存在锁的需求,也就不会出现加锁造成的性能问题
因为同一个数据流的分组必须落在同一个CPU的约束上,而实现的RSS/FDIR需要用到L4信息,这和分片的部分数据包不包含L4信息矛盾,所以核心的转发部分(IPVS)是不支持分片的
但是,IPv4模块本身支持分片,IPv6标准不推荐使用分片,所以DPVS也不支持IPv6分片
多播路由(转发)目前还不支持分片,但可以通过KNI机制透传多播路由到Linux
事实上,L3/L4层分别使用的协议/回调映射表inet_prots[]和类Netfilter的Hook表inet_hooks[]是全局表,且可以修改
但是,假设这样的修改只允许在程序初始化时进行,如注册L4处理函数icmp_rcv()或dp_vs_in()时
一旦初始化完成,在开始收发数据时就不能修改全局表,这时可以去掉锁,以便优化性能
● 路由模块
路由模块的存在可以支持输入/输出路由查找、接口选择、MTU
毕竟,DPVS环境还是会有多个接口和跨网络的需求
● 邻居(ARP,Neighbour)模块
有了邻居模块,DPVS就可以动态解析邻居地址、响应邻居的解析请求、发送免费ARP、缓存未解析成功前传输的分组数据,以及实现简单的邻居表状态和超时机制
从而使得DPVS能够满足基本的IDC环境下的邻居功能
● 地址管理
Linux中的地址是可以被动态添加、删除的,一个接口可以配置多个IP地址
DPVS也不例外,尤其是DPVS的部署环境可能非常复杂,要求有多个接口,且每个接口需要对应多个地址,所以DPVS中有一个地址管理模块可以实现相关的需求
● ICMPv4/ICMPv6.DPVS要能回复ICMP ping(Echo Request),这样才方便调试
同时,DPVS也需要提供API来发送ICMP的报文,例如“超时”“需要分片”“目标端口不可达”等类型的ICMP报文,以便和各个网络节点交互
如果DPVS不能响应用户的ping请求,或者因为不支持“需要分片”ICMP报文而不能支持IP分片,又或者因为不支持“目标端口不可达”ICMP报文而影响了TCP路径、MTU发现和路由追踪功能,则DPVS是不合格的网络设备
3.负载均衡层(IPVS)负载均衡层(IPVS或大模块)完成了DPVS的核心功能,即四层负载均衡
该层的大部分代码参考LVS的IPVS内核模块实现,该模块分为以下几个子模块
● 调度器(模块名:scheduler)
● 协议模块(模块名:proto)
● 服务管理(模块名:service)
● Real Server管理(模块名:dest)
● 连接管理(模块名:conn)
● 传输管理(模块名:xmit)
● FullNAT本地地址及地址池(模块名:laddr及sapool)
● TOA/UOA模块(目的:获取真实客户端IP地址)
这些子模块共同完成了四层负载均衡功能,提供了FullNAT、DR、SNAT等转发(传输)模式,支持TCP、UDP、ICMP等协议的转发,可以实现RR、WRR、WLC及ConHash等调度模式,实现可设置的<proto,vip,vport>或match等均衡服务
同时,配合Keepalived完成Real Server的健康检查和动态摘除、动态恢复、连接模板、限流等功能,以及TCP的防Syn-flood的Syn-proxy功能
这里详细介绍一下Socket地址池(sapool)
首先,资源池就是某种资源的集合,这个集合是以内存的方式存在的
其次,Socket地址是指“IP地址端口对”(对应结构体sockaddr,定义在头文件sys/socket.h中)
该模块在FullNAT或SNAT转发模式中需要用到
以FullNAT转发模式为例,该转发模式会在inbound方向上将“客户端IP(CIP)、端口到DPVS的VIP、端口”转换为“DPVS的本地IP(LIP)、端口到RS的IP(RIP)、端口”,也就是将CIP:CPort->VIP:VPort转换为LIP:LPort->RIP:RPort
使用sapool是由于以下两个需要
● 解决“返程数据CPU亲和性”问题
需要保证从某个CPU内核发出的数据的返程“响应”也落到该CPU上
保证数据连接可以做到CPU本地化(亲和性)才能实现连接Per-Core化,避免加锁
DPVS方案是按照CPU内核号合理分配LIP:LPort(即本地Socket地址)的,创建每个LIP时会针对每个CPU分配特定的LPort,并组成Socket地址放入sapool中,进行FullNAT转换时需要从池中分配LIP:LPort
因为分配是按照一定特征进行的
比如,使“LIP加上LPort的部分bit位”和某个CPU关联,就能结合flow-director将返程的数据定向到正确的CPU内核
而且,这个方案不需要使用很多flow-director条目
因为flow-director只支持8000左右个条目,在有上百万个连接的情况下,不可能为每个连接设置规则
● 提高分配的速度
使用一个预先分配的Socket地址池,需要时直接从池中取出未用的,使用完成后放回池中
因为维护了“未/已使用”的Socket地址池,所以可以以最高的效率分配和释放Socket
Socket地址池使用内存换取了效率,能很好地解决性能问题
DPVS对性能要求很高,可用内存往往不是瓶颈
Alibaba/LVS和F-Stack都使用试错法,尝试分配一个LPort,然后查看5元组的连接是否已存在,如果存在则更换一个LPort,然后设置一个尝试的上限
这样的方法可能会造成CPU资源的大量浪费
而且在使用时,LVS可能会把时间消耗在LPort的选择上
此外,一个LIP并不一定会有65535个LPort可用(除去不适合的端口,大约有60000个),实际上DPVS识别一个连接的依据是5元组,只要5元组不冲突就不是同一个连接
在实现时,我们不需要5元组或4元组那么大的空间——2 96
另外,如果每个LIP只实现60000个左右的端口,则LIP数量不会太多,这会导致可用端口太少
对于上述端口过少的问题,我们可以把RIP:RPort因素一起考虑进去
但将RIP:RPort因素考虑进去会有个问题,无法预先分配整个5元组那么多的空间
为了解决上述分配空间不足的问题,我们可以设置一个哈希表,根据某些RS的RIP:RPort特征进行哈希变换,然后每个LIP使用大小为60000×hash_bucket_size的Socket地址池即可(LIPLPortRS_hash_bucket_size)
需要注意的是,在内存不太充足的情况下,可以减小哈希表的大小,或者修改sa_entry结构,如使用hlist_head和__packed__,无须为每个CPU分配长度为60000的哈希表等
预计可以大幅减少内存的使用,但需要修改代码
此外,也可以考虑优化其他模块的内存使用,如mbuf_pool大小优化,不需要时不要初始化TC模块等
DPVS在公司内部的设备上部署时,内存资源非常充足,因此缩减内存不是设计重点
无论如何,对于内存使用还是有很多优化手段和空间的
4.基础功能模块(Common Module)还有一些模块,本身不完成特定的功能,而是为系统中其他模块提供必要的服务,如定时器模块(timer)、消息模块(message)
另外,像配置管理(config)、统计信息(stats)等这样的模块并不完成主要任务,但并不是说它们就不重要
在实际的部署、运维或排障中都需要用到这些模块,如果这些模块实现得不好,不但影响实际使用,而且可能拖累整体性能
● 定时器(timer)
定时器是大部分系统中必要的组件
在DPVS中,连接的销毁需要定时器,邻居条目需要定时器,地址管理需要定时器
还可以用定时器执行一些辅助任务
DPVS对定时器有特殊的要求,那就是需要支持百万级的定时器实例,同时在定时任务创建、删除和超时时不能影响性能
原本DPVS定时器直接采用了DPDK提供的rte_timer,不过我们很快就会发现,它在高并发连接需要大量建立定时器的情况下,性能无法满足要求
于是,我们借鉴了Linux Kernel中基于“timer wheel”的定时器实现,使用多级Hash的手段来解决高并发百万实例情况下的性能问题
● 消息模块(message)
之前已经介绍过,为了实现DPVS内部跨CPU内核的高性能无锁通信,DPVS实现了支持同步、异步、一对一、一对多的消息模块
其底层使用DPDKrte_ring保证消息的无锁化,不影响性能
● 配置管理(config)
DPVS的有些参数是可以配置的,如mbuf的pkt_pool大小、CPU内核号和网卡、网卡TX/RX队列的映射、TCP各个状态的超时等
为此,DPVS支持通过配置文件/etc/dpvs.conf来配置管理模块,解析和应用这些配置项
有些配置项目是只能在启动时设置的,有些则支持在线修改,只需要修改配置文件,然后发送SIGHUP信号给DPVS进程
● 统计信息(Stats)
统计用于监控、排障,是运维部署过程中必不可少的
实际上,DPVS中的很多模块都有其独立的统计信息
它们大多通过message传递给Master,再由Master通过控制面传递给用户工具
我们可以使用ipvsadm和dpip等命令查看这些统计信息
5.控制面(Control Plane)除 了 配 置 文 件 , DPVS 对 数 据 面 的 控 制 基 本 通 过 ipvsadm 、 dpip 和keepalived共3个命令进行
DPVS和命令的交互使用了UNIX Socket和自定义的、类似socketopt的“协议”
控制消息的收发由Master线程完成,这样不会影响数据面的性能
必要时,Master通过内部的message模块向Worker收集统计信息,并聚合给用户;或者将用户的配置通过message分发到不同的Worker上
在使用UNIX Socket进行对外交互时,各个模块会向ctrl模块注册sockopt的选项值及其回调函数
外部工具可以通过这些sockopt选项和模块相关的数据结构来和DPVS交互
关于DPVS的协议选择,控制面没有选择RESTFul或类似各种bus(dbus)的接口及文本化的协议,而是选择二进制协议
这主要是因为DPVS并非是一个非常复杂的多进程或分布式系统
另外,LVS采用的sockopt选项在开发上用起来比较便利,而RESTful/bus类型的IPC需要基于相对比较重的控制面使用
所以相对来说,二进制协议更易于实现
当然,这种需要预定义的二进制“协议”存在版本兼容的问题,在进行DPVS升级时必须要配合使用命令才能实现
同时,二进制协议不容易调试和扩展,也不容易做到向前兼容
但是,DPVS对控制面性能要求也不高,所以这并不会影响DPVS的总体性能
总之,DPVS系统并不像分布式系统或多进程协作系统一样对控制面通信部分有很高的要求,所以使用二进制类sockopt方式还是可以接受的
7.4.3 数据流大图图7-6所示为DPVS工作原理和数据流示意图,该示意图解释了数据走向及函数调用流
以Two-arm FullNAT转发模式为例,解释了一个客户端请求在DPVS中经历的过程,包含请求在两个方向的处理流程:入口(inbound)和出口(outbound)
eth1是外网接口,eth0是内网接口,它们都支持RSS/FDIR
图7-6 DPVS工作原理和数据流示意图简单解释一下图7-6描述的处理过程,主要解释第一个分组的处理过程,后续的分组处理过程类似
客户端发送请求,数据包到达eth1后,作为inbound的数据,直接通过RSS(Receive Side Scaling)将数据分流到网卡的不同队列中
其中,RSS通过计算数据包的5元组(SIP、SPort、DIP、DPort、Protocol)的哈希值并取余,得到队列的索引,然后将包放入这个队列,来实现数据包在各个队列之间的负载均衡
RSS算法可以设置所接收数据协议类型,默认是TCP,也就是会根据源IP和源TCP端口进行哈希映射,将数据放入某个网卡队列,根据DPVS的亲和性配置,在该队列进行收包(RX)的CPU内核会收到该分组
收到分组后,netif模块会调用netif_deliver_mbuf进行L2/L3层分用
如果是IPv4分组,则netif模块会将其传递给ipv4_rcv()函数;如果是IPv6分组,则netif模块会将其传递给ip6_rcv()函数;如果是ARP分组,则netif模块会将其传递给neigh_resolve_input()函数
经过上述对分组的处理后,数据就从L2层(数据链路层)进入了L3层(IP层)
这里省略了许多在netif层的处理细节,如对ARP环形队列及VLAN等的处理
选用TCP作为RSS的条件是为了方便测试
例如,在一台设备上使用wrk模拟多个客户端时,源IP是一样的,而源端口不同,如果采用基于源IP的RSS hash(将源IP作为键值进行哈希),就无法让流量分布到不同的CPU上
当然,采用基于IP的hash的好处是可以保证分片数据到达同一个CPU上,因为如果是基于TCP的,那么由于分片没有四层信息,所以无法正确分流到第一个分组所在的CPU
另外,在返程方向上,因为受到LIP资源的限制,DPVS会使用<LIP:LPrt>结合FDIR(flow-director)来解决返程数据亲和性问题
因为利用了四层端口,返程数据无法通过处理分片来使其到达正确的CPU
这也是DPVS不支持分片的一个原因
Alibaba/LVS使用为每个CPU分配不同LIP并只利用LIP设置FDIR的方式来实现
分组进入IP层后,需要进行必要的完整性等检查,在查找路由前先经过PRE_ROUTE的Hook点
DPVS的IPv4/IPv6实现了类似Netfilter的Hook机制,为未来可能的扩展留下了足够的灵活度
负责核心负载均衡转发的IPVS模块在PRE_ROUTING点注册了其唯一的回调函数dp_vs_in()
IP分组在查询路由前先进入了IPVS,因此FullNAT等转发模式的优先级高于路由查找和转发
IPVS模块的实现基本和Linux下的IPVS一致,当分组进入IPVS模块后,先查找对应的“协议”对象,然后根据协议“对象”进行连接表的查找,连接inbound的第一个分组是没有连接表的,因此需要创建一个新的连接表,对后续的inbound/outbound的分组进行转发
创建连接的前提是要保证“服务”的存在
DPVS的服务定义如下
● 元组:<proto,vip,vport>
● match类型服务
我们知道,LVS支持上述第一类服务,如tcp:10.123.1.2:80这样的服务,其中协议“proto”包含TCP和UDP两种
而DPVS除了支持TCP和UDP的转发,还支持ICMP的转发
此外,DPVS支持match类型的服务,如proto=tcp,from=192.168.0.0-192.168.0.255:1024-2000,不过主要用于SNAT类型的转发
LVS“支持ICMP”,但限于不支持和Original分组相关的ICMP出错消息
而DPVS支持的ICMP则没有ICMP出错消息的限制
在DPVS中,ICMP和TCP/UDP属于同等的协议,ICMP可以和TCP/UDP一样被DPVS调度并进行FullNAT/SNAT转发
比如,在SNAT情况下,使用DPVS的内网主机可以从内部Ping外网IP,而LVS是不支持这一点的
如果没有找到预先配置的Service,则结束流程,将分组返回IP层继续处理
如果找到了Service,那么接下来就需要为新连接调度一个RS
这时就会用到Service对应的调度器对象,每个Service都被设置了一个调度器
DPVS支持以下多种调度器(模式)
● 轮询(Round Robin,RR)
● 加权轮询(Weighted Round Robin,WRR)
● 加权最少连接(Weighted Least Connection,WLC)
● 一致性哈希(Consistent Hashing,ConHash)
这些调度算法并不复杂,我们也很容易查询它们的原理及使用场景
DPVS实现了Maglev所支持的一致性哈希算法,这是LVS所没有支持的,该算法的实现使得DPVS可以方便地用于“有状态”的业务,如支付业务、QUIC类应用
前者需要每个客户端多次发起的不同请求到达同一个RS,以便查询RS上维护的状态(如购物车、支付);后者因为同一个QUIC连接不是用5元组标识的,而是用CID(Connection ID,连接ID)标识的,且同一个CID的5元组可以改变,因此不使用一致性哈希会导致同一个QUIC连接的数据落在不同的RS上,从而造成连接异常
另外,DPVS没有实现LVS所支持的所有调度算法,原因是在实际应用中使用上述调度算法就足够了(至少在公司内部是这样的),如果确实需要实现其他的算法也并不困难
调度到一个RS后,我们就获得了RS的IP地址和端口,即<RIP:RPort>,对于FullNAT还需要合理地选择LIP和LPort,LIP的选择比较简单,从被绑定到Service的多个LIP中轮询下一个即可,而LPort的选择就比较重要了
我们之前提到,合理分配LPort资源,可以让返程数据通过<LIP,LPort>来确定CPU
DPVS的sapool模块能够解决上述LPort选择的问题
至此,我们就获得了一个连接的所有信息:<Proto,CIP,CPort,VIP,VPort,LIP,LPort>,于是使用dp_vs_conn_new可以创建一个新的连接结构,并将其放入Per-Core的连接表中,之后的outbound数据和后续inbound的数据都可以查询到该连接表项
在新建或获取连接表后,DPVS就可以根据转发模式进行数据转换和传输,这时会涉及xmit模块
如果转发模式是FullNAT,则会将分组的源IP地址、源端口(CIP:CPort)和目的IP地址、端口(VIP:VPort)分别替换为LIP:LPort和RIP:RPort
完成转换(Translation)后,将数据通过IPv4/IPv6模块传输出去,还涉及对路由表、ARP表的查找,在此不再赘述
7.4.4 项目开源的缘由和一些经验开源的好处有很多,主要有两点:一方面能够提高公司和团队的技术影响力,另一方面可以促进项目的演进、bug的修复,同时也方便了其他有类似需求的人
许多公司借助开源提高了技术领域影响,在展示技术实力的同时也利于吸引人才
在DPVS出现之前,业界已有开源的四层负载均衡,但对于DPDK版本的高性能四层负载均衡来说,虽然很多公司都在研发甚至使用,但在DPVS开源之前没有发现开源版本
开源对于项目本身也有很多好处,可以结合社区的力量,有了更多的需求,也有更多开发者参与其中,能更好地发现其不足,使项目更稳定,功能更完善
开源同时意味着需要更高的代码质量,让开发人员对自己的代码更负责
在DPVS的开源过程中,与大家分享一些经验
1.开源许可证需要正确地选择开源许可证,可以先去网上查一下许可证的科普文章
常用许可证有很多不同的版本,如GPLv2、3-Clause BSD等,可以根据需求和实际情况仔细选择
如果项目已经使用了其他的开源项目,则应该了解、遵循它们的规则
不要使用没有来源的任何第三方代码行,或者与代码片段的开源协议和要求不符的代码或文档
同时,要注意项目是否有内部不想开源的代码的依赖
也需要注意是否使用了混合协议
有一些商业或免费的工具可以对源代码进行扫描
建议向公司的法务咨询,以确保没有法律和知识产权的问题,此外还需得到上级审批,确保开源符合公司的利益
2.文档及代码清理在开源之前建议做一些准备
编写README文档,对项目进行简单的介绍,它能做什么、特点是什么等,打造良好的第一印象
最好有简单的quickstart,只需要编写基本的内容,让缺少背景知识的读者尽快熟悉项目,能够展示基本功能即可
如果项目要花费很久才能编译完成并运行,那么没人会愿意耐心继续了解它
部分开发人员并不喜欢写文档,但一个好的文档能够给开源软件的使用者带来很大帮助,建立使用信心
同时也避免维护方被反复提问类似的问题
既然代码要开源,就应该有更高的质量要求,对代码进行必要的整理
3.共同开发开源除了希望更多的用户使用它,也希望借助社区的力量共同开发和修复bug、完善功能
为此,开发者最好能够做到以下几点
● 给出清晰的蓝图
● 广泛收集需求
● 收集bug并修复
● 鼓励在GitHub上提交代码(Pull Request)
4.开源社区维护开源社区需要尽可能做到快速回复提问、及时反馈bug修复的进展
另外,可以提供邮件列表、微信、QQ群等,甚至各种公众号,方便使用者和合作开发者相互交流
本文给大家讲解的内容是使用DPDK加速四层负载均衡下篇文章给大家讲解是七层负载均衡性能优化
如何使用程序员四层负载均衡DPDK(模块数据内核地址开源)
(图片来源网络,侵删)

联系我们

在线咨询:点击这里给我发消息