IP 负载均衡

2017年08月08日 11:09 | 3211次浏览 作者原创 版权保护

我们已经充分了解了反向代理服务器作为负载均衡调度器的工作机制,其本身的开销已经严重制约了这种框架的可扩展性,从而也限制了它的性能极限。

那么,能否在 HTTP 层面以下实现负载均衡呢?答案是肯定的,还记得本书开头那个星际铁路系统的故事吗?回忆一下网络

分层模型,事实上,在数据链路层(第二层)、网络层(第三层)以及传输层(四层)都可以实现不同机制的负载均衡,但有所不同的是,这些负载均衡调度器的工作必须由 Linux 内核来完成,因为我们希望网络数据包在从内核缓冲区进入进程用户地址空间之前,尽早地被转发到其他实际服务器上,没错,Linux 内核当然可以办得到,随后我们会介绍位于内核的 Netfilter和 IPVS,而用户空间的应用程序对此却束手无策。

另一方面,也正是因为可以将调度器工作在应用层以下,这些负载均衡系统可以支持更多的网络服务协议,比如 FTP、SMTP、DNS,以及流媒体和 VoIP 等应用。

这里我们先来介绍基于 NAT 技术的负载均衡,因为它可以工作在传输层,对数据包中的 IP 地址和端口信息进行修改,所以也称为四层负载均衡。

DNAT

前面曾经提到过网络地址转换(Network Address Translation,NAT),它可以让用户身处内部网络却与互联网建立通信,而在这里,为了突出应用场景的差异,我想创造一个更加适合的名称,那就是“反向 NAT”,有点类似于反向代理的命名,是的,我们将实际服务器放置在内部网络,而作为网关的 NAT 服务器将来自用户端的数据包转发给内部网络的实际服务器,这为进一步实现负载均衡提供了可能。

这里说的“反向 NAT”,其实就是 DNAT,不同于 SNAT 的是,它需要修改的是数据包的目标地址和端口,这种技术在很多普通的家用宽带路由器上都有支持,如果你曾经配置过任意一款宽带路由器,或许会看到 NAT 设置,或者也称为端口映射设置,因为我们知道通过 NAT 可以修改数据包的目的地端口,很好地隐藏内网服务器的实际端口,提高安全性。

如图 2-11 所示,我们利用家用宽带路由器进行 NAT 设置,再加上前面提到的动态 DNS,完全可以在家里搭建一个公开的小型 Web 站点


图 2-11 家用宽带路由器的端口映射设置

NAT 服务器做了什么

那么,基于 NAT 的负载均衡系统是如何工作的呢?这里我们举个简单的例子,你将会了解 NAT 服务器在整个过程中做了什么。

如图 2-12 所示,NAT 服务器拥有两块网卡,分别连接外部网络和内部网络,IP 地址分别为 125.12.12.12 和 10.0.1.50。与 NAT服务器同在一个内部网络的是两台实际服务器,IP 地址分别为 10.0.1.210 和 10.0.1.211,它们的默认网关都是 10.0.1.50,并且都在 8000 端口上运行着 Web 服务。另外,我们假想用户端的 IP 地址为 202.20.20.20



图 2-12 基于 NAT 的负载均衡系统网络结构图

表 2-6 基于 NAT 的负载均衡系统网络结构图中的服务器说明


现在我们以一个数据包的真实旅程为例,来跟踪它是如何从用户端到实际服务器,又如何从实际服务器返回用户端,在这一系列过程中,数据包的命运发生了多次改变。

首先,用户端通过 DNS 服务器得知站点的 IP 地址为 125.12.12.12,当然,如果站点不使用域名的话,这一步也可能省略。

接下来,用户端向 125.12.12.12 发送了 IP 数据包,我们将目光放在其中的一个数据包上,它的来源地址和目标地址如下所示:

来源地址: 202.20.20.20:6584

目标地址: 125.12.12.12:80

当数据包到达 125.12.12.12 的内核缓冲区后,NAT 服务器并没有把它交给用户空间的进程去处理,而是挑选了一台实际服务

