轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 架构设计与模式

    • 高可用-分布式基础之CAP理论
    • 高可用-服务容错与降级策略
    • 高可用-故障检测与自动恢复
      • 一、为什么需要故障检测与自动恢复
        • 1、故障无处不在
        • 2、MTTF、MTTR与可用性
        • 3、故障检测与恢复的价值
      • 二、故障检测的核心原理
        • 1、故障检测的挑战
        • 2、故障检测的基本模型
        • 2.1 Ping-Pong 模型
        • 2.2 Gossip 协议
        • 2.3 Phi Accrual 检测器
        • 3、健康检查的层次
        • 3.1 基础存活检查(Liveness Check)
        • 3.2 就绪检查(Readiness Check)
        • 3.3 功能健康检查(Health Check)
        • 3.4 深度健康检查(Deep Health Check)
        • 4、故障检测的关键参数
        • 4.1 心跳间隔(Heartbeat Interval)
        • 4.2 超时时间(Timeout)
        • 4.3 失败阈值(Failure Threshold)
        • 4.4 恢复阈值(Success Threshold)
      • 三、自动恢复机制
        • 1、自动恢复的层次
        • 1.1 进程级恢复
        • 1.2 容器级恢复
        • 1.3 节点级恢复
        • 1.4 服务级恢复
        • 2、常见的自动恢复策略
        • 2.1 重启恢复(Restart)
        • 2.2 故障转移(Failover)
        • 2.3 服务降级(Degradation)
        • 2.4 自动扩容(Auto Scaling)
        • 2.5 自愈(Self-Healing)
        • 3、恢复时间优化
        • 3.1 故障检测时间优化
        • 3.2 故障恢复时间优化
      • 四、实战案例与最佳实践
        • 1、Spring Boot Actuator 健康检查
        • 1.1 基础配置
        • 1.2 自定义健康检查
        • 1.3 健康检查分组
        • 2、Kubernetes 探针配置
        • 2.1 存活探针(Liveness Probe)
        • 2.2 就绪探针(Readiness Probe)
        • 2.3 启动探针(Startup Probe)
        • 3、数据库高可用方案
        • 3.1 MySQL MHA (Master High Availability)
        • 3.2 PostgreSQL Patroni + etcd
        • 4、微服务健康检查
        • 4.1 Spring Cloud LoadBalancer + Health Check
        • 4.2 Sentinel 熔断降级
        • 5、监控告警集成
        • 5.1 Prometheus + Alertmanager
        • 5.2 健康检查指标
      • 五、故障演练与混沌工程
        • 1、为什么需要故障演练
        • 2、常见的故障注入方式
        • 2.1 网络故障
        • 2.2 进程故障
        • 2.3 应用层故障
        • 3、混沌工程平台
        • 3.1 Chaos Mesh (Kubernetes)
        • 3.2 Litmus Chaos
        • 4、故障演练流程
        • 4.1 演练计划
        • 4.2 演练执行
        • 4.3 演练总结
      • 六、最佳实践总结
        • 1、故障检测最佳实践
        • 2、自动恢复最佳实践
        • 3、高可用架构设计
        • 4、运维管理最佳实践
      • 七、未来发展趋势
        • 1、AI 驱动的故障检测
        • 2、预测性维护
        • 3、自适应恢复策略
        • 4、边缘计算的挑战
      • 八、总结
    • 高可用-混沌工程实践
    • 高可用-分布式事务实战
    • 高可用-多活与容灾架构设计
    • 高性能-缓存架构设计
    • 高性能-性能优化方法论
    • 高性能-异步处理与消息队列
    • 高性能-数据库性能优化
  • 代码质量管理

  • 基础

  • 操作系统

  • 计算机网络

  • AI

  • 编程范式

  • 安全

  • 中间件

  • 心得

  • 架构
  • 架构设计与模式
轩辕李
2024-11-24
目录

高可用-故障检测与自动恢复

在分布式系统中,故障是常态而非异常。无论系统设计得多么精良,硬件故障、网络问题、软件缺陷都不可避免。

一个高可用的系统必须具备快速发现故障和自动恢复的能力。

本文将深入探讨故障检测与自动恢复的核心原理、实现方式和最佳实践。

# 一、为什么需要故障检测与自动恢复

# 1、故障无处不在

在分布式环境中,故障的来源多种多样:

硬件层面:

  • 服务器宕机:硬盘损坏、内存故障、电源问题
  • 网络故障:交换机故障、光纤断裂、路由器异常
  • 机房问题:断电、火灾、自然灾害

