weightedroundrobin不能仅靠取模实现,因会导致高权重节点连续被选中、低权重节点饥饿;必须维护各节点当前权重余量与全局累计值,用动态累积差值而非静态比例来保障分配均匀性。

WeightedRoundRobin 为什么不能只靠取模实现
直接用 index % len(backends) 加权重相乘,会导致高权重节点被连续选中、低权重节点长期饥饿。真实流量下,这种“伪加权”会让健康检查失败的节点仍被轮到,或让新上线的高配实例瞬间被打满。
核心问题在于:权重不是静态比例,而是动态累积差值。Go 标准库没提供现成 WRR 实现,必须自己维护每个 backend 的当前权重余量和全局累计值。
- 每次选择前要重算所有 backend 的
delta = weight - (currentWeight % totalWeight) -
currentWeight必须是原子递增,否则并发请求会漏计数 - 跳过
!b.IsHealthy()节点后,不能简单重置索引——得继续找 delta 最大的那个
如何让路由模块支持运行时权重热更新
硬编码权重或从配置文件读一次就固定,根本没法应对突发流量或灰度发布。关键不是“改权重”,而是“不重启、不丢请求、不阻塞 Picker”。
正确做法是把权重计算和路由决策拆开:
- 单独起一个 goroutine,定期(比如每 5 秒)从 etcd 或 Redis 拉取最新权重,写入内存 map,带版本号或 CAS 检查
- Picker 接口的
Pick()方法只做 O(1) 查表,查的是预计算好的map[backendID]float64权重快照 - 绝不在
Pick()里调redis.Get()或etcd.Get()—— 网络 IO + context timeout 容易拖慢整个 gRPC 请求链路
gRPC Balancer.Picker 和 HTTP RoundTripper 的权重同步难题
同一个服务集群,gRPC 客户端用自定义 balancer.Picker,HTTP 客户端用包装过的 http.RoundTripper,但两套权重如果不同步,就会出现“gRPC 流量全打到 A,HTTP 全打到 B”的撕裂现象。
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
解决思路不是强行统一接口,而是共享数据源和更新节奏:
- 两者都监听同一个
/weights/{service}的 etcd key,watch 变更事件 - HTTP 侧在
RoundTrip()开头调selectBackend(),该函数内部查的是和 gRPC 相同的内存权重快照 - 避免用时间戳判断“哪个更新新”——改用 etcd 的
mod_revision做版本比对
权重归一化与小数权重的实际处理
运维给的权重可能是 1.5、2.3 这种浮点数,但整数 WRR 算法没法直接用。强行乘以 10 取整会放大误差,尤其当后端数量少(比如只有 2 个)时,15 和 23 的 GCD 是 1,导致算法退化为纯轮询。
更稳妥的做法是放弃“精确按比例”,转向“近似成功率控制”:
- 把权重映射为概率:比如 A 权重 1.5,B 权重 2.3,则 A 被选中的概率是
1.5 / (1.5+2.3) ≈ 39% - 用
rand.Float64()生成随机数,在累积概率数组里二分查找(O(log n)) - 这个方案天然支持浮点权重,且在节点数
实际最难的不是写对算法,是让权重变更对正在飞行的请求完全透明——这要求所有状态读写都无锁、所有网络依赖都提前缓存、所有分支逻辑都幂等。一旦在 Pick() 或 RoundTrip() 里埋了阻塞点,网关延迟毛刺就藏不住了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










