负载均衡,相信这个名词大家都不陌生。但聊起负载均衡,可能大家相互间说的并不是同一个东西,就跟火锅似的,大家都能聊到一起,但聊了半天,发现北京人说的是铜炉火锅,云南人说的是菌菇火锅,四川人说的是红油火锅,广州人说的是清补凉。我也不知道大家说的负载均衡是不是同一个东西,若是有人要问我如何做高可用,我上来就是冗余集群+负载均衡。本文对于之前没有接触过nginx、LVS、硬件负载均衡的读者,前2节略显枯燥;遇事不决,直接第3节谢谢。
假定我们的服务都是C/S架构,即一侧是客户机(client),一侧是服务器(server),在本文中,所有图都默认左侧为客户机,右侧为服务器。需要注意的是,客户机和服务器的角色是相对的,一台机器既可以是服务器也可以是客户机,因为机器之间可能互相提供服务,比如A向B提供邮件服务,B向A提供文件共享服务。本文所提到的客户机和服务器,针对的是某个具体的应用,在某个具体应用的视角里,一方为客户机,一方为服务器。
最简单的情况,1客户机:1服务器。1台客户机仅可以访问到1台服务器,所有请求和响应都在两者之间,此处将之暂记为1:1架构。在1:1架构的基础上,引入代理服务器。根据代理服务器所在侧的不同,可分为1:1-1(反向代理)架构和1-1:1(正向代理)架构两种。
如何区分代理服务器属于正向代理还是反向代理,看代理服务器由哪一方添加。客户机方添加则为正向代理,服务器方添加则为反向代理。
添加代理服务器的目的往往有以下几项:1.保障访问安全。2.分担服务器压力。3.隐藏所在侧服务器/客户机信息。
客户机多过服务器的情况,n客户机:1服务器。n台客户机访问1台服务器,由1台服务器处理所有请求,此处将之暂记为n:1架构。同1.1节,引入代理,记为n:1-1架构和n-1:1架构。
服务器也可以多过客户机,两者数量对调,1客户机:m服务器。1台客户机可以访问到n台服务器,此处暂将之记为1:m架构。同1.1,引入代理,记为1:1-m架构和1-1:m架构。
此处需要明确,1:m代表的是1台客户机能访问到m台服务器,并非1次请求1台客户机需要同时往m台服务器都发包。通常地,1次请求客户机只发1个包给某1台服务器;如果同时给m台服务器都发,客户机会收到m个回复,这些回复可能是矛盾的或者重复的。
真实线上业务的情况,n客户机:m服务器。n台客户机中的每一台都可以访问到m台服务器,如下所示。同1.1节,引入代理,记为n:1-m架构和n-1:m架构。
n:m可以看作n:1架构或1:m架构的叠加 。从每一台客户机的角度来看,都是1:m的架构;从每一台服务器的角度来看,都是是n:1的架构。可以用线代知识理解:两个单位向量(1,0)和(0,1),分别代表(1客户机,0服务器),(0客户机,1服务器),可以span出(n,1),(1,m)和(n,m)等向量;同时,(n,m)向量也可表达成(n,1)和(1,m)向量的叠加。
n:m架构中添加代理服务器原因往往还有这几项:1.减少连接数量。可将n*m条连接减少到n+m条连接,类似于交换机在全互联拓扑起到的功能。2.集中连接方便管理。所有的连接都可以在代理上被看到和管理。3.统一进行负载均衡。
n:m架构及其添加代理的架构将作为讨论负载均衡的基础。
负载均衡用于分发流量。以n:m架构为例,负载均衡的作用是,在一段时间内,将n台服务器的流量尽量均匀地分摊给m台服务器。可以用积分理解负载均衡,设时间为t,任意两台服务器瞬时流量曲线为f(t) 、 g(t),负载均衡的目标并非f(t) ≈ g(t),而是在a ~ b这段时间内瞬时流量的累积值相等,即∫(a ~ b) ƒ(t) dt ≈ ∫(a ~ b) g(t) dt
如何做负载均衡,需要组合下面三个要素:1.区分流量字段。2.负载均衡算法。3.配套网络设计。
无论TCP/IP七层还是五层模型,前四层是一致的。一层跟数据内容无关排除在外,二三四层类似于千层饼,层层相叠,都有着固定的格式。每种格式里都包含了可标识此层数据的全部字段。
上面这些首部的添加和识别过程被称为封包和解包,封包和解包过程引用之前博客做的一张动图。
实际这些首部里的所有字段都可以用于区分流量。问题在于,区分出怎样的流量。下面是字段常用的区分用途。
在负载均衡中,主要识别的是“从哪儿来,到哪儿去” ,与之契合的是“源MAC地址”、“目MAC地址”、“源IP地址”、“目IP地址”、“源端口”、“目标端口”这些字段。
上面仅列出了二到四层的字段,除此之外,七层的字段在负载均衡也常常会被用到。七层协议HTTP、FTP、SMTP的报文格式里,都有可供负载均衡的字段,甚至协议本身支持自定义字段。比如在HTTP中,除了可以利用URL进行上下文的负载均衡,将/admin和/job这样的url分发到不同的服务器;还可以利用Request Header中的可自定义字段,进行自定义分发。这部分在nginx中将为有大量体现,此处不展开。
常见负载均衡算法如下,目前已有大量资料对每一种算法进行介绍,此处不再赘述,仅列出笔者归纳出的一些区别。
基础算法 | 代理必须 | 算法加权 | 目标固定 | 状态保存 | 整体开销 | 适用协议字段或值 |
---|---|---|---|---|---|---|
轮询(Round-Robin) | × | √ | × | × | 小 | 无 |
随机(Random) | × | √ | × | × | 小 | 无 |
哈希(Hash) | × | √ | √ | × | 小 | 源目MAC、源目IP、源目端口 |
最少连接数(Least-Connection) | √ | √ | √ | √ | 中 | 源IP+目IP+源端口+目端口 |
最短响应时间(Shortest-Response Time) | √ | √ | × | × | 中 | ICMP协议中的时间戳 |
最佳处理能力(Best Processing Capacity) | √ | √ | × | × | 大 | SNMP协议获取到的值 |
代理必须为算法本身是否必须依赖代理服务器实现。例如轮询,可直接在客户机实现,只需在客户机上配置服务器列表,从列表中循环取下一个即可;而最小连接数,则需要代理服务器来统计连接数量,从而得出最小连接数的服务器。
算法加权为算法本身是否有加权版本。几乎所有负载均衡算法都有其加权版本,下面是轮询、随机以及它们对应的加权算法简单实现。
#encoding=utf-8
import random
import time
srvList = ["192.168.1.1:9090","192.168.1.2:9090","192.168.1.3:9090"]
srvWeight = {"192.168.1.1:9090":2}
def roundRobinGen(srvList):
"""轮询生成器"""
index = 0
length = len(srvList)
while True:
yield srvList[index]
index=(index+1)%length
def roundRobinWithWeightGen(srvList,srvWeight):
"""加权轮询生成器"""
indexs = []
for i in range(len(srvList)):
if srvList[i] in srvWeight:
for _ in range(srvWeight[srvList[i]]):
indexs.append(i)
else: #如果没有在srvWeight中,则默认权重为1
indexs.append(i)
random.shuffle(indexs) #这里只应用了简单随机,实际可以采取更优的方式重新均匀分布下标数组
nowIndex = 0
while True:
yield srvList[indexs[nowIndex]]
nowIndex = (nowIndex + 1) % len(indexs)
def randomGen(srvList):
"""随机生成器"""
index = random.randint(0,len(srvList)-1)
while True:
yield srvList[index]
index = random.randint(0,len(srvList)-1)
def randomWithWeightGen(srvList,srvWeight):
"""加权随机生成器"""
borders = []
nowSum = 0
for i in range(len(srvList)):
borders.append(nowSum)
if srvList[i] in srvWeight:
nowSum += srvWeight[srvList[i]]
else:
nowSum+=1
def realIndex(index):
"""
返回真实的Index.
比如权重为{"192.168.1.1":3,"192.168.1.2":2},则从[0,1,2,3,4]中取随机数,
选到0,1则为192,168.1.2,选到2,3,4则为192.168.1.1
"""
nonlocal borders
for i,border in enumerate(borders):
if index>border:
continue
else:
return i
index = realIndex(random.randint(0,nowSum-1))
while True:
yield srvList[index]
index = realIndex(random.randint(0,nowSum-1))
def test(func,*args):
"""测试函数"""
generator = func(*args)
for srv in generator:
print(srv)
time.sleep(1)
test(randomWithWeightGen,srvList,srvWeight)
目标固定为算法是否能将某客户机的访问固定到某一台服务器上。如果服务器是无状态的,每次使用相同的参数返回相同的内容,则不需要目标固定。但是如果服务器会缓存用户信息,如果没有目标固定:1. 相同备份的数据同时存在于多台服务器,内存空间浪费。2. 用户的体验会变差,会遇到诸如访问网页频繁丢session的问题。目标固定能力一般由哈希算法提供。
普通的哈希算法实现没办法适应服务器的动态变化,这个时候会用到一致性哈希算法。其本质上是用到一个环形队列,具体内容以及实现参考此链接。
整体开销为算法整体上的时间和空间开销。
适用协议字段或值为算法可以使用的字段或需要的输入。
区分流量字段和负载均衡算法选定后,便需要进行配套的网络设计。网络设计主要要注意以下两点:
如何保证数据的来回一致性,其实是保证来回地址一致。数据还没进入到操作系统内核前,保证其不被网卡丢弃。
关于这个来回一致性笔者想以LVS(一种负载均衡软件,部署在代理上)的三种网络模式进行举例说明,这三种模式实际上对应了三种有代理情况下的网络设计。
NAT模式
NAT(Network Address Translation)模式下,所有数据包都通过代理来修改IP地址字段,客户机发包、服务器回包都需要经过代理。
TUN模式
TUN(Tunnel)模式下,所有数据包都通过代理来修改IP地址字段,客户机发包需要经过代理,服务器回包则不需要经过代理。此模式会在原本数据报外面嵌套新的二层首部和三层首部,实际上这两层首部只是耗材,在抵达服务器网卡后就会被剥离,然后原来的完整数据包可以顺利通过网卡,进入操作系统内核后,进行程序加工处理。
DR模式
DR(Direct Route)模式下,代理可修改IP地址,服务器也通过编程具有了修改IP地址的能力,客户机发包需要经过代理,服务器回包则不需要经过代理。
注:“通过代理来修改IP地址”和“修改IP地址的能力”代表着除了转置以外的修改IP地址能力,转置为对换源目IP地址。一般服务器或主机不经额外编程只有转置的能力,特殊的代理服务器才会有各种各样的修改IP、嵌套封装操作。
上面3种设计都保证了客户机发包和收包的包中的IP是一致的,只有源目IP地址发生了转置。同上面的3种网络设计一样,我们也可以根据我们选定出的算法和字段,在保证数据的来回一致性的情况下,设计出特定的网络设计。
实际上,上面举例的三种模式,就有着对高可用的思考。LVS的三种网络模式中,
还有一些零碎的点,仅供参考:1. 减少单节点使用代理,尽量多节点冗余。 2.关键节点使用硬件代理产品,比如F5,A10,Citrix的产品。3.使用硬件+软件的多层级负载均衡,硬件只负责四层以下转发,软件负责七层转发。
狭义的负载均衡仅为客户机访问服务器时的流量分发,广义的负载均衡为资源的统筹调度。这个资源可以是线路带宽资源,可以是CPU资源,可以是内存资源,还可以是硬盘资源。负载均衡就是去合理安排这些资源。以下是一些例子,也可以算是负载均衡的一部分。
客户机访问服务器的时候,还可能会存在除了客户机侧和服务器侧外的第三方乃至第四方。如果使用HTTP,本文1.1-1.4节中都可以引入DNS这样的第三方;如果使用HTTPS,还会引入CA认证机构这样的第四方。CA与认证有关,此处不展开,本处仅在1:m架构的基础上增加DNS侧用于举例。
在上图中,客户机如果要以HTTP形式访问服务器,是先以域名的形式发送到DNS服务器进行解析,解析出域名对应的IP地址后再发起HTTP访问的。利用这点,可以使用DNS服务器做负载均衡功能,不管是私网DNS服务器还是公网DNS服务器。主要有下面两种方式:
分库分表,数据库层面上的负载均衡。
应用内也有部分负载均衡途径。如图,网游玩家应该都知道网游会分大区或者服务器。毫无疑问这是用户上的负载均衡。
有的游戏还会分国区/国际区,IOS平台/安卓平台呢,用户负载均衡无疑。
日常生活中,不同的行政区会使用不同的粤核酸小程序,也算是很好的例子了。什么高并发大流量服务器横向扩容,听起来就花里胡哨,还是得粤核酸1-6来教大家什么是返璞归真的负载均衡高可用。
线路负载均衡一般都是三层路由层面的,这里说个二层的,链路聚合,非网络相关技术人员不需要太关注这节。简单来说,链路聚合就是将多条物理线路在逻辑上视为一条逻辑线路,它的变种“跨设备链路聚合”,则在此之上另外结合了堆叠技术。VPC,MLAG,CLAG,MC-LAG是常见的跨设备链路聚合技术,“跨设备”指的是跨物理设备,但所跨的物理设备是要用堆叠技术形成一台逻辑设备。如下图中,一台接入交换机两条上连线分别接两台汇聚交换机,然后在这两条线画个圈,就代表跨设备链路聚合,这样聚合的多条线也可以看成一条线。
两条物理线路经过链路聚合后,流量较少时(占用带宽<单线的带宽),并不会只跑在一条线工作,而是两条线上负载均衡工作,这个负载均衡利用的就是“源MAC地址+哈希算法”,将不同源MAC地址的数据报分配到不同的线路上以保证二层的有序性。
注: 三层首部中有标识可以分出先后顺序,但二层首部中是分不出先后顺序的,同源MAC地址的两个数据包不能在链路聚合组中的两条物理线路分别发。
K8S调度POD到不同节点应该也算负载均衡。
综上所述,欢迎补充。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。