软件层面:

  • 应用崩溃:OOM、空指针异常、死锁
  • 资源耗尽:线程池满、连接池耗尽、磁盘空间不足
  • 性能退化:GC停顿、慢查询、内存泄漏

运维层面:

  • 人为误操作:错误的配置变更、误删数据
  • 部署问题:版本不兼容、配置错误
  • 依赖服务故障:第三方API不可用、数据库主从切换

# 2、MTTF、MTTR与可用性

理解几个关键指标对设计故障检测与恢复系统至关重要:

MTTF (Mean Time To Failure):平均故障时间

  • 系统从正常运行到发生故障的平均时间
  • 衡量系统的可靠性

MTTR (Mean Time To Recovery):平均恢复时间

  • 从故障发生到系统恢复正常的平均时间
  • 包括:故障检测时间 + 故障定位时间 + 故障修复时间

可用性计算公式:

可用性 = MTTF / (MTTF + MTTR)

示例:

  • 如果系统平均 100 天故障一次(MTTF = 100天)
  • 平均 1 小时恢复(MTTR = 1小时 ≈ 0.042天)
  • 可用性 = 100 / (100 + 0.042) ≈ 99.958%

关键洞察:

  • 提升可用性有两个方向:增加 MTTF(提高可靠性)、降低 MTTR(快速恢复)
  • 在分布式系统中,降低 MTTR 往往比提高 MTTF 更实际、更经济
  • 快速的故障检测和自动恢复是降低 MTTR 的关键

# 3、故障检测与恢复的价值

业务价值:

  • 减少收入损失:每分钟的宕机都可能造成巨大的经济损失
  • 提升用户体验:用户感知不到故障或影响时间极短
  • 保护品牌声誉:避免因长时间故障导致的信任危机

技术价值:

  • 降低运维成本:减少人工介入,7×24 小时自动化运维
  • 提高系统可用性:从 99.9%(年停机 8.76 小时)到 99.99%(年停机 52.6 分钟)
  • 快速止损:故障影响范围最小化,防止雪崩效应

# 二、故障检测的核心原理

# 1、故障检测的挑战

在分布式环境中,故障检测面临诸多挑战:

网络不可靠:

  • 丢包:健康检查的请求或响应丢失
  • 延迟:网络拥塞导致响应超时
  • 分区:网络分区导致节点间无法通信

误判问题:

  • 假阳性(False Positive):将正常节点判定为故障
    • 危害:触发不必要的故障转移,增加系统不稳定性
  • 假阴性(False Negative):将故障节点判定为正常
    • 危害:故障节点继续接收请求,影响用户体验

时钟不同步:

  • 分布式系统中各节点时钟可能存在偏差
  • 基于时间戳的检测可能出现误判

部分失效:

  • 节点可能处于"半死不活"状态:进程存活但无法正常处理请求
  • 简单的进程存活检查无法发现这类问题

# 2、故障检测的基本模型

# 2.1 Ping-Pong 模型

最简单的故障检测模型,监控者定期向被监控节点发送心跳:

监控者                被监控节点
  |                      |
  |-------- Ping ------->|
  |<------- Pong --------|
  |                      |
  |-------- Ping ------->|
  |        (超时)        |  
  |                      X (判定故障)

优点:

  • 实现简单
  • 资源消耗低

缺点:

  • 中心化单点问题
  • 监控者故障会影响整个系统
  • 难以区分网络故障和节点故障

# 2.2 Gossip 协议

节点间通过八卦协议相互传播状态信息:

核心思想:

  1. 每个节点维护一个成员列表及其状态
  2. 定期随机选择若干节点交换状态信息
  3. 基于收到的信息更新本地成员列表

状态传播过程:

轮次1: A告诉B、C
轮次2: B告诉D、E,C告诉F、G
轮次3: 信息以指数级速度扩散

优点:

  • 去中心化,无单点故障
  • 可扩展性好,适合大规模集群
  • 容忍网络分区

缺点:

  • 状态收敛需要时间
  • 可能产生短暂的不一致

经典实现:

  • SWIM 协议:Cassandra、Consul 使用
  • Phi Accrual Failure Detector:Akka、Cassandra 使用

# 2.3 Phi Accrual 检测器

传统的故障检测器给出二元判断(正常/故障),Phi Accrual 检测器提供连续的怀疑度:

核心思想:

  1. 记录历史心跳到达时间
  2. 计算心跳间隔的统计分布
  3. 根据当前心跳延迟计算怀疑度 φ(t)
  4. φ(t) 越大,节点故障的可能性越大

优势:

  • 可以根据网络状况动态调整阈值
  • 减少网络抖动导致的误判
  • 不同场景可以设置不同的怀疑度阈值

