唯品会高性能负载均衡VGW揭秘

负载均衡基础简介

负载均衡是指对后端服务进行流量分发的服务。通过负载均衡入口,后端服务可以水平扩展来提升对外服务能力,可以消除单点故障以提升应用系统可用性。我们以Web Server举例,如图1所示,Web Server可以在负载均衡后面透明伸缩,当其中一部分Web Server实例挂掉后,剩余实例仍然能够提供服务。

图 1:典型负载均衡使用示例

负载均衡又分为四层和七层负载均衡,所谓四层和七层就是指对后端服务进行负载均衡时,依据传输层还是应用层的信息来进行流量转发。这两面包含两个问题,一是我们怎么来区分负载均衡转发流量,二是我们对转发流量进行怎样的负载均衡。

当进行四层负载均衡时,依据的是网络发布的IP加上传输层的端口号来区分转发流量,而对转发流量选择怎样的负载均衡时,可能选择用户IP,用户端口,协议号,目标ip和目标端口的5元组Hash的方式,也可能选择普通的Round robin轮转,这个依据使用场景而定,典型四层负载均衡产品如LVS,F5。而七层负载均衡则依据应用层信息区分转发流量,典型如域名信息来区分转发流量,通过URL Hash等方式进行后端服务的负载均衡,典型产品如Nginx,Haproxy,简单对比如图2所示。

图 2:四层与七层负载均衡的差别

 

显然七层负载均衡相比四层负载均衡对转发效率要求更高,且需要支持多样化的应用层协议,而不仅仅是http协议等,而四层负载均衡虽然支持TCP/UDP至上的任何协议,但是无法像七层那样对应用进行定制化转发,比如根据URL Hash等。

总结来说业界通常综合二者优点来进行使用,很多互联网公司的接入架构是采用LVS+Nginx之类的四层+七层方案。介绍完本基础,我们接下来阐述唯品会高性能四层负载均衡VGW的产生背景及高性能秘密,希望能对大家有所借鉴。

 

VGW产生背景

传统的四层负载均衡接入网关大多采用LVS,但是LVS基于内核态转发,存在内核态和用户态切换开销,大流量下的中断开销重等问题。对于四层的接入网关,业界大部分采用Kernel Bypass的技术来提升性能,避开Linux内核复杂和冗长的TCP/IP协议栈处理开销,如Google的Maglev,还有国内大型互联网公司的四层负载均衡接入网关。当然也可通过netmap,pfring方式来提升网络收发数据包性能,目前国内几乎所有大厂都基于DPDK来实现,并在线上充分验证了其转发性能及稳定性,为了避免选型调研成本,我们也基于DPDK实现了唯品会高性能四层接入网关VGW,在不增加硬件成本的基础下,相同普通服务器的转发性能是普通LVS的4倍,在双11大促中平稳承载部分搜索,图片等核心域流量,减轻基础架构设施方面的成本劣势。

 

VGW高性能法宝

CPU多核的亲和性

一般CPU有多层Cache,每级Cache访问时间相差约10倍,如L1的时间为0.5ns,L2的访问时间为7ns,容量也随之增大。因此如果一个进程或线程可能被调度到不同CPU核上,可能会导致CPU上的多级Cache Miss,一次CPU的Cache Miss导致的访问内存开销大约是L1 Cache访问开销的几百倍。因此我们在VGW中将各个线程都固定跑在各自的核上,避免调度到不同核导致的CPU Cache Miss。另外我们也将VGW工作线程跑的核单独隔离,避免被操作系统调度的任务挤占Cache。

我们的核分配结构如图3所示:

 

图 3:VGW多核分配结构

 

大页内存

相对于传统虚拟内存,大页内存不受虚拟内存影响,不会被替换出内存。另外Linux默认页大小为4KB,而大页内存可以支持2M或1G页大小,相同内存容量下能够减少页表条目数,使得TLB的MISS数大大降低。以8G内存为例,如果采用4K页大小,则需要2M的页表项来存虚拟内存到物理页面映射关系,而以大页内存1G的话,只需要8条页表项,因此能够使得TLB冲突开销大大降低。

VGW中采用2M页大小,划分8G内存独占,这个在环境初始化脚本中设置,由于机器是NUMA结构,故需要给亲和的NUMA Node建立大页。

1.echo Pages> /sys/devices/system/node/node-num hugepages/hugepages-Pagesize/nr_hugepages

2.mkdir –p /mnt/huge

3.mount -t hugetlbfs nodev /mnt/huge

num表示numa号,Pages和Pagesize分别表示页数和页大小。

 

无锁化

典型的互斥锁的时间开销是100 ns级,而对于VGW使用的DPDK来说开销过于昂贵,DPDK为此提供了FIFO固定大小的环形无锁队列。它通过name为标志区分,支持单写单读,多写单读,单写多读,多写多读。本质上来说该无锁队列底层实现是通过CAS等CPU支持的原子操作指令和忙等待来实现。

