Skip to content

负载均衡

后端服务一旦从单实例扩成多实例,马上就有个新问题:用户该访问哪个实例?总不能让用户记住每个实例的 IP。负载均衡就是干这个的——它给用户提供一个固定入口(域名或 IP),自己再把请求分发到后面的一堆实例上。

但负载均衡不只是"压力大时分担一下请求"这么简单。它还参与实例下线、健康检查、滚动发布、故障摘除这些事——可以说是多实例架构的"调度中心"。理解负载均衡的运作方式,排查问题的时候才知道请求到底卡在哪一层。

一、入口与流量分发

最直观的形态就是一个入口后面挂多台后端:

用户记住的是负载均衡的域名或者公网 IP,后面的实例怎么增删改,用户都不用感知。这点对运维太重要了——发布新版本时,可以从负载均衡上摘下一台实例,确认没有新请求进来,再替换成新版本,确认健康后再挂回去。整个过程用户基本无感,这就是"滚动发布"的基础。

如果没有负载均衡,发布就得停机——把所有实例一起停掉、换成新版本、再启动,中间所有用户都访问不了。这种发布方式叫"停机发布",小项目还能接受,业务系统基本不可能用。

二、DNS 分流

DNS 也能做最简单的分流:一个域名返回多个 IP,客户端拿到其中一个直接访问。比如:

域名返回的 IP
www.example.com203.0.113.10
www.example.com203.0.113.11
www.example.com203.0.113.12

这种方式实现简单,适合入口最前面的地域调度或者线路调度——北京用户解析到北京机房、上海用户解析到上海机房。

但它的弱点很明显:健康检查很粗糙。203.0.113.11 这台机器上的应用已经挂了,只要 DNS 还把这个 IP 返回给客户端,拿到这个 IP 的用户就会访问失败。故障切换也慢——前面讲过 DNS 有 TTL 缓存,改了记录之后各级缓存还没过期之前,旧 IP 还在被使用,这就出现"有人恢复了、有人还没恢复"的现象。

所以 DNS 分流一般只做粗粒度的入口调度,具体实例的健康检查、连接转发、按路径分流这些细活,交给云负载均衡、Nginx、HAProxy、Ingress 这些专门的组件。

三、四层与七层

负载均衡经常分"四层"和"七层",这俩名字来自 OSI 网络模型。落到实际,区别就是:负载均衡能不能看懂 HTTP 协议

类型能看到什么适合场景
四层负载均衡IP、端口、TCP/UDP 连接MySQL、Redis、通用 TCP 服务、大量短连接
七层负载均衡域名、路径、Header、Cookie、状态码Web 页面、HTTP API、按域名或路径转发

四层只关心"这个连接要转到哪个 IP:Port",不解析 HTTP 报文。LVS、云厂商的 TCP/UDP 负载均衡、Keepalived 这些都属于四层。七层能完整解析 HTTP,所以可以根据域名分流(api.example.com 走 API 服务、www.example.com 走官网)、根据路径分流(/api 到后端、/static 到静态资源)、根据 Cookie 做会话保持。Nginx、HAProxy、Kubernetes Ingress、Gateway API 这些都属于七层。

实际生产里两种经常配合使用:最外面用四层做 TCP 卸载和高吞吐,后面接七层做 HTTP 路由。Kubernetes 集群里典型的链路是云厂商 SLB(四层) → Ingress Controller(七层) → Service → Pod。

四、健康检查

负载均衡光知道后端有哪些地址还不够,得知道这些后端能不能接请求。健康检查就是干这个的:负载均衡定期向后端发探测,连续失败几次就把这台后端标记为"不可用"从转发列表里摘掉,探测恢复之后再重新加回来。

常见的检查方式有几种:

方式判断什么
TCP 检查端口能不能建立 TCP 连接
HTTP 检查请求 /health 这种路径,返回预期状态码(一般 200)
自定义检查按服务内部状态判断,比如依赖的数据库、缓存是否可用

这里有个坑:TCP 检查只能说明端口还开着,不能说明应用还能处理请求。应用线程池打满、数据库连接池耗尽、内存 OOM 之前那段时间,端口可能还能连上,但接口已经返回不了正常响应了。所以关键业务一般用 HTTP 检查,而且检查路径要设计得能反映真实健康状态。

不过 HTTP 检查路径也要轻——/health 这个接口如果每次都去查数据库、Redis、第三方接口,负载均衡探测本身就会成为压力源。常见做法是把"存活检查"(进程还活着)和"就绪检查"(依赖都正常)分开:存活检查只看进程,就绪检查才看依赖。Kubernetes 里的 livenessProbe 和 readinessProbe 就是这个思路。

排查时一个典型场景:入口日志里连续出现某个后端 unhealthy,但应用日志里又没有对应请求——问题大概率在入口到后端这一段。比如 Nginx error log 里看到:

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

这说明入口已经选了 10.0.2.15:8080 转发,但目标端口拒绝连接。下一步就去看这台机器或者这个 Pod 上的应用进程、监听端口、健康检查路径是不是出问题了。

五、反向代理

Nginx 和 HAProxy 经常以"反向代理"的身份出现在入口层。客户端访问的是代理地址,代理再把请求转给后端服务——这就是"反向"的含义(用户感知不到真实后端)。

反向代理在入口能干的事不少:

能力用途
按 Host 转发不同域名转到不同服务
按路径转发/api 到后端接口、/static 到静态资源
TLS 终止在入口层处理 HTTPS 证书,后端用 HTTP,减少后端加解密压力
限流控制单 IP、单接口、全局的请求速率,防刷防爬
访问日志记录来源 IP、状态码、耗时、后端地址、UA 等等

反向代理日志是入口排查的第一份材料。看一行日志基本能定位问题方向:

text
status=502 upstream_status=502 request_time=0.023 upstream_response_time=0.022

status=502 加上 upstream_response_time 只有 22 毫秒,说明入口很快就从上游拿到了失败响应——后端不是慢,是直接拒绝了。再看后端日志,如果同时刻有异常堆栈,问题在应用内部;如果后端压根没收到这个请求,就看 upstream 地址、端口、Service、Endpoints 这些。

另一种典型日志:

text
status=504 upstream_status=504 request_time=60.001 upstream_response_time=60.000

status=504 加上 60 秒整的响应时间——这是入口等到超时阈值才返回。后端慢查询、线程池排队、外部接口卡死都可能是原因,要去后端看为什么这次请求处理了 60 秒。

六、多层入口链路

企业系统的入口链路经常好几层:

每一层都有自己的状态码、日志和错误页。用户看到 403 不一定是应用拒绝了请求,可能是 WAF 命中了规则;用户看到 502 不一定是后端代码崩了,可能是 Ingress 后面没有可用 Pod。所以排查入口问题时,先要分清请求到底停在哪一层:

现象优先看哪里
证书错误CDN、云负载均衡、Nginx/Ingress 的证书配置
全站 403WAF 命中规则、入口鉴权、来源 IP 黑名单
只有某个路径 404七层路由、Ingress path 配置、后端路由
502 且 upstream 连接失败后端端口、Service、Endpoints、Pod 状态
504 且耗时接近超时阈值后端慢请求、数据库慢查询、外部依赖超时

同一个域名后面挂了 CDN、WAF、SLB、Ingress 四层入口,日志最好带上统一的请求 ID 或者 trace id。没有统一标识时,排查只能靠时间戳、客户端 IP、Host、路径、状态码把几层日志拼起来,非常痛苦——所以分布式追踪系统(Jaeger、SkyWalking)在企业里这么重要。