负载均衡器(load balancer)位于客户端和一组后端服务器之间,分发入站流量,让任何单台服务器都不会成为瓶颈。它是横向扩展的基石:加更多服务器,负载均衡器就自动分摊工作。没有它,横向扩展在运维上不可能,且单台服务器故障会拖垮整个服务。

⚡ 速览要点
  • L4 vs L7——L4 按 TCP/UDP 头路由(快、不检查内容);L7 按 HTTP 路径/头/cookie 路由(更聪明、CPU 略高)。
  • 轮询(Round Robin)适合均匀负载;最少连接(Least Connections)适应时长可变的请求。
  • IP 哈希无需存状态就给出会话亲和性,但移除一台服务器时会重排所有客户端。
  • 粘性会话是有状态服务器的权宜之计——真正的修法是把会话状态外置到 Redis。
  • LB 冗余——用带 VIP 的 active-passive 或 active-active 对;LB 本身绝不能是 SPOF。
tldr

负载均衡器用轮询、最少连接或 IP 哈希等算法把流量路由到后端服务器。L4 LB 按 TCP/UDP 元数据路由;L7 LB 检查 HTTP 内容、能做更聪明的路由决策。健康检查自动把不健康的服务器移出池。

负载均衡把流量分发到后端服务器
负载均衡把流量分发到后端服务器

为什么要负载均衡?

它解决三个核心问题:

  • 吞吐——一组较小服务器能处理的总请求比一台大服务器多,且增量扩展更便宜。
  • 高可用——当一台服务器故障,负载均衡器通过健康检查检测到并停止向它路由流量,通常在数秒内。
  • 降低延迟——地理负载均衡器能把用户路由到最近的数据中心,缩短往返时间。

第四层 vs. 第七层

维度第四层(传输层)第七层(应用层)
作用于TCP/UDP 头(IP + 端口)HTTP 头、URL、cookie、body
性能非常快——不检查内容CPU 成本略高
路由智能低——不感知应用协议高——能按路径、host 或头值路由
TLS 终止仅透传可终止 TLS、检查载荷、再加密
示例AWS NLB、HAProxy TCP 模式AWS ALB、Nginx、Envoy

实践中,大多数现代架构在边缘用 L7 负载均衡器(做 HTTP 路由、TLS 终止、头操作),对内部 TCP 服务用 L4 负载均衡器——那里裸吞吐比路由智能更重要。

负载均衡算法

轮询(Round Robin)

请求按循环顺序依次分发到服务器池。当所有服务器容量相同、请求成本均匀时简单有效。加权轮询(Weighted Round Robin)扩展它:给每台服务器分配与其容量成比例的权重,权重 3 的服务器接收的请求是权重 1 的三倍。

最少连接(Least Connections)

每个新请求发给当前处理活跃连接最少的服务器。它自然适应请求时长可变的负载——处理长查询的服务器持有更少的新连接。加权最少连接在比较前先把活跃连接数除以服务器权重。

IP 哈希(IP Hash)

对客户端 IP 地址做哈希以确定性地选服务器。同一客户端总落到同一后端,提供会话亲和性而无需负载均衡器存会话状态。缺点:若移除一台服务器,它的所有客户端都被重新哈希和重分配,可能打断进行中的会话。

随机(Random)

均匀随机选一台服务器。在大请求量下统计上接近轮询,但小请求量无保证。当你想要零协调开销、且池大而同质时有用。

健康检查

负载均衡器定期探测后端以检测故障。一台服务器在连续失败可配置的次数后被标记为不健康并移出池;在连续通过可配置的次数后被重新加入。有两种口味:

  • 被动健康检查——从实际流量推断健康。若服务器返回 5xx 错误,LB 标记它降级。
  • 主动健康检查——LB 按计划发送合成探测请求(TCP 握手、对 /health 的 HTTP GET),独立于真实流量。
nginx
upstream backend {
    least_conn;                          # 最少连接算法
    server 10.0.0.1:8080 weight=3;
    server 10.0.0.2:8080 weight=1;
    server 10.0.0.3:8080 backup;         # 仅在主服务器全挂时使用
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_500;
    }
}