器,这里恰好为 10.0.1.210,接下来 NAT 服务器将刚刚收到的数据包进行了修改,修改后的来源地址和目标地址如下所示:

来源地址: 202.20.20.20:6584

目标地址: 10.0.1.210:8000

可以看到,NAT 服务器修改了数据包的目标地址和端口号,紧接着,NAT 服务器指定内部网卡将这个数据包原封不动地投递

到内部网络中,根据 IP 层的寻址机制,数据包自然会流入 10.0.1.210 这台实际服务器。

接下来,运行在 10.0.1.210 上的 Web 服务处理了这个数据包,然后将要包含结果数据的响应数据包投递到内部网络,它的来源地址和目标地址如下所示:

来源地址: 10.0.1.210:8000

目标地址: 202.20.20.20:6584

显然,这个数据包必须先到达默认网关,于是它再次回到了 NAT 服务器,这时候 NAT 服务器再次修改数据包,修改后的来源地址和目标地址如下所示:

来源地址: 125.12.12.12:80

目标地址: 202.20.20.20:6584

最后,数据包终于回到了用户端。

在整个过程中,NAT 服务器的动作可谓天衣无缝,它欺骗了实际服务器,而在实际服务器上运行的进程总是天真地以为数据包是用户端直接发给自己的,不过,这也许是善意的欺骗。

同时,NAT 服务器也扮演了负载均衡调度器的角色,那么,在讨论调度策略之前,你也许更关心的问题是如何实现 NAT 服务器。

Netfilter/iptables

首先,我们必须得知道 Linux 如何修改 IP 数据包,可以肯定的是,Linux 内核已经具备这样的能力,从 Linux 2.4 内核开始,其内置的 Netfilter 模块便肩负起这样的使命,它在内核中维护着一些数据包过滤表,这些表包含了用于控制数据包过滤的规则。

我们知道,当网络数据包到达服务器的网卡并且进入某个进程的地址空间之前,先要通过内核缓冲区,这时候内核中的 Netfilter便对数据包有着绝对控制权,它可以修改数据包,改变路由规则。

既然 Netfilter 工作在内核中,我们看不见摸不着,那么如何让它按照我们的需要来工作呢?Linux 提供了 iptables,它是工作

在用户空间的一个命令行工具,我们可以通过它来对 Netfilter 的过滤表进行插入、修改或删除等操作,也就是建立了与 Netfilter沟通的桥梁,告诉它我们的意图。

那么,我们要做的就是让 Linux 服务器成为连接外部网络和私有网络的路由器,但这不是普通的路由器,我们知道路由器的工作是存储转发,除了修改数据包的 MAC 地址以外,通常它不会对数据包做其他手脚,而我们要实现的路由器恰恰是要对数据包进行必要的修改,包括来源地址和端口,或者目标地址和端口。

总之,Linux 内核改变数据包命运的惊人能力,决定了我们可以构建强大的负载均衡调度器,将请求分散到其他实际服务器上。


iptables 来实现调度器

可以用 iptables 来实现负载均衡调度器吗?我们来试试吧。

如果你对 iptables 的使用并不熟悉,可以通过丰富的在线文档进行系统的学习,遗憾的是,我们这里不会介绍 iptables 的详细

使用规则,事实上要想在有限的篇幅中把它说清楚并不容易,而且还可能会给你留下阴影。

说到 iptables,最多的应用场景就是防火墙了,我几乎为每台 Linux 服务器都毫不犹豫地进行 iptables 防火墙配置,比如以下

这段简单的 iptables 规则:

iptables -F INPUT
iptables -A INPUT -i eth0 -p tcp --dport 80 -j ACCEPT
iptables -P INPUT DROP

它完成了重要的任务,那就是告诉内核只允许外部网络通过 TCP 与这台服务器的 80 端口建立连接,这项规则可以很好地用在 Web 服务器上。

另外,我们还会用 iptables 来实现本机端口重定向,比如以下的规则:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8000

