
Go 一致性哈希为什么经常“偏斜”?
一致性哈希在 Go 微服务中常被用于 session 粘性或缓存路由,但实际部署后请求分布不均——不是算法错了,是默认实现没处理好虚拟节点和权重。
-
hashicorp/go-immutable-radix或goraft/consistent这类库默认不开启虚拟节点,10 个真实节点可能只映射到 30 个环位置,极易偏斜 - 真实服务节点权重不同(比如新机器 CPU 更强),但多数 Go 实现把
weight当布尔值用,设成2不等于“两倍流量”,而是直接跳过该节点 - 客户端和服务端哈希函数不一致:服务端用
sha256.Sum256,客户端用fnv.New64a(),结果永远对不上
实操建议:用 segmentio/kafka-go 里的 consistenthash 包(它显式支持 Replicas 参数),初始化时至少设 Replicas: 100;key 计算统一走 sha256.Sum256([]byte(key)).Sum(nil),别图快用 int(hash) 截断。
平滑轮询(Smooth Weighted Round Robin)在 Go 中怎么写才不翻车?
标准轮询(Round Robin)扛不住节点性能差异,而网上抄来的“加权轮询”代码往往只是简单累加权重,导致高峰时段某节点被打穿。
- 常见错误是把
current_weight当作计数器重置,比如每次选完就清零——这会让高权重节点连续被选中,失去“平滑”意义 - Go 的
sync/atomic对int64操作是安全的,但如果你用struct{ w, cw int }并原子更新整个 struct,会触发非对齐 panic - 服务发现动态变更时,直接替换节点列表会导致
current_weight累计值错乱;必须带版本号或用 CAS 机制同步重置
实操建议:参考 google.golang.org/grpc/balancer/roundrobin 的思路,每个节点维护 weight 和 currentWeight 字段,每次选择后执行 atomic.AddInt64(&node.currentWeight, int64(node.weight)),再全局找最大值;重载节点时,用 atomic.StoreInt64(&node.currentWeight, 0) 单独重置每个节点。
立即学习“go语言免费学习笔记(深入)”;
gRPC-Golang 默认负载均衡器为什么不走你写的策略?
你在 grpc.Dial 里注册了自定义 balancer,但 target 是 dns:///svc.example.com,结果请求全发到了第一个 IP 上——因为 gRPC Go 默认只在 passthrough 或 dns resolver 下启用自定义 balancer,且要求 resolver 返回的 Address 带 Attributes 元数据。
- 漏掉
balancer.WithDefaultServiceConfig(`{"loadBalancingPolicy":"my_policy"}`),gRPC 会降级用pick_first - resolver 返回的
Address.Addr如果含端口(如"10.0.1.2:8080"),而你的 balancer 按 host 比较去重,就会把同一台机器多个端口当成不同节点 -
UpdateClientConnState回调里没检查state.ResolverState.Addresses是否为空,空切片会导致 panic 或无限 fallback
实操建议:先用 grpc.WithResolvers(&yourResolver{}) 强制接管解析;yourResolver.ResolveNow 必须返回带 Attributes: balancer.Attributes{Map: map[string]any{"weight": 10}} 的地址;在 balancer 的 Build 方法里打印 info.Target 确认是否匹配你注册的 policy name。
HTTP 微服务用 round robin 还是 consistent hash?看这个信号
不是看文档吹得多好,是看你的请求 key 是否天然具备“局部性”:如果下游依赖基于 user_id 的缓存或数据库分片,一致性哈希能减少跨节点查询;如果只是无状态计算,轮询更省心。
- 用
net/http/httputil.NewSingleHostReverseProxy时,别自己实现负载逻辑——它不支持 per-request 路由,所有请求都走同一个Director,一致性哈希毫无意义 - HTTP header 里带
X-User-ID,但部分客户端不传、或传空字符串,这时一致性哈希会把所有空 key 打到同一节点,得提前 fallback 到轮询 - Go 的
http.RoundTripper实现里,连接复用(keep-alive)和负载策略是两层事:即使用了平滑轮询,TCP 连接仍可能长期黏在旧节点上,需设置Transport.MaxIdleConnsPerHost = 0或手动 close idle conn
实操建议:先跑一周真实流量,用 prometheus 抓各节点的 http_request_duration_seconds_count{job="svc", instance=~"10.0.1.*"},如果方差 > 3 倍,再考虑一致性哈希;否则加个 time.Sleep(10 * time.Millisecond) 在 handler 开头,比换算法更快见效。










