负载均衡初学

负载均衡

一、负载均衡简介

1.1.为什么要用负载均衡

当前许多项目都需要面对庞大的用户量,高并发,海量数据等挑战。为了提升系统整体的性能,可以采用垂直扩展和水平扩展两种方式。

垂直扩展: 从单机的角度通过增加硬件处理能力,比如 CPU 处理能力,内存容量,磁盘等方面,实现服务器处理能力的提升。但是,单机是有性能瓶颈的,一旦触及瓶颈,再想提升,付出的成本和代价会极高。这显然不能满足大型分布式系统(网站)所有应对的大流量,高并发,海量数据等挑战。(提升单机性能,得以达到项目的需求,小型项目还可以,大型项目直接淘汰)

水平扩展: 通过集群来分担大型网站的流量。集群中的应用服务器(节点)通常被设计成无状态,用户可以请求任何一个节点,这些节点共同分担访问压力。水平扩展有两个要点:

  • 应用集群: 即应用集群化部署,将同一应用部署到多台机器上,组成处理集群,接收负载均衡设备分发的请求,进行处理,并返回相应数据。

  • 负载均衡: 将用户访问请求,通过某种算法,分发到集群中的节点。

1.2.什么是负载均衡

负载均衡(Load Balance,简称 LB)是高并发、高可用系统必不可少的关键组件,目标是尽力将网络流量平均分发到多个服务器上,以提高系统整体的响应速度和可用性。

高并发: 负载均衡通过算法调整负载,尽力均匀的分配应用集群中各节点的工作量,以此提高应用集群的并发处理能力(吞吐量)。

伸缩性: 添加或减少服务器数量,然后由负载均衡进行分发控制。这使得应用集群具备伸缩性。

高可用: 负载均衡器可以监控候选服务器,当服务器不可用时,自动跳过,将请求分发给可用的服务器。这使得应用集群具备高可用的特性。

安全防护: 有些负载均衡软件或硬件提供了安全性功能,如:黑白名单处理、防火墙,防 DDos 攻击等。

DDos攻击:
分布式拒绝服务(Distributed Denial of Service,简称DDoS)将多台计算机联合起来作为攻击平台,通过远程连接,利用恶意程序对一个或多个目标发起DDoS攻击,消耗目标服务器性能或网络带宽,从而造成服务器无法正常地提供服务。
当被DDoS攻击时,主要表现为:
(1)被攻击主机上有大量等待的TCP连接。

(2)网络中充斥着大量的无用的数据包,源地址为假。

(3)制造高流量无用数据,造成网络拥塞,使受害主机无法正常和外界通讯。

(4)利用受害主机提供的服务或传输协议上的缺陷,反复高速地发出特定的服务请求,使受害主机无法及时处理所有正常请求。

(5)严重时会造成系统死机。

二、负载均衡的分类

2.1根据载体类型分类

从支持负载均衡的载体来看,可以将负载均衡分为两类:硬件负载均衡、软件负载均衡

2.1.1硬件负载均衡

硬件负载均衡,一般是在定制处理器上运行的独立负载均衡服务器

硬件负载均衡的优点:

  • 功能强大: 支持全局负载均衡并提供较全面的、复杂的负载均衡算法。
  • 性能强悍: 硬件负载均衡由于是在专用处理器上运行,因此吞吐量大,可支持单机百万以上的并发。
  • 安全性高: 往往具备防火墙,防 DDos 攻击等安全功能

硬件负载均衡的缺点:

  • 成本昂贵: 购买和维护硬件负载均衡的成本都很高。
  • 扩展性差: 当访问量突增时,超过限度不能动态扩容。
2.1.2 软件负载均衡

软件负载均衡,应用最广泛,无论大公司还是小公司都会使用。

软件负载均衡从软件层面实现负载均衡,一般可以在任何标准物理设备上运行。

软件负载均衡的主流产品有:Nginx、HAProxy、LVS

  • LVS: 可以作为四层负载均衡器。其负载均衡的性能要优于 Nginx。
  • HAProxy: 可以作为 HTTP 和 TCP 负载均衡器,支持七层规则。
  • Nginx: 可以作为四层或七层负载均衡器。

软件负载均衡的优点:

  • 扩展性好: 适应动态变化,可以通过添加软件负载均衡实例,动态扩展到超出初始容量的能力。 (打不过就喊人)
  • 成本低廉: 软件负载均衡可以在任何标准物理设备上运行,降低了购买和运维的成本。

软件负载均衡的缺点:

  • 性能略差: 相比于硬件负载均衡,软件负载均衡的性能要略低一些。 (性能不够数量来凑,单挑不过就群殴)

2.2 网络通信分类