它将所有从外部网络进入服务器 80 端口的请求转移到了 8000 端口,这有什么意义呢?当然是隐藏某些服务的实际端口,同时也便于将一个端口快速切换到其他端口的服务上,提高端口管理的灵活性。

关键的时刻到了,我们将用 iptables 来实现 NAT,在此之前,我们需要执行以下的命令行操作:

echo 1 > /proc/sys/net/ipv4/ip_forward

接下来,我们在作为调度器的服务器上执行以下的 iptables 规则:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8001 -j DNAT --to-destination 10.0.1.210:8000


这条规则几乎完成了所有 DNAT 的实现,它将调度器外网网卡上 8001 端口接收的所有请求转发给 10.0.1.210 这台服务器的8000 端口,至于转发的具体过程,前面我们已经详细介绍过,在此不再赘述。

同样,我们在调度器上执行以下的 iptables 规则:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8002 -j DNAT --to-destination 10.0.1.211:8000

不用多说,这条规则你一定明白了。

这两条规则已经进入了 Netfilter 的过滤表,我们可以通过 iptables 命令来查看它们,如下所示:

可以看到,转发规则已经存在,但是还缺少一个环节,这至关重要,我们必须将实际服务器的默认网关设置为 NAT 服务器,也就是说,NAT 服务器必须为实际服务器的网关,否则,数据包被转发后将一去不返。

添加默认网关非常容易,在实际服务器上执行以下命令行操作:

route add default gw 10.0.1.50

现在,查看路由表,刚才的默认网关已经出现了:



现在,我们终于可以通过调度器的 8001 和 8002 端口分别将请求转发到两个实际服务器上,可是,回想前面的问题,用 iptables来实现负载均衡调度器,看起来有点困难,iptables 似乎只能按照我们的规则来干活,没有调度器应该具备的调度能力和调度策略。

接下来,IPVS 上场的时刻到了。

IPVS/ipvsadm

熟悉了 Netfilter/iptables 的机制后,理解 IPVS(IP Virtual Server)就一点也不难了,它的工作性质类似于 Netfilter 模块,也工作在 Linux 内核中,但是它更专注于实现 IP 负载均衡。

IPVS 不仅可以实现基于 NAT 的负载均衡,同时还包括后面要介绍的直接路由和 IP 隧道等负载均衡。令人振奋的是,IPVS模块已经内置到 Linux 2.6.x 内核中,这意味着使用 Linux 2.6.x 内核的服务器将无须重新编译内核就可以直接使用它。

要想知道内核中是否已经安装 IPVS 模块,可以进行以下查看:

s-mat:~ # modprobe -l | grep ipvs
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_wrr.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_wlc.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_sh.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_sed.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_rr.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_nq.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_lc.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_lblcr.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_lblc.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_dh.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs.ko
/lib/modules/2.6.16.21-0.8-bigsmp/kernel/net/ipv4/ipvs/ip_vs_ftp.ko

这样的结果就意味着 IPVS 已经安装在内核中。

当然,IPVS 也需要管理工具,那就是 ipvsadm,它为我们提供了基于命令行的配置界面,你可以通过它快速实现负载均衡系统,这里也称为 LVS(Linux Virtual Server,Linux 虚拟服务器)或者集群。

你可以从 LVS 的官方站点下载与内核匹配的 ipvsadm 源代码,然后编译它,需要注意的是,在编译过程中,你还需要在

/usr/src/linux 中存有内核源代码(Kernel-source)

接下来,我们将使用 ipvsadm 来实现基于 NAT 的负载均衡系统,也就是 LVS-NAT。

LVS-NAT

当一切都准备好后,你会发现使用 ipvsadm 组建基于 NAT 的负载均衡系统是一件富有乐趣的事情。

现在我们可以暂时完全忘记 iptables,因为 ipvsadm 在这里可以完全取代它。首先不要忘了打开调度器的数据包转发选项,如

下所示:

echo 1 > /proc/sys/net/ipv4/ip_forward

然后,你还需要检查实际服务器是否已经将 NAT 服务器作为自己的默认网关,如果不是,赶快添加它,比如:

