4. 事件树分析(ETA):从初始事件到后果的全链路推演

事件树分析,说白了就是「如果这个坏了,接下来会发生什么」。

我刚开始做稳定性分析时,总喜欢直接拍脑袋说「这个故障会导致系统崩溃」。后来被架构师怼了一次:「你怎么知道一定会崩溃?中间有没有降级?有没有熔断?有没有重试?」

嗯,从那以后,我就老老实实用事件树了。

4.1 事件树的核心逻辑

事件树是一种正向推理方法。它从一个初始事件(比如「数据库连接池耗尽」)开始,沿着系统设计的防护措施逐层推演,最终得到各种可能的后果。

你可以把它想象成一个决策树,只不过每个节点不是「你选A还是B」,而是「这个防护措施生效了,还是失效了」。

关键概念:事件树不是用来分析「为什么会发生」,而是用来分析「发生后会怎样」。

我个人习惯把事件树分成三个要素:

  • 初始事件(IE):触发整个链条的那个故障
  • 安全功能(SF):系统里用来拦截故障的防护措施
  • 后果(Consequence):最终落到用户头上的结果

举个例子:

初始事件是「上游服务超时」。安全功能依次是:重试机制 → 熔断器 → 降级策略 → 兜底缓存。每个环节都有「成功」和「失败」两条分支。

你想想看,如果重试成功了,后面根本不会触发熔断。如果重试失败但熔断生效了,那后续流量就被挡住了。只有所有防护都失效,才会导致「用户看到500」这个最严重的后果。

4.2 事件树的构建方法

构建事件树其实不复杂,但需要你对系统的防护措施了如指掌。我一般按下面几步来:

4.2.1 确定初始事件

初始事件从哪里来?从故障模式库、从历史事故、从混沌工程实验里来。

我在项目中遇到过最典型的初始事件是「缓存节点宕机」。当时我们以为这只是个小问题,结果事件树一画,发现后面连着6层防护,其中2层已经形同虚设了。

我的经验:初始事件不要选得太宽泛。「网络故障」太笼统,要具体到「跨AZ网络延迟超过500ms」这种级别。

4.2.2 列出安全功能

按故障传播路径,把系统里所有可能拦截这个故障的机制列出来。顺序很重要——必须是故障实际经过的顺序。

比如一个典型的微服务调用链路:

  1. 客户端重试(SF1)
  2. 负载均衡健康检查(SF2)
  3. 服务端限流(SF3)
  4. 服务端熔断(SF4)
  5. 降级返回默认值(SF5)

注意,有些安全功能是并行的,有些是串行的。事件树只处理串行关系,并行的情况需要拆成多个分支。

4.2.3 逐层展开分支

每个安全功能都有两个分支:成功(S)和失败(F)。

从初始事件开始,第一层安全功能分出2条路径,第二层再各分出2条,以此类推。n个安全功能,最多有2ⁿ条路径。

但实际中很多路径会被剪枝——比如「重试成功」之后,后面的熔断、降级根本不会触发,那条路径就终止了。

注意:不要机械地画满2ⁿ条路径。事件树的价值在于「剪枝」,而不是「穷举」。我曾经见过有人画了64条路径的事件树,其中50条在现实中根本不可能发生。

4.3 成功/失败路径分析

路径分析的核心是回答两个问题:

  • 这条路径最终导致什么后果?
  • 这条路径发生的概率有多大?

我习惯把路径分成三类:

路径类型 描述 例子
安全路径 所有防护都生效,故障被完全隔离 重试成功 → 用户无感知
降级路径 部分防护失效,但最终有兜底 重试失败 → 熔断生效 → 降级返回缓存数据
失效路径 所有防护全部失效,故障直达用户 重试失败 → 熔断失效 → 降级失败 → 用户看到500

这里有个坑:很多人只关注失效路径,觉得「反正降级路径用户还能用,不用管」。但降级路径的体验往往是「能用但很难受」,比如返回了过期的数据、功能残缺、响应变慢。这些在稳定性分析里同样需要评估。

4.4 后果评估与概率计算

终于到了算账的时候。

4.4.1 后果评估

每条路径的后果,我一般从三个维度打分:

  • 影响范围:多少用户、多少业务受影响
  • 影响时长:故障持续多久
  • 影响程度:完全不可用 vs 部分功能受损

打分标准每个团队不一样,但至少要能区分出「可接受」和「不可接受」的后果。

我的习惯:后果评估一定要拉上业务方一起做。技术团队觉得「返回默认值」可以接受,业务方可能觉得「默认值导致订单金额错误」是P0事故。

4.4.2 概率计算

每条路径的概率 = 初始事件概率 × 各安全功能成功/失败概率的乘积。

举个例子:

初始事件「缓存节点宕机」的概率是 0.01(每100次请求发生1次)。

安全功能概率:

  • 重试成功概率:0.9(重试机制比较靠谱)
  • 熔断成功概率:0.95
  • 降级成功概率:0.8(降级逻辑写得比较糙)

那么「重试失败 → 熔断成功 → 降级成功」这条路径的概率就是:

0.01 × (1-0.9) × 0.95 × 0.8 = 0.00076

也就是每10万次请求,大约有76次会走这条降级路径。

而「所有防护全部失效」的概率是:

0.01 × (1-0.9) × (1-0.95) × (1-0.8) = 0.00001

每10万次请求只有1次。看起来很低,但如果你的系统每天处理1亿请求,那就是每天100次——这个数字你还觉得低吗?

避坑指南:概率数据从哪里来?我一般三个来源:线上监控的统计数据、混沌工程实验的实测数据、以及专家经验估算。千万不要只用「我觉得」来拍概率,那跟算命没区别。

4.5 事件树的可视化

光用文字描述事件树太抽象了。下面这张图展示了一个典型的事件树结构:

事件树示例:缓存节点宕机 缓存节点宕机 重试机制 熔断器 降级策略 成功:用户无感知 成功 失败 成功:熔断生效 成功 失败 成功:降级返回 成功 失败:用户看到500 失败 路径概率计算示例 初始事件概率:0.01 | 重试失败概率:0.1 | 熔断失败概率:0.05 | 降级失败概率:0.2 全部失效路径概率:0.01 × 0.1 × 0.05 × 0.2 = 0.00001(每10万次请求发生1次) 成功分支 失败分支 安全功能节点

4.6 事件树的常见误区

做了这么多年稳定性分析,我总结了几个人容易踩的坑:

误区一:忽略条件概率

安全功能的成功概率不是独立的。比如「熔断成功」的概率,在「重试已经失败3次」的条件下,和正常情况下是不一样的。我见过有人直接把独立概率相乘,算出来的结果跟实际差了10倍。

误区二:只画树,不算账

事件树的价值在于量化。如果你画完树不计算概率、不评估后果,那跟画个思维导图没区别。我曾经评审过一个事件树,画了30多个节点,但一个概率数字都没有——这种分析报告我直接打回去重做。

误区三:忽略时间因素

事件树默认是「瞬间完成」的,但现实中防护措施有响应时间。比如熔断器需要10秒才能触发,那在这10秒内系统可能已经崩溃了。我的建议是:在事件树旁边加一个时间轴,标注每个环节的延迟。

4.7 事件树与故障树的配合

最后说一句,事件树和故障树(FTA)是天生一对。

故障树从顶事件往下推,找原因。事件树从初始事件往上推,看后果。两者结合,就是完整的「原因 → 故障 → 后果」链条。

我自己的习惯是:先用故障树分析出关键初始事件,再用事件树分析每个初始事件的影响。这样既知道「为什么会坏」,也知道「坏了会怎样」。