分布式高可用架构模式:多活、容灾、降级与 K8s 编排高可用

分布式系统高可用完全指南:同城双活/异地多活、容灾演练、熔断降级限流、K8s 编排高可用与混沌工程验证。

在微服务与云原生时代,“高可用” 不再是可选项,而是所有在线系统的刚性约束。本文从 CAP 理论出发,深度拆解同城双活 / 异地多活的部署模式,覆盖熔断、限流、降级等企业级稳定性手段,并结合 Kubernetes 编排、数据库主从切换与负载均衡的多层冗余实践,提供一套可直接落地的分布式高可用技术全景。


一、高可用核心基础:CAP、可用区与量化指标

1.1 CAP 理论在高可用架构中的取舍

CAP 理论指出:一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance)三者最多同时满足两项。在现代分布式系统中,网络分区是无法避免的,因此实际架构设计围绕 CP 还是 AP 进行取舍:

  • CP 系统:ZooKeeper、etcd、Consul。在分区时牺牲可用性,优先保证数据一致性。适用于配置中心、服务注册发现等场景。
  • AP 系统:Cassandra、Eureka、DNS。在分区时牺牲一致性,优先保证服务可用。适用于高并发读、可容忍最终一致的业务数据。
  • 实际生产:大多数系统采用 “基本可用 + 最终一致” 的 BASE 策略,通过异步补偿和幂等设计弥合缝隙。

1.2 可用区与故障域设计

公有云将数据中心划分为多个 可用区(Availability Zone, AZ),同一地域(Region)内的不同 AZ 之间拥有独立的电力、网络与冷却系统,但延迟通常低于 2ms。

故障域设计原则:

  1. 冗余部署:每个关键组件至少跨 2 个 AZ 部署,避免单点故障。
  2. 故障隔离:单个 AZ 故障不得影响其他 AZ 的流量处理。
  3. 就近路由:通过智能 DNS 或服务网格将流量导向延迟最低的可用区。

1.3 RTO 与 RPO:容灾的量化指标

指标全称定义典型值
RTORecovery Time Objective从灾难发生到业务恢复的最大可接受时间同城双活:<30s;异地多活:<5min;冷备:<4h
RPORecovery Point Objective灾难发生时可接受的最大数据丢失量同步复制:0;异步复制:秒级分钟级;冷备:小时级
可用性Availability系统正常运行时间占比99.9%(年停机 8.76h)→ 99.999%(年停机 5.26min)

不同业务等级对 RTO/RPO 的要求存在数量级差异。支付核心通常要求 RTO<30s、RPO≈0,而内容推荐系统可能容忍 RTO<30min、RPO<5min。


二、多活架构:同城双活与异地多活

2.1 同城双活(Same-City Dual-Active)

同城双活指在同一城市或相邻城市的两个数据中心同时对外提供服务,数据通过同步复制保持一致。

+------------------+          +------------------+
|    数据中心 A     |          |    数据中心 B     |
|  (可用区 AZ-1)   | <======> |  (可用区 AZ-2)   |
|                  | 同步复制  |                  |
|  - 应用集群      |          |  - 应用集群      |
|  - 数据库主库    |          |  - 数据库备库    |
|  - 缓存节点      |          |  - 缓存节点      |
+------------------+          +------------------+
         ^                           ^
         |         GTM / LDNS        |
         +-----------+   +-----------+
                     用户流量

核心特征

  • 同城光纤延迟 < 2ms,可采用数据库同步复制(如 MySQL Semi-Sync)。
  • 双中心同时处理读写流量,单中心故障时秒级切换。
  • 依赖全局流量管理(GTM)或智能 DNS 做入口调度。

数据一致性挑战

  • 双写冲突:同一用户的并发请求可能被路由到两个中心,需通过 分片策略全局序列号 避免覆盖。
  • 推荐方案:按用户 ID 取模固定路由到某一中心,另一中心作为热备,实现 “伪双活、真主备” 的折中。

2.2 异地多活(Geo-Distributed Multi-Active)

异地多活跨越数百至数千公里,网络延迟通常在 20ms~100ms 量级,数据同步只能采用异步方式。

+-----------+      +-----------+      +-----------+
| 北京中心   | <==> | 上海中心   | <==> | 广州中心   |
| (华北分区) | async| (华东分区) | async| (华南分区) |
+-----------+      +-----------+      +-----------+
      ^                  ^                  ^
      |    Geo-DNS / CDN AnyCast           |
      +------------------------------------+
                  按地理位置分流

