负载均衡指的是将用户请求分摊到不同的服务器上处理,以提高系统整体的并发处理能力以及可靠性。Nginx 的负载均衡,支持多种不同算法。
1.轮询算法(Round Robin)
将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。
轮询算法假设所有服务器的处理性能都相同,不关心每台服务器的当前连接数和响应速度。当请求服务间隔时间变化比较大时,轮询算法容易导致服务器间的负载不平衡。所以此种均衡算法适合于服务器组中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况。
配置如下:
upstream cluster {
server 127.0.0.1:8181
server 127.0.0.1:8182
}
实现详情:
var (
ipService = []string{
"192.168.1.1",
"192.168.1.2",
"192.168.1.3",
}
pos = 0
mutexsync.Mutex
)
func getIp() string {
mutex.Lock()
defer mutex.Unlock()
if pos >= len(ipService) {
// 防止索引越界
pos = 0
}
ip := ipService[pos]
pos++
return ip
}
func main(){
for i := 0; i < 4; i++ {
fmt.Println(getIp())
}
}
2.加权轮询法
轮询算法并没有考虑每台服务器的处理能力,实际中可能并不是这种情况。由于每台服务器的配置、安装的业务应用等不同,其处理能力会不一样。所以,加权轮询算法的原理就是:根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。
配置如下:
http {
upstream cluster {
server a weight=1;
server b weight=2;
server c weight=3;
}
}
算法用go 实现如下:
package main
import (
"fmt"
"sync"
)
// 定义服务器权重的全局 map
var serviceWeightMap2 = map[string]int{
"192.168.1.100": 1,
"192.168.1.101": 1,
"192.168.1.102": 3,
"192.168.1.103": 1,
}
// 轮询位置的全局变量和互斥锁来保护并发访问
var pos int
var mu sync.Mutex
// 加权轮询选择服务器
func testWeightRoundRobin() string {
// 重新创建一个map,避免并发问题
serverMap := make(map[string]int)
for k, v := range serviceWeightMap {
serverMap[k] = v
}
// 根据权重生成服务器列表
serverList := []string{}
for server, weight := range serverMap {
for i := 0; i < weight; i++ {
serverList = append(serverList, server)
}
}
// 加锁来确保线程安全
mu.Lock()
defer mu.Unlock()
// 如果位置超出列表长度,重置位置
if pos >= len(serverList) {
pos = 0
}
// 选取当前服务器并将位置加1
server := serverList[pos]
pos++
return server
}
func main() {
// 测试加权轮询选择
for i := 0; i < 10; i++ {
server := testWeightRoundRobin()
fmt.Printf("Selected Server: %s\n", server)
}
}
其实在 加权轮询算法中,是有缺陷的,在某些特殊的权重下,加权轮询调度会生成不均匀的实例序列,这种不平滑的负载可能会使某些实例出现瞬时高负载的现象,导致系统存在宕机的风险。而为了解决这个调度的缺陷,就有了平滑加权轮询调度。
3.平滑加权轮询法
平滑加权轮询算法的原理:
- 每个服务器有一个权重 Weight 和一个当前权重 CurrentWeight。
- 每次请求到来时,所有服务器的 CurrentWeight 都增加各自的 Weight。
- 然后选择 CurrentWeight 最大的服务器,减去所有服务器权重的总和。
- 如此重复,逐渐将负载按照平滑的方式分布到各个服务器。
正确的平滑加权轮询实现:
package main
import (
"fmt"
"sync"
)
// 定义服务器结构体
type Server struct {
Address string // 服务器地址
Weight int // 服务器初始权重
CurrentWeight int // 服务器当前权重
}
// 初始化的服务器列表和权重
var servers = []*Server{
{Address: "192.168.1.100", Weight: 1, CurrentWeight: 0},
{Address: "192.168.1.101", Weight: 0, CurrentWeight: 0},
{Address: "192.168.1.102", Weight: 1, CurrentWeight: 0},
{Address: "192.168.1.103", Weight: 1, CurrentWeight: 0},
}
// 互斥锁,用于保护并发访问
var mu sync.Mutex
// 计算所有服务器的总权重
func getTotalWeight(servers []*Server) int {
totalWeight := 0
for _, server := range servers {
totalWeight += server.Weight
}
return totalWeight
}
// 实现平滑加权轮询算法
func smoothWeightedRoundRobin() *Server {
mu.Lock()
defer mu.Unlock()
fmt.Println("Servers:", servers)
// 计算服务器总权重
totalWeight := getTotalWeight(servers)
var bestServer *Server
// 遍历服务器,增加每个服务器的当前权重
for _, server := range servers {
server.CurrentWeight += server.Weight
// 选择当前权重最大的服务器
if bestServer == nil || server.CurrentWeight > bestServer.CurrentWeight {
bestServer = server
}
}
// 选择的服务器的当前权重减去总权重
if bestServer != nil {
bestServer.CurrentWeight -= totalWeight
}
return bestServer
}
func main() {
// 模拟多次请求的服务器选择
for i := 0; i < 10; i++ {
server := smoothWeightedRoundRobin()
fmt.Printf("Selected server: %s\n", server.Address)
}
}
(1)平滑加权轮询算法的特性:
- 平滑性:相较于传统的加权轮询,平滑加权轮询能够更加均匀地分配请求,即使权重差异较大,也能避免服务器在短时间内处理过多请求。
- 负载均衡:每次调用会根据权重均匀选择服务器,并不会过于频繁地选择权重大的服务器。
(2)与传统加权轮询的区别:
- 传统加权轮询:每次轮询时都会从头遍历服务器,并根据权重的数量多次将服务器添加到列表中。在权重差距较大的情况下,可能会导致高权重服务器短时间内处理很多请求,而低权重的服务器很长时间都不会处理请求。
- 平滑加权轮询:通过每次增加当前权重,并在每次选择后进行调整,权重较大的服务器仍然会更多地处理请求,但每次的选择过程会更加均匀和平滑,避免短时间内集中处理某些服务器的请求。
这个算法的优点在于能够实现请求的平滑分配,使得服务器在负载高低变化的情况下,负载分布得更加合理,特别适合服务器权重差异较大的场景。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。