小陈的知识图谱
系统设计L4 高级重要

高可用设计

负载均衡、限流、熔断、降级、多活架构、容灾

高可用设计(High Availability Design)

高可用(HA)是指系统在面对硬件故障、软件异常、流量洪峰等突发状况时,仍能持续对外提供服务的能力。业界通常用 N 个 9 来衡量可用性:

可用性级别年度不可用时间描述
99%(2个9)87.6 小时常规系统
99.9%(3个9)8.76 小时大多数商业系统
99.99%(4个9)52.56 分钟金融核心系统
99.999%(5个9)5.26 分钟电信级系统
99.9999%(6个9)31.5 秒航空/军事系统

---

一、高可用设计原则

1.1 消除单点故障(SPOF)

任何只有一个副本的组件都是潜在的故障点。

❌ 有单点的架构:
   ┌─────────┐
   │  Nginx  │ ← 单点
   └────┬────┘
        │
   ┌────▼────┐
   │  MySQL  │ ← 单点
   └─────────┘
   Nginx 或 MySQL 任何一个宕机,整个系统不可用

✅ 无单点的架构:
        ┌─────────┐
        │  DNS    │ ← 多 IP 解析
        └────┬────┘
         ┌───┴───┐
    ┌────▼────┐ ┌▼────────┐
    │Nginx 主 │ │Nginx 备 │ ← Nginx 主备(Keepalived)
    └────┬────┘ └─────────┘
         │
    ┌────▼────┐
    │MySQL 主  │ ← 主从复制
    └────┬────┘
    ┌────▼────┐
    │MySQL 从  │ ← 从库可切换为主
    └─────────┘

1.2 故障隔离(Bulkhead / 舱壁模式)

将系统划分为独立的隔离舱室,一个舱室的故障不会蔓延到其他舱室。

舱壁模式示意图(源于船舶设计):
┌─────────────────────────────────────┐
│       ┌──────┐  ┌──────┐  ┌──────┐ │
│       │舱室A  │  │舱室B  │  │舱室C  │ │
│       │      │  │      │  │      │ │
│       │漏水→沉│  │正常  │  │正常  │ │
│       └──────┘  └──────┘  └──────┘ │
└─────────────────────────────────────┘
A 舱室漏水不影响 B 和 C

线程池隔离

// 不同的业务使用不同的线程池,避免互相影响
public class ThreadPoolIsolation {

    // 订单业务线程池
    private static final ThreadPoolExecutor orderPool = new ThreadPoolExecutor(
        10, 20, 60, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(100),
        new ThreadPoolExecutor.CallerRunsPolicy()
    );

    // 库存业务线程池
    private static final ThreadPoolExecutor stockPool = new ThreadPoolExecutor(
        5, 10, 60, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(50),
        new ThreadPoolExecutor.AbortPolicy()
    );

    // 支付业务线程池
    private static final ThreadPoolExecutor paymentPool = new ThreadPoolExecutor(
        20, 40, 60, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(200),
        new ThreadPoolExecutor.CallerRunsPolicy()
    );

    public void processOrder(Order order) {
        orderPool.submit(() -> {
            // 订单业务处理
        });
    }
}

Hystrix 线程池隔离

// Hystrix 命令,使用独立的线程池
public class GetProductCommand extends HystrixCommand<Product> {

    private Long productId;

    public GetProductCommand(Long productId) {
        super(Setter
            .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductGroup"))
            .andCommandKey(HystrixCommandKey.Factory.asKey("GetProductKey"))
            .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ProductPool"))
            .andThreadPoolPropertiesDefaults(
                HystrixThreadPoolProperties.Setter()
                    .withCoreSize(10)
                    .withMaxQueueSize(20)
            )
        );
        this.productId = productId;
    }

    @Override
    protected Product run() throws Exception {
        return productService.getById(productId);
    }

    @Override
    protected Product getFallback() {
        // 降级:返回缓存数据
        return cacheService.getProduct(productId);
    }
}

---

二、负载均衡(Load Balancing)

将请求分发到多个服务节点上,提高系统的处理能力和可用性。

2.1 四层 vs 七层负载均衡

