健康检查机制

健康检查是负载均衡器的「眼睛」,负责判断后端节点是否存活、是否能够处理请求。没有健康检查,负载均衡器会把请求发到故障节点,导致服务不可用。本节深入讲解健康检查的原理和配置。

为什么需要健康检查

flowchart TB
    subgraph Problem["无健康检查的问题"]
        R["请求"] --> F["故障节点"]
        F -.x|"无响应"| R
    end

    subgraph Solution["有健康检查"]
        LB["负载均衡器"]
        HC["健康检查器"]
        S1["节点 1\n正常"]
        S2["节点 2\n故障"]

        LB --> HC
        HC -.->|"检测"| S1
        HC -.->|"检测"| S2
        S2 -.x|"故障"| HC

        R2["请求"] --> LB
        LB --> S1
        LB -.->|"摘除"| S2
    end

    style Problem fill:#ffcdd2
    style Solution fill:#c8e6c9

问题场景

  • 后端服务进程崩溃
  • JVM 发生 OOM
  • 数据库连接耗尽
  • 应用hang住无法响应

如果没有健康检查,这些故障节点会持续收到请求,导致大量超时和错误。

健康检查类型

主动检查 vs 被动检查

类型原理优点缺点
主动检查负载均衡器主动探测提前发现问题增加网络流量
被动检查根据实际请求结果判断无额外开销故障已发生

主动检查详解

sequenceDiagram
    participant LB as 负载均衡器
    participant RS as 后端节点

    loop 定时检查
        LB->>RS: 发送探测请求
        RS-->>LB: 响应成功
        Note over LB: 节点健康,继续使用
    end

    loop 故障检测
        LB->>RS: 发送探测请求
        RS--x LB: 无响应/超时
        Note over LB: 节点故障,摘除
    end

协议类型

协议实现方式适用场景
TCP 检测尝试建立 TCP 连接通用场景
HTTP/HTTPS 检测发送 GET 请求,检查响应码Web 服务
UDP 检测发送 UDP 包,检查响应DNS、游戏服务器
自定义协议发送特定报文,验证响应特殊业务服务

TCP 端口检查

最基础的健康检查方式——检查端口是否可连接:

flowchart LR
    subgraph TCP["TCP 健康检查"]
        LB["负载均衡器"] -->|"连接 10.0.1.1:8080"| S1["节点 1"]
        LB -->|"连接 10.0.1.2:8080"| S2["节点 2"]

        S1 -->>|"连接成功"| LB
        S2 --x|"连接失败"| LB
    end

配置示例

# Nginx(TCP 端口检查需要 nginx_upstream_check_module 模块)
upstream backend {
    server 10.0.1.1:8080;
    server 10.0.1.2:8080;

    # 主动健康检查
    check interval=3000 rise=2 fall=3 timeout=1000 type=tcp;
}
# LVS IPVSadm TCP 检查
ipvsadm -A -t 192.168.1.100:80 -s rr
ipvsadm -a -t 192.168.1.100:80 -r 10.0.1.1:80 -w 1
ipvsadm -a -t 192.168.1.100:80 -r 10.0.1.2:80 -w 1

# 设置健康检查
# 使用 piranha 或 keepalived 配置

HTTP 健康检查

HTTP 检查比 TCP 更智能,可以检查应用层的健康状态:

sequenceDiagram
    participant LB as 负载均衡器
    participant RS as 后端节点

    LB->>RS: GET /health HTTP/1.1
    RS-->>LB: HTTP/1.1 200 OK

    Note over RS: 健康检查端点返回详细信息
    Note over RS: {
    Note over RS:   "status": "UP",
    Note over RS:   "components": {
    Note over RS:     "db": {"status": "UP"},
    Note over RS:     "redis": {"status": "UP"}
    Note over RS:   }
    Note over RS: }

健康检查端点实现

// Spring Boot Actuator 健康检查
@RestController
@RequestMapping("/health")
public class HealthController {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping
    public Map<String, Object> health() {
        Map<String, Object> result = new HashMap<>();
        result.put("status", "UP");

        // 检查数据库
        Map<String, String> dbHealth = new HashMap<>();
        try (Connection conn = dataSource.getConnection()) {
            dbHealth.put("status", "UP");
        } catch (Exception e) {
            dbHealth.put("status", "DOWN");
            result.put("status", "DOWN");
        }
        result.put("database", dbHealth);

        // 检查 Redis
        Map<String, String> redisHealth = new HashMap<>();
        try {
            redisTemplate.getConnectionFactory().getConnection().ping();
            redisHealth.put("status", "UP");
        } catch (Exception e) {
            redisHealth.put("status", "DOWN");
            result.put("status", "DOWN");
        }
        result.put("redis", redisHealth);

        return result;
    }
}

HTTP 健康检查配置