route add default gw 10.0.1.50

接下来,就是见证 IPVS 的时刻了,我们执行以下命令行操作:

ipvsadm -A -t 125.12.12.12:80 -s rr
ipvsadm -a -t 125.12.12.12:80 -r 10.0.1.210:8000 -m
ipvsadm -a -t 125.12.12.12:80 -r 10.0.1.211:8000 -m

第一行规则用来添加一台虚拟服务器,也就是负载均衡调度器,这里的-s rr 是指采用简单轮询的 RR 调度策略。后面两行用来为调度器添加实际服务器,其中-m 表示采用 NAT 方式来转发数据包,这正是我们所希望的。

接下来,我们还可以通过 ipvsadm 来查看所有实际服务器的状态,如下所示:



大功告成,我们对调度器进行连续的 HTTP 请求,没错,它正是采用 RR 方式将请求均衡地分散到了两台实际服务器上。

关键的问题来了,我们费了很大的力气,终于搭建起基于 NAT 的负载均衡系统,它的性能如何呢?

性能

还记得前面对反向代理负载均衡系统的一系列不同内容的压力测试吗?在使用了 LVS-NAT 后,我们同样针对这些内容,再次对调度器进行压力测试,同时和之前的数据进行比较,如表 2-7 所示。

表 2-7 后端服务器、反向代理服务器、NAT 服务器分别对于不同内容的压力测试结果



对于以上的数据,我们仍然绘制了柱状图和曲线图,如图 2-13 和图 2-14 所示。通过前面对基于反向代理的负载均衡系统扩展能力的分析,我们知道,当实际服务器的吞吐率达到一定高度时,反向代理服务器的吞吐率将很快达到极限,而从以下对比图中可以看出,在基于 NAT 的负载均衡系统中,作为调度器的 NAT 服务器可以将吞吐率继续提升到一个新的高度,几乎是反向代理服务器的两倍以上,这当然归功于在内核中进行请求转发的较低开销。


图 2-13 反向代理负载均衡和 LVS-NAT 对比柱状图


图 2-14 反向代理负载均衡和 LVS-NAT 对比曲线图

另外,从图 2-14 中可以直观地看出,对于 num 大于 500的动态内容,也就是实际服务器的吞吐率小于 3000reqs/s 时,不论

是基于反向代理,还是基于 NAT,负载均衡的整体吞吐率都差距不大,这意味着对于一些开销较大的内容,使用简单的反向代理来搭建负载均衡系统是非常值得考虑的,至少在初期是一个快速有效的方案,而且它可以非常容易地迁移到 NAT 方式。


动态调度策略

在刚才的 LVS-NAT 中,我们使用了 RR 调度策略,它是一种静态调度策略,当在集群中实际服务器承载能力相当的环境下,它可以很好地实现均衡调度。同时,LVS 也支持带权重的 RR 调度,只需要配置实际服务器的 weight 值即可,当然,这也属于静态调度策略。

除此之外,LVS 提供了一系列的动态调度策略,比如最小连接(LC)、带权重的最小连接(WLC)、最短期望时间延迟(SED)等,它们都可以根据实际服务器的各种实时状态做出调度决策,可谓是察言观色,全面兼顾。

由于篇幅限制,我们不打算对这些动态调度策略进行详细的介绍和测试,另一方面,不同的动态调度策略适合于不同的场景,由于测试环境的局限,我们也无法全都模拟这些场景。但是,你可以通过 LVS 官方文档详细了解这些动态调度策略的机制,一旦了解后,你可以根据站点的需要和部署环境,选择合适的动态调度策略并充分测试性能。

网关瓶颈

尽管如此,作为 NAT 服务器的网关也成为制约集群扩展的瓶颈,我们知道,NAT 服务器不仅要将用户的请求转发给实际服务器,同时还要将来自实际服务器的响应转发给用户,所以,当实际服务器数量较多,并且响应数据流量较大时,来自多个实际服务器的响应数据包将有可能在 NAT 服务器发生拥挤。