# 3、健康检查的层次

故障检测不应只是简单的进程存活检查,应该分层次进行:

# 3.1 基础存活检查(Liveness Check)

检查进程是否存活:

# HTTP 端点检查
curl http://service:8080/health

# TCP 端口检查
nc -zv service 8080

# 进程检查
ps aux | grep java

局限性:

  • 进程存活不代表服务正常
  • 无法检测性能退化、资源耗尽

# 3.2 就绪检查(Readiness Check)

检查服务是否准备好接收流量:

# Kubernetes Readiness Probe 示例
readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  successThreshold: 1
  failureThreshold: 3

检查内容:

  • 依赖服务是否可用(数据库、缓存、消息队列)
  • 必要的配置是否加载完成
  • 必要的数据是否预热完成

# 3.3 功能健康检查(Health Check)

检查服务的核心功能是否正常:

@Component
public class DatabaseHealthIndicator implements HealthIndicator {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    public Health health() {
        try {
            jdbcTemplate.queryForObject("SELECT 1", Integer.class);
            return Health.up()
                .withDetail("database", "MySQL")
                .withDetail("validation", "SELECT 1")
                .build();
        } catch (Exception e) {
            return Health.down()
                .withDetail("error", e.getMessage())
                .build();
        }
    }
}

检查内容:

  • 数据库连接池状态
  • 缓存读写功能
  • 关键业务接口的响应时间

# 3.4 深度健康检查(Deep Health Check)

检查服务的性能和资源使用情况:

@Component
public class PerformanceHealthIndicator implements HealthIndicator {
    
    @Override
    public Health health() {
        Runtime runtime = Runtime.getRuntime();
        long maxMemory = runtime.maxMemory();
        long usedMemory = runtime.totalMemory() - runtime.freeMemory();
        double memoryUsage = (double) usedMemory / maxMemory;
        
        if (memoryUsage > 0.9) {
            return Health.down()
                .withDetail("memoryUsage", String.format("%.2f%%", memoryUsage * 100))
                .withDetail("reason", "Memory usage too high")
                .build();
        }
        
        return Health.up()
            .withDetail("memoryUsage", String.format("%.2f%%", memoryUsage * 100))
            .build();
    }
}

检查内容:

  • JVM 堆内存使用率
  • GC 频率和停顿时间
  • 线程池队列长度
  • 数据库慢查询数量
  • 接口响应时间 P99、P95

# 4、故障检测的关键参数

# 4.1 心跳间隔(Heartbeat Interval)

权衡因素:

  • 间隔太短:增加网络开销和 CPU 消耗
  • 间隔太长:故障检测延迟增加

建议值:

  • 局域网环境:1-5 秒
  • 跨地域环境:5-30 秒
  • 根据业务 SLA 要求调整

# 4.2 超时时间(Timeout)

设置原则:

超时时间 = 正常响应时间的 P99 + 网络延迟的 P99 + 安全余量

示例:

  • 如果正常响应时间 P99 = 100ms
  • 网络延迟 P99 = 50ms
  • 超时时间可设置为:200ms(100 + 50 + 50)

# 4.3 失败阈值(Failure Threshold)

连续失败判定:

连续失败次数 >= 阈值 → 判定为故障

好处:

  • 避免网络抖动导致的误判
  • 提高检测的稳定性

典型值:

  • 3-5 次连续失败

# 4.4 恢复阈值(Success Threshold)

连续成功判定:

连续成功次数 >= 阈值 → 判定为恢复

重要性:

  • 防止节点频繁在正常/故障状态间切换
  • 避免"抖动"(Flapping)

典型值:

  • 2-3 次连续成功

# 三、自动恢复机制

# 1、自动恢复的层次

# 1.1 进程级恢复

Supervisor 模式:

# systemd 配置示例
[Service]
Type=simple
ExecStart=/usr/bin/myapp
Restart=always
RestartSec=10
StartLimitInterval=200
StartLimitBurst=5

重启策略:

  • always:总是重启
  • on-failure:只在异常退出时重启
  • on-abort:只在收到未捕获信号时重启

注意事项:

  • 防止无限重启循环
  • 记录重启次数和原因
  • 配置合理的重启间隔

# 1.2 容器级恢复

Kubernetes Pod 重启策略:

apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  restartPolicy: Always  # Always, OnFailure, Never
  containers:
  - name: app
    image: myapp:v1
    livenessProbe:
      httpGet:
        path: /health
        port: 8080
      initialDelaySeconds: 30
      periodSeconds: 10
      failureThreshold: 3