# Nginx Plus HTTP 健康检查
upstream backend {
    zone backend 64k;

    server 10.0.1.1:8080;
    server 10.0.1.2:8080;

    # HTTP 健康检查
    check interval=5000 rise=2 fall=3 timeout=3000 type=http;
    check_http_send "GET /health HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}
# HAProxy HTTP 健康检查
backend api_backend
    mode http
    option httpchk GET /health

    # 健康检查配置
    http-check expect status 200

    server api1 10.0.1.1:8080 check inter 2000 fall 2 rise 1
    server api2 10.0.1.2:8080 check inter 2000 fall 2 rise 1

自定义健康检查

有些场景需要更复杂的健康检查逻辑:

场景一:检查依赖服务

// 带依赖检查的健康检查
@GetMapping("/health/detailed")
public ResponseEntity<Map<String, Object>> detailedHealth() {
    Map<String, Object> health = new LinkedHashMap<>();

    boolean allHealthy = true;

    // 数据库健康
    try {
        jdbcTemplate.execute("SELECT 1");
        health.put("database", Map.of("status", "UP"));
    } catch (Exception e) {
        health.put("database", Map.of("status", "DOWN", "error", e.getMessage()));
        allHealthy = false;
    }

    // Redis 健康
    try {
        redisTemplate.opsForValue().get("health_check");
        health.put("redis", Map.of("status", "UP"));
    } catch (Exception e) {
        health.put("redis", Map.of("status", "DOWN", "error", e.getMessage()));
        allHealthy = false;
    }

    // 外部 API 健康
    try {
        ResponseEntity<String> response = restTemplate.getForEntity(
            "https://external-api.com/health",
            String.class
        );
        health.put("externalApi", Map.of("status", "UP"));
    } catch (Exception e) {
        health.put("externalApi", Map.of("status", "DOWN", "error", e.getMessage()));
        // 外部 API 故障不影响主服务
    }

    health.put("status", allHealthy ? "UP" : "DOWN");

    return allHealthy
        ? ResponseEntity.ok(health)
        : ResponseEntity.status(503).body(health);
}

场景二:渐进式健康检查

@GetMapping("/health")
public ResponseEntity<Health> health() {
    Health.Builder builder = new Health();

    // 基础检查(必须通过)
    builder.up()
        .withDetail("timestamp", System.currentTimeMillis());

    // 详细检查(异步)
    CompletableFuture.runAsync(() -> {
        checkDatabase();
        checkRedis();
        checkExternalServices();
    });

    return ResponseEntity.ok(builder.build());
}

检查参数设计

核心参数

参数说明推荐值
检查间隔两次检查之间的时间3~10s
超时时间等待响应的最大时间1~3s
成功阈值连续成功次数后才恢复1~2次
失败阈值连续失败次数后才摘除2~5次

参数计算公式

故障发现时间 = 检查间隔 × 失败阈值 + 超时时间

恢复发现时间 = 检查间隔 × 成功阈值

不同场景的配置

场景检查间隔超时失败阈值说明
金融交易2~3s1s2快速发现,快速切换
Web 服务5~10s2s3避免网络抖动误判
长连接30s+5s3基于应用层心跳

健康检查的 trade-off

检查太频繁

问题影响
增加负载均衡器压力资源消耗
增加网络流量带宽浪费
可能误判网络抖动导致误摘

检查间隔太长

问题影响
故障发现慢用户体验差
故障时间长系统不稳定

建议:根据业务对故障的容忍度调整参数。

常见问题

问题一:健康检查占用资源

问题:大量节点时,健康检查流量大

场景:
- 1000 个节点
- 每 5 秒检查一次
- 每次检查 100 字节
- 流量:1000 × 60/5 × 100 = 12MB/min

解决:
1. 批量健康检查
2. 抽样检查
3. 降低检查频率

问题二:误判

问题:网络抖动导致健康检查失败

解决:
1. 增加失败阈值
2. 增加超时时间
3. 使用更智能的检查(连续失败才摘除)

问题三:健康检查端点被攻击

问题:健康检查端点暴露,可能被恶意扫描

解决:
1. 只允许负载均衡器 IP 访问
2. 使用 HTTPS
3. 简单响应(不暴露内部信息)

总结

健康检查是负载均衡器的核心能力:

检查类型

  • TCP 端口检查:基础,简单
  • HTTP 健康检查:智能,可检查应用状态
  • 自定义检查:灵活,可检查依赖

参数设计

  • 检查间隔:3~10s
  • 超时时间:1~3s
  • 失败阈值:2~5 次
  • 成功阈值:1~2 次

常见问题

  • 检查太频繁:增加资源消耗
  • 检查间隔太长:故障发现慢
  • 误判:合理设置阈值

下一节我们将讲解会话保持——如何保证同一用户的请求路由到同一节点。