粘性会话(Sticky Sessions)

有些应用把会话状态存在服务器上(进程内缓存、进行中的文件上传)。粘性会话(也叫会话保持)确保某客户端的请求总去同一后端。负载均衡器通常通过在客户端首次请求时设 cookie、在后续请求读它来实现。

粘性会话是双刃剑:它改善与有状态应用的兼容性,但降低负载的均匀分布并使故障处理复杂化——若粘住的服务器宕机,它的所有会话反正都丢了。首选修法是把会话状态移到共享存储(Redis、数据库),让任何服务器都能处理任何请求。

负载均衡器冗余

负载均衡器本身是潜在的单点故障。生产部署用 active-passiveactive-active 对,常配一个在节点间浮动的虚拟 IP(VIP)。DNS 或 BGP 把 VIP 路由到活跃节点;若它故障,被动节点通过 VRRP(HAProxy + Keepalived)等协议或更新 DNS 记录在数秒内接管 VIP。

一致性哈希(Consistent Hashing)

IP 哈希和简单的取模路由有一个灾难性缺陷:增删一台服务器几乎会重排所有客户端分配。若你有 10 台服务器加 1 台,模 11 对大多数 key 产生完全不同的目标——突然 90% 的缓存 key 在新服务器映射上未命中,在后端造成 thundering herd。一致性哈希通过把服务器和请求都映射到一个环形哈希环来解决:增删一台服务器只影响它与最近邻之间范围内的 key——对 n 台服务器大约 1/n 的 key。

环怎么工作

每台服务器被哈希到 0–2³²−1 环上的一个或多个点。每个请求 key 也被哈希到环上一个点;请求路由到从该点顺时针走遇到的第一台服务器。用虚拟节点(vnode),每台物理服务器占环上多个点(如每台 150 个 vnode),平滑掉服务器哈希聚集在环某一区域时的不均匀分布。

python
import hashlib, bisect

class ConsistentHashRing:
    def __init__(self, nodes, vnodes=150):
        self.ring = {}        # hash → node
        self.keys = []        # 排序的哈希值
        for node in nodes:
            for i in range(vnodes):
                h = self._hash(f"{node}-{i}")
                self.ring[h] = node
                self.keys.append(h)
        self.keys.sort()

    def _hash(self, key):
        return int(hashlib.md5(key.encode()).hexdigest(), 16)

    def get_node(self, key):
        h = self._hash(key)
        idx = bisect.bisect(self.keys, h) % len(self.keys)
        return self.ring[self.keys[idx]]   # 顺时针后继

一致性哈希被用于:分布式缓存(Memcached、Redis Cluster)、CDN 边缘服务器、数据库分片路由器,以及 Kafka 的分区分配。每当会话亲和性或缓存局部性重要、且你需要增删节点而不造成灾难性 key 重映射时,它就是答案。

L4 vs L7——更深入

高层区别众所周知,但实现细节决定每种场景该选哪个。

第四层的底层

L4 负载均衡器在 TCP/UDP 传输层工作。它看到源 IP、目的 IP、源端口、目的端口——别的什么都看不到。两种主流转发策略是:

  • NAT 模式——LB 把目的 IP 改写成所选后端的 IP、把源 IP 改写成自己的地址,这样后端回复给 LB,再由 LB 翻译回去。简单但加一跳网络。
  • 直接服务器返回(DSR)——LB 只改写目的 MAC 地址(或用隧道),让后端直接回复客户端,在返回路径上绕过 LB。响应流量(对 HTTP 通常是请求流量的 10–100 倍)从不经过 LB。这就是 Google 的 Maglev 和 Facebook 基于 IPVS 的 LB 实现每秒百万包吞吐的方式——让 LB 完全离开高流量返回路径。

第七层的底层