在VGW中无锁队列主要有两个用途,一个是用于内存池中的内存分配,另外一个是用于接收线程和发送线程,接收线程和KNI线程的通信中,控制线程和转发线程中控制信息交互。具体可参考图3。

另外VGW中也将读多写少情况进行RCU化,避免采用读写锁,RCU机制通过少量的内存开销来换取无锁化, IP黑名单是典型的读多写少情形,激活该模块后,数据包都会去读取黑名单来判断自身的源IP是否匹配,只有少数情况下才会更新这个黑名单,因此我们在IP黑名单模块的实现中应用了RCU,极大简化了该功能模块的实现,避免通过无锁队列出现的更新通知冗余及维护成本。 

 

用户态轮询驱动

传统Linux系统以中断的方式通知CPU来收发数据包。在每秒到来的数据包量小的时候没有问题,一旦大量数据包蜂拥,中断就占了大量的开销,挤占了处理数据包的时间。为避免中断开销,VGW使用DPDK的PMD轮询驱动直接在用户态获取数据包,简单来说就是通过轮询的方式收发包,避免大流量下的中断开销,同时用户态驱动避免包拷贝, 如图4所示。

                              图4:轮询驱动                               

 

其他优化措施

多核访问数据per-lcore化及Cache line对齐

在VGW程序中尽量不在多核间分配全局变量,而是在各个lcore上分配,这样避免读写时加锁冲突,同时也使得各个lcore的访问都是它最近的内存。例如VGW的统计信息,arp表,连接表等结构都是per-lcore化。

 

路由查找优化

VGW转发数据包需要知道该数据包的下一跳IP,相当于需要维护路由信息,因此怎样快速的根据目的地址来查找到下一跳地址也是关键问题。最开始我们直接通过Linux ioctl API获取系统路由,但在数据转发的逻辑中使用系统调用不是一个好方法,系统调用的开销很可能影响极限转发性能,后面通过利用dpdk的LPM(Longest Prefix Match)模块来加快每个数据包的路由信息查找,LPM维护了前缀24位和后8位的两张表,前缀24位表匹配概率较大,采取预先分配,大部分情况下只需要一次访问就能匹配到,而后缀8位表则访问概率较小,采取按需分配,需要两次访问命中,从而达到时间和空间的折中。

 

合适的批量值及预取

在我们VGW的收包中,一次从网卡批量收包的数目也需要权衡优化,太低会导致网卡硬件缓存区满,因为存在多次获取的开销,太高则可能导致处理延迟增加,也使得网卡缓存区满,我们通过测试调优选取了一个比较合理的值。

预取能够使得CPU将待处理的数据提前从内存加载到CPU Cache中,除了底层驱动使用预取来加速收发包外,VGW应用程序也在收到每个包后,将包体内容预取到cache中,加速后续包处理的过程。如rte_prefetch0(rte_pktmbuf_mtod(m, void *));

分支预测

CPU通过流水线重叠连续指令增加处理吞吐量,要充分利用流水线,必须知道执行指令的序列和先后顺序,而CPU在执行分支判断语句时,无法知道后续该执行哪个分支,故无法将后续指令填充进入流水线。这个时候就可以通过分支预测显示告诉CPU大概率执行的分支,加速流水线指令处理。

比如VGW的收包判断逻辑中,对于收到的每个数据包,都有两种流向,一种是被转发到后端真实服务器,另外一种是转化成skb_buff结构交给VGW的本机操作系统的协议栈处理,而正常情况下大部分的流量应该是转发流,故我们在收包线程的判断逻辑中加入likely函数来指示CPU最可能执行的分支为转发逻辑。

if(likely(packet to real server)) {

process(packet);

}

NUMA亲和

由于我们的服务器是NUMA结构,NUMA亲和包括内存与CPU,设备与CPU两个方面,VGW在分配内存时,通过rte_pktmbuf_pool_create函数的socket_id参数来分配离指定CPU所在NUMA的内存。VGW在初始化网卡收发队列时,通过函数rte_eth_rx_queue_setup的socket_id参数来控制传入的numa id,保证网卡和对应numa上的CPU亲和性。

VGW保证最开始只关注了内存和CPU的numa亲和,忽略了网卡设备与cpu numa的亲和,性能测试没达到预期后,才开始关注修复,目前接收和发送线程的数据包使用的内存和网卡设备都处于同一numa node,保证了性能最优化。

 

总结

本文简要总结了负载均衡基础及VGW高性能的原因,VGW建立于Dpdk之上,充分了利用Dpdk的一些软件优化思想,结合自身业务逻辑进行了性能优化,使得在普通低成本的服务器上也有与专用硬件媲美的转发能力,性能优化是个系统工程,需要CPU,内存,网卡,软件代码实现等各个方面的协同,本文阐述的诸多性能优化思想不仅仅是对VGW这样对性能有着极致要求的四层负载均衡接入网关有用,相信也会对普通的应用程序也是值得借鉴和思考,后续我们也会发一些VGW具体实现细节,运营监控及高可用的设计的文章,欢迎大家继续关注。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值