第2463篇:自愈系统的工程设计——AI驱动的自动故障检测和修复
2026/4/30大约 7 分钟
第2463篇:自愈系统的工程设计——AI驱动的自动故障检测和修复
适读人群:SRE、后端工程师、平台工程师 | 阅读时长:约17分钟 | 核心价值:从理论到实现,设计一个能自动检测故障并执行修复动作的生产级自愈系统
我有个习惯,每次凌晨被电话叫醒处理故障,第一件事不是去查问题,而是记下来:这次故障,有没有可能被系统自动处理掉,根本不需要叫人?
记录了一年,发现大概60%的凌晨故障属于以下几类:
- 某个Pod OOM了,重启就好
- 磁盘满了,清一下旧日志就好
- 某个服务实例卡死了,重启就好
- 数据库连接池耗尽,等一会儿就恢复了,但没人等,触发了告警
这些都是有固定处置SOP的故障,而且SOP很简单。如果系统能自己执行这些SOP,60%的凌晨电话本可以不存在。
自愈系统的设计原则
在设计之前,先确定几个原则,因为自愈系统的设计稍有不慎就会适得其反:
原则一:安全优先于效率。宁可多问一次人工确认,也不要贸然执行可能有副作用的操作。
原则二:可观测优先于自动化。每一个自动执行的动作,都要有完整的日志记录,告诉人类"我做了什么、为什么做、结果是什么"。
原则三:降级优先于修复。有些故障来不及修复,先快速降级保住主链路,再慢慢修。
原则四:幂等性是底线。所有修复动作必须幂等——执行两次和执行一次效果一样,防止重复触发造成更大混乱。
系统架构
核心实现
1. 故障检测层
@Component
public class HealthCheckOrchestrator {
private final List<HealthChecker> checkers;
private final FaultEventBus eventBus;
@Scheduled(fixedDelay = 30000) // 每30秒检查一次
public void runHealthChecks() {
List<CompletableFuture<HealthCheckResult>> futures = checkers.stream()
.map(checker -> CompletableFuture.supplyAsync(
() -> runWithTimeout(checker),
healthCheckExecutor
))
.collect(toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.join();
List<HealthCheckResult> results = futures.stream()
.map(f -> f.getNow(HealthCheckResult.unknown()))
.collect(toList());
// 发布不健康的事件
results.stream()
.filter(r -> !r.isHealthy())
.forEach(r -> eventBus.publish(FaultEvent.from(r)));
}
private HealthCheckResult runWithTimeout(HealthChecker checker) {
try {
return checker.check()
.orTimeout(10, TimeUnit.SECONDS)
.join();
} catch (Exception e) {
return HealthCheckResult.unhealthy(
checker.getName(),
"检查超时或异常: " + e.getMessage()
);
}
}
}
// 各种具体的健康检查器
@Component
public class KubernetesPodsHealthChecker implements HealthChecker {
private final KubernetesClient k8sClient;
@Override
public CompletableFuture<HealthCheckResult> check() {
return CompletableFuture.supplyAsync(() -> {
List<FaultDetail> faults = new ArrayList<>();
// 检查所有命名空间的Pod状态
PodList pods = k8sClient.pods().inAnyNamespace().list();
for (Pod pod : pods.getItems()) {
PodStatus status = pod.getStatus();
// 检测CrashLoopBackOff
boolean hasCrashLoop = status.getContainerStatuses().stream()
.anyMatch(cs -> {
ContainerStateWaiting waiting = cs.getState().getWaiting();
return waiting != null &&
"CrashLoopBackOff".equals(waiting.getReason());
});
if (hasCrashLoop) {
faults.add(FaultDetail.builder()
.type(FaultType.CRASH_LOOP)
.target(pod.getMetadata().getNamespace() + "/" + pod.getMetadata().getName())
.details(extractCrashDetails(status))
.build());
}
// 检测OOMKilled
boolean hasOOMKill = status.getContainerStatuses().stream()
.anyMatch(cs -> {
ContainerStateTerminated terminated = cs.getLastState().getTerminated();
return terminated != null && "OOMKilled".equals(terminated.getReason());
});
if (hasOOMKill) {
faults.add(FaultDetail.builder()
.type(FaultType.OOM_KILLED)
.target(pod.getMetadata().getNamespace() + "/" + pod.getMetadata().getName())
.build());
}
}
return faults.isEmpty() ?
HealthCheckResult.healthy("kubernetes-pods") :
HealthCheckResult.unhealthy("kubernetes-pods", faults);
});
}
}2. 故障分类和决策引擎
@Service
public class FaultDecisionEngine {
private final RuleBasedClassifier ruleClassifier;
private final LLMDecisionAdvisor llmAdvisor;
private final RemediationActionRegistry actionRegistry;
public RemediationPlan decide(FaultEvent fault) {
// 先用规则引擎做快速分类
Optional<RemediationPlan> rulePlan = ruleClassifier.classify(fault);
if (rulePlan.isPresent() && rulePlan.get().getConfidence() > 0.9) {
// 规则高置信度命中,直接使用
log.info("规则引擎命中故障类型: {}, 置信度: {}", fault.getType(),
rulePlan.get().getConfidence());
return rulePlan.get();
}
// 规则引擎没有高置信度命中,使用LLM辅助判断
LLMAdvice advice = llmAdvisor.advise(fault, rulePlan.orElse(null));
return buildPlan(fault, advice);
}
private RemediationPlan buildPlan(FaultEvent fault, LLMAdvice advice) {
List<RemediationAction> actions = advice.getSuggestedActions().stream()
.map(actionRegistry::getAction)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toList());
double overallRisk = actions.stream()
.mapToDouble(a -> a.getRiskLevel().getScore())
.max()
.orElse(0.5);
ExecutionPolicy policy = determinePolicy(overallRisk, advice.getConfidence());
return RemediationPlan.builder()
.faultId(fault.getId())
.actions(actions)
.executionPolicy(policy)
.rationale(advice.getRationale())
.confidence(advice.getConfidence())
.build();
}
private ExecutionPolicy determinePolicy(double risk, double confidence) {
if (risk < 0.3 && confidence > 0.85) {
return ExecutionPolicy.AUTO_EXECUTE;
} else if (risk < 0.7 && confidence > 0.7) {
return ExecutionPolicy.NOTIFY_AND_EXECUTE; // 执行后通知
} else {
return ExecutionPolicy.REQUIRE_APPROVAL; // 需要审批
}
}
}3. LLM决策顾问
@Service
public class LLMDecisionAdvisor {
private final ChatClient chatClient;
private final KnowledgeBaseService knowledgeBase;
public LLMAdvice advise(FaultEvent fault, RemediationPlan ruleBasedPlan) {
// 从知识库检索相似案例
List<HistoricalCase> similarCases = knowledgeBase.findSimilar(fault, 5);
String prompt = buildPrompt(fault, ruleBasedPlan, similarCases);
ChatResponse response = chatClient.call(new Prompt(
List.of(
new SystemMessage(ADVISOR_SYSTEM_PROMPT),
new UserMessage(prompt)
),
OpenAiChatOptions.builder()
.withModel("gpt-4o")
.withTemperature(0.1f)
.withResponseFormat(new ResponseFormat(ResponseFormat.Type.JSON_OBJECT))
.build()
));
return parseAdvice(response.getResult().getOutput().getContent());
}
private String buildPrompt(FaultEvent fault, RemediationPlan rulePlan, List<HistoricalCase> cases) {
StringBuilder sb = new StringBuilder();
sb.append("## 当前故障\n");
sb.append("故障类型: ").append(fault.getType()).append("\n");
sb.append("影响目标: ").append(fault.getTarget()).append("\n");
sb.append("故障详情:\n").append(fault.getDetails()).append("\n");
sb.append("发现时间: ").append(fault.getDetectedAt()).append("\n\n");
if (rulePlan != null) {
sb.append("## 规则引擎建议\n");
sb.append("建议动作: ").append(rulePlan.getActions()).append("\n");
sb.append("置信度: ").append(rulePlan.getConfidence()).append("\n\n");
}
if (!cases.isEmpty()) {
sb.append("## 历史相似案例\n");
for (HistoricalCase c : cases) {
sb.append(String.format("- [%s] 故障: %s, 处置: %s, 结果: %s\n",
c.getTimestamp(), c.getFaultSummary(),
c.getRemediationSummary(), c.getOutcome()
));
}
sb.append("\n");
}
sb.append("请给出:\n");
sb.append("1. 故障根因的最可能解释\n");
sb.append("2. 推荐的修复动作列表(从可用动作中选择)\n");
sb.append("3. 执行这些动作的风险评估\n");
sb.append("4. 整体置信度\n");
return sb.toString();
}
private static final String ADVISOR_SYSTEM_PROMPT = """
你是一个SRE专家,负责评估系统故障并给出修复建议。
可用的修复动作(只能从这些中选择):
- restart_pod: 重启指定Pod(风险低)
- scale_up: 扩容Deployment副本数(风险中)
- rollback_deployment: 回滚到上一个版本(风险中)
- clean_disk: 清理指定路径的旧文件(风险低)
- flush_cache: 刷新缓存(风险低)
- circuit_breaker_open: 开启熔断器(风险低,但会降级服务)
- notify_oncall: 只通知值班工程师,不做任何自动操作(兜底选项)
返回JSON格式:
{
"rootCauseHypothesis": "根因假设",
"suggestedActions": ["action_id1", "action_id2"],
"riskAssessment": "风险评估",
"rationale": "推荐理由",
"confidence": 0.0-1.0,
"requiresHumanJudgement": true/false,
"humanJudgementReason": "如果需要人工判断,说明原因"
}
""";
}4. 执行和验证
@Service
public class RemediationExecutor {
private final Map<String, RemediationActionHandler> handlers;
private final FaultEventRepository faultRepository;
private final NotificationService notificationService;
@Transactional
public ExecutionResult execute(RemediationPlan plan) {
FaultRecord fault = faultRepository.findById(plan.getFaultId()).orElseThrow();
// 记录执行开始
fault.setStatus(FaultStatus.REMEDIATING);
fault.setRemediationStartedAt(Instant.now());
faultRepository.save(fault);
List<ActionResult> actionResults = new ArrayList<>();
for (RemediationAction action : plan.getActions()) {
try {
// 执行动作
ActionResult result = executeAction(action, fault);
actionResults.add(result);
// 记录执行日志
auditLog(fault, action, result);
// 如果某个动作失败,停止执行后续动作
if (!result.isSuccess()) {
log.warn("修复动作失败: {} -> {}", action.getId(), result.getFailureReason());
break;
}
// 等待动作生效
Thread.sleep(action.getSettleTimeMs());
} catch (Exception e) {
log.error("执行修复动作异常: {}", action.getId(), e);
actionResults.add(ActionResult.failed(action.getId(), e.getMessage()));
break;
}
}
// 验证修复效果
boolean repaired = verifyRepair(fault);
if (repaired) {
fault.setStatus(FaultStatus.RESOLVED);
fault.setResolvedAt(Instant.now());
notificationService.notifyResolved(fault, actionResults);
} else {
fault.setStatus(FaultStatus.ESCALATED);
notificationService.escalateToHuman(fault, actionResults, "自动修复后验证失败");
}
faultRepository.save(fault);
return ExecutionResult.of(actionResults, repaired);
}
private boolean verifyRepair(FaultRecord fault) {
// 等待15秒后验证
try { Thread.sleep(15000); } catch (InterruptedException ignored) {}
// 重新检查故障目标的健康状态
HealthChecker checker = getCheckerForFaultType(fault.getType());
if (checker == null) return true; // 没有对应检查器,假定成功
HealthCheckResult result = checker.check().join();
return result.isHealthy() || !result.hasIssueFor(fault.getTarget());
}
}从告警风暴到精准处置的数字变化
落地自愈系统6个月后,我们的变化:
- 凌晨故障电话:从平均每月15次降到3次(-80%)
- 故障MTTR(平均修复时间):从35分钟降到8分钟(-77%)
- 自动修复成功率:约72%的故障被完全自动处置
- 误操作风险:发生过2次自动执行了不必要的重启,但都是低风险操作,无影响
最重要的经验
自愈系统最重要的不是技术,而是信任建立的过程。
刚开始上线时,团队对自动执行操作非常不安,总是质疑"万一系统判断错了怎么办"。我们的策略是:先跑3个月的"影子模式"——系统做出决策但不执行,只是记录日志"如果执行会怎么做"。团队看了3个月的日志,发现系统的判断准确率挺高,信任建立起来了,才开始逐步开放自动执行权限。
这个信任建立的过程不能省。