┌──────────────────────────┐
                    │       客户端请求           │
                    └───────────┬──────────────┘
                                │
               ┌────────────────┴────────────────┐
               │                                 │
      ┌────────▼────────┐            ┌───────────▼─────────┐
      │   四层 LVS       │            │   七层 Nginx         │
      │                 │            │                      │
      │ 基于 IP + 端口  │            │ 基于 HTTP 内容       │
      │ 转发 TCP 流量   │            │ 解析 URL/Cookie/Header│
      │ 性能极高        │            │ 功能丰富             │
      │ 无法检查内容    │            │ 可做 SSL 卸载         │
      └────────┬────────┘            └───────────┬──────────┘
               │                                 │
               └────────────────┬────────────────┘
                                │
                    ┌───────────▼───────────┐
                    │    后端服务器集群       │
                    │  Server1  Server2  S3  │
                    └───────────────────────┘

2.2 常见负载均衡算法

// 1. 轮询(Round Robin)
public class RoundRobinStrategy implements LoadBalanceStrategy {
    private AtomicInteger index = new AtomicInteger(0);

    public Server select(List<Server> servers) {
        int i = index.getAndIncrement() % servers.size();
        return servers.get(i);
    }
}

// 2. 加权轮询(Weighted Round Robin)
public class WeightedRoundRobinStrategy implements LoadBalanceStrategy {
    // 权重高的服务器获得更多请求
    // Server A: weight=5, Server B: weight=3, Server C: weight=2
    // 10次请求中 A 收到5次,B 收到3次,C 收到2次
    private AtomicInteger index = new AtomicInteger(0);

    public Server select(List<Server> servers) {
        int totalWeight = servers.stream().mapToInt(Server::getWeight).sum();
        int i = index.getAndIncrement() % totalWeight;
        for (Server server : servers) {
            i -= server.getWeight();
            if (i < 0) return server;
        }
        return servers.get(0);
    }
}

// 3. 一致性哈希(Consistent Hash)
// 适合缓存场景:相同 key 始终路由到同一节点
public class ConsistentHashStrategy implements LoadBalanceStrategy {
    private TreeMap<Integer, Server> hashRing = new TreeMap<>();
    private HashFunction hashFunction = new MD5HashFunction();

    public ConsistentHashStrategy(List<Server> servers) {
        // 每个节点添加 150 个虚拟节点
        for (Server server : servers) {
            for (int i = 0; i < 150; i++) {
                int hash = hashFunction.hash(server.getId() + "_VN_" + i);
                hashRing.put(hash, server);
            }
        }
    }

    public Server select(String key) {
        if (hashRing.isEmpty()) return null;
        int hash = hashFunction.hash(key);
        // 找到第一个大于等于 hash 的节点
        Map.Entry<Integer, Server> entry = hashRing.ceilingEntry(hash);
        if (entry == null) {
            entry = hashRing.firstEntry();  // 回绕到环的起点
        }
        return entry.getValue();
    }
}

2.3 Nginx 配置示例

# 七层负载均衡
http {
    upstream backend {
        # 负载均衡算法
        # 默认:轮询
        # ip_hash:根据客户端 IP 哈希(会话保持)
        # least_conn:最少连接
        # consistent_hash:一致性哈希(需要 ngx_http_upstream_consistent_hash 模块)

        # 加权轮询
        server 192.168.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
        server 192.168.1.11:8080 weight=3 max_fails=3 fail_timeout=30s;
        server 192.168.1.12:8080 weight=2 max_fails=3 fail_timeout=30s backup;  # 备用节点
    }

    server {
        listen 80;

        location /api/ {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            # 超时配置
            proxy_connect_timeout 5s;
            proxy_read_timeout 10s;
            proxy_send_timeout 10s;
        }
    }
}

---

三、限流(Rate Limiting)

限流是保护系统不被突发流量冲垮的第一道防线。

3.1 单机限流

Guava RateLimiter(令牌桶)

public class GuavaRateLimiterExample {
    // 每秒生成 100 个令牌(QPS = 100)
    private RateLimiter rateLimiter = RateLimiter.create(100);