设计要点

  1. 单元化架构:将业务按用户维度切分为独立单元(Cell),每个单元包含完整的服务与数据闭环,单元之间通过消息队列异步同步。
  2. 最终一致:接受跨中心的数据延迟,通过 对账系统 定时校验并修复不一致数据。
  3. 读写分离:写操作收敛到用户归属的单元,读操作可由就近中心处理,降低跨地域延迟。

2.3 多活路由层代码示例

/**
 * 多活路由决策器:根据用户 ID 与数据中心状态决定请求去向
 */
@Component
public class MultiActiveRouter {

    private final DataCenterStatusService statusService;
    private final ConsistentHashRouter hashRouter;

    public RouteResult route(String userId, RequestType type) {
        // 1. 获取所有健康的数据中心
        List<String> healthyDCs = statusService.getHealthyDataCenters();
        if (healthyDCs.isEmpty()) {
            throw new NoHealthyDataCenterException("所有数据中心均不可用");
        }

        // 2. 使用一致性哈希确定用户的归属中心
        String homeDC = hashRouter.getNode(userId, healthyDCs);

        // 3. 若归属中心故障,降级到就近可用中心
        if (!healthyDCs.contains(homeDC)) {
            String fallbackDC = findNearest(healthyDCs, homeDC);
            return RouteResult.builder()
                .targetDC(fallbackDC)
                .mode(RouteMode.FALLBACK)
                .reason("归属中心 " + homeDC + " 不可用,降级路由到 " + fallbackDC)
                .build();
        }

        // 4. 写请求必须路由到归属中心,读请求可就近处理
        if (type == RequestType.WRITE) {
            return RouteResult.of(homeDC, RouteMode.HOME);
        }
        return RouteResult.of(findLowestLatencyDC(healthyDCs), RouteMode.READ_NEARBY);
    }

    private String findNearest(List<String> healthyDCs, String targetDC) {
        // 基于拓扑距离与实时延迟选择最近可用中心
        return healthyDCs.stream()
            .min(Comparator.comparingInt(dc -> getTopologyDistance(dc, targetDC)))
            .orElse(healthyDCs.get(0));
    }
}

三、容灾设计深度解析

3.1 容灾等级与架构模式映射

根据 RTO/RPO 要求,容灾架构可分为四个等级:

容灾等级架构模式RTORPO适用场景
Level 1数据备份(冷备)4~24h1~24h开发测试、非核心日志
Level 2主备(Warm Standby)5~30min分钟级内部管理系统、BI 报表
Level 3同城双活(Hot Standby)<30s~0电商核心交易、支付网关
Level 4异地多活(Active-Active)<10s秒级全球化社交、金融核心

3.2 自动化容灾切换控制器

/**
 * 容灾编排器:监控健康状态并自动触发切换
 */
@Service
@Slf4j
public class DisasterRecoveryOrchestrator {

    private static final int FAILURE_THRESHOLD = 3;    // 连续异常阈值
    private static final long CHECK_INTERVAL_MS = 5000; // 探测间隔 5s

    private final Map<String, AtomicInteger> failureCounters = new ConcurrentHashMap<>();
    private final Map<String, HealthStatus> dcStatus = new ConcurrentHashMap<>();

    @Scheduled(fixedRate = CHECK_INTERVAL_MS)
    public void healthCheck() {
        for (String dc : getAllDataCenters()) {
            boolean healthy = probeDataCenter(dc);
            if (!healthy) {
                int failures = failureCounters.computeIfAbsent(dc, k -> new AtomicInteger(0))
                                              .incrementAndGet();
                if (failures >= FAILURE_THRESHOLD) {
                    triggerFailover(dc);
                }
            } else {
                failureCounters.getOrDefault(dc, new AtomicInteger(0)).set(0);
                if (dcStatus.get(dc) == HealthStatus.FAILED) {
                    triggerRecovery(dc); // 故障恢复后自动回切
                }
            }
        }
    }

    private void triggerFailover(String failedDC) {
        log.warn("[容灾] 数据中心 {} 触发故障切换", failedDC);
        dcStatus.put(failedDC, HealthStatus.FAILED);

        // 1. 从 DNS / GTM 中剔除故障中心
        dnsManager.removeEndpoint(failedDC);

        // 2. 提升备库为主库(若使用主备架构)
        databaseManager.promoteStandby(failedDC);

        // 3. 通知下游服务更新路由表
        eventPublisher.publishEvent(new FailoverEvent(failedDC, getStandbyDC(failedDC)));

        // 4. 告警通知
        alertService.sendCriticalAlert("数据中心 " + failedDC + " 已切换至容灾模式");
    }