显然,考验 NAT 服务器转发能力的时刻到了,由于转发数据包工作在内核中,我们几乎可以不考虑额外的开销,所以,转发能力主要取决于 NAT 服务器的网络带宽,包括内部网络和外部网络。

举个例子,假如 NAT 服务器通过 100Mbps 的交换机与多台实际服务器组成内部网络,通过前面介绍带宽的章节,我们知道这些实际服务器到 NAT 服务器的带宽为共享 100Mbps,这样一来,尽管实际服务器本身可以很容易达到 100Mbps 的响应流量,比如提供下载服务等,但是 NAT 服务器的 100Mbps 出口带宽成了制约条件,使得无论添加多少台实际服务器,整个集群最多只能提供 100Mbps 的服务。

要解决网关带宽的瓶颈也并不困难,我们可以为 NAT 服务器使用千兆网卡,并且为内部网络使用千兆交换机,图 2-15 展示了理想的带宽配置,其中提及的网卡和交换设备都是工作在全双工模式下的,也就是两个方向的数据传输都具备同样的带宽


图 2-15 LVS-NAT 的理想带宽配置

这样做的出发点没错,但是,如果我们希望继续扩展带宽,比如实际服务器使用千兆网卡,那么 NAT 服务器将使用万兆(10Gb)网卡以及万兆交换机,可是,这些设备的昂贵价格足可以让你再购买几台服务器,至少目前是这样。

即便是为 NAT 服务器使用万兆网卡,充分满足网络带宽,但是服务器本身能够以这样的速度转发数据包吗?我们得看服务器的总线带宽,这取决于总线频率和总线位宽,比如对于支持 400MHz 前端总线频率的 32 位处理器,理论上它的总线带宽为:

32bit × 400MHz = 12.8Gbps = 1.6GB/s

这意味着 CPU 和内存直接交换数据的速度,当网络数据包通过 10Gbps 的带宽进入内核缓冲区后,理论上可以快速地执行转发,也就是将缓冲区中的数据包进行简单修改后复制到另一块负责发送数据的缓冲区。当然,实际的转发速度肯定是达不到12.8Gbps 的,还要考虑处理时间等其他因素。

当然,如果条件允许,你也可以购买专业级的负载均衡硬件产品,比如 Cisco 的 LocalDirector 或 F5 的 BIG-IP Local TrafficManager,它们都可以实现基于 NAT 的负载均衡,并且支持丰富的调度策略,它们可以达到较高级别的带宽,而且拥有强大的管理功能,但价格不菲。

我们不打算详细介绍这些硬件产品,如果有兴趣,你可以查阅它们的官方介绍。但可以肯定的是,它们实现数据转发和调度策略的方式和我们这里的介绍没有太大的差异。

假如你不想花钱去购买千兆交换机或万兆交换机,甚至负载均衡硬件设备,又不想让 NAT 服务器成为制约扩展的瓶颈,怎么办呢?一个简单有效的办法是,将基于 NAT 的集群和前面的 DNS-RR 混合使用,你可以组建多个条件允许的 NAT 集群,比如 5 个100Mbps 出口带宽的集群,然后通过 DNS-RR 方式来将用户请求均衡地指向这些集群,同时,你还可以利用 DNS 智能解析实现地域就近访问。

事实上,对于大多数中等规模的站点,拥有 5 个 100Mbps 的 NAT 集群,再加上 DNS-RR,通常足够应付全部的业务。

但是,对于提供下载或视频等服务的大规模站点,100Mbps 的集群带宽就显得微不足道了,其中一台实际服务器甚至就要吞没上百兆的带宽,现在,NAT 服务器的瓶颈出现了,你会选择提高 NAT 服务器的带宽,还是选择其他方案呢?幸运的是,LVS提供了另一种实现负载均衡的方式,那就是直接路由(Direct Route,DR),接下来我们会详细介绍它。



小说《我是全球混乱的源头》
此文章本站原创,地址 https://www.vxzsk.com/1298.html   转载请注明出处!谢谢!

感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程