Appearance
第 8 讲|负载均衡
后端服务从单实例扩展到多实例后,立即面临一个新问题:用户该访问哪个实例? 总不能让用户记住每个实例的 IP。**负载均衡(Load Balancer)**就是解决这一问题的组件——它给用户提供一个固定入口(域名或 IP),把请求分发到后面的多个实例。
但负载均衡的职责远不止"分担请求"。它还参与实例上下线、健康检查、滚动发布、故障摘除、TLS 终止、限流、访问日志——可以说是多实例架构的"调度中心"。理解负载均衡的运作方式,排查问题时才知道请求卡在哪一层。
本讲覆盖:负载均衡的基本作用、DNS 分流的局限、四层与七层的差异、健康检查的设计、反向代理的能力、多层入口的排查方法。
一、入口与流量分发
最直观的负载均衡形态:一个入口后面挂多台后端实例。

用户记住的是负载均衡的域名或公网 IP,后面的实例增删变更对用户完全透明。这一特性对运维至关重要——发布新版本时,可以:
- 从负载均衡上摘下一台实例
- 等待该实例上的存量连接处理完毕(drain)
- 替换为新版本
- 健康检查通过后,重新挂回负载均衡
- 重复上述步骤直到所有实例更新完毕
整个过程用户无感知,这就是**滚动发布(Rolling Update)**的基础。
没有负载均衡的发布方式
没有负载均衡的情况下,发布只能采用停机发布:
1. 停掉所有应用实例 → 业务全部不可用
2. 替换为新版本
3. 启动所有实例 → 业务恢复中间这段时间所有用户都访问不了。对于内部小工具勉强可以接受,业务系统几乎不可能用。负载均衡是支撑"高可用 + 平滑发布"的最基础组件。
常见的负载均衡分发算法
| 算法 | 行为 | 适用场景 |
|---|---|---|
| 轮询(Round Robin) | 按顺序依次分发 | 实例配置一致、请求处理时间相近 |
| 加权轮询 | 按权重分发,高权重实例拿更多请求 | 实例配置不一致(老机器低权重,新机器高权重) |
| 最少连接 | 把请求分给当前连接数最少的实例 | 请求处理时间差异大,避免某实例堆积 |
| IP 哈希 | 按客户端 IP 哈希,同一 IP 总到同一实例 | 实现会话粘性的简单方式 |
| 一致性哈希 | 类似 IP 哈希但实例变化时重新分布的影响小 | 缓存类服务、需要 affinity 的场景 |
| 随机 | 随机选一个实例 | 简单场景 |
二、DNS 分流:最粗粒度的分发
DNS 也能实现最简单的"分流"——一个域名返回多个 IP,客户端随机或按顺序拿到其中一个:

$ dig www.example.com +short
203.0.113.10
203.0.113.11
203.0.113.12DNS 分流的优势
- 实现简单,只是 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。

四层负载均衡(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 可以重:检查依赖是否可用。数据库挂了的时候,这个实例应该被摘除流量,但不应该被重启
健康检查相关的典型故障判定
入口层日志中出现某实例 unhealthy 或 connect 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_time | Nginx 处理整个请求的总时间(从收到请求到返回响应) |
upstream_connect_time | Nginx 与后端建立连接的时间 |
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 的证书配置 |
| 全站 403 | WAF 拦截、入口鉴权、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。