L7 负载均衡器终止来自客户端的 TCP 连接,读取 HTTP/gRPC/WebSocket 请求,基于内容做路由决策,再与后端建立一条独立的 TCP 连接。这种双连接模型使得 L4 无法提供的特性成为可能:

  • 请求级路由——POST /api/orders → 订单服务;GET /api/products → 商品服务。
  • 头操作——加 X-Request-ID、改写 Host、在发给后端前剥离内部头。
  • TLS 终止——在 LB(或服务网格 sidecar)解密,检查明文,可选地重新加密到后端(TLS origination)。集中证书管理。
  • 认证卸载——在 LB 校验 JWT 或 API key,只把已认证请求转发给后端。
  • 限流和熔断——按客户端/API key 计数请求,在到达后端前拒绝超额。
  • gRPC 负载均衡——L4 LB 无法在一条长连接的 HTTP/2 内均衡单个 gRPC 调用;只有理解 HTTP/2 帧的 L7 代理才能把调用分摊到后端。
gRPC 与 HTTP/2

gRPC 在单条长连接 HTTP/2 上多路复用许多调用。L4 负载均衡器把整条连接路由到一个后端——该连接上的所有调用都落到同一台服务器,彻底破坏了负载均衡。修法是用理解 HTTP/2 的 L7 代理(Envoy、带 gRPC 代理模块的 Nginx、Linkerd)把单个 gRPC 帧分摊到后端。

SSL/TLS 终止策略

负载均衡架构中处理 TLS 有三种标准方式,各有不同的安全/性能取舍:

策略描述优点缺点
在 LB 终止 TLSLB 解密流量;后端收到明文 HTTP集中证书管理;LB 能按内容检查/路由;后端更简单内部流量不加密;LB 成了解密 oracle
TLS 透传(L4)LB 转发加密字节;后端终止 TLS端到端加密;LB 从不见明文无法做 L7 路由;每后端管理证书
TLS 重加密LB 终止来自客户端的 TLS,再开一条新 TLS 连接到后端两段都加密;LB 能检查和路由CPU 成本更高;要管两对证书

大多数云架构用边缘 LB 的 TLS 终止(AWS ALB、Cloudflare)结合内部 mTLS(服务间的双向 TLS,由 Istio 或 Linkerd 等服务网格强制)。这给你边缘的 L7 路由、集中的公网证书管理,以及端到端加密,而无需手工管理后端证书。

nginx
# TLS 终止 + 重加密到后端
server {
    listen 443 ssl;
    ssl_certificate     /etc/ssl/certs/api.example.com.crt;
    ssl_certificate_key /etc/ssl/private/api.example.com.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

    location / {
        proxy_pass              https://backend;  # 重加密到后端
        proxy_ssl_verify        on;
        proxy_ssl_trusted_certificate /etc/ssl/certs/internal-ca.crt;
        proxy_ssl_session_reuse on;              # 复用 TLS 会话以提性能
    }
}

健康检查——高级配置

基础健康检查发一个 TCP SYN 或对 /health 的 HTTP GET。生产中,"进程还活着"和"进程能服务生产流量"之间的间隙正是大多数故障藏身之处。高级健康检查设计弥合这个间隙。

健康检查端点设计

设计良好的 /health 端点应检查所有关键依赖——数据库连通性、缓存可用性、下游服务可达性——并返回带每依赖状态的结构化 JSON。这让负载均衡器能区分"服务器彻底死了"(TCP 失败)和"服务器活着但数据库挂了"(HTTP 503),据此路由。

json
// GET /health/readiness — 就绪返回 200 OK,否则 503
{
  "status": "degraded",
  "checks": {
    "database":  { "status": "up",   "latency_ms": 3   },
    "cache":     { "status": "up",   "latency_ms": 0.5 },
    "payment_gw":{ "status": "down", "latency_ms": 5000 }
  }
}

HAProxy 健康检查配置

haproxy
backend api_servers
    balance leastconn
    option httpchk GET /health/readiness
    http-check expect status 200
    default-server inter 5s fall 3 rise 2   # 每 5s 检查;3 次失败=down,2 次成功=up
    server api1 10.0.0.1:8080 check weight 10
    server api2 10.0.0.2:8080 check weight 10
    server api3 10.0.0.3:8080 check weight 5   # 小实例——权重减半
    server api4 10.0.0.4:8080 check backup      # 仅在所有主服务器全挂时激活

