详解 Nginx 权重轮询算法(附代码实现)| 豆包MarsCode AI刷题

304 阅读5分钟

权重轮询(Weighted Round Robin,简称 WRR)是 Nginx 中常用的一种负载均衡算法,用于将客户端请求按照设定权重分配给不同的后端服务器。权重轮询特别适用于后端服务器性能差异较大的场景,通过设置权重,性能更强的服务器可以处理更多的请求,而性能较弱的服务器只需处理较少的请求,从而优化整体的资源利用率。

本文将深入讲解权重轮询算法的原理,并使用 Go 语言实现这一算法。


权重轮询算法的原理

权重轮询的目标是通过分配权重,按照比例将请求分配给不同的服务器。例如,假设有三台服务器:

  • Server A 权重为 5
  • Server B 权重为 1
  • Server C 权重为 1

权重总和为 5 + 1 + 1 = 7,即每 7 个请求分配如下:

  • 5 个请求到 Server A
  • 1 个请求到 Server B
  • 1 个请求到 Server C

权重轮询的核心思想是通过轮询索引动态权重计算,找到下一台应该处理请求的服务器。


权重轮询的实现步骤

权重轮询的实现通常包括以下步骤:

  1. 初始化服务器与权重信息:记录每台服务器的名称和对应的权重。
  2. 动态调整权重:根据当前状态动态调整权重以分配请求。
  3. 轮询服务器:每次循环遍历服务器列表,按照动态调整的权重选择目标服务器。

接下来,我们用 Go 语言实现这一逻辑。


Go 语言实现代码

以下是完整的 Go 实现代码。

package main

import (
	"fmt"
)

// Server 结构体,用于存储服务器信息
type Server struct {
	Name   string
	Weight int // 原始权重
}

// WeightedRoundRobin 结构体,用于实现权重轮询
type WeightedRoundRobin struct {
	servers       []Server // 服务器列表
	currentIndex  int      // 当前轮询索引
	currentWeight int      // 当前动态权重
	maxWeight     int      // 最大权重
	gcdWeight     int      // 权重的最大公约数
}

// NewWeightedRoundRobin 初始化权重轮询
func NewWeightedRoundRobin(servers []Server) *WeightedRoundRobin {
	maxWeight := getMaxWeight(servers)
	gcdWeight := getGCDOfWeights(servers)
	return &WeightedRoundRobin{
		servers:       servers,
		currentIndex:  -1,
		currentWeight: 0,
		maxWeight:     maxWeight,
		gcdWeight:     gcdWeight,
	}
}

// getMaxWeight 获取服务器权重的最大值
func getMaxWeight(servers []Server) int {
	max := 0
	for _, server := range servers {
		if server.Weight > max {
			max = server.Weight
		}
	}
	return max
}

// getGCDOfWeights 计算权重的最大公约数
func getGCDOfWeights(servers []Server) int {
	gcd := servers[0].Weight
	for _, server := range servers[1:] {
		gcd = gcdTwoNumbers(gcd, server.Weight)
	}
	return gcd
}

// gcdTwoNumbers 计算两个数的最大公约数
func gcdTwoNumbers(a, b int) int {
	if b == 0 {
		return a
	}
	return gcdTwoNumbers(b, a%b)
}

// GetNextServer 获取下一台服务器
func (wrr *WeightedRoundRobin) GetNextServer() *Server {
	totalServers := len(wrr.servers)
	for {
		wrr.currentIndex = (wrr.currentIndex + 1) % totalServers
		if wrr.currentIndex == 0 {
			wrr.currentWeight -= wrr.gcdWeight
			if wrr.currentWeight <= 0 {
				wrr.currentWeight = wrr.maxWeight
				if wrr.currentWeight == 0 {
					return nil
				}
			}
		}
		if wrr.servers[wrr.currentIndex].Weight >= wrr.currentWeight {
			return &wrr.servers[wrr.currentIndex]
		}
	}
}

func main() {
	// 定义服务器及其权重
	servers := []Server{
		{Name: "Server A", Weight: 5},
		{Name: "Server B", Weight: 1},
		{Name: "Server C", Weight: 1},
	}

	// 初始化权重轮询
	wrr := NewWeightedRoundRobin(servers)

	// 模拟请求分配
	fmt.Println("Request Distribution:")
	for i := 0; i < 20; i++ {
		server := wrr.GetNextServer()
		fmt.Printf("Request %d -> %s\n", i+1, server.Name)
	}
}

代码解析

1. 初始化服务器和权重

服务器信息通过 Server 结构体存储,包括名称和权重。
NewWeightedRoundRobin 中,计算所有服务器权重的最大值和最大公约数,用于后续的权重调整。