    public void handleRequest(Request request) {
        // 阻塞直到获取到令牌(允许突发流量)
        double waitTime = rateLimiter.acquire();

        // 或者:非阻塞,获取不到直接返回 false
        // if (rateLimiter.tryAcquire(100, TimeUnit.MILLISECONDS)) {
        //     process(request);
        // } else {
        //     return error("系统繁忙,请稍后重试");
        // }

        process(request);
    }
}

Semaphore(信号量,控制并发数)

public class SemaphoreLimiter {
    // 最多允许 50 个并发请求
    private Semaphore semaphore = new Semaphore(50);

    public void handleRequest(Request request) {
        if (!semaphore.tryAcquire()) {
            throw new RuntimeException("系统繁忙");
        }
        try {
            process(request);
        } finally {
            semaphore.release();
        }
    }
}

3.2 分布式限流(Redis + Lua)

-- 滑动窗口限流 Lua 脚本
-- KEYS[1] = 限流 key (e.g., "rate_limit:user:123")
-- ARGV[1] = 窗口大小(毫秒)
-- ARGV[2] = 窗口内最大请求数

local key = KEYS[1]
local window = tonumber(ARGV[1])  -- 窗口大小(ms)
local limit = tonumber(ARGV[2])   -- 限流阈值

local now = redis.call('TIME')[1]  -- 当前时间戳(秒)
local currentWindow = math.floor(now / (window / 1000))

-- ZSET 清理过期元素
redis.call('ZREMRANGEBYSCORE', key, 0, now - window / 1000)

-- 统计当前窗口请求数
local count = redis.call('ZCARD', key)

if count >= limit then
    return 0  -- 限流
end

-- 添加当前请求
redis.call('ZADD', key, now, now .. ':' .. math.random())
redis.call('EXPIRE', key, window / 1000)

return 1  -- 通过

// Java 调用分布式限流
@Component
public class DistributedRateLimiter {

    @Autowired
    private StringRedisTemplate redisTemplate;

    // 加载 Lua 脚本
    private DefaultRedisScript<Long> rateLimitScript;

    @PostConstruct
    public void init() {
        rateLimitScript = new DefaultRedisScript<>();
        rateLimitScript.setScriptSource(new ResourceScriptSource(
            new ClassPathResource("rate_limit.lua")));
        rateLimitScript.setResultType(Long.class);
    }

    public boolean tryAcquire(String key, long windowMs, int limit) {
        Long result = redisTemplate.execute(
            rateLimitScript,
            Arrays.asList(key),
            String.valueOf(windowMs),
            String.valueOf(limit)
        );
        return result != null && result == 1L;
    }
}

// 使用
if (rateLimiter.tryAcquire("rate_limit:user:" + userId, 1000, 10)) {
    // QPS 限制:每秒最多 10 次
    processRequest(request);
} else {
    return ResponseEntity.status(429).body("请求过于频繁");
}

3.3 Sentinel 限流

// Sentinel 限流注解
@RestController
public class OrderController {

    @GetMapping("/order/create")
    @SentinelResource(
        value = "createOrder",
        blockHandler = "createOrderBlockHandler",
        fallback = "createOrderFallback"
    )
    public Result createOrder(@RequestParam Long userId) {
        // 业务逻辑
        return Result.success(orderService.createOrder(userId));
    }

    // 限流后的处理
    public Result createOrderBlockHandler(Long userId, BlockException e) {
        return Result.error("系统繁忙,请稍后重试");
    }

    // 熔断降级后的处理
    public Result createOrderFallback(Long userId, Throwable e) {
        return Result.error("服务异常,请稍后重试");
    }
}

# Sentinel 规则配置

限流规则

flowRules: - resource: createOrder grade: 1 # 0=线程数, 1=QPS count: 1000 # 限流阈值 controlBehavior: 0 # 0=直接拒绝, 1=WarmUp, 2=排队等待 warmUpPeriodSec: 10 # 预热时间(秒)

熔断规则

degradeRules: - resource: createOrder grade: 0 # 0=慢调用比例, 1=异常比例, 2=异常数 count: 100 # 阈值(慢调用:MS;异常比例:0.5=50%) timeWindow: 30 # 熔断时长(秒) minRequestAmount: 5 # 最小请求数 statIntervalMs: 1000 # 统计窗口

---

四、熔断与降级

4.1 熔断器状态机

