故障预防措施

某创业公司的技术团队,在成立两年内经历了 3 次 P1 级故障之后,终于痛下决心:花一年时间,系统性地建设故障防护体系。

一年后,这家公司的故障率下降了 80%,平均故障恢复时间(MTTR)从 45 分钟缩短到 8 分钟。这一节就用他们的真实经历,讲述故障预防应该怎么做。

背景:他们的问题出在哪

在开始建设防护体系之前,这家公司的问题是典型的「创业公司早期积累」:

  • 发布流程靠人肉:代码 review 全靠同事之间的口头交流,没有强制机制
  • 数据库没有备份验证:有备份脚本,但从来没执行过恢复
  • 监控只报警不联动:告警发到群里,值班工程师要自己判断怎么处理
  • 没有限流和熔断:外部服务挂了,调用方全部超时,最终拖垮整个系统
  • 测试环境形同虚设:测试环境和生产环境配置完全不同,很多问题测不出来

简单来说,这家公司的问题不是「某个技术点没做好」,而是整个非功能性保障体系几乎为零

第一阶段(第 1~3 个月):打牢基础

1. 建立强制 Code Review 机制

Code Review 是最简单也最有效的质量保障手段,问题是很多团队把它做成了形式——「帮忙点个同意吧」。

这家公司做了一件关键的事:把 Code Review 和 CI/CD 流程绑定。没有通过 review 的代码,合并按钮是灰的,CI 流水线跑不过。

.github/workflows/pr-check.yml
name: PR Quality Gates
on:
  pull_request:
    branches: [main]

jobs:
  quality-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Run tests
        run: npm test

      - name: Check branch protection
        run: |
          # 强制要求至少 1 人 approval
          approvals=$(gh pr view ${{ github.event.pull_request.number }} --json reviews -q '.reviews[] | select(.state == "APPROVED") | .author.login' | wc -l)
          if [ "$approvals" -lt 1 ]; then
            echo "需要至少 1 人审批才能合并"
            exit 1
          fi

但真正让 Code Review 起作用的,不是工具强制,而是建立了 Review Checklist

Code
## 必须检查的项目

### 异常处理
- [ ] 是否有 try-catch?catch 后的处理是否合理?
- [ ] 是否吞掉了异常(空的 catch 块)?
- [ ] 是否在 finally 中释放了资源?

### 并发安全
- [ ] 共享变量是否做了线程安全处理?
- [ ] 是否引入了新的线程池?配置是否合理?
- [ ] 是否有死锁风险?

### 数据库
- [ ] 是否有新 SQL?是否验证了执行计划?
- [ ] 是否有大批量操作?是否做了分批处理?
- [ ] 是否使用了事务?事务边界是否合理?

### 性能
- [ ] 是否有 N+1 查询风险?
- [ ] 是否有大对象一次性加载到内存?
- [ ] 循环内是否有数据库或网络操作?

### 安全
- [ ] 是否有 SQL 注入风险?
- [ ] 敏感数据是否做了脱敏处理?
- [ ] 是否有新的外部接口调用?是否配置了超时?

2. 完善测试环境

测试环境和生产环境的差异,是很多故障的隐藏根源。测试的时候好好的,上线就出问题——往往就是因为测试环境根本测不出问题。

这家公司花了 2 个月做测试环境对齐:

# 测试环境问题清单
问题 1:测试库数据量太小
 现状:测试库只有 1000 条记录
 真相:生产库有 5000 万条,索引在生产环境才生效
 修复:每月从生产库脱敏导出数据,同步到测试库

问题 2:测试环境网络配置和线上不一致
 现状:测试环境直连数据库,没有经过网关
 真相:上线后网关层的限流规则在测试环境没有覆盖
 修复:测试环境强制走网关链路,和生产完全一致

问题 3:测试环境配置参数和生产不一致
 现状:JVM 堆大小测试环境 512MB,生产 4GB
 真相:内存敏感的功能在测试环境表现正常,生产触发 OOM
 修复:测试环境和生产环境参数完全对齐

3. 建立监控体系

创业公司常见的监控问题是:有监控,但不知道看什么

他们的策略是从业务视角出发倒推监控指标

业务视角:用户能正常下单吗?

↓ 拆解

前端:页面能打开吗? → 监控 HTTP 5xx 率
网关:请求能进来吗? → 监控网关 QPS 和延迟
服务:业务逻辑能跑通吗? → 监控核心接口成功率
数据库:数据能读写吗? → 监控数据库连接池和慢查询
缓存:缓存能命中吗? → 监控缓存命中率

