Skip to content
第 25 章 架构 ⏱ 12 分钟阅读

第 25 章:高可用设计 ​

学习目标 ​

  • 理解 SLA 与可用性计算
  • 掌握单点故障的识别与消除
  • 学会多活容灾与故障演练
  • 避免过度设计、容灾失效的坑

一、可用性指标 SLA ​

text
可用性 = 正常时间 / 总时间

99.9% = 每年 8.76 小时不可用(三九,3 个 9)
99.99% = 每年 52 分钟不可用(四个 9)
99.999% = 每年 5 分钟不可用(五个 9)
java
// 业务选择
电商普通业务: 99.9% (3 个 9)
支付核心: 99.99% (四个 9)
金融核心: 99.999% (五个 9)
SLA故障时间/年故障时间/月故障时间/周
99.9%8.76 小时43.2 分钟10.1 分钟
99.99%52.6 分钟4.38 分钟1.01 分钟
99.999%5.26 分钟26.3 秒6.05 秒

⚠️ 坑 1:"99.99% 太难,99.9% 就行" → 实际达到 99.5% = 每月 4 小时不可用,老板直接拍桌子。SLA 必须确切可达。

二、单点故障识别 ​

text
单点故障清单
├── 入口: Nginx / API Gateway
├── 应用: 单实例服务
├── 数据库: 单 MySQL / 单 Redis
├── 消息: 单 Kafka
└── 存储: 单 OSS / 单 MinIO
java
// 排查清单
public class SpofCheck {
    public List<String> check() {
        List<String> issues = new ArrayList<>();
        if (nginx.isSingleNode()) issues.add("Nginx 单点");
        if (mysql.isSingleNode()) issues.add("MySQL 单点");
        if (redis.isSingleNode()) issues.add("Redis 单点");
        return issues;
    }
}

三、入口层高可用 ​

nginx
# Nginx + Keepalived
# 两台 Nginx + 一个 VIP(虚拟 IP)
# 主挂了,备接管 VIP

# 主 nginx
vrrp_script check_nginx {
    script "killall -0 nginx"
    interval 2
    weight -20
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.1.100
    }
    track_script {
        check_nginx
    }
}
text
LVS + Keepalived
├── LVS: 四层负载均衡(LVS-DR 模式)
├── Keepalived: VIP 漂移
└── 2 + 2 部署,主备切换

四、应用层高可用 ​

yaml
# 1. 多实例部署
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3                  # 至少 3 个实例
  selector:
    matchLabels:
      app: order-service
  template:
    spec:
      containers:
      - name: order-service
        image: order-service:1.0.0
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
java
// 2. 无状态化(关键)
// ❌ 有状态:本地内存存 session
public class Session {
    private static Map<String, User> sessions = new HashMap<>();
    // 节点 1 存的 session,节点 2 看不到
}

// ✅ 无状态:session 存 Redis
public class Session {
    public User getUser(String token) {
        return redis.get("session:" + token);
    }
}
java
// 3. 优雅停机
@Component
public class GracefulShutdown implements DisposableBean {
    @Resource private TomcatServletWebServerFactory tomcat;

    @Override
    public void destroy() throws Exception {
        tomcat.getWebServer().shutdown();
        // 等待正在处理的请求完成
        Thread.sleep(30000);
    }
}

// application.yml
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

⚠️ 坑 2:K8s 滚动更新时,旧 Pod 被 kill,正在处理的请求中断。配合 readinessProbe,先把 Pod 摘出 LB 再停。

五、数据库高可用 ​

text
MySQL 高可用方案
├── 主从复制:简单,故障需手动切换
├── MHA: 故障自动切换,30 秒
├── MGR: MySQL Group Replication,强一致
├── Orchestrator: 自动拓扑 + 故障恢复
└── PolarDB / Aurora: 云数据库
yaml
# 主从配置
spring:
  datasource:
    master:
      url: jdbc:mysql://master:3306/order
    slave:
      url: jdbc:mysql://slave:3306/order
java
// 读写分离
@DS("master")
public void createOrder(OrderDto dto) {
    orderRepository.save(new Order(dto));
}

@DS("slave")
public List<Order> listOrders() {
    return orderRepository.findAll();
}

六、Redis 高可用 ​

text
Redis 集群方案
├── 主从复制:1 主 N 从,故障手动切换
├── 哨兵:自动故障转移,3 节点
├── Redis Cluster:分片集群,6 节点起
└── Codis: 豌豆荚开源,代理分片
yaml
# Redis Sentinel
spring:
  redis:
    sentinel:
      master: mymaster
      nodes: sentinel1:26379,sentinel2:26379,sentinel3:26379
java
// Redis Cluster
public class RedisClusterConfig {
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        List<RedisNode> nodes = List.of(
            new RedisNode("redis1", 6379),
            new RedisNode("redis2", 6379),
            new RedisNode("redis3", 6379)
        );
        return new LettuceConnectionFactory(new RedisClusterConfiguration(nodes));
    }
}