    private void triggerRecovery(String recoveredDC) {
        log.info("[容灾] 数据中心 {} 恢复健康,执行流量回切", recoveredDC);
        // 渐进式切流,先切 5% 流量验证再全量
        dnsManager.gradualShift(recoveredDC, 0.05);
        dcStatus.put(recoveredDC, HealthStatus.HEALTHY);
    }
}

四、熔断器模式(Circuit Breaker)

4.1 状态机与恢复策略

熔断器有三个核心状态:

  • Closed(关闭):请求正常通过,持续监控错误率。
  • Open(打开):错误率超过阈值,所有请求快速失败,保护下游。
  • Half-Open(半开):经过冷却时间后,允许少量探测请求通过,验证下游是否恢复。
    +--------+  错误率超限   +--------+
    | Closed | ----------> | Open   |
    +--------+             +--------+
       ^    |  成功           | 冷却超时
       |    |  错误率正常      v
       |    +------------- Half-Open
       +------------------------+
          探测成功(半开状态)

4.2 Resilience4j 实现

/**
 * 使用 Resilience4j 实现熔断器配置
 */
@Configuration
public class CircuitBreakerConfig {

    @Bean
    public CircuitBreakerRegistry circuitBreakerRegistry() {
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            // 失败率阈值 50%,超过则熔断
            .failureRateThreshold(50)
            // 慢调用阈值 80%,超过则视为失败
            .slowCallRateThreshold(80)
            // 慢调用时间阈值 2s
            .slowCallDurationThreshold(Duration.ofSeconds(2))
            // 半开状态下允许的探测请求数
            .permittedNumberOfCallsInHalfOpenState(5)
            // 熔断后等待 30s 进入半开
            .waitDurationInOpenState(Duration.ofSeconds(30))
            // 滑动窗口大小:最近 100 次调用
            .slidingWindowSize(100)
            // 最小调用次数才计算失败率
            .minimumNumberOfCalls(20)
            .build();

        return CircuitBreakerRegistry.of(config);
    }
}

@Service
public class OrderService {

    private final CircuitBreaker circuitBreaker;
    private final PaymentClient paymentClient;

    public OrderService(CircuitBreakerRegistry registry, PaymentClient paymentClient) {
        this.circuitBreaker = registry.circuitBreaker("payment");
        this.paymentClient = paymentClient;
    }

    public PaymentResult processPayment(Order order) {
        // 使用熔断器包装下游调用
        return Decorators.ofSupplier(() -> paymentClient.charge(order))
            .withCircuitBreaker(circuitBreaker)
            .withFallback(e -> {
                log.warn("[熔断] 支付服务异常,触发降级:{}", e.getMessage());
                return PaymentResult.deferred(order.getId());
            })
            .decorate()
            .get();
    }
}

4.3 Sentinel 实现(阿里生态)

/**
 * Sentinel 熔断规则配置
 */
@Component
public class SentinelCircuitBreakerInit {

    @PostConstruct
    public void init() {
        List<DegradeRule> rules = new ArrayList<>();

        // 规则:响应时间超过 1s 且持续 5 个请求,触发熔断,冷却 10s
        DegradeRule slowRtRule = new DegradeRule("orderService")
            .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
            .setCount(1000)           // 慢调用阈值:1000ms
            .setTimeWindow(10)        // 熔断时长 10s
            .setStatIntervalMs(1000)  // 统计周期 1s
            .setSlowRatioThreshold(0.5) // 慢调用比例阈值 50%
            .setMinRequestAmount(5);   // 最小触发请求数

        // 规则:异常比例超过 50% 触发熔断
        DegradeRule errorRatioRule = new DegradeRule("orderService")
            .setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType())
            .setCount(0.5)
            .setTimeWindow(30)
            .setMinRequestAmount(10);

        rules.add(slowRtRule);
        rules.add(errorRatioRule);
        DegradeRuleManager.loadRules(rules);
    }
}

五、限流模式:令牌桶、漏桶与滑动窗口

5.1 三种经典算法对比

  • 令牌桶(Token Bucket):以固定速率生成令牌,请求消耗令牌。允许突发流量,适用于 API 网关限流。
  • 漏桶(Leaky Bucket):请求进入固定容量的队列,以恒定速率流出。强制平滑流量,适用于下游保护。
  • 滑动窗口(Sliding Window):统计最近时间窗口内的请求数,精确但内存开销较大。适用于细粒度限流。

5.2 Guava RateLimiter(令牌桶)

/**
 * Guava 令牌桶限流:支持突发流量快速获取许可
 */
@Service
public class TokenBucketRateLimiter {

    // QPS = 100,即每秒生成 100 个令牌
    private final RateLimiter rateLimiter = RateLimiter.create(100.0);

