分布式学习-负载均衡

概述

分布式系统的关键是做冗余,让这些冗余能发挥高可用作用的就是负载均衡负载均衡的作用是一个“连接者”,让上下游之间以期望的方式“连接”起来。所以,有必要先了解一下这些上下游的全貌,并且从中找到做负载均衡的地方。
img

分布式系统有各式各样的架构方式,不过本质上都是上图这样的一个分层架构。图中红点标记出的地方就是需要做负载均衡的地方,可以看到,就是每两层之间的连接处。这些连接处在实际做负载均衡的时候,需要结合所处的网络层次。因为在不同的网络层次有不同的做法。如下图。
img

一般主流的四层负载均衡和七层负载均衡,前者指的就是传输层,主要涉及的协议是TCP、UDP等,后者指的应用层,主要涉及的协议是Http、Https和FTP等。
用来实现负载均衡的解决方案有很多,分为基于硬件或者基于软件的,比较成熟的诸如:F5(支持四层、七层)、LVS(支持四层)、Nginx(支持七层)等等。

常用负载均衡策略

轮询

img
这是最常用也最简单策略,平均分配,人人都有、一人一次。大致的代码如下。

1
2
3
4
5
6
7
8
9
10
11
int globalIndex = 0; 
try
{
return servers[globalIndex];
}
finally
{
globalIndex++;
if (globalIndex == 3)
globalIndex = 0;
}

加权轮询

img

在轮询的基础上,增加了一个权重的概念。权重是一个泛化后的概念,可以用任意方式来体现,本质上是一个能者多劳思想。比如,可以根据宿主的性能差异配置不同的权重。大致的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int matchedIndex = -1;
int total = 0;
for (int i = 0; i < servers.Length; i++)
{
//每次循环的时候做自增(步长=权重值)
servers[i].cur_weight += servers[i].weight;
//将每个节点的权重值累加到汇总值中
total += servers[i].weight;
//如果 当前节点的自增数 > 当前待返回节点的自增数,则覆盖。
if (matchedIndex == -1 || servers[matchedIndex].cur_weight < servers[i].cur_weight)
{
matchedIndex = i;
}
}

servers[matchedIndex].cur_weight -= total;//④被选取的节点减去②的汇总值,以降低下一次被选举时的初始权重值。
return servers[matchedIndex];

这段代码的过程如下图的表格。”()”中的数字就是自增数,代码中的cur_weight。
img

值得注意的是,加权轮询本身还有不同的实现方式,虽说最终的比例都是2:1:2。但是在请求送达的先后顺序上可以所有不同。比如「5-4,3,2-1」和上面的案例相比,最终比例是一样的,但是效果不同。「5-4,3,2-1」更容易产生并发问题,导致服务端拥塞,且这个问题随着权重数字越大越严重。例子:10:5:3的结果是「18-17-16-15-14-13-12-11-10-9,8-7-6-5-4,3-2-1」

最少连接数

img
这是一种根据实时的负载情况,进行动态负载均衡的方式。维护好活动中的连接数量,然后取最小的返回即可。大致的代码如下。

1
2
3
4
var matchedServer = servers.orderBy(e => e.active_conns).first();
matchedServer.active_conns += 1;
return matchedServer;
//在连接关闭时还需对active_conns做减1的动作。

最快响应

img
这也是一种动态负载均衡策略,它的本质是根据每个节点对过去一段时间内的响应情况来分配,响应越快分配的越多。具体的运作方式也有很多,上图的这种可以理解为,将最近一段时间的请求耗时的平均值记录下来,结合前面的「加权轮询」来处理,所以等价于2:1:3的加权轮询。

Hash法

img
hash法的负载均衡与之前的几种不同在于,它的结果是由客户端决定的。通过客户端带来的某个标识经过一个标准化的散列函数进行打散分摊。

常用负载均衡策略优缺点和适用场景

img

健康探测保障高可用

不管是什么样的策略,难免会遇到机器故障或者程序故障的情况。所以要确保负载均衡能更好的起到效果,还需要结合一些「健康探测」机制。定时的去探测服务端是不是还能连上,响应是不是超出预期的慢。如果节点属于“不可用”的状态的话,需要将这个节点临时从待选取列表中移除,以提高可用性。一般常用的「健康探测」方式有3种。

HTTP探测

使用Get/Post的方式请求服务端的某个固定的URL,判断返回的内容是否符合预期。一般使用Http状态码、response中的内容来判断。

TCP探测

