Skip to content

第 8 讲|负载均衡

后端服务从单实例扩展到多实例后,立即面临一个新问题:用户该访问哪个实例? 总不能让用户记住每个实例的 IP。**负载均衡(Load Balancer)**就是解决这一问题的组件——它给用户提供一个固定入口(域名或 IP),把请求分发到后面的多个实例。

但负载均衡的职责远不止"分担请求"。它还参与实例上下线、健康检查、滚动发布、故障摘除、TLS 终止、限流、访问日志——可以说是多实例架构的"调度中心"。理解负载均衡的运作方式,排查问题时才知道请求卡在哪一层。

本讲覆盖:负载均衡的基本作用、DNS 分流的局限、四层与七层的差异、健康检查的设计、反向代理的能力、多层入口的排查方法。

一、入口与流量分发

最直观的负载均衡形态:一个入口后面挂多台后端实例。

负载均衡把入口流量分发到多个后端

用户记住的是负载均衡的域名或公网 IP,后面的实例增删变更对用户完全透明。这一特性对运维至关重要——发布新版本时,可以:

  1. 从负载均衡上摘下一台实例
  2. 等待该实例上的存量连接处理完毕(drain)
  3. 替换为新版本
  4. 健康检查通过后,重新挂回负载均衡
  5. 重复上述步骤直到所有实例更新完毕

整个过程用户无感知,这就是**滚动发布(Rolling Update)**的基础。

没有负载均衡的发布方式

没有负载均衡的情况下,发布只能采用停机发布:

1. 停掉所有应用实例 → 业务全部不可用
2. 替换为新版本
3. 启动所有实例 → 业务恢复

中间这段时间所有用户都访问不了。对于内部小工具勉强可以接受,业务系统几乎不可能用。负载均衡是支撑"高可用 + 平滑发布"的最基础组件。

常见的负载均衡分发算法

算法行为适用场景
轮询(Round Robin)按顺序依次分发实例配置一致、请求处理时间相近
加权轮询按权重分发,高权重实例拿更多请求实例配置不一致(老机器低权重,新机器高权重)
最少连接把请求分给当前连接数最少的实例请求处理时间差异大,避免某实例堆积
IP 哈希按客户端 IP 哈希,同一 IP 总到同一实例实现会话粘性的简单方式
一致性哈希类似 IP 哈希但实例变化时重新分布的影响小缓存类服务、需要 affinity 的场景
随机随机选一个实例简单场景

二、DNS 分流:最粗粒度的分发

DNS 也能实现最简单的"分流"——一个域名返回多个 IP,客户端随机或按顺序拿到其中一个:

DNS 分流粒度粗且受缓存影响

$ dig www.example.com +short
203.0.113.10
203.0.113.11
203.0.113.12

DNS 分流的优势

  • 实现简单,只是 DNS 配置
  • 没有中间组件,客户端直连后端
  • 适合地域调度线路调度——北京用户解析到北京机房 IP、上海用户解析到上海机房 IP(基于 GeoDNS)
  • 适合多机房入口的粗粒度分配

DNS 分流的根本局限

DNS 分流不能替代真正的负载均衡,有以下硬伤:

1. 健康检查极其粗糙

如果某台机器 203.0.113.11 上的应用挂了,只要 DNS 还返回这个 IP,客户端就会持续访问失败。云厂商的智能 DNS(如阿里云解析、AWS Route53)有健康检查能力,但探测和摘除间隔通常在分钟级,远不如真正的负载均衡敏捷。

2. 故障切换慢

DNS 有 TTL 缓存——改了记录后,各级缓存(浏览器、操作系统、运营商 DNS)需要等 TTL 过期才会拉新值。这导致:

  • 出问题时,改记录到新生效有几分钟到几十分钟延迟
  • 部分用户已经恢复、部分用户还在访问旧 IP

3. 负载分配不均

DNS 不知道每台后端实例的实时负载,只能按固定策略返回 IP。客户端 DNS 缓存让"轮询"变成"某些 IP 被某些区域用户长期使用"。

