权重轮询(Weighted Round Robin,简称 WRR)是 Nginx 中常用的一种负载均衡算法,用于将客户端请求按照设定权重分配给不同的后端服务器。权重轮询特别适用于后端服务器性能差异较大的场景,通过设置权重,性能更强的服务器可以处理更多的请求,而性能较弱的服务器只需处理较少的请求,从而优化整体的资源利用率。
本文将深入讲解权重轮询算法的原理,并使用 Go 语言实现这一算法。
权重轮询算法的原理
权重轮询的目标是通过分配权重,按照比例将请求分配给不同的服务器。例如,假设有三台服务器:
Server A权重为 5Server B权重为 1Server C权重为 1
权重总和为 5 + 1 + 1 = 7,即每 7 个请求分配如下:
- 5 个请求到
Server A - 1 个请求到
Server B - 1 个请求到
Server C
权重轮询的核心思想是通过轮询索引与动态权重计算,找到下一台应该处理请求的服务器。
权重轮询的实现步骤
权重轮询的实现通常包括以下步骤:
- 初始化服务器与权重信息:记录每台服务器的名称和对应的权重。
- 动态调整权重:根据当前状态动态调整权重以分配请求。
- 轮询服务器:每次循环遍历服务器列表,按照动态调整的权重选择目标服务器。
接下来,我们用 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 B 和 Server 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;
}
}
}
测试与验证
测试方法
-
使用
curl发送多个请求,并观察 Nginx 的分发日志:for i in {1..20}; do curl -s http://yourdomain.com; done -
检查后端服务器日志,确认请求分配比例是否与权重匹配。
最后
希望这个文章帮助了正在学习Nginx的你