软件负载均衡从通信层面来看,又可以分为四层和七层负载均衡。

七层负载均衡: 就是可以根据访问用户的 HTTP 请求头、URL 信息将请求转发到特定的主机。

  • DNS 重定向

  • HTTP 重定向

  • 反向代理

四层负载均衡: 基于 IP 地址和端口进行请求的转发。

  • 修改 IP 地址

  • 修改 MAC 地址

2.2.1 DNS 负载均衡

DNS负载均衡一般用于互联网公司,复杂的业务系统不适合使用。大型网站一般使用 DNS 负载均衡作为 第一级负载均衡手段,然后在内部使用其它方式做第二级负载均衡。DNS 负载均衡属于七层负载均衡。

DNS 实现负载均衡是最基础简单的方式。一个域名通过 DNS 解析到多个 IP,每个 IP 对应不同的服务器实例,这样就完成了流量的调度,虽然没有使用常规的负载均衡器,但实现了简单的负载均衡功能。

因此,DNS 查询相对于 DNS 层级结构,是一个逆向的递归流程,DNS 客户端依次请求本地 DNS 服务器,上一级 DNS 服务器,上上一级 DNS 服务器,… ,根 DNS 服务器(又叫权威 DNS 服务器),一旦命中,立即返回。为了减少查询次数,每一级 DNS 服务器都会设置 DNS 查询缓存。

DNS 负载均衡的工作原理: 基于 DNS 查询缓存,按照负载情况返回不同服务器的 IP 地址。在这里插入图片描述
DNS 负载均衡的优点:

  • 实现简单
  • 成本低,无需自己开发或维护负载均衡设备

DNS 负载均衡的缺点:

  • 服务器故障切换延迟大,服务器升级不方便。我们知道 DNS 与用户之间是层层的缓存,即便是在故障发生时及时通过 DNS 修改或摘除故障服务器,但中间经过运营商的 DNS 缓存,且缓存很有可能不遵循 TTL 规则,导致 DNS 生效时间变得非常缓慢,有时候一天后还会有些许的请求流量。

  • 流量调度不均衡,粒度太粗。DNS 调度的均衡性,受地区运营商 LocalDNS 返回 IP 列表的策略有关系,有的运营商并不会轮询返回多个不同的 IP 地址。另外,某个运营商 LocalDNS 背后服务了多少用户,这也会构成流量调度不均的重要因素。

  • 流量分配策略太简单,支持的算法太少。DNS 一般只支持 rr 的轮询方式,流量分配策略比较简单,不支持权重、Hash 等调度算法。

  • DNS 支持的 IP 列表有限制。我们知道 DNS 使用 UDP 报文进行信息传递,每个 UDP 报文大小受链路的 MTU 限制,所以报文中存储的 IP 地址数量也是非常有限的,阿里 DNS 系统针对同一个域名支持配置 10 个不同的 IP 地址。

2.2.2 HTTP 负载均衡

HTTP 负载均衡是基于 HTTP 重定向实现的。HTTP 负载均衡属于七层负载均衡。
HTTP 重定向原理是:根据用户的 HTTP 请求计算出一个真实的服务器地址,将该服务器地址写入 HTTP 重定向响应中,返回给浏览器,由浏览器重新进行访问。
在这里插入图片描述HTTP 重定向的优点:

  • 方案简单

HTTP 重定向的缺点:

  • 性能较差:每次访问需要两次请求服务器,增加了访问的延迟
  • 如果负载均衡器宕机,就无法访问该站点
2.2.3 反向代理负载均衡

反向代理(Reverse Proxy)方式是指以代理服务器来接受网络请求,然后 将请求转发给内网中的服务器,并将从内网中的服务器上得到的结果返回给网络请求的客户端。反向代理负载均衡属于七层负载均衡。
反向代理服务的主流产品:Nginx

正向代理与反向代理有什么区别?

  • 正向代理: 发生在客户端,是由用户主动发起的。客户端通过主动访问代理服务器,让代理服务器获得需要的数据,然后转发回客户端。

  • 反向代理: 发生在服务端,用户不知道代理的存在。
    在这里插入图片描述
    反向代理是如何实现负载均衡的呢?以 Nginx 为例,如下所示:
    nginx
    Nginx 实现负载均衡步骤:

  • 在代理服务器上设定好负载均衡规则

  • 当收到客户端请求,反向代理服务器拦截指定的域名或 IP 请求,根据负载均衡算法,将请求分发到候选服务器上

  • 当某台候选服务器宕机,反向代理服务器会有容错处理,比如分发请求失败 3 次以上,将请求分发到其他候选服务器上