⚠️ 坑 3:Redis 单节点做缓存,挂了缓存击穿到 DB = 雪崩。集群 + 多级缓存。

七、MQ 高可用 ​

text
Kafka 高可用
├── topic 多 partition
├── 副本数 >= 3
├── min.insync.replicas = 2
└── 跨机架部署

RocketMQ 高可用
├── 主从架构
├── Dledger 集群(3 节点)
└── NameServer 无状态
yaml
# Kafka topic
num.partitions: 6
default.replication.factor: 3
min.insync.replicas: 2

八、容灾:多活架构 ​

text
同城双活
├── 两个机房,同城
├── 流量分配 50/50
├── 数据库双向同步
└── 任一机房故障,流量切到另一机房

异地多活
├── 多个城市机房
├── 单元化(用户按区域路由)
├── 异地容灾
└── 成本高,复杂度高
java
// 单元化路由
public class UnitRouter {
    private static final Map<String, String> userRegion = new HashMap<>();

    public String getUnit(Long userId) {
        String region = userRegion.getOrDefault(userId, "default");
        return "unit-" + region;
    }

    public DataSource getDataSource(Long userId) {
        return dataSourceMap.get(getUnit(userId));
    }
}
java
// 流量切换
@Component
public class TrafficSwitch {
    @Value("${traffic.unit-a.weight:50}")
    private int unitAWeight;

    public boolean isUnitA() {
        return ThreadLocalRandom.current().nextInt(100) < unitAWeight;
    }
}

九、灾备演练 ​

java
// 1. 故障注入
public class FailureInjection {
    public void killJvm() {
        log.warn("故障注入: kill JVM");
        System.exit(1);
    }

    public void killNetwork() {
        log.warn("故障注入: 网络中断");
        while (true) {
            // 模拟网络
        }
    }

    public void slowDown() {
        log.warn("故障注入: 慢调用");
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
text
演练类型
├── 断网演练
├── 杀节点演练
├── 数据库主从切换
├── 区域故障(机房断电)
└── 重大变更演练
yaml
# 演练场景清单
- 场景: 1 个 Pod 故障
  期望: Pod 自动重启,流量自动转移
  验证: kubectl delete pod xxx,观察 HPA

- 场景: 数据库主节点故障
  期望: 30 秒内自动切换主从
  验证: 主库 stop,观察应用

- 场景: Redis 集群 1 节点故障
  期望: 集群自动恢复,数据不丢失
  验证: redis-cli shutdown nosave

- 场景: Kafka broker 1 节点故障
  期望: 生产消费自动转移
  验证: kill broker,观察消费者重平衡

⚠️ 坑 4:演练只在测试环境做,生产环境真出问题没人会切。生产环境定期演练(Chaos Engineering)。

十、限流降级与高可用 ​

java
// 高可用三剑客
// 1. 限流:挡住突发流量
// 2. 降级:非核心服务降级
// 3. 熔断:依赖服务故障时隔断

@Service
public class OrderService {
    @SentinelResource(value = "createOrder", fallback = "fallback")
    public Order createOrder(OrderDto dto) {
        return orderRepository.save(new Order(dto));
    }

    public Order fallback(OrderDto dto, Throwable e) {
        return Order.queued(dto);  // 进入队列,异步处理
    }
}

十一、监控告警 ​

yaml
# 关键告警
- 核心业务 QPS 下降 50%
- 错误率超 5%
- P99 延迟超 1s
- 数据库主从延迟超 60s
- Kafka 消费积压
- 磁盘使用率超 85%
- 内存使用率超 90%
text
告警分级
├── P0: 立即响应,电话
├── P1: 30 分钟内,短信
├── P2: 2 小时内,IM
└── P3: 24 小时内,邮件

十二、高可用清单 ​

text
入口层
├── Nginx 集群 + VIP
├── CDN 加速
└── L7 负载均衡

应用层
├── 多实例
├── 无状态
├── 优雅停机
└── 依赖隔离

数据层
├── 主从切换
├── 读写分离
├── 备份
└── 异地容灾

业务层
├── 限流
├── 降级
├── 熔断
└── 重试

运维层
├── 监控
├── 告警
├── 故障演练
└── 应急响应

本章小结 ​

维度措施
入口VIP + Keepalived
应用多实例 + 无状态
数据主从 + 集群
业务限流 + 降级 + 熔断
运维监控 + 演练
SLA实施成本
99.9%低
99.99%中
99.999%高

动手练习 ​

  1. SpOF 排查:列出你的项目所有单点,标注优先级
  2. Nginx 集群:部署 2 个 Nginx + VIP,演练主备切换
  3. 应用多实例:部署 3 个应用实例,模拟 1 个故障,观察自动恢复
  4. Chaos 演练:用 ChaosBlade 注入故障,验证系统容错

下一章:第 26 章:限流与降级 →

本站基于 VitePress 构建 · 由 StackHub 团队维护