Consul不原生支持权重,需将weight存入Meta字段;服务发现后须在Gin中间件中自行实现累积权重法选实例,并缓存刷新。

Consul 服务注册时必须显式带权重字段,否则默认为 1
Consul 本身不原生支持“权重”这个概念——它的 AgentServiceRegistration 结构体里压根没有 Weight 字段。所谓“权重”,是业务层在服务发现阶段自己解析元数据(Tags 或 Meta)后做的路由决策。你不能指望 consul api.Health().Service("xxx") 返回的实例列表里自动按权重排序或加权轮询。
常见错误现象:注册服务后,在 Consul UI 里看到所有实例都标为 passing,但流量始终打到某一台,以为是 Consul 自动做了加权,其实只是 DNS 轮询或客户端没做负载逻辑。
- 正确做法是在注册时把权重塞进
Meta字段,例如:Meta: map[string]string{"weight": "80"} -
Tags也可用,但不推荐混用业务标签(如"gray","v2"),容易污染语义 - 注册代码中别漏掉
Check字段,否则实例状态为critical,根本不会出现在PassingOnly: true的查询结果里
从 Consul 拉取实例后,Gin 中间件需自行实现加权轮询逻辑
Gin 本身不处理服务发现,它只负责 HTTP 请求分发。你要在调用下游服务前,从 Consul 拿到带 Meta["weight"] 的实例列表,再基于权重选一个地址——这个过程得写在中间件或业务逻辑里,不是配置开关能开出来的。
使用场景:比如灰度发布时,让 20% 流量打到 v2 实例(Meta["weight"]="20"),其余打 v1("80")。
- 别直接用
api.Health().Service("svc-name")默认返回值,要显式传&api.QueryOptions{RequireConsistent: true}防止读到陈旧数据 - 权重计算建议用「累积权重法」:把所有实例按
weight累加得总和,生成 [0, sum) 区间随机数,落在哪个区间就选对应实例 - 务必缓存实例列表并定时刷新(如 5s 间隔),避免每次请求都查 Consul,否则延迟飙升
- 示例片段:
instances := getHealthyInstancesFromConsul("order-svc") // 返回含 Meta 的 []*api.ServiceEntry total := 0 for _, inst := range instances { w, _ := strconv.Atoi(inst.Service.Meta["weight"]) total += w } randVal := rand.Intn(total) cur := 0 for _, inst := range instances { w, _ := strconv.Atoi(inst.Service.Meta["weight"]) if randVal
Consul DNS 接口不支持权重,Nginx + Consul KV 才能真正调度流量
如果你试图用 curl http://order-svc.service.consul 让 DNS 自动做加权转发,会失败。Consul DNS 只做简单轮询或 SRV 记录返回,Meta 和权重完全不可见。
真正落地的流量调度,得靠 Nginx 主动拉取 Consul KV 中维护的上游配置,再重载 upstream 块。这是目前最稳定、可观察、易调试的方式。
- Nginx 配置里不能写死
server 10.0.1.10:8080 weight=80,而应通过lua-resty-consul或自定义脚本定期 GEThttp://consul:8500/v1/kv/upstreams/order-svc - KV 值格式建议用 JSON:
[{"addr":"10.0.1.10:8080","weight":80},{"addr":"10.0.1.11:8080","weight":20}] - Nginx reload 不等于重启 worker 进程;但频繁 reload 仍可能丢连接,建议用
consul-template监听 KV 变更 + 延迟 reload - 注意:Nginx 的
ip_hash和hash $arg_uid会覆盖权重逻辑,若需用户级一致性哈希,得在权重基础上二次路由
etcd 用户别误套 Consul 方案:租约、watch、序列化全得手动管
如果实际用的是 etcd 而非 Consul,上面所有基于 api.Health() 和 Meta 的做法都不成立。etcd 没有服务健康状态抽象,所有“权重”“状态”都得你自己存成 key-value,并用 lease 绑定心跳。
容易踩的坑:直接把 Consul 示例代码改个 import 就跑 etcd,结果服务挂了也不下线,或者 watch 断连后静默停止监听。
- 注册时必须用
clientv3.Put(ctx, "/services/order-svc/inst-1", addr, clientv3.WithLease(leaseID)) - Watch 变更时,遇到
ErrCompacted必须重建 watcher,且要重放最近变更(etcd 不保证事件不丢) - 查服务列表要用
WithSerializable(),否则高并发下可能读到过期快照 - 权重字段得自己设计存储路径,比如
/weights/order-svc/inst-1 → "80",再合并查询结果
真实线上环境里,“权重”从来不是单点配置问题,而是注册、发现、缓存、路由、降级五层联动的结果。Consul 提供的是注册与健康检查底座,权重解析和流量落地必须由业务代码或边缘网关(Nginx / Envoy)承担——指望框架自动完成,只会卡在某个环节反复超时或路由错乱。