    public ApiResponse handleRequest(Request req) {
        // 尝试获取许可,最多等待 200ms
        boolean acquired = rateLimiter.tryAcquire(200, TimeUnit.MILLISECONDS);
        if (!acquired) {
            return ApiResponse.reject("系统繁忙,请稍后重试");
        }
        return processBusinessLogic(req);
    }

    // 预热令牌桶:启动时逐步增加 QPS,避免冷启动压垮下游
    public void initWarmup() {
        RateLimiter warmingLimiter = RateLimiter.create(
            1000.0,           // 目标 QPS
            10,               // 预热时间
            TimeUnit.SECONDS  // 从 0 逐步提升到 1000 QPS
        );
    }
}

5.3 漏桶算法自实现

/**
 * 漏桶限流器:请求进入队列,以固定速率处理
 */
public class LeakyBucketLimiter {

    private final BlockingQueue<Runnable> queue;
    private final int leakRatePerSecond;
    private volatile boolean running = true;

    public LeakyBucketLimiter(int capacity, int leakRatePerSecond) {
        this.queue = new LinkedBlockingQueue<>(capacity);
        this.leakRatePerSecond = leakRatePerSecond;
        startLeaking();
    }

    // 启动漏桶线程,匀速处理队列中的请求
    private void startLeaking() {
        Thread leakThread = new Thread(() -> {
            long intervalMs = 1000 / leakRatePerSecond;
            while (running) {
                try {
                    Runnable task = queue.poll(intervalMs, TimeUnit.MILLISECONDS);
                    if (task != null) {
                        task.run(); // 匀速执行
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });
        leakThread.setName("leaky-bucket-worker");
        leakThread.start();
    }

    // 尝试将请求放入漏桶,桶满则拒绝
    public boolean tryEnqueue(Runnable task) {
        return queue.offer(task); // 非阻塞,立即返回
    }

    public void shutdown() {
        running = false;
    }
}

5.4 滑动窗口计数器(Redis 实现)

/**
 * 基于 Redis 的滑动窗口限流(分布式场景)
 */
@Component
public class SlidingWindowRateLimiter {

    private final StringRedisTemplate redisTemplate;
    private static final String PREFIX = "ratelimit:";

    /**
     * 检查并记录请求是否在允许范围内
     * @param key     限流维度(用户 ID / IP / API)
     * @param limit   窗口内最大请求数
     * @param window  窗口大小(秒)
     * @return true 表示允许通过
     */
    public boolean isAllowed(String key, int limit, int window) {
        String redisKey = PREFIX + key;
        long now = System.currentTimeMillis();
        long windowStart = now - window * 1000L;

        // 使用 Redis 有序集合,移除窗口外的旧记录
        redisTemplate.opsForZSet().removeRangeByScore(redisKey, 0, windowStart);

        // 统计当前窗口内的请求数
        Long currentCount = redisTemplate.opsForZSet().zCard(redisKey);
        if (currentCount != null && currentCount >= limit) {
            return false; // 已超限
        }

        // 记录本次请求时间戳
        redisTemplate.opsForZSet().add(redisKey, String.valueOf(now), now);
        redisTemplate.expire(redisKey, window + 1, TimeUnit.SECONDS);
        return true;
    }
}

六、降级策略:功能开关与静态兜底

6.1 降级层次模型

降级层级触发条件手段用户体验
L1 优雅降级下游响应慢异步化 + 缓存返回延迟略增
L2 功能关闭服务不可用功能开关关闭非核心功能功能受限,核心可用
L3 静态兜底全链路故障返回静态页面 / 默认数据只读浏览
L4 页面降级前端异常CDN 返回离线页面基础信息可见

6.2 功能开关实现(基于 Apollo / Nacos 配置中心)

/**
 * 功能开关管理器:动态控制特性的开启与降级
 */
@Component
public class FeatureSwitchManager {

    @ApolloConfigChangeInterceptor
    private Config config;

    // 使用本地缓存减少配置中心查询压力
    private final Map<String, Boolean> featureCache = new ConcurrentHashMap<>();

    /**
     * 判断功能是否开启
     */
    public boolean isEnabled(String featureKey, boolean defaultValue) {
        return featureCache.computeIfAbsent(featureKey, k ->
            config.getBooleanProperty(k, defaultValue)
        );
    }

    /**
     * 灰度发布:按用户 ID 百分比逐步放量
     */
    public boolean isEnabledForUser(String featureKey, String userId, int percentage) {
        if (!isEnabled(featureKey, false)) {
            return false;
        }
        int hash = Math.abs(userId.hashCode()) % 100;
        return hash < percentage;
    }

    /**
     * 监听配置变更,实时刷新本地缓存
     */
    @ApolloConfigChangeListener
    public void onChange(ConfigChangeEvent event) {
        for (String key : event.changedKeys()) {
            if (key.startsWith("feature.")) {
                featureCache.remove(key); // 清除缓存,下次读取新值
                log.info("[开关] 功能配置变更: {} = {}", key, event.getChange(key).getNewValue());
            }
        }
    }
}

// 业务层使用示例
@Service
public class RecommendationService {

    @Autowired private FeatureSwitchManager featureSwitch;
    @Autowired private MLModelClient modelClient;
    @Autowired private CacheManager cacheManager;

    public List<Item> getRecommendations(String userId) {
        // 若推荐模型故障,降级到规则引擎
        if (!featureSwitch.isEnabled("feature.ml.recommendation", true)) {
            return getRuleBasedRecommendations(userId);
        }

        // 灰度放量新模型
        if (featureSwitch.isEnabledForUser("feature.ml.newmodel", userId, 10)) {
            return modelClient.callNewModel(userId);
        }
        return modelClient.callLegacyModel(userId);
    }

    private List<Item> getRuleBasedRecommendations(String userId) {
        // 基于热门榜单等简单规则的兜底推荐
        return cacheManager.getHotItems(20);
    }
}

6.3 静态降级兜底(Feign Fallback)

/**
 * Feign 客户端静态降级工厂
 */
@Component
@Slf4j
public class InventoryServiceFallbackFactory implements FallbackFactory<InventoryServiceClient> {

    @Override
    public InventoryServiceClient create(Throwable cause) {
        log.error("[降级] 库存服务调用异常,触发静态兜底: {}", cause.getMessage());
        return new InventoryServiceClient() {
            @Override
            public StockCheckResult checkStock(Long skuId) {
                // 降级时默认假设有库存,避免误拦截正常下单
                return StockCheckResult.available(skuId, Integer.MAX_VALUE);
            }

            @Override
            public DeductResult deductStock(Long skuId, int quantity) {
                // 库存扣减失败,转异步消息队列补偿
                return DeductResult.asyncQueued(skuId, quantity);
            }
        };
    }
}

@FeignClient(
    name = "inventory-service",
    fallbackFactory = InventoryServiceFallbackFactory.class
)
public interface InventoryServiceClient {
    @GetMapping("/stock/{skuId}")
    StockCheckResult checkStock(@PathVariable Long skuId);
}

七、Kubernetes 高可用编排

7.1 控制平面多主节点部署

K8s 控制平面包含 API Server、Scheduler、Controller Manager 与 etcd。生产环境至少部署 3 个主节点组成高可用集群。

# kubeadm 初始化配置:三主节点高可用集群
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: stable
controlPlaneEndpoint: "k8s-api.example.com:6443"  # 负载均衡器虚拟 IP
 networking:
  podSubnet: "10.244.0.0/16"
  serviceSubnet: "10.96.0.0/12"
etcd:
  local:
    dataDir: /var/lib/etcd
    extraArgs:
      # etcd 心跳与选举超时调整(跨可用区时适当增大)
      heartbeat-interval: "250"
      election-timeout: "1250"
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: "10.0.1.10"  # 本节点 IP
  bindPort: 6443
nodeRegistration:
  name: master-1

7.2 etcd 备份与恢复

etcd 是 K8s 的唯一状态存储,必须定期备份。

#!/bin/bash
# etcd 定时备份脚本(建议每 15 分钟执行一次)
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%Y%m%d-%H%M%S).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

# 保留最近 72 个备份(约 18 小时)
ls -1tr /backup/etcd-*.db | head -n -72 | xargs -r rm -f

# etcd 灾难恢复流程(在目标节点执行)
# 1. 停止 kubelet 与所有控制平面容器
# 2. 恢复数据:etcdctl snapshot restore snapshot.db --data-dir=/var/lib/etcd-restored
# 3. 更新 /var/lib/etcd 指向恢复后的目录
# 4. 重启 kubelet,等待集群自愈

7.3 PodDisruptionBudget 保障可用性

# PodDisruptionBudget:确保驱逐时至少保留指定数量的副本
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-server-pdb
  namespace: production
spec:
  minAvailable: 2           # 或 maxUnavailable: 1
  selector:
    matchLabels:
      app: api-server
---
# 配合反亲和性实现物理机 / AZ 级别分散
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 3
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - api-server
              topologyKey: topology.kubernetes.io/zone  # 跨可用区调度

7.4 水平 Pod 自动伸缩(HPA)与集群自动伸缩(CA)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 3
  maxReplicas: 50
  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"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
        - type: Percent
          value: 100
          periodSeconds: 15   # 15s 内最多扩容 100%
    scaleDown:
      stabilizationWindowSeconds: 300  # 缩容冷却 5min,防止抖动

八、数据库高可用

8.1 MySQL Group Replication(MGR)

MySQL Group Replication 提供原生多主集群能力,自动故障检测与切换。

# my.cnf — MGR 节点配置
[mysqld]
server_id = 1                      # 每个节点唯一
bind_address = 0.0.0.0
binlog_format = ROW
log_bin = mysql-bin
log_slave_updates = ON

# Group Replication 插件加载
plugin_load_add = group_replication.so
group_replication_group_name = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
group_replication_start_on_boot = OFF
group_replication_local_address = "10.0.1.11:33061"
group_replication_group_seeds = "10.0.1.11:33061,10.0.1.12:33061,10.0.1.13:33061"

# 单主模式(生产推荐,减少冲突)
group_replication_single_primary_mode = ON
group_replication_enforce_update_everywhere_checks = OFF

# 事务一致性级别:保证读取时数据已同步到多数节点
group_replication_consistency = EVENTUAL

8.2 PostgreSQL Patroni + etcd 高可用

Patroni 是 PostgreSQL 的开源高可用模板,依赖分布式共识存储(etcd / ZooKeeper / Consul)管理主备状态。

# patroni.yml — Patroni 配置示例
scope: postgres_cluster
namespace: /service/
name: pg_node_1

restapi:
  listen: 10.0.1.21:8008
  connect_address: 10.0.1.21:8008

etcd:
  hosts: 10.0.1.10:2379,10.0.1.11:2379,10.0.1.12:2379

bootstrap:
  dcs:
    ttl: 30                    # 租约存活时间
    loop_wait: 10              # 健康检查间隔
    retry_timeout: 10          # 重试超时
    maximum_lag_on_failover: 1048576  # 备库最大落后字节数,超限不参与选举
    master_start_timeout: 300
    synchronous_mode: true     # 同步复制模式,保证 RPO≈0
    synchronous_mode_strict: false
  initdb:
    - encoding: UTF8
    - locale: en_US.UTF-8
  pg_hba:
    - host replication replicator 10.0.1.0/24 md5
    - host all all 10.0.1.0/24 md5

postgresql:
  listen: 10.0.1.21:5432
  connect_address: 10.0.1.21:5432
  data_dir: /var/lib/postgresql/data
  pgpass: /tmp/pgpass
  authentication:
    replication:
      username: replicator
      password: repl_pass
    superuser:
      username: postgres
      password: super_pass
  parameters:
    wal_level: replica
    max_wal_senders: 10
    max_replication_slots: 10
    hot_standby: "on"

tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false
  nosync: false

8.3 数据库读写分离与延迟检测

/**
 * 动态数据源路由:写操作走主库,读操作走从库并检测延迟
 */
@Component
public class RoutingDataSource extends AbstractRoutingDataSource {

    private final ReplicationHealthChecker healthChecker;

    @Override
    protected Object determineCurrentLookupKey() {
        // 写请求强制路由到主库
        if (TransactionSynchronizationManager.isCurrentTransactionReadOnly() == false
            && DbContextHolder.isWriteOperation()) {
            return "master";
        }

        // 读请求:若从库延迟过大降级到主库
        List<String> healthySlaves = healthChecker.getHealthyReplicas(1000); // 延迟阈值 1s
        if (!healthySlaves.isEmpty()) {
            // 轮询选择健康从库
            String selected = healthySlaves.get(
                (int) (System.currentTimeMillis() % healthySlaves.size())
            );
            return selected;
        }

        // 所有从库均不健康,回退主库
        return "master";
    }
}

九、负载均衡高可用

9.1 LVS(Linux Virtual Server)DR 模式

LVS 工作在内核态,性能极高,适合四层负载均衡。

#!/bin/bash
# LVS-DR 模式 Director 配置脚本

VIP="10.0.0.100"
RIPS=("10.0.0.11" "10.0.0.12" "10.0.0.13")

# 1. 配置 VIP 到本地回环网卡
ip addr add $VIP/32 dev lo:0

# 2. 抑制 ARP 响应,避免 VIP 冲突
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce

# 3. 使用 ipvsadm 配置虚拟服务
ipvsadm -A -t $VIP:80 -s wrr          # 加权轮询调度算法
for rip in "${RIPS[@]}"; do
    ipvsadm -a -t $VIP:80 -r $rip:80 -g  # -g 表示 DR 模式
    echo "[LVS] 添加真实服务器: $rip"
done

ipvsadm -Ln  # 查看当前规则

# 4. 健康检查脚本(配合 keepalived 使用)
# keepalived 配置见下文

9.2 Keepalived + HAProxy 高可用组合

# /etc/keepalived/keepalived.conf
vrrp_script chk_haproxy {
    script "/usr/bin/killall -0 haproxy"  # 检查 haproxy 进程存活
    interval 2                            # 每 2s 检查一次
    weight -20                            # 失败时降低优先级
    fall 3                                # 连续 3 次失败才判定故障
    rise 2                                # 连续 2 次成功恢复
}

vrrp_instance VI_1 {
    state MASTER           # 另一台设为 BACKUP
    interface eth0
    virtual_router_id 51   # 虚拟路由 ID,主备必须相同
    priority 100           # BACKUP 节点设为 90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1234abcd
    }
    virtual_ipaddress {
        10.0.0.100/24      # VIP 漂移地址
    }
    track_script {
        chk_haproxy
    }
    notify_master "/etc/keepalived/notify.sh master"
    notify_backup "/etc/keepalived/notify.sh backup"
    notify_fault  "/etc/keepalived/notify.sh fault"
}
# /etc/haproxy/haproxy.cfg
global
    maxconn 4096
    daemon

defaults
    mode http
    timeout connect 5s
    timeout client  50s
    timeout server  50s
    option httpchk GET /health

frontend http_front
    bind *:80
    default_backend app_servers

backend app_servers
    balance roundrobin
    option httpchk GET /health
    server app1 10.0.0.11:8080 check weight 3
    server app2 10.0.0.12:8080 check weight 3
    server app3 10.0.0.13:8080 check weight 3 backup  # 备用节点

9.3 NGINX 七层负载均衡与健康检查

# nginx.conf — 高可用负载均衡配置
upstream backend_cluster {
    least_conn;                          # 最少连接数算法
    server 10.0.0.11:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.0.13:8080 weight=2 backup;  # 仅在前端均不可用时启用

    # NGINX Plus / OpenResty 可启用主动健康检查
    # health_check interval=5s fails=3 passes=2;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend_cluster;
        proxy_connect_timeout 5s;
        proxy_send_timeout 10s;
        proxy_read_timeout 10s;

        # 连接失败时重试其他后端
        proxy_next_upstream error timeout http_502 http_503;
        proxy_next_upstream_tries 2;
    }