DNS 负载均衡与全局流量管理

基于 DNS 的负载均衡完全在网络层之上工作:DNS 服务器给不同客户端返回不同的 A 记录,把它们分散到服务器池或地理区域。它是全局流量管理和多区域部署的基础。

DNS LB 怎么工作

你域名的权威 DNS 服务器根据策略返回不同 IP 地址——跨 IP 的简单轮询,或基于解析器 IP 的地理路由。客户端在 TTL 期间(常 30–300 秒)缓存响应,然后重新解析。这在 DNS 层、任何 TCP 连接建立之前,创造了天然的负载分布。

dns
# 简单 DNS 轮询:连续查询返回不同 IP
api.example.com.  30  IN  A  54.1.2.3    # US-East LB VIP
api.example.com.  30  IN  A  52.4.5.6    # US-West LB VIP
api.example.com.  30  IN  A  13.7.8.9    # EU-West LB VIP

# DNS TTL = 30s:客户端缓存 30s,然后重新解析到不同 IP
# 低 TTL 实现快速故障转移,但增加 DNS 查询负载

DNS LB 的局限

  • TTL 缓存——客户端和中间解析器在 TTL 期间缓存 DNS 响应。若一台服务器宕机,缓存了它 IP 的客户端会在最多 TTL 秒内一直尝试连接它,才拿到更新记录。设 TTL=0 有帮助但许多解析器忽略它。
  • 无连接状态——DNS 对服务器负载、连接数或响应时间一无所知。活着但过载的服务器继续接收它那份流量。
  • 默认粘性——长生命周期应用解析一次就把结果缓存在自己的连接池里。无论 TTL 如何,它们好几个小时都不会重新解析。

GeoDNS 与基于延迟的路由

AWS Route 53、Cloudflare、Akamai 等服务给 DNS 加上地理智能:它们识别解析器的位置(通常代理用户位置)并返回最近的健康端点 IP。欧洲用户把 api.example.com 解析到法兰克福 IP;新加坡用户解析到新加坡 IP。结合把不健康端点移出轮转的健康检查,GeoDNS 除了延迟优化还提供区域级故障转移。

Anycast 与全局负载均衡

Anycast 是一种路由技术:同一个 IP 地址通过 BGP 从多个地理位置同时通告。互联网的路由协议自然把包送到拓扑上最近的源——来自欧洲的流量到达欧洲 PoP,来自亚洲的到达亚洲 PoP,全都指向同一目的 IP。这就是 CDN 和 DDoS 清洗服务的工作方式:一个 IP 地址出现在数百个边缘位置。

Anycast 与 DNS LB 有一个重要区别:路由决策在网络层(BGP)做,不在应用层。没有 TTL 缓存问题——包在每一跳被路由到最近 PoP。而且因为 PoP 故障时 BGP 在数秒内重新收敛,故障转移比等 DNS TTL 过期更快更可靠。

cloudflare 示例

Cloudflare 从全球 300+ 数据中心通告同一个 IP 地址(如其 DNS 解析器的 1.1.1.1,或其 CDN 的 anycast 网络)。东京用户连到东京边缘节点;圣保罗用户连到圣保罗边缘节点——全都指向同一 IP。这就是规模化的 Anycast:用单个 IP 地址实现全局负载分布和容错。

会话保持深入

粘性会话有几种实现变体,取舍差异显著。理解它们对生产运维和面试中讨论有状态应用都重要。

基于 Cookie 的亲和性

负载均衡器在客户端首次请求时设一个 cookie(如 SERVERID=api2)。后续请求,LB 读 cookie 并路由到指定后端。这是最常见且最灵活的方式——它跨 IP 变化也能工作(移动客户端切网络)、不暴露后端 IP,且 cookie TTL 控制亲和性持续多久。

