直接用 net/http 硬编码地址调用会绕过注册中心的负载均衡,因跳过了服务发现、健康检查与权重计算;需客户端主动拉取实例列表并实现本地负载均衡(如轮询),配合定期刷新与健康校验。
net/http 调用服务会绕过注册中心负载均衡微服务注册中心(如 Consul、Etcd、Nacos)本身不转发请求,只提供服务发现能力。如果你用 http.Get("http://10.0.1.5:8080/api") 这种硬编码地址调用,就完全跳过了服务列表拉取、健康检查、权重计算等环节,负载均衡形同虚设。
真正起作用的是「客户端负载均衡」:你的 Go 程序需主动从注册中心获取可用实例列表,并在本地实现选择逻辑(如轮询、随机、加权最小连接数)。
host:port 地址,不是单一入口*http.Client 直接发往不同后端——需动态构造 URL 或使用中间代理层go-micro v4 实现带健康检查的轮询负载均衡go-micro v4(即 github.com/asim/go-micro/v4)内置了基于注册中心的客户端负载均衡器,但默认策略是随机(random),且不自动剔除不健康节点,需手动集成健康检查。
import ( "github.com/asim/go-micro/v4" "github.com/asim/go-micro/v4/registry" "github.com/asim/go-micro/v4/registry/consul" "github.com/asim/go-micro/v4/client" "github.com/asim/go-micro/v4/client/selector" ) // 初始化 Consul 注册中心 reg := consul.NewRegistry(registry.Addrs("127.0.0.1:8500")) // 构建 selector:支持健康检查过滤 + 轮询策略 sel := selector.NewSelector( selector.Registry(reg), selector.SetStrategy(selector.RoundRobin), // 显式设为轮询 selector.WithFilter(func(services []*registry.Service) []*registry.Service { // 过滤掉没有健康检查通过的节点(假设服务注册时带 metadata.health = "pass") var filtered []*registry.Service for _, svc := range services { for _, node := range svc.Nodes { if node.Metadata["health"] == "pass" { filtered = append(filtered, svc) break } } } return filtered }), ) // 创建 client 并绑定 selector c := micro.NewClient( client.Selector(sel), )
注意:go-micro v4 已弃用 client.Call 的旧式 RPC 封装,推荐搭配 micro.Service 使用其 Client() 方法;若坚持直调 HTTP,需自己解析 selector.Next() 返回的 node 并拼接 URL。
很多项目不需要完整 RPC 框架,只需对一组 HTTP 服务做带重试的轮询。这时自己封装一个 RoundRobinBalancer 更可控,也更容易调试。
sync.Mutex 锁整个选择过程——高并发下成为瓶颈;改用原子计数器或分段锁Next() 前应先校验节点是否 still alive(比如发个 HEAD 请求):0),必须过滤:if node.Port
type RoundRobinBalancer struct {
nodes []string
mu sync.RWMutex
offset uint64
}
func (b *RoundRobinBalancer) Add(node string) {
b.mu.Lock()
defer b.mu.Unlock()
b.nodes = append(b.nodes, node)
}
func (b *RoundRobinBalancer) Next() string {
b.mu.RLock()
defer b.mu.RUnlock()
if len(b.nodes) == 0 {
return ""
}
idx := atomic.AddUint64(&b.offset, 1) % uint64(len(b.nodes))
return b.nodes[idx]
}
// 使用示例
balancer := &RoundRobinBalancer{}
balancer.Add("http://192.168.1.10:8080")
balancer.Add("http://192.168.1.11:8080")
url := balancer.Next() + "/api/users"
resp, _ := http.Get(url)
有人会尝试用 dig @127.0.0.1 -p 8600 user-service.service.consul 解析出多个 A 记录,再用 Go 的 net.Resolver 获取 IP 列表——这看似“免代码”,但实际问题很多:
net.Resolver 会缓存结果长达数分钟version=v2 的实例)它只适合极简场景(如脚本一次性探测),绝不能用于生产级服务调用链路。真要简化,宁可用 consul-api 库直连 HTTP 接口:GET /v1/health/service/user-service?passing,再自己选节点。