重启流程:

  1. livenessProbe 检测到容器不健康
  2. kubelet 杀死容器
  3. 根据 restartPolicy 决定是否重启
  4. 拉取镜像并创建新容器

# 1.3 节点级恢复

虚拟机自动重启:

# AWS EC2 Auto Recovery
aws ec2 create-instance-status-alarm \
  --instance-id i-1234567890abcdef0 \
  --action-type recover

物理机自动重启:

  • IPMI/BMC:远程管理接口,可远程重启服务器
  • Watchdog Timer:硬件看门狗,系统无响应时自动重启

# 1.4 服务级恢复

流量切换:

# Kubernetes Service 自动剔除不健康 Pod
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  # 只有通过 readinessProbe 的 Pod 才会接收流量

主备切换:

# 数据库主从自动切换(MHA for MySQL 示例)
[server1]
hostname=db1
port=3306
candidate_master=1

[server2]
hostname=db2
port=3306
candidate_master=1

# 2、常见的自动恢复策略

# 2.1 重启恢复(Restart)

适用场景:

  • 临时性故障:内存泄漏、资源耗尽
  • 软件缺陷:死锁、状态错误

实现方式:

public class RestartStrategy {
    private static final int MAX_RESTART_COUNT = 5;
    private static final long RESTART_WINDOW_MS = 60_000;
    
    private final List<Long> restartTimestamps = new ArrayList<>();
    
    public boolean shouldRestart() {
        long now = System.currentTimeMillis();
        
        restartTimestamps.removeIf(ts -> now - ts > RESTART_WINDOW_MS);
        
        if (restartTimestamps.size() < MAX_RESTART_COUNT) {
            restartTimestamps.add(now);
            return true;
        }
        
        return false;
    }
}

注意事项:

  • 避免重启循环
  • 保留现场信息(堆转储、线程栈)
  • 监控重启频率,触发告警

# 2.2 故障转移(Failover)

主从切换:

主库故障 → 从库提升为主库 → 更新应用配置 → 流量切换到新主库

实现案例 - Redis Sentinel:

# Sentinel 配置
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 10000

切换流程:

  1. Sentinel 检测到主库故障
  2. 多数 Sentinel 同意后发起选举
  3. 选择一个从库提升为主库
  4. 其他从库重新配置复制新主库
  5. 通知客户端新主库地址

注意事项:

  • 确保数据一致性(避免脑裂)
  • 自动切换 vs 人工确认
  • 切换时间与数据丢失的权衡

# 2.3 服务降级(Degradation)

自动降级触发条件:

  • 错误率超过阈值(如 50%)
  • 响应时间超过阈值(如 P99 > 1000ms)
  • 资源使用超过阈值(如 CPU > 80%)

降级策略:

@RestController
public class OrderController {
    
    @Autowired
    private RecommendationService recommendationService;
    
    @GetMapping("/order/{id}")
    public Order getOrder(@PathVariable Long id) {
        Order order = orderService.getOrder(id);
        
        if (CircuitBreaker.isOpen("recommendation")) {
            order.setRecommendations(Collections.emptyList());
        } else {
            try {
                order.setRecommendations(recommendationService.getRecommendations(id));
            } catch (Exception e) {
                order.setRecommendations(Collections.emptyList());
            }
        }
        
        return order;
    }
}

降级等级:

  1. 静态降级:返回缓存数据或默认值
  2. 功能降级:关闭非核心功能
  3. 读降级:只提供读服务,拒绝写入
  4. 熔断降级:完全关闭服务

# 2.4 自动扩容(Auto Scaling)

基于指标的扩容:

# Kubernetes HPA 示例
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "1000"

扩容策略:

  • Scale Up:CPU > 70% 持续 5 分钟
  • Scale Down:CPU < 30% 持续 15 分钟
  • Cooldown Period:扩容后 5 分钟内不再扩容

# 2.5 自愈(Self-Healing)

Kubernetes 的自愈机制:

  1. Pod 重启:容器崩溃自动重启
  2. Pod 重新调度:节点故障时自动调度到其他节点
  3. 副本保持:ReplicaSet 确保副本数量
  4. 自动剔除:不健康的 Pod 自动从 Service 中移除

示例配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    spec:
      containers:
      - name: app
        image: myapp:v1
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5

# 3、恢复时间优化

# 3.1 故障检测时间优化

减少检测延迟:

故障检测时间 = 心跳间隔 × 失败阈值

优化策略:

  • 降低心跳间隔(增加网络开销)
  • 使用主动推送而非轮询
  • 采用 Phi Accrual 等自适应算法

# 3.2 故障恢复时间优化

快速重启:

public class FastRestartOptimizer {
    