反向代理的优点:

  • 支持多种负载均衡算法
  • 基于 HTTP 协议,可以监控转发服务器的状态

反向代理的缺点:

  • 额外的转发开销:反向代理的转发操作本身是有性能开销的,可能会包括创建连接,等待连接响应,分析响应结果等操作
  • 反向代理服务如果自身宕机,就无法访问站点,所以需要有高可用 方案,常见的方案有:主备模式(一主一备)、双主模式(互为主备)
  • 反向代理服务自身也存在性能瓶颈,随着需要转发的请求量不断攀升,需要有可扩展方案
2.2.4 IP负载均衡

IP 负载均衡是在网络层通过修改请求目的地址进行负载均衡。
在这里插入图片描述
IP负载均衡步骤:

  • 客户端请求 192.168.137.10,由负载均衡服务器接收到报文
  • 负载均衡服务器根据算法选出一个服务节点 192.168.0.1,然后将报文请求地址改为该节点的 IP
  • 真实服务节点收到请求报文,处理后,返回响应数据到负载均衡服务器
  • 负载均衡服务器将响应数据的源地址改负载均衡服务器地址,返回给客户端

IP 负载均衡的优点:

  • IP 负载均衡在内核进程完成数据分发,较反向代理负载均衡有更好的从处理性能

IP 负载均衡的缺点:

  • 所有请求响应都要经过负载均衡服务器,集群的吞吐量受制于负载均衡服务器的带宽。

三、负载均衡算法

负载均衡器的实现可以分为两个部分:

  • 负载均衡算法在候选服务器列表选出一个服务器
  • 将请求数据发送到该服务器上

负载均衡算法就是负载均衡服务核心中的核心

负载均衡算法有很多种,最为常见的负载均衡算法有:

  • 轮询
  • 随机
  • 最小活跃数
  • 源地址哈希
  • 一致性哈希

3.1 随机

3.1.1 随机算法

随机(Random) 算法将请求随机分发到候选服务器

在这里插入图片描述
随机算法适用场景: 客户端调用量大的情况比较适用。调用量越大,负载越均衡;调用量较小的时候,可能负载并不均匀

  • 随机法go语言代码实现如下:
package main

import (
	"fmt"
	"math/rand"
)

type Random struct {
	servers []string
}

func (R *Random) next() string {
	return R.servers[rand.Intn(len(R.servers))]
}

func main()  {
	r := Random{
		servers: []string{"192.168.10.10", "192.168.10.11", "192.168.10.12"},
	}

	for i := 0; i < 10; i++ {
		fmt.Println(r.next())
	}
}
3.1.2 加权随机算法

加权随机(Weighted Random) 算法在随机算法的基础上,按照概率调整权重,进行负载分配

  • 加权随机法go语言代码实现如下:
package main

import (
	"fmt"
	"math/rand"
)

type Server struct {
	host string  // 主机地址
	weight int // 配置的权重
}

func getSever(servers []*Server) string {
	hostList := []string{}
	// 按权重生成服务器列表
	for _, server := range servers {
		for i := 0; i < server.weight; i++ {
			hostList = append(hostList, server.host)
		}
	}

	return hostList[rand.Intn(len(hostList))]
}


func main() {
	servers := []*Server{
		{"192.168.10.10", 5},
		{"192.168.10.11", 2},
		{"192.168.10.12", 1},
	}

	for i := 0; i < 20; i++ {
		server := getSever(servers)

		fmt.Printf(server)
	}
}

3.2 轮询

3.2.1 轮询算法

轮询(Round Robin) 算法的策略是:将请求依次分发到候选服务器

如下图所示,负载均衡器收到来自客户端的 6 个请求,(1, 3, 5) 的请求会被发送到服务器 1,(2, 4, 6) 的请求会被发送到服务器 2
在这里插入图片描述
轮询算法适用场景: 各服务器处理能力相近,且每个事务工作量差异不大的时候比较适用。如果存在较大差异,那么处理较慢的服务器就可能会积压请求,最终无法承担过大的负载。
在这里插入图片描述

  • 轮询法go语言代码实现如下:
package main

import (
	"fmt"
)

type RoundRobin struct {
	servers []string
	current int
}

/**
获取下一个服务器
*/
func (R *RoundRobin) next() string {
	R.current++
	R.current = R.current % len(R.servers) // 访问到最后一个服务器之后,重置会第一台。 5%5=0。
	return R.servers[R.current]
}

func main() {

	r := &RoundRobin{
		servers: []string{"192.168.10", "192.168.11", "192.168.12"},
		current: -1,
	}
	
	for i := 0; i < 10; i++ {
		fmt.Printf("| %d | %s |\n", i + 1, r.next())
	}
}

3.2.2 加权轮询算法