2. 动态权重调整

权重轮询的核心逻辑在 GetNextServer 方法中实现:

  • 轮询索引:每次增加 currentIndex,以循环方式遍历服务器列表。
  • 权重递减:每轮次中,减少当前权重,当权重为零时重置为最大权重。
  • 权重比较:如果服务器的权重大于或等于当前权重,选择该服务器。

3. 最大公约数优化

计算所有服务器权重的最大公约数 gcdWeight,减少不必要的轮询次数。

4. 模拟请求分配

main 函数中,通过循环模拟 20 次请求,打印每次请求分配到的服务器。


运行结果

运行上述代码,输出类似如下结果:

Request Distribution:
Request 1 -> Server A
Request 2 -> Server A
Request 3 -> Server A
Request 4 -> Server A
Request 5 -> Server A
Request 6 -> Server B
Request 7 -> Server C
Request 8 -> Server A
Request 9 -> Server A
Request 10 -> Server A
Request 11 -> Server A
Request 12 -> Server A
Request 13 -> Server B
Request 14 -> Server C
...

从结果中可以看出,Server A(权重为 5)分配到的请求数量显著高于 Server BServer C


Nginx 中配置权重轮询

在实际应用中,Nginx 通过 upstream 模块支持权重轮询负载均衡。我们可以为每台服务器设置权重,Nginx 将根据权重比例将请求分配到对应的后端服务器。

以下是具体的配置方式和示例。


1. 基本配置:权重轮询

权重轮询是 Nginx 默认的负载均衡方式。如果后端服务器的性能不一致,可以为它们指定不同的权重。

示例配置

http {
    upstream backend {
        server server1.example.com weight=5; # 权重 5
        server server2.example.com weight=3; # 权重 3
        server server3.example.com weight=2; # 权重 2
    }

    server {
        listen 80;
        server_name yourdomain.com;

        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

在这个配置中:

  • server1.example.com 的权重是 5,将处理约 50% 的请求。
  • server2.example.com 的权重是 3,将处理约 30% 的请求。
  • server3.example.com 的权重是 2,将处理约 20% 的请求。

2. 使用健康检查

为保证服务稳定性,可以配合第三方模块(如 ngx_http_healthcheck_module)使用健康检查功能,确保请求只分发到健康的后端服务器。

示例配置

http {
    upstream backend {
        server server1.example.com weight=5;
        server server2.example.com weight=3;
        server server3.example.com weight=2;
        
        # 启用健康检查
        health_check interval=10 fails=3 passes=2;
    }

    server {
        listen 80;
        server_name yourdomain.com;

        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

这里,健康检查每 10 秒运行一次。如果某台服务器连续 3 次检查失败,将被标记为不可用,直到连续 2 次通过检查。


3. 动态权重调整(通过 upstream_conf 模块)

在某些场景下,您可能需要动态调整服务器的权重。例如,当后端服务器负载增加时,可以通过 Nginx 的动态模块实时更新权重。

示例: 假设后端服务器的权重需要动态调整,可以通过以下命令(需要 ngx_http_upstream_conf 模块支持)更新配置:

curl -X POST "http://localhost/upstream_conf?upstream=backend&add=&server=server1.example.com&weight=10"

4. 设置超时和失败策略

为避免因某些服务器响应过慢或不可用而导致请求卡顿,可以设置连接超时和失败次数。

示例配置

http {
    upstream backend {
        server server1.example.com weight=5 max_fails=3 fail_timeout=30s;
        server server2.example.com weight=3 max_fails=3 fail_timeout=30s;
        server server3.example.com weight=2 max_fails=3 fail_timeout=30s;
    }

    server {
        listen 80;
        server_name yourdomain.com;

        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}
  • max_fails=3:在 30 秒内,服务器连续 3 次请求失败后被标记为不可用。
  • fail_timeout=30s:30 秒后重新尝试该服务器。

5. 完整配置示例

以下是一个完整的配置示例,包括权重轮询、健康检查和故障切换:

http {
    upstream backend {
        server server1.example.com weight=5 max_fails=3 fail_timeout=30s;
        server server2.example.com weight=3 max_fails=3 fail_timeout=30s;
        server server3.example.com weight=2 max_fails=3 fail_timeout=30s;

        # 启用健康检查
        health_check interval=10 fails=3 passes=2;
    }

    server {
        listen 80;
        server_name yourdomain.com;

        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

测试与验证

测试方法

  1. 使用 curl 发送多个请求,并观察 Nginx 的分发日志:

    for i in {1..20}; do curl -s http://yourdomain.com; done
    
  2. 检查后端服务器日志,确认请求分配比例是否与权重匹配。


最后

希望这个文章帮助了正在学习Nginx的你