Load Balancer的7大功能:详细解释与示例

本文有一个短视频:TikTok

1. 概述

提到Load Balancer(LB),很多人只是简单地认为:“它将请求分配到多个服务器”。实际上LB的功能远不止于此——从减轻SSL负担、保持用户会话,到防御DDoS和监控后端健康状态。

本文介绍了Load Balancer的7项主要功能,每项功能都配有具体的配置示例或代码(nginx、Spring Boot、Kubernetes),帮助你清楚地理解它在实际系统中的工作方式。读完后,你将知道何时在LB上启用哪些功能,以及避免一些常见的配置错误。

2. 流量分配 (Traffic Distribution)

这是最基本的功能:LB从客户端接收请求,并根据某种算法将其转发到众多后端服务器之一——轮询、最少连接、加权轮询等。

upstream order_service {
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
    server 10.0.1.13:8080;
}

server {
    listen 80;
    location /orders/ {
        proxy_pass http://order_service;
    }
}

upstream声明了3个OrderService实例。默认情况下nginx使用轮询——请求1转到.11,请求2转到.12,请求3转到.13,然后回到.11。如果某个实例处理较重的请求,你可以改用least_conn;让nginx优先选择打开连接数最少的服务器。

注意: 轮询不关心服务器是否健康。如果各实例的能力不同(例如一台机器配置更强),请使用weight:

server 10.0.1.11:8080 weight=3;
server 10.0.1.12:8080 weight=1;

3. SSL终止

与其让每个后端自己解密TLS(消耗CPU),不如把这项工作交给LB。LB持有证书,将HTTPS解密为纯HTTP,然后转发到内部网络。

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

    ssl_certificate     /etc/nginx/certs/nonladev.crt;
    ssl_certificate_key /etc/nginx/certs/nonladev.key;

    location / {
        proxy_pass http://order_service;   # 纯HTTP,无需TLS
        proxy_set_header X-Forwarded-Proto https;
    }
}

这样,OrderService(用Spring Boot编写)根本不需要配置任何server.ssl.*——它只需在8080端口以普通HTTP运行。X-Forwarded-Proto头帮助后端了解原始请求是HTTPS,以便生成正确的重定向URL。

注意: 如果LB与后端之间的内部网络不可信(例如跨多个子网、不同的云),不要只做termination——应该做“SSL bridging”:LB先解密,再用内部证书重新加密,然后转发。

4. 会话持久化 (Session Persistence)

当后端在内存中存储会话(本地会话)时,来自同一用户的请求必须始终转到同一个实例,否则用户会被随机’登出’。

