第2302篇:AI系统的混沌工程——主动注入故障来验证系统韧性
第2302篇:AI系统的混沌工程——主动注入故障来验证系统韧性
适读人群:想提升AI系统可靠性的工程师和SRE | 阅读时长:约14分钟 | 核心价值:掌握AI系统特有的混沌实验设计,提前发现可靠性弱点
Netflix用混沌工程(Chaos Engineering)的理念很简单:与其等着生产环境出问题,不如主动在受控条件下制造问题,提前发现并修复系统的弱点。
对于AI系统,这个理念同样适用,甚至更重要。因为AI系统的依赖链更复杂:AI API可能不可用、响应可能质量极低、向量数据库可能超时、GPU内存可能耗尽……这些故障场景在普通测试里很难覆盖。
我们团队做了第一次AI系统混沌实验,发现了三个生产环境从未暴露但实际存在的严重问题。这篇文章把方法论和实现分享出来。
AI系统的特有故障模式
普通分布式系统的故障模式(网络分区、节点宕机、磁盘故障)AI系统都有,但AI系统还有几个独特的故障模式:
AI API降级响应:AI提供商可能在高负载时返回质量明显下降的响应(短、不完整、偏离主题),而不是直接报错。这种"软故障"比硬故障更难检测。
Token超限错误:Prompt超过模型的上下文窗口,导致请求失败。在数据量增大时可能突然出现。
速率限制级联:AI API触发速率限制后,请求积压在队列里,导致超时错误雪崩。
向量数据库一致性问题:向量索引更新期间,新旧数据混合,导致RAG返回不一致结果。
慢响应积累:AI推理比平时慢3-4倍(比如模型服务器高负载),导致线程池耗尽。
混沌实验框架设计
@Component
public class AiChaosExperimentRunner {
/**
* 执行一个混沌实验
*/
public ExperimentResult run(ChaosExperiment experiment) {
log.warn("开始混沌实验: {}", experiment.getName());
// 1. 记录实验前的基准指标
SystemSnapshot beforeSnapshot = systemObserver.snapshot();
// 2. 注入故障
FaultInjector injector = experiment.getFaultInjector();
FaultHandle handle = injector.inject();
try {
// 3. 在故障状态下运行验证场景
ValidationResult validation = runValidationScenarios(
experiment.getValidationScenarios(),
experiment.getDurationSeconds()
);
// 4. 检查系统是否满足稳态假设
SystemSnapshot duringSnapshot = systemObserver.snapshot();
boolean steadyStateMaintained = experiment.getSteadyStateCheck()
.check(beforeSnapshot, duringSnapshot);
return ExperimentResult.builder()
.experiment(experiment)
.steadyStateMaintained(steadyStateMaintained)
.validationResult(validation)
.beforeSnapshot(beforeSnapshot)
.duringSnapshot(duringSnapshot)
.insights(generateInsights(experiment, steadyStateMaintained, validation))
.build();
} finally {
// 5. 恢复故障(无论实验结果如何)
handle.restore();
log.info("混沌实验结束,故障已恢复: {}", experiment.getName());
}
}
}核心故障注入器
// 故障注入器1:AI API延迟注入
@Component
public class AiApiLatencyInjector implements FaultInjector {
private final AiClientInterceptor interceptor;
@Override
public FaultHandle inject(LatencyFaultConfig config) {
// 注入额外延迟:在AI API返回后,人为增加N秒延迟
AtomicBoolean active = new AtomicBoolean(true);
interceptor.addLatency(request -> {
if (!active.get()) return 0;
// 随机化延迟,更接近真实网络抖动
int baseMs = config.getBaseLatencyMs();
int jitterMs = config.getJitterMs();
return baseMs + (int)(Math.random() * jitterMs);
});
log.warn("注入AI API延迟: 基础延迟={}ms, 抖动={}ms",
config.getBaseLatencyMs(), config.getJitterMs());
return () -> {
active.set(false);
interceptor.removeLatency();
log.info("AI API延迟注入已恢复");
};
}
}
// 故障注入器2:AI API错误率注入
@Component
public class AiApiErrorInjector implements FaultInjector {
private final AiClientInterceptor interceptor;
@Override
public FaultHandle inject(ErrorFaultConfig config) {
AtomicBoolean active = new AtomicBoolean(true);
interceptor.addErrorRate(request -> {
if (!active.get()) return false;
return Math.random() < config.getErrorRate();
}, config.getErrorType());
log.warn("注入AI API错误: 错误率={}, 错误类型={}",
config.getErrorRate(), config.getErrorType());
return () -> {
active.set(false);
interceptor.removeErrorRate();
};
}
}
// 故障注入器3:AI响应质量降级
@Component
public class AiResponseDegradationInjector implements FaultInjector {
@Override
public FaultHandle inject(DegradationConfig config) {
// 不是让请求失败,而是让AI返回"垃圾"响应
// 测试系统的响应质量检测和降级处理
AtomicBoolean active = new AtomicBoolean(true);
interceptor.addResponseTransformer(response -> {
if (!active.get() || Math.random() > config.getDegradationRate()) {
return response; // 不降级
}
return switch (config.getDegradationType()) {
case EMPTY -> "";
case TRUNCATED -> response.substring(0, Math.min(20, response.length()));
case IRRELEVANT -> "今天天气不错。";
case REPETITIVE -> "是的。".repeat(50);
};
});
return () -> {
active.set(false);
interceptor.removeResponseTransformer();
};
}
}
// 故障注入器4:向量数据库慢查询
@Component
public class VectorDbSlowQueryInjector implements FaultInjector {
@Override
public FaultHandle inject(SlowQueryConfig config) {
AtomicBoolean active = new AtomicBoolean(true);
vectorStoreInterceptor.addDelay(query -> {
if (!active.get()) return 0;
// 模拟向量检索超时(从通常的50ms到5000ms)
return config.getSlowQueryMs();
});
return () -> {
active.set(false);
vectorStoreInterceptor.removeDelay();
};
}
}验证场景设计
注入故障后,要验证系统是否保持了"稳态"——关键功能是否仍然可用:
@Service
public class AiSystemSteadyStateValidator {
/**
* 验证AI系统的核心稳态指标
*/
public SteadyStateResult validate() {
List<SteadyStateCheck> results = new ArrayList<>();
// 检查1:基本问答功能是否可用
results.add(checkBasicQa());
// 检查2:错误率是否在可接受范围内
results.add(checkErrorRate());
// 检查3:P95延迟是否在可接受范围内(注意:混沌实验中延迟可能更高)
results.add(checkP95Latency());
// 检查4:降级服务是否正常工作
results.add(checkFallbackService());
return new SteadyStateResult(results);
}
private SteadyStateCheck checkBasicQa() {
try {
// 发一个简单的测试请求,看是否能得到基本正确的回答
String response = aiService.answer("1加1等于几?");
boolean passed = response != null && !response.isEmpty() &&
(response.contains("2") || response.contains("两") || response.contains("二"));
return SteadyStateCheck.of("基本问答", passed,
passed ? null : "基本问答返回了空或不合理的结果: " + response);
} catch (Exception e) {
return SteadyStateCheck.of("基本问答", false, "基本问答抛出异常: " + e.getMessage());
}
}
private SteadyStateCheck checkFallbackService() {
// 验证当AI主服务不可用时,降级服务是否被触发
boolean fallbackTriggered = fallbackMetrics.getFallbackActivationCount() > 0;
boolean fallbackWorking = fallbackMetrics.getFallbackSuccessRate() > 0.9;
return SteadyStateCheck.of("降级服务",
!fallbackTriggered || fallbackWorking,
fallbackTriggered && !fallbackWorking ? "降级服务被触发但成功率低" : null);
}
}我们发现的三个问题
做完第一轮混沌实验,我们发现了几个之前没意识到的问题:
问题1:当AI API延迟从2秒增加到20秒时,线程池耗尽
对话服务用的是Tomcat的HTTP线程池,每个请求占用一个线程等待AI响应。当AI响应变慢,线程被大量占用,新请求无法处理,直到出现"Too many connections"。
修复:把AI调用改为异步+响应式,用WebFlux处理,一个线程可以处理多个并发等待的AI请求。
问题2:AI返回空字符串时,下游系统没有处理,导致NPE
AI偶尔返回空字符串(内容审核触发),我们的代码没有处理这个情况,直接把空字符串存进去,导致后续处理NullPointerException。
修复:加入输出校验,空响应触发降级处理而不是传播空值。
问题3:向量数据库超时时,RAG返回空结果,但AI仍然给出了"有把握"的错误答案
向量检索超时,RAG什么都没检索到,但AI在没有任何参考资料的情况下仍然"自信地"回答了问题,给出了错误信息。
修复:加入RAG结果空检查,当检索结果为空时,在prompt里明确告知"没有找到相关文档,如果不确定请如实说",并提高答案的不确定性标记。
混沌工程不是一次性活动,要持续进行。每次系统有重大变更后,重跑混沌实验,确保新的变更没有引入新的脆弱点。