    location /health {
        # 用于负载均衡器探测的简捷健康端点
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

十、混沌工程验证

10.1 混沌工程原则

混沌工程不是盲目制造故障,而是通过 假设-验证-改进 的闭环提升系统韧性:

  1. 建立稳态假设:定义系统正常运行的可观测指标(QPS、错误率、P99 延迟)。
  2. 引入真实世界事件:网络延迟、节点宕机、磁盘满载、时钟偏移。
  3. 生产环境运行:在真实流量下验证才能暴露隐蔽问题。
  4. 最小化爆炸半径:从非高峰时段、小范围流量开始。

10.2 Chaos Mesh 实验配置

Chaos Mesh 是云原生混沌工程平台,支持 K8s 环境下的丰富故障注入。

# network-delay.yaml — 模拟跨可用区网络延迟
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: az-network-delay
  namespace: chaos-testing
spec:
  action: delay
  mode: all
  selector:
    namespaces:
      - production
    labelSelectors:
      app: order-service
  delay:
    latency: "50ms"           # 注入 50ms 延迟
    correlation: "100"        # 相关性 100%
    jitter: "10ms"            # 抖动范围
  duration: "10m"             # 实验持续 10 分钟
  scheduler:
    cron: "@every 24h"        # 每天定时执行

---
# pod-kill.yaml — 随机杀死 Pod 验证自愈能力
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: random-pod-kill
  namespace: chaos-testing
spec:
  action: pod-kill
  mode: one                   # 每次只杀死一个 Pod
  selector:
    namespaces:
      - production
    labelSelectors:
      app: payment-service
  scheduler:
    cron: "0 */6 * * *"       # 每 6 小时执行一次

---
# stress-test.yaml — CPU / 内存压力测试
apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
  name: cpu-memory-stress
  namespace: chaos-testing
spec:
  mode: all
  selector:
    labelSelectors:
      app: api-gateway
  stressors:
    cpu:
      workers: 4
      load: 80                # CPU 负载 80%
    memory:
      workers: 2
      size: "512Mi"           # 内存分配压力
  duration: "5m"

10.3 自动化混沌实验流水线

# .github/workflows/chaos-experiment.yml
name: Weekly Chaos Engineering

on:
  schedule:
    - cron: '0 2 * * 0'   # 每周日凌晨 2 点执行

jobs:
  chaos:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup kubectl
        uses: azure/setup-kubectl@v3

      - name: Install Chaos Mesh
        run: |
          curl -sSL https://mirrors.chaos-mesh.org/latest/install.sh | bash

      - name: Run Network Delay Experiment
        run: |
          kubectl apply -f chaos-experiments/network-delay.yaml
          sleep 600  # 等待 10 分钟实验完成

      - name: Verify SLO
        run: |
          # 查询 Prometheus:P99 延迟应 < 500ms,错误率 < 0.1%
          ./scripts/verify-slo.sh --latency-p99=500 --error-rate=0.001

      - name: Cleanup Experiments
        if: always()
        run: |
          kubectl delete -f chaos-experiments/ --ignore-not-found

十一、综合防御体系:多层高可用架构全景

从最外层 CDN 到最底层数据库,每一层都需具备冗余与自愈能力:

┌─────────────────────────────────────────────────────────────┐
│                    CDN / WAF / DDoS 防护                     │
│                   全球 Anycast + 边缘缓存                     │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────────────▼──────────────────────────────────────┐
│              全局流量管理 (GTM / Geo-DNS)                    │
│            按地理位置与健康状态调度到最近集群                   │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────────────▼──────────────────────────────────────┐
│              负载均衡层 (LVS / HAProxy / NGINX)              │
│           四层 / 七层负载均衡 + 健康检查 + 会话保持            │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────────────▼──────────────────────────────────────┐
│              API 网关层 (Spring Cloud Gateway / Istio)       │
│     认证鉴权 + 限流熔断 + 灰度发布 + 请求染色                 │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────────────▼──────────────────────────────────────┐
│              微服务层 (Kubernetes + Service Mesh)            │
│     多副本 + HPA + PDB + 反亲和性 + 优雅启停                 │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────────────▼──────────────────────────────────────┐
│              数据层 (MGR / Patroni / Redis Cluster)          │
│     主从复制 + 读写分离 + 自动切换 + 异构备份                 │
└─────────────────────────────────────────────────────────────┘

十二、常见问题 FAQ

Q1:同城双活和异地多活的核心差异是什么?

同城双活依赖同步复制实现 RPO≈0,延迟低,适合金融级强一致场景;异地多活采用异步复制,容忍秒级数据延迟,通过单元化架构实现全球就近访问与水平扩展。选择时需综合业务一致性要求与部署成本。

Q2:熔断器打开后,如何确保流量恢复时系统不会立即被压垮?

熔断器在半开(Half-Open)状态下只允许少量探测请求通过,若探测成功率达标才逐步关闭熔断。配合渐进式放量(如 5% → 25% → 100%)和 Warmup 机制,可避免冷启动流量洪峰。

Q3:Kubernetes 中 etcd 备份应该多久执行一次?

生产环境建议每 15~30 分钟执行一次增量逻辑备份,每天执行一次完整快照备份。备份文件应存储在独立的对象存储(如 S3 / OSS)中,并定期演练恢复流程,确保 RTO 可预期。

Q4:限流与降级的触发顺序应该如何设计?

标准顺序为:限流(保护系统不被流量冲垮)→ 熔断(隔离故障下游)→ 降级(关闭非核心功能释放资源)。限流作用于入口,熔断作用于依赖,降级作用于业务功能,三者互补形成纵深防御。

Q5:混沌工程实验会不会影响真实用户?

通过以下措施最小化影响:1) 选择低峰时段执行;2) 严格控制爆炸半径(小范围节点 / 小比例流量);3) 配置自动熔断与回滚机制;4) 预先定义 SLO 基线与自动终止条件。成熟的企业会建立 “混沌日” 机制,将故障演练常态化。


十三、总结

分布式高可用不是单一技术的堆砌,而是从架构设计、编码实践到运维演练的系统工程。本文覆盖的完整技术栈可归纳为:

  • 架构层:多活部署、单元化、故障域隔离
  • 流量层:智能路由、负载均衡、服务网格
  • 防护层:限流、熔断、降级、超时重试
  • 基础设施层:K8s 多主、etcd 备份、数据库 MGR/Patroni
  • 验证层:混沌工程、容灾演练、SLO 监控

建议读者从自身业务的 RTO/RPO 指标出发,按优先级逐步落地:先完成单中心内的冗余与自动恢复,再扩展为多活架构,最后引入混沌工程持续验证。高可用的终极目标不是 “永不失败”,而是 “失败时用户无感知”。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「distributed-systems」更多文章

  1. 分布式链路追踪实战:OpenTelemetry、Jaeger 与 W3C Trace Context
  2. 分布式缓存深度策略:Redis Cluster、一致性哈希与多级缓存架构
  3. 分布式消息队列深度选型:Kafka、RocketMQ、Pulsar 与 RabbitMQ 多维对比