轮询策略需加原子操作,如atomic.addint32;节点动态变化需sync.rwmutex保护;健康检查须带状态机与衰减机制;grpc需显式导入roundrobin包;权重调度应使用平滑加权轮询(swrr)。

轮询策略必须加原子操作,否则并发下会错乱
Go里用 index++ 这种非原子操作在高并发场景下会导致多个goroutine同时读写同一个整数,结果是跳过节点、重复选同一个节点,甚至 panic。这不是理论风险,而是真实压测时立刻暴露的问题。
- 改用
atomic.AddInt32(&rb.index, 1),再对长度取模 - 不要把
index放在结构体里直接暴露;封装成私有字段 + 方法访问 - 如果节点列表动态变化(比如热扩容),单纯轮询会漏掉新节点——得配合
sync.RWMutex保护servers切片读写
健康检查不能只靠一次 HTTP GET 就标记为宕机
后端偶尔超时或返回 503 是常态,直接踢出节点会造成频繁震荡。真正可用的健康检查必须带状态机和衰减机制。
- 用两个计数器:
successCount和failCount,连续 3 次失败才标记为 unhealthy - 恢复逻辑要滞后:标记为 healthy 前需连续 2 次成功,且间隔 ≥ 30 秒
- 探针路径别硬编码
/health,从配置读取,允许不同服务用不同探测端点(比如/readyz) - 注意:HTTP client 要设
Timeout: 2 * time.Second,否则一个卡死连接拖垮整个检查 goroutine
gRPC 客户端负载均衡必须显式注册 balancer,否则 round_robin 不生效
很多人写了 grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`) 却发现还是走 pick_first,根本原因是 Go 的 round_robin balancer 默认没注册,得手动导入触发 init。
- 必须加这行:
import _ "google.golang.org/grpc/balancer/roundrobin" - 如果用自定义 balancer,
balancer.Register()调用必须在grpc.Dial()之前,且全局唯一 -
resolver.Build()返回的地址列表为空时,balancer.Picker.Pick()会返回 error,得提前兜底返回status.Error(codes.Unavailable, "no endpoints")
权重不是简单乘法,平滑加权轮询(SWRR)才能避免请求堆积
直接按权重重复添加节点到数组(比如权重 3 就塞 3 次地址)看似简单,但会导致请求扎堆——同一秒内连续发给同一个高权重点,瞬间打满连接数。
- 用 SWRR:每个节点维护
currentWeight和maxWeight,每次选完后更新currentWeight += weight,选最大者,然后currentWeight -= totalWeight - 权重值建议归一化到 1–100 区间,避免整数溢出;别用浮点数做权重,gRPC 和 HTTP 都不认
- 权重变更不能热 reload —— 修改后需重建 balancer 实例,否则旧状态残留导致调度失准
实际部署时最容易被忽略的是节点摘除后的连接 draining:旧连接还在跑,新请求已不再打过去,但未完成的请求仍需等 timeout 才关闭。这个窗口期如果没配好 KeepAlive 和 WriteTimeout,会卡住资源。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











