第2438篇:AI系统的灾难恢复计划——当核心AI服务不可用时的应急预案
2026/4/30大约 9 分钟
第2438篇:AI系统的灾难恢复计划——当核心AI服务不可用时的应急预案
适读人群:AI系统架构师、运维工程师、技术负责人 | 阅读时长:约13分钟 | 核心价值:建立完整的AI服务灾难恢复体系,确保业务连续性
2024年3月,OpenAI的API出现了一次持续约4小时的大范围故障。那天上午,我们的AI客服系统彻底失去响应——用户发消息进来,系统要么超时要么返回错误,客服工单量在两小时内涨了600%。
运营团队打电话来问:系统能不能切回人工?技术上可以,但那要多少人?没有AI的话,我们需要50个坐席才能覆盖当前流量,但我们只有12个人。
那次事故之后,我们花了两个月时间系统性地重设计了灾难恢复计划。今天把这套思路分享出来。
一、AI系统灾难的分类
首先要搞清楚"灾难"有哪些类型,不同类型需要不同的恢复策略:
每种灾难类型有不同的检测方法、恢复时间目标(RTO)和恢复点目标(RPO)。
二、核心指标定义
在制定应急预案之前,先明确恢复目标:
# DR目标定义
DR_OBJECTIVES = {
"rto": {
"definition": "Recovery Time Objective - 从灾难发生到恢复服务的最长时间",
"examples": {
"critical_ai_service": "15分钟", # 核心AI服务(如支付相关的风控AI)
"standard_ai_service": "1小时", # 标准业务AI(如推荐、内容生成)
"non_critical_ai": "4小时" # 非关键AI(如内部工具)
}
},
"rpo": {
"definition": "Recovery Point Objective - 恢复后数据的最大允许丢失时间",
"examples": {
"user_conversation_history": "0分钟(不能丢失)",
"model_configuration": "1小时(每小时备份)",
"knowledge_base_updates": "24小时(每日备份可接受)"
}
},
"degraded_service_level": {
"definition": "降级服务水平 - 在灾难恢复期间能保证的最低服务能力",
"example": "核心功能可用,响应速度降低50%,AI能力简化为基础问答"
}
}三、分层降级策略
好的应急预案不是二进制的"全好/全坏",而是有层次的降级体系:
class DegradedServiceStrategy:
"""分层降级策略"""
DEGRADATION_LEVELS = {
"level_0_normal": {
"description": "正常运行",
"ai_capability": "完整AI能力",
"trigger": "所有系统正常"
},
"level_1_degraded": {
"description": "轻度降级",
"ai_capability": "切换到备用模型(质量略低)",
"trigger": "主要LLM API响应时间 P95 > 5秒 或 错误率 > 1%",
"actions": [
"切换到备用LLM服务商",
"降低response_length上限,减少延迟",
"关闭非核心AI功能(如内容摘要、个性化推荐)"
]
},
"level_2_heavily_degraded": {
"description": "重度降级",
"ai_capability": "使用缓存和规则引擎替代AI",
"trigger": "所有LLM API不可用 或 错误率 > 20%",
"actions": [
"开启回答缓存:历史上相似问题的回答直接返回",
"启用规则引擎:处理高频标准场景",
"增加人工干预比例",
"通知用户当前系统处于维护状态"
]
},
"level_3_minimal": {
"description": "最低可用",
"ai_capability": "无AI能力,纯人工或静态响应",
"trigger": "level_2无法满足业务最低要求",
"actions": [
"所有请求转入人工处理队列",
"向用户展示预设的静态响应",
"发送告警给所有相关人员"
]
}
}
def determine_current_level(self, metrics: dict) -> str:
"""根据实时指标确定当前降级级别"""
llm_error_rate = metrics.get("llm_error_rate", 0)
llm_p95_latency = metrics.get("llm_p95_latency_ms", 0)
if llm_error_rate > 0.20:
return "level_2_heavily_degraded"
elif llm_error_rate > 0.01 or llm_p95_latency > 5000:
return "level_1_degraded"
else:
return "level_0_normal"四、多LLM服务商故障切换
最常见的灾难类型是外部LLM API不可用,应对方案是多服务商冗余:
import asyncio
import time
from typing import Optional
class MultiProviderLLMClient:
"""多服务商LLM客户端,支持自动故障切换"""
def __init__(self):
self.providers = {
"primary": {
"name": "openai",
"client": None, # 初始化时注入
"model": "gpt-4o",
"weight": 1.0,
"status": "healthy",
"failure_count": 0,
"last_failure_time": None
},
"secondary": {
"name": "anthropic",
"client": None,
"model": "claude-3-5-sonnet-20241022",
"weight": 0.0, # 正常情况下不使用
"status": "healthy",
"failure_count": 0,
"last_failure_time": None
},
"tertiary": {
"name": "azure_openai",
"client": None,
"model": "gpt-4o",
"weight": 0.0,
"status": "healthy",
"failure_count": 0,
"last_failure_time": None
}
}
self.circuit_breaker_threshold = 5 # 连续失败N次后熔断
self.circuit_breaker_timeout = 60 # 熔断后N秒再次尝试
async def complete(self, messages: list, **kwargs) -> dict:
"""发送请求,自动处理故障切换"""
providers_to_try = self._get_available_providers()
last_error = None
for provider_key in providers_to_try:
provider = self.providers[provider_key]
# 检查熔断器状态
if not self._is_circuit_closed(provider):
continue
try:
start_time = time.time()
result = await self._call_provider(provider, messages, **kwargs)
latency_ms = (time.time() - start_time) * 1000
# 成功,重置失败计数
provider["failure_count"] = 0
return {
"content": result,
"provider_used": provider_key,
"latency_ms": latency_ms
}
except Exception as e:
last_error = e
provider["failure_count"] += 1
provider["last_failure_time"] = time.time()
# 记录切换事件
print(f"Provider {provider_key} failed ({provider['failure_count']} times), trying next...")
raise Exception(f"All providers failed. Last error: {last_error}")
def _is_circuit_closed(self, provider: dict) -> bool:
"""检查熔断器是否关闭(服务正常可用)"""
if provider["failure_count"] < self.circuit_breaker_threshold:
return True
# 检查是否已过恢复等待时间
if provider["last_failure_time"]:
elapsed = time.time() - provider["last_failure_time"]
if elapsed > self.circuit_breaker_timeout:
# 重置,允许尝试
provider["failure_count"] = 0
return True
return False
def _get_available_providers(self) -> list:
"""获取按优先级排序的可用服务商列表"""
return ["primary", "secondary", "tertiary"]
async def _call_provider(self, provider: dict, messages: list, **kwargs):
"""调用具体的服务商(实际实现需注入具体client)"""
# 此处为示意,实际需要根据provider类型调用对应SDK
raise NotImplementedError("实现各provider的具体调用逻辑")五、知识库和数据的灾难恢复
除了LLM API,RAG系统的向量数据库和知识库也需要灾难恢复方案:
class KnowledgeBaseRecoveryPlan:
"""知识库灾难恢复计划"""
def backup_strategy(self):
"""知识库备份策略"""
return {
"raw_documents": {
"backup_frequency": "每日增量备份,每周全量备份",
"storage": "对象存储(OSS/S3),跨区域复制",
"retention": "保留90天",
"verification": "每周随机抽样验证备份完整性"
},
"embeddings": {
"strategy": "不需要单独备份,从原始文档重新生成",
"rebuild_time_estimate": "100万条文档约需4小时重建",
"note": "如果embedding模型更新,所有embedding都需要重建"
},
"vector_index": {
"backup_frequency": "每日快照",
"restore_priority": "如果快照可用,恢复比重建更快",
"cross_region": "主备区域各保留一份"
}
}
def recovery_procedure(self, failure_type: str) -> list:
"""根据故障类型确定恢复步骤"""
procedures = {
"vector_db_corruption": [
"1. 切换到只读备份副本(10分钟内)",
"2. 评估损坏范围",
"3. 如损坏较小:从最新快照增量恢复",
"4. 如损坏较大:从原始文档完整重建",
"5. 重建期间:使用只读副本提供有限的查询服务"
],
"embedding_model_change": [
"1. 新旧embedding模型同时运行(双写期)",
"2. 批量重新为所有文档生成新embedding",
"3. 验证新embedding的查询质量",
"4. 切换查询到新embedding",
"5. 旧embedding保留7天后清理"
],
"data_poisoning": [
"1. 立即停止新数据写入",
"2. 从备份中恢复到污染前的版本",
"3. 分析污染范围和来源",
"4. 加强输入验证和内容审核",
"5. 通知受影响的用户(如适用)"
]
}
return procedures.get(failure_type, ["未定义此故障类型的恢复流程"])六、应急响应流程
当故障发生时,响应流程要提前定义好:
AI服务故障应急响应流程
【发现阶段 - 0~5分钟】
□ 监控告警触发
□ 值班工程师确认告警非误报
□ 通知相关人员(技术负责人、产品负责人)
□ 创建事故跟踪工单(记录发现时间、初始影响范围)
【评估阶段 - 5~15分钟】
□ 确认故障范围(哪些功能受影响,影响多少用户)
□ 确认故障类型(外部API、自有服务、数据问题)
□ 根据影响范围升级通知(若影响>10%用户,通知高管)
□ 初步判断恢复时间
【处置阶段 - 15分钟~RTO】
□ 按预案执行降级策略
□ 如有备用服务商,执行切换
□ 每30分钟向相关人员同步进展
□ 对外部沟通:状态页更新、用户通知
【恢复阶段】
□ 确认服务已恢复
□ 验证核心功能正常(跑冒烟测试)
□ 逐步恢复正常服务级别(不要一次性全流量切回)
□ 更新状态页
【复盘阶段 - 48小时内】
□ 写事故复盘报告(5Why分析)
□ 确定改进行动项(有owner、有deadline)
□ 更新应急预案文档七、应急预案演练
预案写了不演练,等于没写:
DR_DRILL_SCHEDULE = {
"monthly_drills": [
{
"name": "LLM API切换演练",
"description": "模拟主要LLM API不可用,测试切换到备用服务商的流程",
"duration": "30分钟",
"scope": "在测试环境中切断主服务商连接",
"success_criteria": "备用服务商切换完成时间 < 5分钟"
}
],
"quarterly_drills": [
{
"name": "知识库恢复演练",
"description": "模拟向量数据库损坏,测试从备份恢复",
"duration": "4小时",
"scope": "在预生产环境执行",
"success_criteria": "知识库查询恢复到可用状态的时间 < 2小时"
},
{
"name": "全服务降级演练",
"description": "模拟所有AI服务不可用,测试最低可用状态的运营能力",
"duration": "2小时",
"scope": "在低峰期对5%真实流量执行",
"success_criteria": "业务核心流程可以在无AI情况下运转"
}
],
"annual_drills": [
{
"name": "全栈灾难恢复演练",
"description": "模拟数据中心级别的故障,测试完整DR能力",
"duration": "全天",
"scope": "完整的灾难恢复演练(切换到备用数据中心)"
}
]
}八、必备的监控和告警
没有监控,灾难发生时就是盲飞:
CRITICAL_MONITORING_METRICS = {
"llm_api_health": {
"metrics": [
"llm_request_error_rate", # 错误率(告警阈值:>1%)
"llm_p95_latency_ms", # P95延迟(告警阈值:>3000ms)
"llm_timeout_rate", # 超时率(告警阈值:>0.5%)
],
"alerting": {
"warning": "错误率>0.5% 持续2分钟",
"critical": "错误率>2% 持续1分钟 或 P95>5秒"
}
},
"ai_service_quality": {
"metrics": [
"response_quality_score", # 响应质量分(通过embedding相似度评估)
"refusal_rate", # 内容过滤拒绝率
"output_length_anomaly" # 输出长度异常(突然变短可能意味着模型行为变化)
],
"note": "这些指标的异常往往早于用户投诉出现"
},
"system_resources": {
"metrics": [
"vector_db_query_latency",
"cache_hit_rate", # 缓存命中率下降可能意味着负载增加
"queue_depth" # 请求队列深度(积压意味着处理能力不足)
]
}
}灾难恢复计划最重要的一点是:要在灾难发生之前就做好,而不是在灾难发生的时候临时拼凑。预案的价值在于演练,演练的价值在于发现真实问题,而不是在生产故障时才发现预案有漏洞。