    @PreDestroy
    public void gracefulShutdown() {
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
        }
    }
    
    @PostConstruct
    public void warmup() {
        cacheService.preload();
        connectionPool.initialize();
        
        for (int i = 0; i < 100; i++) {
            businessService.warmupCall();
        }
    }
}

预热策略:

  1. 缓存预热:启动时加载热点数据
  2. 连接池预热:提前建立数据库连接
  3. 代码预热:JIT 编译热点代码
  4. 依赖检查:启动时验证依赖服务可用

流量预热:

启动后先接收少量流量 → 逐步增加流量 → 完全恢复

# 四、实战案例与最佳实践

# 1、Spring Boot Actuator 健康检查

# 1.1 基础配置

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      show-details: always
      probes:
        enabled: true
  health:
    livenessState:
      enabled: true
    readinessState:
      enabled: true

# 1.2 自定义健康检查

@Component
public class CustomHealthIndicator implements HealthIndicator {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Override
    public Health health() {
        try {
            String pong = redisTemplate.getConnectionFactory()
                .getConnection()
                .ping();
            
            if ("PONG".equals(pong)) {
                return Health.up()
                    .withDetail("redis", "available")
                    .build();
            } else {
                return Health.down()
                    .withDetail("redis", "unexpected response")
                    .build();
            }
        } catch (Exception e) {
            return Health.down()
                .withDetail("redis", "unavailable")
                .withDetail("error", e.getMessage())
                .build();
        }
    }
}

# 1.3 健康检查分组

management:
  endpoint:
    health:
      group:
        liveness:
          include: livenessState,diskSpace
        readiness:
          include: readinessState,db,redis,kafka

访问端点:

  • GET /actuator/health/liveness - 存活检查
  • GET /actuator/health/readiness - 就绪检查

# 2、Kubernetes 探针配置

# 2.1 存活探针(Liveness Probe)

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
    scheme: HTTP
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  successThreshold: 1
  failureThreshold: 3

探测失败处理:

  • 杀死容器并根据重启策略重启

# 2.2 就绪探针(Readiness Probe)

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
    scheme: HTTP
  initialDelaySeconds: 10
  periodSeconds: 5
  timeoutSeconds: 3
  successThreshold: 1
  failureThreshold: 3

探测失败处理:

  • 从 Service 的 Endpoints 中移除,不再接收流量
  • 容器继续运行,等待恢复

# 2.3 启动探针(Startup Probe)

startupProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 0
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 30

适用场景:

  • 启动时间较长的应用(如需要加载大量数据)
  • 避免 livenessProbe 在启动期间误杀容器

# 3、数据库高可用方案

# 3.1 MySQL MHA (Master High Availability)

架构:

MHA Manager (监控节点)
    |
    ├── MySQL Master (写)
    ├── MySQL Slave1 (读 + 候选主)
    └── MySQL Slave2 (读 + 候选主)

故障切换流程:

  1. MHA Manager 检测到 Master 故障
  2. 选择同步延迟最小的 Slave 作为新 Master
  3. 应用差异日志到新 Master
  4. 将其他 Slave 指向新 Master
  5. 通过 VIP 漂移切换流量

配置示例:

[server default]
user=mha
password=mha_password
ssh_user=root
repl_user=repl
repl_password=repl_password
ping_interval=3
master_ip_failover_script=/usr/local/bin/master_ip_failover

[server1]
hostname=mysql1
port=3306
candidate_master=1
check_repl_delay=0

[server2]
hostname=mysql2
port=3306
candidate_master=1

# 3.2 PostgreSQL Patroni + etcd

架构:

etcd 集群 (分布式配置存储)
    |
    ├── Patroni + PostgreSQL (Leader)
    ├── Patroni + PostgreSQL (Standby)
    └── Patroni + PostgreSQL (Standby)

自动切换:

patroni.yml:
scope: postgres-cluster
name: postgres1

etcd:
  hosts:
    - etcd1:2379
    - etcd2:2379
    - etcd3:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      
postgresql:
  listen: 0.0.0.0:5432
  connect_address: postgres1:5432
  data_dir: /var/lib/postgresql/data
  authentication:
    replication:
      username: replicator
      password: replicator_password
    superuser:
      username: postgres
      password: postgres_password

切换流程:

  1. Patroni 通过心跳向 etcd 报告健康状态
  2. Leader 故障时无法更新 etcd 中的 TTL
  3. TTL 过期后,Standby 节点发起选举
  4. 新 Leader 提升为主库,接管写入流量

# 4、微服务健康检查

# 4.1 Spring Cloud LoadBalancer + Health Check

@Configuration
public class LoadBalancerConfig {
    
