3. 负载均衡策略:DNS负载均衡、Nginx/HAProxy反向代理、客户端负载均衡(Ribbon)

负载均衡这东西,说白了就是把流量分摊到多台机器上。但怎么分、在哪分、分完怎么保证不出事,这里头的门道可不少。我这些年踩过的坑,有一半都跟负载策略没选对有关系。

今天咱们就把三种主流方案掰开揉碎了讲。你想想看,一个在线特征服务,每天要扛几十亿次请求,如果负载均衡没做好,那真是「一挂挂一片」。嗯,咱们从最上层的 DNS 开始聊。

3.1 DNS负载均衡:最上层的第一道关卡

DNS 负载均衡,其实就是域名解析的时候给你返回多个 IP。客户端拿到哪个 IP,就去访问哪个机房。这是最粗粒度的负载策略,也是成本最低的。

核心思路:同一个域名配置多条 A 记录,DNS 服务器轮询返回。用户请求自然就被分散到不同入口。

我记得有一次做跨国业务,国内用户访问延迟高得离谱。查了半天,发现 DNS 解析总是把国内用户指向了美国机房。后来我们按地域做了智能 DNS 解析,问题才解决。

3.1.1 常见的DNS策略

策略 原理 适用场景 我踩过的坑
轮询 按顺序返回不同IP 各机房能力对等 某机房挂了,DNS还在返回它的IP
地域解析 根据来源IP返回最近机房 跨国/跨省部署 IP库不准,用户被导到错误机房
权重解析 按比例返回IP 机房容量不同 权重配错,小机房直接被冲垮
主备解析 主IP正常时只返回主IP 主备容灾 切换不及时,业务中断好几分钟

注意:DNS 有缓存。TTL 设得太短,DNS 服务器压力大;设得太长,故障切换慢。我个人习惯把 TTL 设在 60~120 秒,既保证切换速度,又不至于把 DNS 服务器打爆。

3.1.2 健康检查与自动摘除

DNS 本身不做健康检查。它只管解析,不管后端死活。所以你得配合其他手段:

  • 自研 DNS 管理平台:定期探测各机房入口,发现异常自动摘除 A 记录
  • 结合 GSLB:全局负载均衡设备,能感知后端状态
  • 短 TTL + 重试:客户端发现连不上,立即重试另一个 IP

我曾经遇到过一个案例:某机房网络抖动,DNS 没摘除,结果 30% 的请求超时。从那以后,我们给 DNS 管理平台加了「连续 3 次探测失败就自动摘除」的逻辑。

3.2 Nginx/HAProxy反向代理:七层与四层的选择

到了反向代理这一层,咱们就有更多控制权了。Nginx 和 HAProxy 是业界最常用的两个方案。它们俩各有侧重,我一般这么选:

  • Nginx:七层(HTTP/HTTPS)处理能力强,适合做协议解析、路由转发
  • HAProxy:四层(TCP)性能极致,适合做纯流量转发

我的经验:在线特征服务大部分走 HTTP/gRPC,所以我倾向用 Nginx 做七层负载。但如果对延迟极其敏感(比如毫秒级),我会在前面再加一层 HAProxy 做四层接入。

3.2.1 Nginx的负载均衡配置

Nginx 的 upstream 模块提供了多种负载算法。我挑几个最常用的说说:

upstream feature_service {
    # 默认是轮询
    server 10.0.1.1:8080 weight=5;
    server 10.0.1.2:8080 weight=3;
    server 10.0.1.3:8080 backup;  # 备用节点

    # 最少连接数
    least_conn;

    # IP哈希,保持会话
    ip_hash;

    # 一致性哈希(需要第三方模块)
    hash $request_uri consistent;
}

server {
    listen 80;
    location /api/feature {
        proxy_pass http://feature_service;
        proxy_next_upstream error timeout http_500;
        proxy_connect_timeout 3s;
        proxy_read_timeout 5s;
    }
}

这里有个细节:proxy_next_upstream 一定要配。默认 Nginx 只对 error 和 timeout 重试,但 http_500 也得加上。我遇到过后端返回 500,Nginx 直接把这个响应返回给客户端,导致大量报错。加上 http_500 后,Nginx 会自动换一台重试。

3.2.2 HAProxy的四层转发

HAProxy 的配置更简洁,性能也更好。它支持 active-backup 和 roundrobin 两种主流模式:

frontend fe_feature
    bind *:80
    default_backend be_feature

