题目
设计一个支持加权轮询算法的负载均衡中间件
信息
- 类型:问答
- 难度:⭐⭐
考点
负载均衡算法,中间件设计,并发安全
快速回答
加权轮询负载均衡的核心实现要点:
- 使用
currentWeight动态调整服务器选择权重 - 每次选择时:
- 计算所有服务器权重之和
- 遍历服务器列表,选择当前权重最大的服务器
- 被选中的服务器权重减去总权重
- 所有服务器权重增加原始配置权重
- 使用互斥锁保证并发安全
- 支持动态增减后端服务器
1. 加权轮询算法原理
加权轮询(Weighted Round Robin)在基础轮询上引入权重概念,使高性能服务器处理更多请求。核心是动态权重调整:
- 每个服务器有两个权重值:
- 固定权重:配置的初始权重(如ServerA:5, ServerB:3)
- 当前权重:运行时动态变化的权重值
- 选择过程:
- 当前权重 = 当前权重 + 固定权重
- 选择当前权重最大的服务器
- 被选中的服务器:当前权重 -= 总固定权重
示例:两台服务器(A:5, B:3),总权重=8
| 请求序号 | 调整前权重 | 选中服务器 | 调整后权重 |
|---|---|---|---|
| 1 | A:5+5=10, B:3+3=6 | A | A:10-8=2, B:6 |
| 2 | A:2+5=7, B:6+3=9 | B | A:7, B:9-8=1 |
| 3 | A:7+5=12, B:1+3=4 | A | A:12-8=4, B:4 |
2. Go语言实现示例
type Server struct {
Host string
Weight int // 固定权重
curWeight int // 当前权重
}
type LoadBalancer struct {
servers []*Server
mu sync.Mutex
totalWt int // 总固定权重
}
func (lb *LoadBalancer) AddServer(host string, weight int) {
lb.mu.Lock()
defer lb.mu.Unlock()
lb.servers = append(lb.servers, &Server{
Host: host,
Weight: weight,
})
lb.totalWt += weight
}
func (lb *LoadBalancer) Next() string {
lb.mu.Lock()
defer lb.mu.Unlock()
if len(lb.servers) == 0 {
return ""
}
var selected *Server
maxWeight := -1
// 1. 增加当前权重并选择最大值
for _, srv := range lb.servers {
srv.curWeight += srv.Weight
if srv.curWeight > maxWeight {
maxWeight = srv.curWeight
selected = srv
}
}
// 2. 减去总权重
selected.curWeight -= lb.totalWt
return selected.Host
}3. 最佳实践
- 健康检查:集成主动/被动健康检查,自动剔除故障节点
- 动态配置:支持运行时增减服务器(示例中已实现)
- 平滑权重调整:修改权重时逐步过渡,避免流量突变
- 会话保持:通过cookie或IP哈希解决有状态服务的负载均衡
4. 常见错误
- 并发问题:未加锁导致权重计算错误(示例中使用
sync.Mutex解决) - 权重溢出:未重置权重导致数值过大(算法本身不会无限增长)
- 0权重处理:特殊处理权重为0的服务器(应视为不可用)
- 性能瓶颈:每次选择需遍历所有服务器(可优化为最大堆)
5. 扩展知识
- 其他算法对比:
- 随机加权:实现简单但分布不均匀
- 最小连接数:更精确的负载分配,需维护连接状态
- 一致性哈希:适用于分布式缓存场景
- 中间件集成:
- Nginx加权配置:
upstream { server 10.0.0.1 weight=5; } - Spring Cloud Ribbon:通过
IRule接口扩展
- Nginx加权配置:
- 云原生方案:Kubernetes Service通过
kube-proxy实现负载均衡