haproxy
backend web_servers
    balance roundrobin
    cookie SERVERID insert indirect nocache   # LB 管理的粘性 cookie
    server web1 10.0.0.1:80 check cookie web1
    server web2 10.0.0.2:80 check cookie web2
    server web3 10.0.0.3:80 check cookie web3

IP 哈希亲和性

对客户端 IP 哈希以确定性选后端。无 cookie 开销,但在网络间漫游的移动客户端会被重分配,而企业客户端(许多用户在一个 NAT IP 后)全落到同一后端。增删服务器会改变与 1/n 成比例的客户端的哈希结果(假设一致性哈希)或几乎所有客户端(朴素取模)。

正确修法:外置会话状态

两种方式都是对一个更深层问题的权宜之计:会话状态存在应用服务器内存里。生产级解法是把会话状态移到共享、快速的存储如 Redis 或 Memcached。任何后端都能处理任何请求,因为会话从外部存储查,而非本地内存。这实现了真正弹性的横向扩展:增删服务器而无任何客户端可见的中断。

高可用与故障转移模式

本身有单点故障的负载均衡器解决了错误的问题。生产 LB 配置用两种高可用模式之一。

带 VIP 的 Active-Passive

两个负载均衡器节点共享一个虚拟 IP(VIP)。正常运行时,只有活跃节点占据 VIP 并处理所有流量。被动节点通过心跳(VRRP、keepalived)监视活跃节点。若活跃节点在可配置窗口内未发心跳,被动节点通过 gratuitous ARP 占据 VIP 并接管。故障转移通常在 1–2 秒内完成。AWS Elastic Load Balancer、Google Cloud LB、Azure Load Balancer 在内部管理这套——你从不见 VIP 机制。

bash
# keepalived.conf(VRRP active-passive LB 对)
vrrp_instance VI_1 {
    state MASTER            # 被动节点设为 BACKUP
    interface eth0
    virtual_router_id 51
    priority 110           # 优先级越高越优先做 master;BACKUP = 100
    advert_int 1           # VRRP 心跳每 1s
    authentication {
        auth_type PASS
        auth_pass secret123
    }
    virtual_ipaddress {
        203.0.113.10        # 两节点竞争的 VIP
    }
    track_script {
        chk_haproxy         # haproxy 进程死掉则降级
    }
}

Active-Active

两个 LB 节点同时处理流量,通常通过两个 VIP 间的 DNS 轮询或上游 Anycast 地址。总吞吐翻倍。故障时,存活节点吸收所有流量。取舍:配置和状态同步稍复杂(若要 TCP 会话无缝挺过故障转移,连接表必须复制)。

算法对比与选择指南

算法最适合弱点复杂度
轮询请求成本均匀、服务器相同请求时长不同或服务器容量不同则不均O(1)
加权轮询异构服务器容量仍忽略当前负载;静态权重需手动调O(1)
最少连接请求时长可变(long-polling、流式)所有新连接同时发给一台空闲服务器(恢复时 thundering herd)O(log n)
IP 哈希无 cookie 开销的会话亲和性增删服务器时重排;NAT 客户端造成热点O(1)
一致性哈希规模化的会话亲和性 + 缓存局部性无 vnode 时分布略不均;实现更复杂O(log n)
随机无状态请求的超大同质池小池无保证;不感知服务器负载O(1)
最短响应时间延迟敏感 API;按实际响应速度加权需要主动测量开销;可能放大 thundering herdO(log n)

现代架构中的负载均衡

服务网格(Service Mesh)

在微服务架构中,服务网格(Istio、Linkerd、Consul Connect)把负载均衡逻辑从中心 LB 设备移到一个与每个应用 Pod 并行运行的 sidecar 代理(通常是 Envoy)。每个 sidecar 拦截所有入站和出站流量,应用负载均衡、重试、熔断和 mTLS——应用代码毫不知情。控制平面(Istio 的 istiod、Linkerd 的控制平面)把配置更新分发给所有 sidecar。

优势:在每个服务间调用都做带每请求路由决策的 L7 负载均衡,而非只在边缘。代价:每个 Pod 现在有一个消耗约 50–100 MB RAM、每跳加约 1–2 ms 延迟的 sidecar。