┌──────────────────────┐
         │                      │
         │       CLOSED         │ ← 正常状态,请求正常通过
         │  (正常调用)          │
         └──────┬───────────────┘
                │ 失败达到阈值
                ▼
         ┌──────────────────────┐
         │                      │
         │        OPEN          │ ← 熔断打开,请求快速失败
         │  (熔断打开)          │
         └──────┬───────────────┘
                │ 时间窗口到期
                ▼
         ┌──────────────────────┐
         │                      │
         │      HALF_OPEN       │ ← 半开状态,放行少量请求
         │  (探测恢复)          │     探测服务是否恢复
         └──────┬───────────────┘
           ┌────┴────┐
           ▼         ▼
       成功率达标   成功率仍低
        → CLOSED   → OPEN (重置时间窗口)

4.2 熔断降级实现(Resilience4j)

// Resilience4j 熔断器配置
@Bean
public CircuitBreaker orderCircuitBreaker() {
    CircuitBreakerConfig config = CircuitBreakerConfig.custom()
        .failureRateThreshold(50)                   // 失败率阈值:50%
        .waitDurationInOpenState(Duration.ofSeconds(30))  // 熔断持续时间
        .permittedNumberOfCallsInHalfOpenState(3)   // 半开状态允许请求数
        .slidingWindowSize(100)                     // 滑动窗口大小
        .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
        .build();

    return CircuitBreakerRegistry.of(config)
        .circuitBreaker("orderService");
}

// 使用
@Component
public class OrderServiceClient {

    @Autowired
    private CircuitBreaker circuitBreaker;

    public Order getOrder(Long orderId) {
        return circuitBreaker.executeSupplier(() -> {
            // 可能失败的远程调用
            return restTemplate.getForObject(
                "http://order-service/order/" + orderId, Order.class);
        });
    }
}

面试重点:熔断和降级的区别

  • 熔断:自动触发,由系统状态决定(失败率过高、慢调用比例过高)
  • 降级:主动关闭非核心功能,由业务决定(秒杀期间关闭评论功能)
  • 熔断后通常触发降级(返回兜底数据)

---

五、多活架构

5.1 架构对比

┌──────────────────────────────────────────────────────────┐
│  主备架构                                                 │
│                                                          │
│  机房A(主)                   机房B(备)                 │
│  ┌──────────────┐            ┌──────────────┐            │
│  │  Nginx       │            │  Nginx       │            │
│  │  App Server  │ ← 同步 →  │  App Server  │            │
│  │  MySQL Master│            │  MySQL Slave │            │
│  └──────────────┘            └──────────────┘            │
│  正常:流量全部到 A                                       │
│  故障:DNS 切换到 B                                       │
│  RTO=分钟级  RPO=秒级                                     │
└──────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────┐
│  双活架构                                                 │
│                                                          │
│  机房A                        机房B                       │
│  ┌──────────────┐            ┌──────────────┐            │
│  │ 流量进入      │            │ 流量进入      │            │
│  │  App Server  │ ← 双向 →  │  App Server  │            │
│  │  MySQL A     │   同步     │  MySQL B     │            │
│  └──────────────┘            └──────────────┘            │
│  正常:流量同时进入 A 和 B                                 │
│  故障:故障机房流量切换到正常机房                           │
│  RTO=秒级  RPO=秒级                                       │
│  难点:数据双向同步冲突处理                                 │
└──────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────┐
│  三地五中心架构                                           │
│                                                          │
│  华北(北京)          华东(上海)          华南(深圳)        │
│  ┌──┬──┐            ┌──┬──┐            ┌──┬──┐          │
│  │A1│A2│            │B1│B2│            │C1│  │          │
│  └──┴──┘            └──┴──┘            └──┴──┘          │
│  5个数据中心,任意 2 个故障不影响整体                       │
│  成本极高,适合金融、电信等关键系统                         │
└──────────────────────────────────────────────────────────┘

5.2 多活数据同步方案

// Canal 监听 binlog 实现数据同步
@Component
public class BinlogSyncService {