    @Bean
    public ServiceInstanceListSupplier healthCheckServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                .withDiscoveryClient()
                .withHealthChecks()
                .build(context);
    }
}

健康检查逻辑:

@Component
public class CustomHealthCheckServiceInstanceListSupplier 
        extends DelegatingServiceInstanceListSupplier {
    
    private final WebClient.Builder webClientBuilder;
    
    public CustomHealthCheckServiceInstanceListSupplier(
            ServiceInstanceListSupplier delegate,
            WebClient.Builder webClientBuilder) {
        super(delegate);
        this.webClientBuilder = webClientBuilder;
    }
    
    @Override
    public Flux<List<ServiceInstance>> get() {
        return delegate.get().flatMap(this::healthCheck);
    }
    
    private Flux<List<ServiceInstance>> healthCheck(List<ServiceInstance> instances) {
        return Flux.fromIterable(instances)
                .filterWhen(this::isHealthy)
                .collectList();
    }
    
    private Mono<Boolean> isHealthy(ServiceInstance instance) {
        String healthUrl = instance.getUri() + "/actuator/health";
        
        return webClientBuilder.build()
                .get()
                .uri(healthUrl)
                .retrieve()
                .bodyToMono(String.class)
                .map(response -> response.contains("UP"))
                .timeout(Duration.ofSeconds(2))
                .onErrorReturn(false);
    }
}

# 4.2 Sentinel 熔断降级

@Service
public class OrderService {
    
    @SentinelResource(
        value = "getOrderById",
        fallback = "getOrderByIdFallback",
        blockHandler = "getOrderByIdBlockHandler"
    )
    public Order getOrderById(Long orderId) {
        return orderRepository.findById(orderId)
                .orElseThrow(() -> new OrderNotFoundException(orderId));
    }
    
    public Order getOrderByIdFallback(Long orderId, Throwable ex) {
        log.error("Fallback for orderId: {}, error: {}", orderId, ex.getMessage());
        
        Order order = new Order();
        order.setId(orderId);
        order.setStatus("UNKNOWN");
        return order;
    }
    
    public Order getOrderByIdBlockHandler(Long orderId, BlockException ex) {
        log.warn("Blocked for orderId: {}, rule: {}", orderId, ex.getRule());
        throw new ServiceUnavailableException("Order service is busy");
    }
}

降级规则配置:

@Configuration
public class SentinelConfig {
    
    @PostConstruct
    public void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();
        
        DegradeRule rule = new DegradeRule();
        rule.setResource("getOrderById");
        rule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());
        rule.setCount(0.5);
        rule.setTimeWindow(10);
        rule.setMinRequestAmount(5);
        rule.setStatIntervalMs(1000);
        
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }
}

# 5、监控告警集成

# 5.1 Prometheus + Alertmanager

指标采集:

scrape_configs:
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    scrape_interval: 15s
    static_configs:
      - targets: ['app1:8080', 'app2:8080', 'app3:8080']

告警规则:

groups:
  - name: application_alerts
    interval: 30s
    rules:
      - alert: HighErrorRate
        expr: rate(http_server_requests_seconds_count{status=~"5.."}[5m]) > 0.05
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "{{ $labels.instance }} has error rate {{ $value }}"
      
      - alert: HighResponseTime
        expr: histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m])) > 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High response time detected"
          description: "{{ $labels.instance }} P99 latency is {{ $value }}s"
      
      - alert: ServiceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Service is down"
          description: "{{ $labels.instance }} has been down for more than 1 minute"

告警通知:

route:
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 12h
  receiver: 'team-X-mails'
  routes:
    - match:
        severity: critical
      receiver: 'team-X-pager'
    - match:
        severity: warning
      receiver: 'team-X-mails'

receivers:
  - name: 'team-X-mails'
    email_configs:
      - to: 'team-X@example.com'
  
  - name: 'team-X-pager'
    pagerduty_configs:
      - service_key: <team-X-key>
    webhook_configs:
      - url: 'http://internal.myorg.net/hook'

# 5.2 健康检查指标

@Component
public class HealthMetricsCollector {
    
    private final MeterRegistry meterRegistry;
    private final HealthEndpoint healthEndpoint;
    
    @Scheduled(fixedRate = 10000)
    public void collectHealthMetrics() {
        Health health = healthEndpoint.health();
        Status status = health.getStatus();
        
        meterRegistry.gauge("application.health.status", 
            status == Status.UP ? 1 : 0);
        
        if (health.getDetails() != null) {
            health.getDetails().forEach((key, value) -> {
                if (value instanceof Health) {
                    Health componentHealth = (Health) value;
                    meterRegistry.gauge("application.health." + key, 
                        componentHealth.getStatus() == Status.UP ? 1 : 0);
                }
            });
        }
    }
}

