Appearance
负载均衡
后端服务一旦从单实例扩成多实例,马上就有个新问题:用户该访问哪个实例?总不能让用户记住每个实例的 IP。负载均衡就是干这个的——它给用户提供一个固定入口(域名或 IP),自己再把请求分发到后面的一堆实例上。
但负载均衡不只是"压力大时分担一下请求"这么简单。它还参与实例下线、健康检查、滚动发布、故障摘除这些事——可以说是多实例架构的"调度中心"。理解负载均衡的运作方式,排查问题的时候才知道请求到底卡在哪一层。
一、入口与流量分发
最直观的形态就是一个入口后面挂多台后端:
用户记住的是负载均衡的域名或者公网 IP,后面的实例怎么增删改,用户都不用感知。这点对运维太重要了——发布新版本时,可以从负载均衡上摘下一台实例,确认没有新请求进来,再替换成新版本,确认健康后再挂回去。整个过程用户基本无感,这就是"滚动发布"的基础。
如果没有负载均衡,发布就得停机——把所有实例一起停掉、换成新版本、再启动,中间所有用户都访问不了。这种发布方式叫"停机发布",小项目还能接受,业务系统基本不可能用。
二、DNS 分流
DNS 也能做最简单的分流:一个域名返回多个 IP,客户端拿到其中一个直接访问。比如:
| 域名 | 返回的 IP |
|---|---|
www.example.com | 203.0.113.10 |
www.example.com | 203.0.113.11 |
www.example.com | 203.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.022status=502 加上 upstream_response_time 只有 22 毫秒,说明入口很快就从上游拿到了失败响应——后端不是慢,是直接拒绝了。再看后端日志,如果同时刻有异常堆栈,问题在应用内部;如果后端压根没收到这个请求,就看 upstream 地址、端口、Service、Endpoints 这些。
另一种典型日志:
text
status=504 upstream_status=504 request_time=60.001 upstream_response_time=60.000status=504 加上 60 秒整的响应时间——这是入口等到超时阈值才返回。后端慢查询、线程池排队、外部接口卡死都可能是原因,要去后端看为什么这次请求处理了 60 秒。
六、多层入口链路
企业系统的入口链路经常好几层:
每一层都有自己的状态码、日志和错误页。用户看到 403 不一定是应用拒绝了请求,可能是 WAF 命中了规则;用户看到 502 不一定是后端代码崩了,可能是 Ingress 后面没有可用 Pod。所以排查入口问题时,先要分清请求到底停在哪一层:
| 现象 | 优先看哪里 |
|---|---|
| 证书错误 | CDN、云负载均衡、Nginx/Ingress 的证书配置 |
| 全站 403 | WAF 命中规则、入口鉴权、来源 IP 黑名单 |
| 只有某个路径 404 | 七层路由、Ingress path 配置、后端路由 |
| 502 且 upstream 连接失败 | 后端端口、Service、Endpoints、Pod 状态 |
| 504 且耗时接近超时阈值 | 后端慢请求、数据库慢查询、外部依赖超时 |
同一个域名后面挂了 CDN、WAF、SLB、Ingress 四层入口,日志最好带上统一的请求 ID 或者 trace id。没有统一标识时,排查只能靠时间戳、客户端 IP、Host、路径、状态码把几层日志拼起来,非常痛苦——所以分布式追踪系统(Jaeger、SkyWalking)在企业里这么重要。