加权轮询(Weighted Round Robbin)算法在轮询算法的基础上,增加了权重属性来调节转发服务器的请求数目。性能高、处理速度快的节点应该设置更高的权重,使得分发时优先将请求分发到权重较高的节点上

如下图所示,服务器 A 设置权重为 5,服务器 B 设置权重为 1,负载均衡器收到来自客户端的 6 个请求,那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 A,(6) 请求会被发送到服务器 B
在这里插入图片描述

  • 加权轮询法go语言代码实现如下:
package main

import (
	"fmt"
)

type Server struct {
	host string  // 主机地址
	weight int // 配置的权重
}

type RoundRobin struct {
	servers []string
	current int
}

func (R *RoundRobin) next() string {
	R.current++
	R.current = R.current % len(R.servers) // 访问到最后一个服务器之后,重置会第一台。 5%5=0。
	return R.servers[R.current]
}

func main()  {
	servers := []*Server{
		{"192.168.10.10", 5},
		{"192.168.10.11", 2},
		{"192.168.10.12", 1},
	}
	hostList := []string{}
	// 按权重生成服务器列表
	for _, server := range servers {
		for i := 0; i < server.weight; i++ {
			hostList = append(hostList, server.host)
		}
	}
	r := RoundRobin{
		servers: hostList,
		current: -1,
	}

	for i := 0; i < 10; i++ {
		fmt.Println(r.next())
	}
}

3.3 最小活跃数

最小活跃数(Least Active)算法 将请求分发到连接数/请求数最少的候选服务器(目前处理请求最少的服务器)

  • 特点: 根据候选服务器当前的请求连接数,动态分配
  • 适用场景: 适用于对系统负载较为敏感或请求连接时长相差较大的场景

轮循或随机算法的劣势:

  • 当每个请求的连接时长不一样时,如果采用轮循或随机算法,都可能出现某些服务器当前连接数过大,导致另一些服务器的连接过小的情况,这就造成了负载并非真正均衡。
  • 即使轮询或随机算法都可以通过加权重属性的方式进行负载调整,但加权方式难以应对动态变化

例如下图中,采用轮询算法,(1, 3, 5) 请求会被发送到服务器 1,但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1;(2, 4, 6) 请求被发送到服务器 2,只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担过大的负载

在这里插入图片描述
最小活跃数算法的优势:

  • 会记录当前时刻,每个候选节点正在处理的连接数,然后选择连接数最小的节点
  • 该策略能够动态、实时地反应服务器的当前状况,较为合理地将负载分配均匀

例如下图中,服务器 1 当前连接数最小,那么新到来的请求 6 就会被发送到服务器 1 上

在这里插入图片描述

3.4 源地址哈希

源地址哈希(IP Hash)算法: 根据请求源 IP,通过哈希计算得到一个数值,用该数值在候选服务器列表的进行取模运算,得到的结果便是选中的服务器。

可以保证同一 IP 的客户端的请求会转发到同一台服务器上,用来实现会话粘滞(Sticky Session)

特点: 保证特定用户总是请求到相同的服务器,若服务器宕机,会话会丢失。

3.5 go zero的负载均衡算法

  • 只有一个服务节点,此时直接返回供gRPC使用即可
  • 有两个服务节点,通过 EWMA 值计算负载,并返回负载低的节点返回供 gRPC 使用
  • 有多个服务节点,此时通过 p2c 算法选出两个节点,比较负载情况,返回负载低的节点供 gRPC 使用

p2c算法: 在所有节点中,三次随机分别挑选两个节点,挑出两个健康节点

p2c算法go主要实现代码如下:

switch len(p.conns) {
	case 0:// 没有节点,返回错误
		return emptyPickResult, balancer.ErrNoSubConnAvailable
	case 1:// 有一个节点,直接返回这个节点
		chosen = p.choose(p.conns[0], nil)
	case 2:// 有两个节点,计算负载,返回负载低的节点
		chosen = p.choose(p.conns[0], p.conns[1])
	default:// 有多个节点,p2c 挑选两个节点,比较这两个节点的负载,返回负载低的节点
		var node1, node2 *subConn
        // 3次随机选择两个节点
		for i := 0; i < pickTimes; i++ {
			a := p.r.Intn(len(p.conns))
			b := p.r.Intn(len(p.conns) - 1)
			if b >= a {
				b++
			}
			node1 = p.conns[a]
			node2 = p.conns[b]
			// 如果这次选择的节点达到了健康要求, 就中断选择
			if node1.healthy() && node2.healthy() {
				break
			}
		}
		// 比较两个节点的负载情况,选择负载低的
		chosen = p.choose(node1, node2)
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值