4. 无法做七层路由

DNS 只能返回 IP,无法根据 HTTP 路径、Header 做精细分流。

DNS 分流的合理用法

DNS 分流的合理位置:入口最前端的粗粒度地域调度。具体的实例选择、健康检查、连接转发、七层路由,交给云负载均衡、Nginx、HAProxy、Ingress 这些专门组件。

完整的典型链路:

用户 DNS 查询 → 智能 DNS 按地域返回区域负载均衡 IP
        → 区域负载均衡(四层)→ 应用层负载均衡(七层)→ 后端实例

三、四层与七层负载均衡

"四层""七层"的命名来自 OSI 网络模型,实际工程上的差别就是一句话:负载均衡能不能看懂 HTTP

四层看连接七层看 HTTP 内容

四层负载均衡(L4)

维度内容
工作层TCP/UDP 层
能看到IP、端口、协议
不能看到HTTP 路径、Header、域名、状态码
性能高(只转发,不解析 HTTP)
代表LVS、HAProxy(四层模式)、云厂商 SLB(TCP/UDP)
适用场景MySQL、Redis、Kafka、自定义 TCP 协议、高吞吐场景

四层负载均衡只关心"这个 TCP 连接转到哪个 IP:Port",不解析连接上传输的内容。优点是吞吐高、延迟低、适合长连接;缺点是无法基于 HTTP 内容做路由决策。

七层负载均衡(L7)

维度内容
工作层HTTP 层(应用层)
能看到HTTP 完整内容(URL、Header、Body、状态码)
性能较低(需要完整解析 HTTP)
代表Nginx、HAProxy(七层模式)、K8s Ingress、API 网关、Envoy
适用场景Web 页面、HTTP API、按域名/路径/Header 路由