upstream order_service {
    ip_hash;                       # 同一客户端IP -> 同一服务器
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

ip_hash对客户端IP进行哈希处理,以固定选择upstream中的一个服务器。另一种方式是基于cookie的粘性会话——LB自动添加cookie来标记实例,即使客户端共享同一个NAT IP也能使用。

更可靠的解决方案是放弃粘性会话,使用Spring Session + Redis将会话外置:

@Configuration
@EnableRedisHttpSession
public class SessionConfig {
}
spring.data.redis.host=redis.internal
spring.data.redis.port=6379

有了这个配置,会话存储在Redis而不是各实例的内存中。无论请求转到哪个服务器都能读取会话——LB可以使用普通的轮询,不再需要ip_hash。

注意: 如果多个用户共享同一NAT(例如公司办公室),ip_hash容易导致负载不均——他们的所有流量都会被转到一个服务器。当系统规模扩大时,优先考虑外置会话。

5. 高可用性 (High Availability)

LB持续检查后端;如果一个实例宕机,LB会自动将其从服务列表中删除,并将流量转向其他活跃实例——整个过程对客户端完全透明。

upstream order_service {
    server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
    server 10.0.1.12:8080 max_fails=3 fail_timeout=30s;
}

max_fails=3表示在连续3次请求失败后,nginx将该服务器标记为“down”,持续30秒(fail_timeout),不再向其发送新请求。30秒后,nginx会重试——如果服务器已恢复,它将被重新纳入轮询。

[14:02:01] 10.0.1.12 timeout -> fail count 1
[14:02:05] 10.0.1.12 timeout -> fail count 2
[14:02:09] 10.0.1.12 timeout -> fail count 3 -> marked DOWN for 30s
[14:02:09] traffic rerouted to 10.0.1.11
[14:02:39] 10.0.1.12 retried -> healthy -> back in pool

由于这种机制,一个实例的崩溃不会导致整个系统宕机——这正是网络层高可用性的基础。

6. 水平扩展能力 (Scalability)

LB是水平扩展的先决条件:你添加新实例,LB会自动识别并分配负载给它,无需修改后端代码。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 2
  template:
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.4.0
          ports:
            - containerPort: 8080
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

此处,Kubernetes Service(充当内部LB的角色)在order-service pod之间自动平衡流量。HorizontalPodAutoscaler监控CPU——当超过70%时,Kubernetes会创建更多pod(最多10个),LB立即将新pod纳入服务列表,无需重新部署或重启任何内容。

注意: 扩展只有在后端是无状态的(或会话已外置如第4项所述)时才有效。否则,新添加的pod将无法获得用户的旧会话。

7. DDoS缓解 (DDoS Mitigation)

LB位于边界层,是在异常流量到达后端之前拦截它的理想位置——通过速率限制或将流量分散到更广的范围。

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s;

server {
    location /orders/ {
        limit_req zone=api_limit burst=10 nodelay;
        proxy_pass http://order_service;
    }
}

limit_req_zone限制每个IP最多20个请求/秒。burst=10允许短期内最多10个请求排队,nodelay立即处理突发而不是延迟处理。超过此限制时,nginx返回503 Service Temporarily Unavailable而不是让请求堆积到后端。

如果要在应用层面进行速率限制(补充而非替代LB),在Spring Boot中使用Bucket4j:

<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>8.10.1</version>
</dependency>
Bucket bucket = Bucket.builder()
    .addLimit(Bandwidth.simple(20, Duration.ofSeconds(1)))
    .build();

@GetMapping("/orders")
public ResponseEntity<?> getOrders() {
    if (!bucket.tryConsume(1)) {
        return ResponseEntity.status(429).body("Too many requests");
    }
    return ResponseEntity.ok(orderService.findAll());
}

注意: 基于IP的速率限制容易被伪造IP或分布式僵尸网络绕过。对于大规模DDoS攻击,需要在LB前层结合CDN/WAF,LB只是第二道防线。

8. 后端健康监控 (Health Monitoring)

与第5项(在请求失败时反应)不同,健康监控是LB主动定期调用一个端点来了解服务器是否还活着——这称为主动健康检查。

@RestController
public class HealthController {

    @GetMapping("/health")
    public ResponseEntity<String> health() {
        boolean dbUp = checkDatabaseConnection();
        if (!dbUp) {
            return ResponseEntity.status(503).body("DB_DOWN");
        }
        return ResponseEntity.ok("OK");
    }

    private boolean checkDatabaseConnection() {
        // 检查连接池,返回true/false
        return true;
    }
}

使用Spring Boot Actuator,你可以获得类似的端点,而无需手动编写:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoints.web.exposure.include=health
management.endpoint.health.show-details=always

LB(或Kubernetes)每隔几秒调用/actuator/health:

livenessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  periodSeconds: 5
  failureThreshold: 3

编写测试以确保当DB出错时端点响应正确:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class HealthControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void shouldReturn503WhenDatabaseDown() {
        // 通过测试配置文件或mock bean模拟DB宕机
        ResponseEntity<String> response =
            restTemplate.getForEntity("/health", String.class);

        assertThat(response.getStatusCode().value()).isIn(200, 503);
    }
}

此测试确保/health端点始终返回有效的状态(200或503),而不会抛出异常——因为如果健康检查本身失败,LB会误认为服务器宕机,错误地删除健康实例。

注意: 健康检查端点必须轻量且快速(低于几百毫秒)。如果它查询多个表或调用其他服务,健康检查本身反而会成为超时的原因,被LB错误地剔除。

9. 总结

Load Balancer不仅仅是’分担负载的东西’——它还在减轻SSL负担、保持会话、故障检测、支持水平扩展和防御DDoS方面发挥作用。在设计系统时,你应该明确需要上述7个功能中的哪些,以便在配置LB时选择正确的功能——nginx、HAProxy、AWS ALB或Kubernetes Service都支持大多数这些用例,只是声明方式不同。

最后的建议:不要把所有责任都压在一个LB上。结合主动健康检查、速率限制和会话外置,将帮助你的系统不仅扩展性强,容错能力也会远优于只启用轮询就置之不理。

system-designload-balancerscalabilityhigh-availabilitynginx