给高并发降温,美团高性能、高可靠四层负载均衡MGW优化实践

点击蓝字

关注我们

负载均衡是实现网络就近接入的核心技术之一,它的作用是把海量涌入的流量相对均匀的调配到 N 个可完成相同任务的网络节点或服务器,来规避载压不均的状况。负载均衡作为应用流量的入口,直接影响应用的性能和可靠性。

通过本文,我们可以了解到承载美团点评数十 Gbps 的流量、上千万的并发连接的 MGW 如何实现高性能、高可靠的详尽优化过程等。

负载均衡的作用及分类

互联网初期阶段,业务逻辑简单、流量不大,单台服务器就可满足日常需求。随着互联网的发展,业务不仅会流量爆发、逻辑越来越复杂且对可靠性的需求也逐步递增。

这时,就需要多台服务器来应对单台服务器在性能、单点等方面凸显出来的问题,进行性能的水平扩展和灾备。但客户端的流量要如何顺利访问到这么多不同的服务器是个问题。

对于这个问题,可选择使用 DNS 做负载,对客户端不同 IP 地址进行解析,使得流量直接到达不同的应用服务器。

但这个调度策略相对简单却不能很好的满足业务需求,当改变调度策划后,DNS 各级节点的缓存不能及时在客户端生效,会导致很严重的延时性问题。这时可选择使用负载均衡,如下图:

如图所示,客户端的流量先到达负载均衡服务器,再由负载均衡服务器采用一些调度算法,把流量分发到不同的应用服务器。

与此同时,负载均衡服务器对应用服务器进行周期性健康检查,一旦查出故障节点,直接剔除,进而保证应用的高可用。

负载均衡分为四层和七层两种,如下图:

四层负载均衡

核心是转发,收到客户端流量,对数据包地址信息进行修改后,转发到应用服务器,整个过程均在 OSI 模型的传输层完成。

七层复杂均衡

核心是代理,当接收到客户端的流量后,通过完整的 TCP/IP 协议栈对应用层流量进行解析。之后,基于调度算法选择合适的应用服务器,并与应用服务器建立连接将请求发送,整个过程在 OSI 模型的应用层完成。

四层负载均衡的转发模式

转发是四层负载均衡的主要工作,目前主要有四种转发模式:

DR 模式

NAT 模式

TUNNEL 模式

FULLNAT 模式

如下是四种转发模式的优缺点对比图表:

从图中可以直观的看到四种转发模式的优缺点:

DR 模式(三角传输)的实现方式是修改数据包的目的 MAC 地址

NAT 模式的实现方式是修改数据包的目的 IP 地址

TUNNEL 模式的实现方式和 DR 相同,但要求应用服务器必须支持 TUNNEL 功能

美团点评最终选择 FULLNAT 作为 MGW 的转发模式,它是在 NAT 模式的基础上做一次源地址转换(即 SNAT)。

此模式的优势在于可使应答流量经过正常三层路由回归负载均衡,负载均衡就不需以网管的形式存在于网络中,进而降低了对网络环境的要求。不足之处在于做 SNAT,应用服务器会出现丢失客户端的真实 IP 地址的情况。

如下图,是 FULLNAT 转发模式的具体实现流程:

如图在负载均衡上布设 localip 池,做 SNAT 的过程中,源 IP 取自其中。当客户端流量到达负载均衡设备,负载均衡依据调度策略在应用服务器池中选择一个应用服务器,将数据包的目的 IP 改为应用服务器 IP。

同时从 localip 池中选择一个 localip 将数据包的源 IP 改为 localip,这样应用服务器在应答时,目的 IP 是 localip,而 localip 是真实存在于负载均衡上的 IP 地址,因此可以经过正常的三层路由到达负载均衡。

与 NAT 模式相比,FULLNAT 模式会多做一次 SNAT,过程中还有选端口的操作,在性能方面要稍弱于 NAT 模式,但 FULLNAT 模式在网络适应性方面要占优势。

采用四层负载均衡的原因

为什么美团点评会选择自研的四层负载均衡呢?主要有两个原因:

业务流量递增,LVS 出现性能瓶颈及运维成本的上升。

采用硬件负载均衡,成本是门槛。

LVS 方面,美团点评最初的方式是使用 LVS 做四层负载均衡、Nginx 做七层负载均衡。

如上图所示,流量高速增长,LVS 在性能上出现瓶颈,故障率增加。

在硬件负载均衡方面,凸显出来的问题有三方面:

异常高的硬件成本

人力成本

时间成本

综合这两个原因,美团点评决定自研四层负载均衡 MGW 来替换 LVS,MGW 需要满足高性能、高可靠两个特性。那么如何实现高性能、高可靠呢?

MGW 实现高性能的