基于Tcp的三次握手机制来探测指定的IP + 端口。最佳实践可以借鉴阿里云的SLB机制,如下图。
img

UDP探测

可能有部分应用使用的UDP协议。在此协议下可以通过报文来进行探测指定的IP + 端口。最佳实践同样可以借鉴阿里云的SLB机制,如下图。
img
结果的判定方式是:在服务端没有返回任何信息的情况下,默认正常状态。否则会返回一个ICMP的报错信息。

实施负载均衡

硬件负载均衡

硬件这块名气最大的还属F5。此类硬件负载均衡器的特点是“壕”,毕竟是纯商业化的东西,投入的资源和精力自然是众多开源软件负载均衡所无法比拟的,所以功能非常强大。包含访问加速、压缩、安全等等负载均衡之外的许多附加功能。

如果用F5组网的话,有两种结构,串行结构和并行结构,也称为直连模式和旁路模式。前者的优势在对硬件产生压力较小、且天然安全性高,而后者对原网络架构的改动较小、且扩展性较好。大家在实际的使用中结合自身情况来部署。

软件负载均衡(L7)

img

针对Web应用的L7负载均衡,比较主流的产品是2个Nginx、HAProxy。在L7做负载均衡,最大的特点就是灵活,请求的URL、Header都是可以去掌控的,所以可以利用其中的任何信息为负载均衡策略所用。实际操作中主要做2步:

  1. 在公网的域名解析中,配置解析到「反向代理」。记录类型是「A」,记录值是「反向代理」的IP。

  2. 配置真实提供服务的Web应用IP和端口,和负载均衡均衡策略。上图中的配置是Nginx中的示例,负载均衡策略的缺省值是轮询。

软件负载均衡(L4)

img

当Web应用所依赖的TCP协议的服务需要横向扩展,或者需要做数据库、分布式缓存的多主、主从集群时,那么就需要一个支持L4的负载均衡软件。这里最知名的就属LVS了,它是内核态的程序,所以相比用Nginx、HAProxy来做L4的负载均衡,在性能、资源的消耗上会更优一些。实际运用中的操作步骤主要也是2步:

  1. 在LVS中添加一个IP虚拟服务(IPVS),并指定它的IP、端口和负载均衡策略。

  2. 将IP虚拟服务关联到真实的服务上,并指定模式和权重的信息。(做L4的负载均衡可以使用NAT或者FULLNAT模式)

LVS的模式一共有四种,除了NAT和FULLNAT(NAT的增强版)模式外,它的TUN模式可以在L3做负载均衡,DR模式可以在L2做负载均衡,到这个层面其实就和做硬件同处于一个层次了。并且,随着层次的深入,虽然对功能性上有所弱化,但是如果不考虑端口的话,单从IP层面的负载均衡来说,用DR模式做,则对数据包的加工介入度会降到最低,因此也是通过软件做负载均衡能够达到的性能极致。
另外,LVS中运用的虚拟IP概念,本质上和Nginx中的“server”概念一样,定义了一个统一入口,作用上并没有差别。将Nginx中的upstream关联到server,就如LVS操作步骤第2点中的关联一般。

优缺点

img
不同的解决方案有不同的侧重点。因此在单个解决方案已经无法满足的情况下,可以组合使用,各尽所长。

推荐方案

负载均衡这个领域还是以高可用和性能为2个最重要因素,下面是推荐的一种组合方式,也是在系统量级达到每小时上亿PV之后最被广泛使用的一种。理论上,利用第一步DNS的域名解析所带的负载均衡效果,只要复制多套LVS主备出来,绑上多个不同的虚IP,可以做到无限横向扩展,以支撑不断增长的流量。
img
用到的3个软件目前都是开源产品,LVS+Keepalived负责做Nginx的负载均衡,而Nginx负责分发到实际的请求到Http和Tcp协议的应用上。
关于LVS的模式选择,如果在同网段内的话优先使用DR模式进行L2转发,性能最好。否则使用TUN模式进行L3分发。与此同时,在L4、L7的分发上使用Nginx来做,可以发挥其灵活易扩展的特点以及其它的一些额外特性如缓存等。

本文标题:分布式学习-负载均衡

文章作者:王洪博

发布时间:2019年05月23日 - 09:05

最后更新:2019年09月12日 - 10:09

原始链接:http://whb1990.github.io/posts/95c2b1da.html

▄︻┻═┳一如果你喜欢这篇文章,请点击下方"打赏"按钮请我喝杯 ☕
0%