第 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 / 单 MinIOjava
// 排查清单
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: 5java
// 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/orderjava
// 读写分离
@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:26379java
// 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% | 高 |
动手练习
- SpOF 排查:列出你的项目所有单点,标注优先级
- Nginx 集群:部署 2 个 Nginx + VIP,演练主备切换
- 应用多实例:部署 3 个应用实例,模拟 1 个故障,观察自动恢复
- Chaos 演练:用 ChaosBlade 注入故障,验证系统容错
下一章:第 26 章:限流与降级 →