四大问题和解决方法

对比 LVS 的一些性能瓶颈,我们可以直观了解 MGW 实现高性能的解决方法。这里主要涉及 LVS 遇到的中断、过长的协议栈路径、锁和上下文切换这四大问题。

MGW 应对这些问题的解决办法分别是 PMD 驱动、Kernelbypass、无锁的设计和 CPU 绑定、隔离。

中断

LVS 是做在内核中的,而内核在设计时要兼顾通用性,所以采用中断的方式感知流量的接收。

中断对 LVS 性能的影响非常大,如果每秒处理 500 万数据包,每 5 个数据包产生一个硬件中断,那么一秒会产生 100 万个硬件中断,每一次产生的硬件中断都会打断正在进行密集计算的负载均衡程序,中间产生大量的 cache miss,这对性能的影响非常大。

过长协议栈路径

因为 LVS 是基于内核 netfilter 开发的应用程序,而 netfilter 是运行在内核协议栈的一个钩子框架。

当数据包到达 LVS 时,要经过一段很长的协议栈处理。实际情况是负载均衡在接到流量以后,只需对 MAC、IP、端口进行修改发出即可,并不需要这段处理来增加额外的消耗。

针对中断和过长协议栈路径这两个问题的解决方法是采用由 DPDK 提供的用户态 PMD 驱动以及做 Kernelbypass。

DPDK 在设计过程中,使用了大量硬件特性,如 numa、 memory channel、 DDIO 等对性能提升很大。同时提供网络方面的库,进而减小开发难度,提升开发效率。

这些都是美团点评选择 DPDK 作为 MGW 的开发框架的因素。

内核是较通用的应用程序,为了兼顾不同硬件,在设计过程中会加一些锁。而自主研发只需对某些特定场景进行定制设计,不用兼顾很多硬件,进而通过一些方法去掉锁,实现完全无锁的状态,方便后续扩展。

先来看看什么情况需要进行锁的设计,首先介绍一下 RSS(Receive Side Scaling)。

RSS 是一个通过数据包的元组信息将数据包散列到不同网卡队列的功能,这时不同 CPU 再去对应的网卡队列读取数据进行处理,就可充分利用 CPU 资源。

MGW 使用的是 FULLNAT 模式,会将数据包的元组信息全部改变。

这样同一个连接,请求和应答方向的数据包有可能会被 RSS 散列到不同的网卡队列中,在不同的网卡队列也就意味着在被不同的 CPU 进行处理,这时在访问 session 结构就需要对这个结构进行加锁保护。

解决这个问题,一般情况下有如下两种方案:

在做 SNAT 选端口时,通过选择一个端口 lport0 让 RSS(cip0,cport0,vip0,vport0) = RSS(dip0,dport0,lip0,lport0)相等。

为每个 CPU 分配一个 localip,做 SNAT 选 IP 时,不同 CPU 选择自有的 localip,当应答回来后,再通过 lip 和 CPU 的映射关系,将指定目的 IP 的数据包送到指定队列上。

 

由于第二种方法恰好可被网卡的 flow director 特性支持,因此 MCW 选择第二种方法来去掉 session 结构的锁。

flow director 可依据一定策略将指定的数据包送到指定网卡队列,其在网卡中的优先级要比 RSS 高,因此在做初始化的时候就为每个 CPU 分配一个 localip。

如为 cpu0 分配 lip0,为 cpu1 分配 lip1,为 cpu2 分配 lip2,为 cpu3 分配 lip3。

当一个请求包(cip0,cport0,vip0,vport0)到达负载均衡后,被 RSS 散列到了队列 0 上,这时这个包被 cpu0 处理。

cpu0 在对其做 fullnat 时,选择 cpu0 自己的 localiplip0,然后将数据包(lip0,lport0,dip0,dport0)发到应用服务器,在应用服务器应答后,应答数据包(dip0,dport0,lip0,lport0)被发到负载均衡服务器。

这样就可以在 flow director 下一条将目的 IP 为 lip0 的数据包发送到队列 0 的规则,这样应答数据包就会被送到队列 0 让 cpu0 处理。

这时 CPU 在对同一个连接两个方向的数据包进行处理的时候就是完全串行的一个操作,也就不需再对 session 结构进行加锁保护。

上下文切换

针对上下文切换的问题,MGW 设计有对 CPU 进行分组,实现控制平面与数据平面完全分离的状态,可以很好的避免数据平面做处理时被打断的现象,如下图所示:

同时,对数据平面 CPU 进行隔离,实现控制平面进程不会调度数据平面这组 CPU;对数据平面线程进行 CPU 绑定,实现每个数据线程独占一个 CPU。