然后,他们定义了告警阈值

alerting/prometheus_rules.yml
groups:
  - name: business_health
    rules:
      # 核心接口成功率
      - alert: CoreApiSuccessRateLow
        expr: |
          sum(rate(http_requests_total{status=~"2.."}[5m]))
          / sum(rate(http_requests_total[5m])) < 0.99
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "核心接口成功率低于 99%"
          description: "{{ $labels.job }} 接口 {{ $labels.path }} 成功率 {{ $value | humanizePercentage }}"

      # 数据库慢查询
      - alert: DatabaseSlowQueryHigh
        expr: |
          sum(rate(mysql_slow_queries_total[5m])) > 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "数据库慢查询过多"
          description: "过去 5 分钟慢查询速率 {{ $value }}/s"

      # 缓存命中率
      - alert: CacheHitRateLow
        expr: |
          sum(rate(cache_hits_total[5m]))
          / sum(rate(cache_requests_total[5m])) < 0.8
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "缓存命中率低于 80%"
          description: "当前命中率 {{ $value | humanizePercentage }}"

关键的一点是:告警之后必须有联动。他们接入了 PagerDuty,告警自动触发值班工程师的电话,不接听就升级。

第二阶段(第 4~6 个月):构建容错能力

4. 限流与熔断

在有一次外部支付渠道挂了导致整站瘫痪之后,他们终于意识到:必须有容错机制,否则任何一个依赖方的故障都会扩散到整个系统

// 引入 Sentinel 作为限流和熔断组件
@Configuration
public class SentinelConfig {

    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}

// 对外部支付接口配置熔断
@Service
public class PaymentService {

    @SentinelResource(value = "payment",
        fallback = "paymentFallback",
        blockHandler = "paymentBlockHandler")
    public PaymentResult pay(Order order) {
        return paymentGateway.process(order);
    }

    // 降级方法:支付失败时的兜底处理
    public PaymentResult paymentFallback(Order order, Throwable t) {
        log.warn("Payment service failed, return pending: {}", t.getMessage());
        // 记录为待支付状态,让用户稍后重试
        return PaymentResult.pending(order.getId());
    }

    // 限流方法:被限流时的处理
    public PaymentResult paymentBlockHandler(Order order, BlockException e) {
        log.warn("Payment rate limited: {}", e.getMessage());
        return PaymentResult.retryLater(order.getId());
    }
}
Sentinel
sentinel:
  rules:
    # 支付接口限流:每秒最多 1000 次调用
    flow:
      - resource: /api/payment
        grade: 1              # QPS 模式
        count: 1000
        controlBehavior: 0    # 直接拒绝

    # 外部渠道熔断:当错误率 > 30% 时熔断 10 秒
    degrade:
      - resource: payment-gateway
        grade: 1              # 错误率模式
        count: 0.3
        timeWindow: 10        # 熔断持续 10 秒
        minRequestAmount: 10  # 至少 10 个请求才触发熔断

5. 灰度发布

发布是故障的高发节点。哪怕经过充分测试,生产环境的真实流量和测试环境仍有差异。很多问题只有在真实流量下才会暴露。

他们选择了金丝雀发布(Canary Release)

Argo
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: order-service
spec:
  replicas: 10
  strategy:
    canary:
      steps:
        - setWeight: 5      # 初始 5% 流量到新版本
        - pause: {duration: 10m}  # 观察 10 分钟
        - setWeight: 20     # 扩大到 20%
        - pause: {duration: 10m}
        - setWeight: 50     # 扩大到 50%
        - pause: {duration: 10m}
        - setWeight: 100    # 全量
      canaryMetadata:
        labels:
          role: canary
      stableMetadata:
        labels:
          role: stable
      trafficRouting:
        nginx:
          stableIngress: stable-ingress
          additionalIngressAnnotations:
            canary-by-header: X-Canary

同时配置了自动回滚条件

# 自动回滚触发条件
rollback:
  triggers:
    - type: ErrorRate
      threshold: 0.05      # 错误率超过 5% 自动回滚
    - type: Latency
      threshold: 2000ms    # p99 延迟超过 2s 自动回滚

6. 故障演练

知道系统能扛住故障,和真的验证过能扛住,是两回事。这家公司每季度做一次故障演练(Chaos Engineering)