backend be_feature
    balance roundrobin
    server s1 10.0.1.1:8080 check inter 3s fall 2 rise 2
    server s2 10.0.1.2:8080 check inter 3s fall 2 rise 2
    server s3 10.0.1.3:8080 check inter 3s fall 2 rise 2 backup

注意那个 check inter 3s fall 2 rise 2。这是健康检查的配置:每 3 秒探测一次,连续失败 2 次就摘除,连续成功 2 次才恢复。这个参数我调过很多次,太频繁会加重后端压力,太稀疏又发现不了故障。

关键参数对比:

参数 Nginx HAProxy
最大连接数 约 5 万(单进程) 约 50 万+
健康检查 被动(依赖 proxy_next_upstream) 主动(check 指令)
SSL 卸载 原生支持,性能好 支持,但不如 Nginx 灵活
配置热加载 reload 不丢连接 reload 会断开旧连接

3.3 客户端负载均衡:Ribbon 与更轻量的选择

前面两种都是「服务端负载均衡」,请求先到中间代理,再转发到后端。但客户端负载均衡不一样——客户端自己维护一份服务列表,直接选一台发起调用。

Spring Cloud 时代的 Ribbon 是典型代表。虽然现在官方进入维护期,但它的设计思想仍然值得学习。

3.3.1 Ribbon的工作原理

  1. 服务发现:从注册中心(如 Eureka)拉取服务列表
  2. 本地缓存:客户端维护一份服务列表快照
  3. 负载算法:按规则选一台(轮询、权重、最少并发等)
  4. 故障处理:调用失败后重试其他节点
// Ribbon 客户端配置示例
@Configuration
public class RibbonConfig {
    @Bean
    public IRule ribbonRule() {
        // 使用加权响应时间策略
        return new WeightedResponseTimeRule();
    }

    @Bean
    public IPing ribbonPing() {
        // 每 10 秒 ping 一次后端
        return new PingUrl(false, "/health");
    }
}

避坑指南:我曾经把 Ribbon 的 MaxAutoRetries 设成 3,结果一次超时触发了 3 次重试,3 次重试又各自超时,最终请求耗时超过 15 秒。后来我改成 MaxAutoRetries=1,配合 ReadTimeout=2s,效果好了很多。

3.3.2 客户端负载的优缺点

维度 优势 劣势
延迟 少一跳网络,延迟更低 客户端需要维护服务列表
扩展性 无中心瓶颈,随客户端数线性扩展 每个客户端都要实现负载逻辑
故障隔离 客户端可独立熔断 故障感知依赖客户端心跳
运维 不需要维护代理集群 升级负载策略需要更新所有客户端

现在很多团队转向了 Spring Cloud LoadBalancer 或 gRPC 内置负载均衡。我个人更推荐 gRPC 的 pick_first 和 round_robin 策略,它们原生支持健康检查和重连,省去了很多维护成本。

3.4 三种策略的协同与选型

实际生产环境里,这三种策略不是互斥的,而是层层递进的关系。我画了一张图,帮你理清它们的分工:

负载均衡三层架构 DNS 负载均衡 地域解析 · 轮询 · 权重 反向代理负载均衡 Nginx(七层) · HAProxy(四层) 客户端负载均衡 Ribbon · gRPC 内置 · 自研 各层职责: • DNS:全局流量调度,跨机房容灾,粗粒度 • 反向代理:单机房内流量分发,健康检查,限流熔断 • 客户端:最终调用决策,故障重试,超时控制

你看,这三层各司其职。DNS 管「去哪栋楼」,反向代理管「去哪个楼层」,客户端管「敲哪扇门」。少了任何一层,高可用都会打折扣。

3.4.1 我的选型建议

  • 中小规模(日活百万级):DNS + Nginx 就够了。客户端用简单的轮询或随机。
  • 大规模(日活千万级):DNS + HAProxy(四层入口)+ Nginx(七层路由)+ 客户端负载均衡。
  • 超大规模(日活亿级):自研 DNS 管理平台 + LVS(四层)+ Nginx(七层)+ 客户端自适配。

最后提醒一句:负载均衡不是配完就完事了。你得持续监控各层的流量分布、延迟、错误率。我见过太多团队配了 Nginx 就不管了,结果某台机器 CPU 飙到 90% 都没发现。定期做压测,验证负载策略是否按预期工作——这是 SRE 的基本功。