控制平面的程序均跑在控制平面的 CPU 上,如 Linux kernel、SSH 等。

MGW实现高可靠的解决方法

这里主要介绍在 MGW 集群、MGW 单机及应用服务器这三层实现高可靠的方法。

MGW 集群

集群的高可靠主要涉及三部分:

session 同步

故障切换

故障恢复与扩容

如下图所示,MGW 采用 ECMP+OSPF 的模式组成集群:

ECMP 的作用是将数据包散列到集群中各个节点,再通过 OSPF 保证单台机器故障以后将这台机器的路由动态的剔除出去,这样 ECMP 就不会再给这台机器分发流量,也就做到了动态的 failover。

session 同步

传统 ECMP 在算法方面存在一个严重问题,当复杂均衡集群中节点数量发生变化时,会导致大部分流量的路径发生改变。

当发生改变的流量到达其他 MGW 节点上,找不到自身的 session 结构,就会导致大量的连接出现异常,对业务影响很大,且当在对集群做升级操作时会将每个节点都进行一次下线操作,这样就加重了这个问题的影响。

传统的解决方式是使用支持一致性 hash 的交换机,如下图所示:

当节点发生变化的时候,只对发生变化的节点上面的连接会有影响,其他连接都会保持正常,但是支持这种算法的交换机比较少,并且也没有完全实现高可用。

因此需要做集群间的 session 同步功能,如下图所示:

集群中每个节点都会全量的将自身 session 进行同步,使集群中每个节点都维护一份全局 session 表。当节点发生变化,流量路径无论发生任何形式的改变,流量都可以找到自身的 session 结构。

故障切换与故障恢复及扩容是在做集群间的 session 同步功能的整个过程中首要考虑的两大问题。

故障切换

针对故障切换问题,当机器发生故障后,交换机可立刻将流量切到其他机器,避免大量丢包的情况。

经过调研测试,MGW 采用如下图所示的操作方法,可做到升级操作 0 丢包,主程序故障 0 丢包,其他异常(网线等)会有一个最长 500ms 的丢包,因为这种异常需要靠自检程序去检测,而自检程序的周期是 500ms。

具体包括如下四方面内容:

交换机侧不使用虚拟接口。全部使用物理接口且服务器侧对接口进行断电时,交换机会瞬间将流量切换到其他机器。

通过 100ms 发两个包的测试(客户端和服务端各发一个),表明这种操作方法可实现 0 丢包。

半秒一次机器健康自检。故障切换主要依赖于交换机的感知,当服务器上出现异常,交换机感知不到时,交换机就无法进行故障切换操作。

因此需要一个健康自检程序,每半秒进行一次健康自检,当发现服务器存在异常时就对服务器执行网口断电操作,从而将流量立刻切走。

检测到故障自动给网口断电。故障切换主要依赖于网口断电操作且网卡驱动是跑在主程序里面的,当主程序挂掉以后,就无法再对网口执行断电操作。

主进程会捕获异常信号。针对主程序挂掉后,就无法再对网口执行断电操作的现象,主进程会捕获异常信号,当发现异常时就对网卡进行断电操作,在断电操作结束后再继续将信号发给系统进行处理。

故障恢复与扩容

系统进行故障恢复、扩容操作时,会使得集群节点数量发生变化,进一步导致流量路径发生变化。

因为已经发生变化的流量到达集群中原有节点时,原有节点都维护一个全局 session 表,所以这些流量是可以被正常转发的;但当流量到达一个新机器,这台新机器由于没有全局 session 表,这部分流量会全部被丢弃。

针对这个问题,MGW 在上线后会经历预上线的中间状态,此状态下,MGW 不会让交换机感知到自己上线,所以交换机也就不会把流量切过来。

实现流程是首先 MGW 会对集群中其他节点发送批量同步的请求,其他节点收到请求以后会将自己的 session 全量同步到新上线的节点,新上线节点在收到全部 session 以后才会让交换机感知到自己上线,这时交换机再将流量切入,就可正常被转发。

这里需要提醒两点:

由于集群中并没有一个主控节点来维护一个全局的状态,如果 request 报数据丢失或者 session 同步的数据丢失的话,那么新上线节点就没办法维护一个全局的 session 状态。

但考虑到所有节点都维护着一个全局的 session 表,因此所有节点拥有的 session 数量都是相同的,那么就可以在所有节点每次做完批量同步以后发送一个 finish 消息,finish 消息中带着自己拥有的 session 数量。

当新上线节点收到 finish 消息以后,便会以自己的 session 数量与 finish 中的数量做对比。当达到数量要求以后,新上线节点就控制自己进行上线操作。否则在等待一定的超时时间以后,重新进行一次批量同步操作,直到达到要求为止。