Chaos
# 演练场景 1:数据库连接耗尽
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: simulate-db-connection-exhaustion
spec:
  action: delay
  mode: one
  selector:
    namespaces:
      - production
    labelSelectors:
      app: order-service
  delay:
    latency: "5000ms"
  target:
    mode: one
    selector:
      namespaces:
        - production
      labelSelectors:
        db: mysql

---
# 演练场景 2:Redis 不可用
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: redis-node-failure
spec:
  action: pod-kill
  mode: one
  selector:
    namespaces:
      - production
    labelSelectors:
      app: redis

每次演练之后,他们会记录:

故障演练报告 - 2026-01-15

演练场景:Redis 单节点宕机
预期结果:Redis 自动主从切换,服务在 30 秒内恢复
实际结果:服务在 45 秒内恢复(慢了 15 秒)
差距分析:应用端没有配置 Redis 连接池重连策略,
          故障发生后旧连接没有被及时释放
改进措施:应用端增加连接池自动重连逻辑
完成时间:2 月 15 日
状态:已完成

第三阶段(第 7~12 个月):持续改进

7. 数据安全体系

在有一次误删数据的惊魂之后(幸好有 binlog 恢复回来了),他们把数据安全提到了最高优先级:

  • 所有生产库开启 binlog:每条数据变更都有记录
  • 备份 3-2-1 原则:3 份副本、2 种介质、1 份异地
  • 每月恢复演练:每个备份集都要能成功恢复
# 数据备份策略
# 每小时增量备份(binlog)
00 * * * * mysqladmin flush-logs

# 每天凌晨 3 点全量备份
0 3 * * * mysqldump --single-transaction --routines --triggers \
  --master-data=2 --all-databases | gzip > /backup/mysql_$(date +%Y%m%d).sql.gz

# 每周同步到异地对象存储
0 4 * * 0 s3 sync /backup/ s3://company-backup/mysql/

8. 变更管理

他们发现,故障最常见的触发点是一次「小改动」——看起来不起眼的配置变更,往往造成灾难性后果。

于是建立了变更管理流程

变更类型:
- 普通变更:代码提交、配置修改,需要 Code Review
- 高危变更:生产数据库写操作、高危命令执行,需要额外审批 + 变更窗口
- 紧急变更:故障修复中的临时变更,需要事后复盘

变更窗口:
- 常规发布窗口:周二、周四 14:00 - 17:00
- 禁止变更窗口:周五 17:00 后、节假日全天

9. 事后复盘机制

每一次故障,无论大小,都必须有复盘。他们的复盘机制有一个关键规则:复盘不追责,但改进措施必须有人负责

复盘改进跟踪表
| 复盘编号 | 故障日期 | 主要改进措施 | 负责人 | 计划完成 | 状态 |
| --- | --- | --- | --- | --- | --- |
| INC-001 | 2026-01-15 | 增加 Redis 重连策略 | @张三 | 2026-02-15 | 已完成 |
| INC-002 | 2026-03-20 | 备份恢复演练标准化 | @李四 | 2026-04-20 | 进行中 |
| INC-003 | 2026-06-01 | 限流规则细化 | @王五 | 2026-07-01 | 待开始 |

一年后的结果

12 个月后,防护体系建设的成效体现在数字上:

指标建设前(年均)建设后(年均)变化
P1/P2 故障数15 次3 次-80%
平均 MTTR45 分钟8 分钟-82%
监控覆盖率30%95%+217%
灰度发布比例10%80%+700%
数据备份成功率60%100%+67%

总结

这家公司的经历说明一个道理:故障预防不是一次性工程,而是持续投入的系统性建设

回顾他们的路径,可以总结出三层防护体系:

第一层:代码质量(最基础)

  • 强制 Code Review + CI 质量门禁
  • 测试环境与生产环境完全对齐
  • 充分的单元测试和集成测试

第二层:容错设计(防止扩散)

  • 限流 + 熔断,防止依赖方故障拖垮系统
  • 灰度发布 + 自动回滚,把故障影响控制在最小范围
  • 故障演练,定期验证系统的容错能力

第三层:监控与数据安全(兜底手段)

  • 完整的业务监控,发现问题后第一时间告警
  • 数据多级备份 + 定期恢复演练,确保数据可恢复
  • 变更管理 + 事后复盘,从流程上减少人为失误

三层缺一不可。第一层做好,可以减少大部分代码质量问题;第二层做好,可以把故障影响控制在局部;第三层做好,可以在最坏的情况下依然有兜底手段。

下一节,我们来看看故障真的发生时,怎么做到快速止血、快速恢复。