# 五、故障演练与混沌工程

# 1、为什么需要故障演练

Netflix 的教训:

"最好的防御就是进攻。你不能等到生产环境出问题才测试你的故障恢复能力。"

故障演练的价值:

  • 验证自动恢复能力:确保故障检测和恢复机制真正有效
  • 发现潜在问题:在受控环境下发现系统弱点
  • 提升团队能力:训练团队的应急响应能力
  • 建立信心:对系统的可靠性有更高的信心

# 2、常见的故障注入方式

# 2.1 网络故障

延迟注入:

tc qdisc add dev eth0 root netem delay 100ms 20ms

丢包注入:

tc qdisc add dev eth0 root netem loss 10%

网络分区:

iptables -A INPUT -s 192.168.1.100 -j DROP
iptables -A OUTPUT -d 192.168.1.100 -j DROP

# 2.2 进程故障

杀死进程:

kill -9 $(pgrep java)

CPU 压力:

stress-ng --cpu 4 --timeout 60s

内存压力:

stress-ng --vm 2 --vm-bytes 1G --timeout 60s

# 2.3 应用层故障

Spring Boot Chaos Monkey:

chaos:
  monkey:
    enabled: true
    watcher:
      controller: true
      service: true
      repository: true
    assaults:
      level: 5
      latencyActive: true
      latencyRangeStart: 1000
      latencyRangeEnd: 3000
      exceptionsActive: true
      killApplicationActive: false

使用示例:

@Service
public class OrderService {
    
    public Order getOrder(Long id) {
        return orderRepository.findById(id)
                .orElseThrow(() -> new OrderNotFoundException(id));
    }
}

启用 Chaos Monkey 后,服务方法会随机注入延迟或异常。

# 3、混沌工程平台

# 3.1 Chaos Mesh (Kubernetes)

apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: pod-failure-example
spec:
  action: pod-failure
  mode: one
  duration: "30s"
  selector:
    namespaces:
      - default
    labelSelectors:
      "app": "myapp"

支持的故障类型:

  • PodChaos:Pod 杀死、容器杀死
  • NetworkChaos:延迟、丢包、分区、带宽限制
  • IOChaos:IO 延迟、IO 错误
  • StressChaos:CPU、内存压力
  • TimeChaos:时钟偏移

# 3.2 Litmus Chaos

apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: nginx-chaos
spec:
  appinfo:
    appns: 'default'
    applabel: 'app=nginx'
  chaosServiceAccount: pod-delete-sa
  experiments:
    - name: pod-delete
      spec:
        components:
          env:
            - name: TOTAL_CHAOS_DURATION
              value: '30'
            - name: CHAOS_INTERVAL
              value: '10'
            - name: FORCE
              value: 'false'

# 4、故障演练流程

# 4.1 演练计划

  1. 定义目标:验证数据库主从切换的自动恢复能力
  2. 选择场景:模拟数据库主库宕机
  3. 设置范围:测试环境 → 预发布环境 → 生产环境(小流量)
  4. 准备回滚:确保能快速终止演练

# 4.2 演练执行

步骤:

  1. 提前通知相关团队
  2. 确认监控告警正常
  3. 注入故障(如杀死数据库主库进程)
  4. 观察系统行为
  5. 记录恢复时间和过程
  6. 恢复正常状态

检查清单:

  • ✅ 故障是否被及时检测到?
  • ✅ 告警是否正常触发?
  • ✅ 自动切换是否成功?
  • ✅ 数据是否一致?
  • ✅ 业务是否受到影响?
  • ✅ 恢复时间是否符合预期?

# 4.3 演练总结

记录内容:

  • 故障注入时间和方式
  • 故障检测时间
  • 自动恢复时间
  • 业务影响范围
  • 发现的问题和改进点

持续改进:

发现问题 → 修复问题 → 再次演练 → 验证修复效果

# 六、最佳实践总结

# 1、故障检测最佳实践

  1. 多层次健康检查:

    • 基础存活检查(进程是否运行)
    • 就绪检查(服务是否准备好)
    • 功能检查(核心功能是否正常)
    • 性能检查(响应时间、资源使用)
  2. 合理设置检测参数:

    • 心跳间隔:根据业务 SLA 设置,通常 5-30 秒
    • 超时时间:P99 响应时间 + 网络延迟 + 安全余量
    • 失败阈值:3-5 次连续失败,避免误判
    • 恢复阈值:2-3 次连续成功,避免抖动
  3. 使用自适应算法:

    • 采用 Phi Accrual 等算法,根据历史数据动态调整
    • 避免网络抖动导致的误判
  4. 监控和告警:

    • 所有健康检查失败都应记录日志
    • 关键服务故障触发告警
    • 监控误判率和漏判率