七层负载均衡能完整解析 HTTP 协议,因此能做:

  • 按域名分流:api.example.com → API 服务,www.example.com → 官网
  • 按路径分流:/api/* → 后端,/static/* → 静态资源
  • 按 Header 分流:灰度发布(X-Canary: true → 新版本)
  • 按 Cookie 分流:会话粘性
  • 请求改写:URL 重写、Header 增删

四层与七层的配合

实际生产中,两种经常配合使用:最外面用四层做 TCP 卸载和高吞吐,后面接七层做 HTTP 路由

Kubernetes 集群的典型入口链路:

公网 → 云厂商 SLB(四层)→ Ingress Controller(七层)→ Service → Pod

云厂商的 SLB 做四层流量分发到 K8s 节点,Ingress Controller(Nginx/Traefik/Envoy)做七层 HTTP 路由,各司其职。

四、健康检查

负载均衡需要知道"后端实例哪些可用、哪些已经坏了"。健康检查就是定期探测后端状态,把异常实例从转发列表中摘除,恢复后重新加入

健康检查要判断实例是否真的能接流量

常见健康检查方式

方式检查内容优势局限
TCP 检查端口能建立 TCP 连接简单、通用不能反映应用是否真正可用
HTTP 检查请求 /health,返回 200能检测应用层健康增加应用负担
自定义检查业务逻辑判断(依赖是否可用)最贴近业务复杂度高

TCP 检查的局限

TCP 检查只能说明端口还开着,不能说明应用还能处理请求。以下场景下端口仍然可连,但应用已经无法响应:

  • 应用线程池打满
  • 数据库连接池耗尽
  • 应用陷入死循环或死锁
  • 内存即将 OOM,GC 不停
  • 慢查询导致请求堆积

这种情况下 TCP 检查仍然显示健康,但用户请求实际全部失败。关键业务务必用 HTTP 检查,且检查路径要能反映真实健康状态

HTTP 检查路径的设计

/health 接口的设计需要平衡两个目标:

  • 能反映健康状况:依赖出问题时,该接口也应返回失败
  • 不能成为压力源:负载均衡每秒探测多次,接口本身不能很重

直接在 /health 里查数据库、查缓存、调下游接口,会让健康检查本身就成为压力源——多个负载均衡 + 多个 Kubernetes Probe 同时探测,数据库被探测请求打爆。

Liveness 与 Readiness 分离

Kubernetes 提出的设计模式,把健康检查分为两类:

类型检查内容失败时
Liveness(存活)进程是否还活着重启容器
Readiness(就绪)是否准备好接受请求从 Service 端点摘除,不重启
  • Liveness 应当轻:只检查进程,不依赖外部。即使数据库挂了,进程本身没问题就不应该被重启
  • Readiness 可以重:检查依赖是否可用。数据库挂了的时候,这个实例应该被摘除流量,但不应该被重启

健康检查相关的典型故障判定

入口层日志中出现某实例 unhealthyconnect refused,但应用日志没有对应请求 → 问题在入口到后端这一段,而非后端处理逻辑。

例如 Nginx error log:

connect() failed (111: Connection refused) while connecting to upstream,
    upstream: "http://10.0.2.15:8080/"

排查方向:

bash
# 1. 目标实例是否还在
ping 10.0.2.15

# 2. 应用进程是否监听对应端口
ssh 10.0.2.15
ss -lntp | grep 8080

# 3. K8s 场景:Pod 是否 Ready
kubectl get pods -o wide
kubectl describe pod <pod-name>

# 4. 看应用日志,是否启动失败或刚崩溃
journalctl -u myapp -n 100

五、反向代理:七层负载均衡的能力扩展

Nginx 和 HAProxy 经常以**反向代理(Reverse Proxy)**的身份出现在入口层。客户端访问的是代理的地址,代理把请求转发给真实后端——"反向"是相对于"正向代理"(客户端代理,如 VPN、SOCKS)而言。

反向代理的核心能力

能力用途
按 Host 转发不同域名转到不同后端集群
按路径转发/api → API 集群,/static → 静态资源
TLS 终止入口层解 HTTPS,后端用 HTTP,减少后端加解密压力
限流控制单 IP、单接口、全局的请求速率
缓存缓存静态资源、API 响应
压缩启用 gzip / brotli
访问日志完整记录请求详情
请求改写修改 URL、增删 Header、重写状态码
灰度路由按 Header、Cookie 路由到新旧版本

Nginx 反向代理基础配置

nginx
upstream backend {
    server 10.0.2.10:8080 weight=10;
    server 10.0.2.11:8080 weight=10;
    server 10.0.2.12:8080 weight=10 backup;     # 备用,主节点都挂了才用
}

server {
    listen 443 ssl;
    server_name api.example.com;

    ssl_certificate     /etc/nginx/certs/api.example.com.crt;
    ssl_certificate_key /etc/nginx/certs/api.example.com.key;

    location /api/ {
        proxy_pass http://backend/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_connect_timeout 5s;       # 与上游建立连接的超时
        proxy_send_timeout    60s;      # 给上游发请求的超时
        proxy_read_timeout    60s;      # 等上游响应的超时
    }

    location /static/ {
        root /var/www;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

反向代理日志:排查入口问题的第一份材料

合理设计的反向代理日志能直接指向问题层级。Nginx 推荐的访问日志格式:

nginx
log_format detailed '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time" '
                    'us=$upstream_status backend=$upstream_addr';

字段含义:

字段含义
request_timeNginx 处理整个请求的总时间(从收到请求到返回响应)
upstream_connect_timeNginx 与后端建立连接的时间
upstream_header_time从开始到收到后端响应头的时间
upstream_response_time从开始到收到后端响应体的时间
upstream_status后端返回的状态码
upstream_addr实际转发到的后端实例

通过日志快速定位故障

场景 1:返回 502,upstream_response_time 很短

status=502 upstream_status=502 request_time=0.023 upstream_response_time=0.022
  • 22 毫秒就拿到 502 — 说明后端不是慢,是直接拒绝
  • 排查方向:后端进程是否启动、端口是否监听
  • 同时刻看后端日志:无异常 → 进程或端口问题;有异常 → 应用启动失败

场景 2:返回 504,upstream_response_time 接近超时阈值

status=504 upstream_status=504 request_time=60.001 upstream_response_time=60.000
  • 60 秒整 — 入口等到超时阈值返回
  • 排查方向:后端处理慢
  • 同时刻看后端:慢查询日志、外部 API 调用、CPU 使用情况

场景 3:返回 200,但 request_time 很大,upstream_response_time 很小

status=200 upstream_response_time=0.050 request_time=10.000
  • 后端响应快,但整体慢 — 慢的是 Nginx 自己或者客户端
  • 排查方向:客户端网络慢(响应体大、用户带宽小)、Nginx 自身压力大

通过日志字段的对照,绝大多数入口层问题能在几秒钟内定位到具体故障层

六、多层入口的排查

企业系统的入口链路通常不止一层:

每一层都有自己的状态码、日志、错误页。用户看到的错误不能直接归因到应用 —— 必须先确定请求停在哪一层。

按现象快速定位

用户看到优先排查
证书错误CDN / 云 LB / Nginx / Ingress 的证书配置
全站 403WAF 拦截、入口鉴权、IP 黑名单
某路径 404七层路由配置(Ingress path、Nginx location)
502 + upstream connect failed后端进程、端口、Service、Endpoints、Pod 状态
504 + 接近超时阈值后端慢查询、外部依赖超时
间歇性 502某个后端实例健康检查异常,流量轮询命中时失败
全站 503入口限流触发、所有后端不可用

全链路 Trace ID

入口链路超过两层后,没有统一 Trace ID 几乎不可能完整追踪一次请求。每层日志独立,通过时间戳 + IP + Path 拼接的方式排查极其低效。

标准做法:每一层在请求 Header 中插入或透传 Trace ID,所有组件的日志中都记录这个 ID。任何一层出问题,通过 Trace ID 一次查到所有相关日志。

Nginx 配置生成或透传 Trace ID:

nginx
# 如果没有 Trace ID,生成一个
map $http_x_trace_id $trace_id {
    "~^.+$" $http_x_trace_id;       # 如果已有,使用上游传来的
    default $request_id;             # 否则使用 Nginx 自带的 $request_id(每请求唯一)
}

server {
    location / {
        proxy_set_header X-Trace-ID $trace_id;
        proxy_pass http://backend;
    }
}

log_format with_trace '... trace_id=$trace_id ...';
access_log /var/log/nginx/access.log with_trace;

应用层把收到的 X-Trace-ID 放入日志:

python
import logging
@app.middleware("http")
async def trace_middleware(request, call_next):
    trace_id = request.headers.get("X-Trace-ID", "")
    logging.LoggerAdapter(logger, {"trace_id": trace_id})
    response = await call_next(request)
    return response

完整的分布式追踪系统(Jaeger、SkyWalking、Zipkin)在此基础上扩展,记录每一次调用的具体耗时、调用链关系——这是大型分布式系统排障的核心基础设施。

七、负载均衡选型

按使用场景对常见负载均衡方案的简要对照:

方案类型适用场景
Nginx七层Web 入口、反向代理,部署简单,生态成熟
HAProxy四层/七层高性能、企业级,支持复杂路由策略
LVS四层超高吞吐(百万 QPS+),通常作为 Nginx 集群的前端
Envoy七层云原生服务网格(Istio)、API 网关
Traefik七层容器原生,与 Docker / K8s 集成好
云厂商 SLB四层/七层托管服务,无需自运维,适合云上首选
K8s Ingress七层K8s 集群内部路由,后端通常是 Nginx/Traefik/Envoy

技术选型本身没有"哪个最好"——根据团队熟悉度、生态匹配、运维能力综合判断。云上业务优先用云厂商的 SLB,自建复杂业务的入口考虑 Nginx + HAProxy + LVS 多层组合,K8s 内部用 Ingress + Service Mesh。