AWS Application Load Balancer——关键特性

  • 基于内容的路由——按路径(/api/* → ECS 服务)、host 头、query string 或 HTTP 方法路由。
  • 目标组加权——把 10% 流量发给新服务版本做金丝雀部署。
  • Lambda 目标——把特定路径路由到 Lambda 函数,在一个 LB 后混合容器化和 serverless 后端。
  • WebSocket 和 HTTP/2 支持——ALB 足够连接感知,能维持长连接 WebSocket 并分发 HTTP/2 流。
  • WAF 集成——挂上 AWS WAF,在流量到达后端前在 LB 阻断常见攻击模式。

常见坑与最佳实践

  • 永远别忘了 LB 是 SPOF——跑 active-passive 或 active-active 对。100 个高可用后端前的单个 LB,让整个架构的可用性不超过那个 LB。
  • 健康检查要紧的东西,不只是 TCP 端口开着——进程可能在 8080 监听,而它的数据库连接池已耗尽。检查 /health/readiness,不只 TCP SYN。
  • 设置连接排空(deregistration delay)——从池移除后端时,允许进行中的请求完成(通常 30–300 秒)再关闭连接。AWS ALB 叫它"deregistration delay";Nginx 用 proxy_next_upstream 结合优雅关闭。
  • DNS LB 的低 TTL 不等于即时故障转移——解析器忽略 TTL 下限,浏览器缓存,OS stub 解析器缓存。即便 30 秒 TTL,也要为 DNS 变更完全传播预留 60–120 秒。
  • 监控后端队列深度,不只连接数——最少连接算法路由到连接最少的服务器,但处理慢的 30 秒 DB 查询的服务器比服务快速 50ms 请求的连接更少。对延迟敏感层,最短响应时间或队列深度指标更准确。
  • 把健康检查流量与生产流量分开——每秒对 50 个后端猛敲 /health 的吵闹健康检查产生 50 req/秒负载。用一个做最少工作的轻量端点,并把依赖检查结果缓存几秒。
总结

需要路径路由、TLS 终止或头检查的 HTTP 负载选第七层。需要裸 TCP 吞吐、非 HTTP 协议或通过直接服务器返回获极致性能选第四层。按请求同质性选算法:均匀负载用轮询,可变负载用最少连接,缓存局部性和会话亲和性用一致性哈希。始终至少跑两个 active-passive 或 active-active 配置的负载均衡器,以免引入你本要消除的那个 SPOF。

🎯 面试速答

L4 vs L7——何时选哪个?L4 用于裸 TCP 吞吐、非 HTTP 协议,或需要直接服务器返回让返回流量绕开 LB 时;L7 用于需要基于路径的路由、TLS 终止、gRPC 支持或基于头的决策时。
负载均衡器怎么检测故障服务器?主动健康检查(按计划对 /health 的合成探测)或被动健康检查(从真实流量的 5xx 错误率推断)。主动检查检测流量前故障;被动检查检测进行中故障。
粘性会话为什么是问题?它们降低负载分布、使扩展复杂化,并意味着该服务器宕机时所有会话数据都丢——修法是把会话状态外置到 Redis,让任何服务器处理任何请求。
一致性哈希是什么、何时用?把服务器和请求映射到环;增删服务器只影响约 1/n 的 key,而非重排一切。用于分布式缓存、CDN、数据库分片和 Kafka 分区分配。
Anycast 怎么工作?同一 IP 通过 BGP 从多个地理 PoP 通告;互联网路由自然把包送到拓扑最近的 PoP——无 DNS 解析、无 TTL 缓存、PoP 宕机时亚秒级故障转移。
L4 LB 为什么不能均衡 gRPC 流量?gRPC 在单条 HTTP/2 连接上多路复用许多调用;L4 LB 把整条连接路由到一个后端。只有理解 HTTP/2 帧的 L7 代理才能把单个调用分摊到后端。

← 上一篇
Kubernetes (k8s)