# 2、自动恢复最佳实践

  1. 快速失败,快速恢复:

    • 设置合理的超时时间,避免长时间等待
    • 故障检测到后立即触发恢复流程
  2. 防止恢复循环:

    • 限制重启次数和频率
    • 重启失败后触发人工介入
  3. 保留故障现场:

    • 在重启前生成堆转储、线程栈
    • 保留日志便于事后分析
  4. 优雅启停:

    • 关闭时:停止接收新请求 → 处理完现有请求 → 关闭连接 → 退出
    • 启动时:初始化资源 → 预热 → 健康检查通过 → 开始接收流量
  5. 预留恢复时间:

    • initialDelaySeconds 要考虑应用启动时间
    • 避免在启动阶段误判为故障

# 3、高可用架构设计

  1. 无状态设计:

    • 应用本身不保存状态,便于快速扩缩容
    • 状态存储在外部存储(数据库、Redis)
  2. 冗余部署:

    • 至少部署 2 个副本
    • 跨机架、跨机房部署
  3. 隔离故障域:

    • 使用线程池隔离不同服务
    • 使用熔断器防止故障扩散
  4. 限流降级:

    • 保护核心服务,牺牲非核心功能
    • 避免雪崩效应

# 4、运维管理最佳实践

  1. 自动化优先:

    • 能自动化的尽量自动化
    • 减少人工介入,降低人为错误
  2. 可观测性:

    • 完善的监控、日志、链路追踪
    • 快速定位问题根因
  3. 故障演练:

    • 定期进行故障注入演练
    • 验证自动恢复机制有效性
  4. 文档化:

    • 记录故障恢复流程
    • 建立故障知识库
  5. 持续改进:

    • 每次故障后进行复盘
    • 总结经验教训,优化系统

# 七、未来发展趋势

# 1、AI 驱动的故障检测

异常检测算法:

  • 基于机器学习的异常检测
  • 自动学习正常行为模式
  • 识别微小的性能退化

AIOps 平台:

  • 自动关联分析(日志、指标、链路)
  • 根因定位(Root Cause Analysis)
  • 智能告警降噪

# 2、预测性维护

趋势预测:

  • 根据历史数据预测故障
  • 提前进行预防性维护
  • 避免故障发生

容量规划:

  • 预测资源使用趋势
  • 提前扩容,避免资源耗尽

# 3、自适应恢复策略

动态调整:

  • 根据系统状态自动调整恢复策略
  • 高峰期快速恢复,低峰期谨慎恢复

智能决策:

  • 评估恢复代价和业务影响
  • 选择最优的恢复方案

# 4、边缘计算的挑战

新的故障模式:

  • 边缘节点数量庞大,故障频繁
  • 网络延迟高,故障检测困难

解决方案:

  • 边缘自治,减少对中心节点的依赖
  • 边缘节点间协同,提高可用性

# 八、总结

故障检测与自动恢复是构建高可用系统的基石。本文从理论到实践,系统地介绍了:

  1. 故障检测的核心原理:

    • 理解 MTTF、MTTR 对可用性的影响
    • 掌握 Ping-Pong、Gossip、Phi Accrual 等检测模型
    • 设计多层次的健康检查机制
  2. 自动恢复的多种策略:

    • 进程级、容器级、节点级、服务级恢复
    • 重启、故障转移、降级、扩容等手段
    • 优化恢复时间,降低 MTTR
  3. 工程实践:

    • Spring Boot Actuator 健康检查
    • Kubernetes 探针配置
    • 数据库高可用方案
    • 微服务熔断降级
  4. 混沌工程:

    • 通过故障演练验证系统可靠性
    • 使用 Chaos Mesh、Litmus Chaos 等工具
    • 建立持续改进的文化

核心要点:

  • 快速检测:故障检测时间直接影响 MTTR
  • 自动恢复:减少人工介入,降低恢复时间
  • 分层防御:从进程到服务的多层次保护
  • 持续演练:定期验证恢复能力

记住:故障不可避免,但可以通过精心设计的检测和恢复机制,将影响降到最低。

祝你变得更强!

编辑 (opens new window)
#高可用#故障检测#自动恢复#健康检查
上次更新: 2025/12/14
高可用-服务容错与降级策略
高可用-混沌工程实践

← 高可用-服务容错与降级策略 高可用-混沌工程实践→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式