在进行批量同步操作时,如果出现新建连接,那么新建连接就不会通过批量同步同步到新上线的机器上。如果新建连接特别多,就会导致新上线机器一直达不到要求。

因此,需要保证处于预上线状态的机器能接收到增量同步数据,因为新建连接可以通过增量同步同步出来。通过增量同步和批量同步就可以保证新上线机器可以最终获得一个全局的 session 表。

MGW单机

自动化测试平台

单机高可靠方面,MGW 研发了自动化测试平台,通过连通性和配置的正确性来判断一个测试用例是否执行成功,失败的测试用例平台可以通过邮件通知测试人员。

在每次新功能迭代结束以后,都会将新功能的测试用例加到自动化平台里面,这样在每次上线之前都进行一次自动化测试,可以大大避免改动引发的问题。

在这之前,每次上线之前都需要进行一次手动的回归测试,回归测试非常耗时并且很容易遗漏用例,但是为了避免改动引发新问题又不得不做,有了自动化测试平台以后,可提升回归测试的效率和可靠性。

应用服务器

在应用服务器靠性方面,MGW 布设节点平滑下线和一致性源 IP Hash 调度器两大功能。

节点平滑下线功能主要是为了解决当用户需要对 RS 进行升级操作时,如果直接将需要升级的 RS 下线,那这个 RS 上存在的所有连接都会失败,影响到业务。

此时如果调用 MGW 的平滑下线功能,MGW 就可以保证此 RS 已有连接正常工作,但不会往上面调度新的连接。

当所有已有连接结束以后,MGW 会上报一个结束的状态,用户就可以根据这个结束的状态对 RS 进行升级操作,升级后再调用上线接口让这个 RS 进行正常的服务。

如果用户平台支持自动化应用部署,那就可以通过接入云平台使用平滑下线功能,实现完全自动化且对业务无影响的升级操作。

一致性源 IP Hash 调度器

源 IP Hash 调度器主要是保证相同的客户端的连接被调度到相同应用服务器上,也就是说建立一个客户端与应用服务器一对一的映射关系。

普通源 IP Hash 调度器在应用服务器发生变化以后会导致映射关系发生改变,会对业务造成影响。

一致性源 IP Hash 调度器,保证在应用服务器集群发生变化时,只有发生变化的应用服务器与客户端的映射关系发生改变,其他保持不变。

为了保证流量的均衡,首先在 Hash 环上分配固定数量的虚拟节点,然后将虚拟机节点均衡的重分布到物理节点上,重分布算法需要保证:

在物理节点发生变化时,只有少数虚拟节点映射关系发生变化,也就是要保证一致性 Hash 的基本原则。

因为 MGW 是以集群的形式存在的,当多个应用服务器发生上线、下线操作时,反馈到不同的 MGW 节点上就有可能会出现顺序不一致的问题。

因此无论不同的 MGW 节点产生何种应用服务器上下线顺序,都需要保证最终的映射关系一致,因为如果不一致就导致相同客户端的连接会被不同的 MGW 节点调度到不同的应用服务器上,也就违背源 IP Hash 调度器的原则。

技术展望

未来,美团点评将主要在这三方面发力:升级自动化、集中式配置管理和降低运维成本。

升级自动化

最初自研 MGW 是为解决 LVS 的性能问题,在这些问题被解决之后,随着业务的快速发展,IDC 不断增长,负载集群越来越多,像某个新 IDC 上线,一般都要上线两套集群,分别用于 IDC 入口和内部业务。

这样的情况下,又涌现出更大的问题,如一个新功能发布时, 周期会非常长,所以需要实现自动化升级。当然,在完善监控措施方面也要同步,对异常进行监控。

集中式配置管理

目前业务配置已经实现集中式配置,未来希望机器的配置也能实现。

更低的运维成本

实现自动化升级,集中式配置最根本的目的就是降低运维成本。

作者:王雪燕

来源:51CTO技术栈

今日推荐阅读

Jenkins 2.x(Pipeline),SonarQube 和 Artifactory 搭建持续交付流水线

为什么我从 Git Flow 开发模式切换到了 Trunk Based 开发模式?

用代码描述流水线 - Jenkins Pipeline 详解

比较 Spring AOP 和 AspectJ

关于JFrog

世界领先DevOps平台

公司成立于2008年,在美国、以色列、法国、西班牙,以及中国北京市拥有超过200名员工。JFrog 拥有4000多个付费客户,其中知名公司包括如腾讯、谷歌、思科、Netflix、亚马逊、苹果等。关注 JFrog,感受原汁原叶的硅谷技术!我们不仅仅提供最优秀的产品,也提供最优秀的持续交付平台的解决方案,详情请洽[email protected]

点击“阅读原文”,进入 JFrog 官网