    @PostConstruct
    public void startSync() {
        CanalConnector connector = CanalConnectors.newSingleConnector(
            new InetSocketAddress("192.168.1.1", 11111),
            "example", "", "");

        connector.connect();
        connector.subscribe("order_db\\..*");
        connector.rollback();

        while (true) {
            Message message = connector.getWithoutAck(100);
            for (CanalEntry.Entry entry : message.getEntries()) {
                if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
                    CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(
                        entry.getStoreValue());

                    for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
                        // 同步到异地机房
                        syncToRemote(rowData);
                    }
                }
            }
            connector.ack(message.getId());
        }
    }
}

---

六、容灾演练与混沌工程

6.1 Chaos Engineering(混沌工程)

Netflix 提出的理念:在生产环境中主动注入故障,验证系统的弹性

// 使用 Chaos Monkey(Spring Boot 集成)
@Component
@ConditionalOnProperty(name = "chaos.monkey.enabled", havingValue = "true")
public class LatencyChaosAssault extends ChaosMonkeyBaseAssault {

    @Override
    public boolean isActive() {
        return true;
    }

    @Override
    public void attack() {
        long latency = ThreadLocalRandom.current().nextLong(100, 500);
        log.warn("混沌工程:注入 {}ms 延迟", latency);
        try {
            Thread.sleep(latency);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

# application.yml
chaos:
  monkey:
    enabled: true  # 生产环境谨慎开启
    assaults:
      latency-active: true
      exceptions-active: true
      kill-application-active: false  # 杀进程攻击默认关闭

6.2 常见容灾演练场景

演练场景模拟方式验证目标
网络延迟TC 命令注入延迟超时处理、熔断是否正常
网络分区防火墙阻断节点间通信CAP 策略是否正确
CPU 飙升stress 工具压满 CPU限流是否触发、服务降级是否正常
内存溢出OOM killerJVM 参数、自动重启是否正常
磁盘满dd 填充磁盘日志清理、监控告警是否正常
节点宕机kill 服务进程负载均衡摘除、主从切换是否正常

---

七、全链路监控体系

┌──────────────────────┐
                     │    统一监控面板       │
                     │    (Grafana)         │
                     └──────┬───────┬───────┘
                            │       │
          ┌─────────────────┼───────┼──────────────────┐
          │                 │       │                   │
    ┌─────▼─────┐   ┌──────▼──┐ ┌──▼────────┐   ┌─────┴─────┐
    │  指标     │   │  日志   │ │  链路     │   │  告警     │
    │ Metrics   │   │ Logging │ │ Tracing   │   │ Alert     │
    ├───────────┤   ├─────────┤ ├───────────┤   ├───────────┤
    │Prometheus │   │  ELK    │ │  Jaeger   │   │ Alertmanager│
    │Node-Exporter│ │Logstash │ │  Zipkin   │   │ 钉钉/邮件  │
    │           │   │ES+Kibana│ │  SkyWalking│   │           │
    └───────────┘   └─────────┘ └───────────┘   └───────────┘

7.1 指标监控(Metrics)

# Prometheus + Micrometer 配置
management:
  metrics:
    export:
      prometheus:
        enabled: true
  endpoints:
    web:
      exposure:
        include: prometheus,health,info

7.2 链路追踪(Tracing)

// Spring Cloud Sleuth + Zipkin
@Bean
public Sampler defaultSampler() {
    // 采样率:100%(生产环境适当降低)
    return Sampler.ALWAYS_SAMPLE;
}

// 链路信息自动传递
// 每个请求自带 Trace ID,串联整个调用链
// [order-service, trace-id, span-id, parent-span-id]

面试核心要点:可观测性三支柱(Metrics、Logging、Tracing)缺一不可。

---

八、面试小贴士

1. 高可用设计的核心是冗余和自动故障转移 — 没有冗余就没有高可用

2. 限流、熔断、降级是面试中必须说清楚的三件套,要能说明它们的区别和联系

3. 多活架构要结合实际场景讨论,不是所有公司都需要三地五中心

4. 混沌工程是高级话题,可以用来展示你的深度思考

5. 面试中可以说 "我们通过全链路压测发现瓶颈,通过混沌工程验证弹性,通过限流熔断保护系统"

核心要点

  • 高可用设计原则(冗余/隔离/降级)
  • 负载均衡四层 vs 七层
  • 分布式限流实现方案
  • 多活架构对比
  • 全链路监控体系