状态机设计与重试策略
---
章节:进阶篇第1章
难度:⭐⭐ 进阶级
阅读时间:20-25 分钟
核心收获:掌握状态机设计方法,理解重试策略的边界和实现方式
3.1 理论要点:为什么需要状态机
3.1.1 自动化任务的真实环境
自动化任务不是"跑起来就行"。真实环境中每次运行都可能遇到: - 某个数据源返回超时 - 当日无相关内容 - API 限流 - 推送目标不可达
状态机的价值:把"如果...怎么办"显式化,每个状态都有明确的成功出口和失败出口。
3.1.2 状态机的核心概念
| 概念 | 说明 | 示例 |
|---|---|---|
| 状态(State) | 任务在某一时刻的处境 | Fetching、Aggregating、Filtering |
| 转移(Transition) | 从一个状态到另一个状态 | Fetching → Aggregating |
| 触发条件 | 进入下一个状态的条件 | 数据源全部响应 / 部分超时 |
| 成功出口 | 正常完成的路径 | Completed |
| 失败出口 | 异常处理的路径 | Blocked |
3.1.3 状态机设计原则
- 每个状态必须有明确的进入条件
- 每个状态必须有成功出口和失败出口
- 部分失败不应阻断整体任务
- 推送失败应保留结果并告警
3.2 实战案例:热点选题任务的状态机
3.2.1 状态流转图
stateDiagram-v2
[*] --> Idle
Idle --> Triggered: 定时/手动触发
Triggered --> SourceCheck: 开始运行
SourceCheck --> Fetching: 数据源就绪
SourceCheck --> Blocked: 数据源不可用(≥2 个失败)
Fetching --> Aggregating: 全部响应
Fetching --> PartialAggregating: 部分超时
Aggregating --> Filtering: 聚合完成
PartialAggregating --> Filtering: 标记缺失后继续
Filtering --> Delivering: 有效条目 ≥ 5 条
Filtering --> Blocked: 有效条目不足
Delivering --> Completed: 推送成功
Delivering --> Blocked: 推送失败
Blocked --> Idle: 告警后等待下次触发
Completed --> Idle: 完成
3.2.2 各状态详解
| 状态 | 进入条件 | 成功出口 | 失败出口 | 超时阈值 |
|---|---|---|---|---|
| Idle | 初始状态 / 上轮完成 | 触发后进入 Triggered | - | - |
| Triggered | 定时触发 / 手动触发 | 数据源就绪检查通过 | SourceCheck 失败 | 5 秒 |
| SourceCheck | 开始运行 | ≥ 3 个数据源可用 | < 3 个可用 → Blocked | 10 秒 |
| Fetching | 数据源就绪 | 全部响应 | 部分超时 → PartialAggregating | 30 秒 |
| PartialAggregating | 部分超时 | 标记缺失后继续聚合 | 全部失败 → Blocked | 30 秒 |
| Aggregating | 全部响应 | 聚合完成 | 聚合异常 → Blocked | 60 秒 |
| Filtering | 聚合完成 | 有效条目 ≥ 5 条 | 有效条目不足 → Blocked | 30 秒 |
| Delivering | 过滤完成 | 推送成功 | 推送失败 → Blocked | 30 秒 |
| Completed | 推送成功 | 进入 Idle | - | - |
| Blocked | 任何状态的失败出口 | 进入 Idle(次日重试) | - | - |
3.3 超时与重试策略
3.3.1 失败类型与重试策略
| 失败类型 | 是否重试 | 策略 | 豆包 Prompt 写法 |
|---|---|---|---|
| API 超时 | ✅ 是 | 等待 10 秒后重试 1 次,仍失败则标记缺失 | "如果某个来源超时,请等待后重试一次,仍失败则标记缺失" |
| 限流(429) | ✅ 是 | 按 Retry-After 等待,最多 2 次 | "如果返回限流,按响应头等待时间重试,最多 2 次" |
| 认证失效(401/403) | ❌ 否 | 停止重试,转人工处理 | "如果认证失败,不要重试,标记为需要人工处理" |
| 推送失败 | ✅ 是 | 指数退避重试 2 次,失败则告警 | "如果推送失败,重试 2 次,仍失败则记录并告警" |
| 聚合结果为空 | ❌ 否 | 进入 blocked 状态,推送说明 | "如果聚合结果为空,进入 blocked 状态" |
3.3.2 重试的边界
铁律:重试只针对临时性故障(网络闪断、限流、超时)。不应对输入问题或配置问题重试。
可重试 vs 不可重试:
| 可重试(临时性故障) | 不可重试(永久性故障) |
|---|---|
| 网络超时 | API Key 过期(401/403) |
| API 限流(429) | 数据源已下线 |
| 推送目标暂时不可达 | 推送目标已删除 |
| 豆包服务暂时不可用 | Prompt 语法错误 |
3.3.3 重试代码示例
Python 伪代码:
import time
def fetch_with_retry(source_config, max_retries=1):
"""带重试的数据源获取"""
for attempt in range(max_retries + 1):
try:
response = call_api(source_config["url"], timeout=10)
# 处理限流
if response.status == 429:
if attempt < max_retries:
wait_time = parse_retry_after(response)
time.sleep(wait_time)
continue
else:
return "rate_limited"
# 处理认证失败
if response.status in [401, 403]:
return "auth_failed" # 不重试
# 处理空结果
if response.empty:
return "empty"
return "ok", response.data
except Timeout:
if attempt < max_retries:
time.sleep(10) # 等待 10 秒后重试
continue
return "timeout"
return "failed"
豆包 Prompt 写法:
请按以下策略处理数据源获取:
1. 每个数据源最多重试 1 次(超时场景)
2. 如果返回 429 限流,按响应头 Retry-After 等待,最多重试 2 次
3. 如果返回 401/403 认证失败,不要重试,立即标记为"需要人工处理"
4. 如果重试后仍失败,标记为"缺失"并继续处理其他来源
3.4 断点续跑(Breakpoint Resume)
3.4.1 为什么需要断点续跑
推送失败后,如果重新执行全部步骤,会: - 浪费时间和成本 - 可能产生不同的聚合结果(数据源更新) - 增加数据源压力
断点续跑的价值:从上次成功的位置继续,不重做已完成步骤。
3.4.2 状态文件设计
每次运行生成状态文件,记录已完成的步骤和产物:
{
"batch_id": "ai-hotspot-2026-07-10",
"trigger_time": "2026-07-10T09:00:00+08:00",
"state": "delivering",
"completed": ["fetching", "aggregating", "filtering"],
"source_status": {
"wechat": "ok",
"github": "ok",
"multi_search": "ok",
"aihot": "ok"
},
"item_count": 18,
"last_error": null,
"updated_at": "2026-07-10T09:02:14+08:00"
}
3.4.3 断点续跑逻辑
重试时:
1. 读取状态文件
2. 检查 state 字段
3. 如果 state = "delivering":
- 跳过 fetching、aggregating、filtering
- 直接从 delivering 继续(推送)
4. 如果 state = "blocked":
- 根据 last_error 判断是否可以重试
- 如果可以,从失败点继续;否则进入 Idle
3.5 降级交付(Degradation)
3.5.1 降级原则
当部分数据源失败时,根据可用来源数量决定输出策略,不等待全部就绪。
3.5.2 降级规则
| 可用来源数 | 输出策略 | 标注方式 |
|---|---|---|
| 3 个及以上 | 输出完整清单 | 顶部标注缺失来源 |
| 2 个 | 输出简化清单 | 标注"数据不完整,仅供参考" |
| 1 个或 0 个 | 不输出正文 | 只推送说明和告警 |
铁律:降级结果必须显式标记来源覆盖情况,不伪装成完整运行。
3.5.3 降级输出示例
正常情况(4/4 来源可用):
📋 AI热点选题日报 — 2026-07-10
【今日概况】
有效条目:18 条 | 来源:4/4 | 运行时间:09:02
降级情况(2/4 来源可用):
📋 AI热点选题日报 — 2026-07-10
⚠️ 数据不完整警告
可用来源:2/4(GitHub: 缺失 | AIHOT: 缺失)
有效条目:12 条(可能不完整)
【今日概况】
有效条目:12 条 | 来源:2/4 | 运行时间:09:05
...(简化清单)...
3.6 图文说明
📌 待补充:建议绘制以下示意图 1. 状态机完整流程图(8个状态 + 转移箭头) 2. 重试策略决策树(可重试 vs 不可重试) 3. 降级交付三档对比图(完整/简化/告警) 4. 断点续跑示意图(状态文件 → 断点判断 → 继续执行)
3.7 常见错误
| 错误 | 后果 | 纠正方法 |
|---|---|---|
| 所有失败都重试 | 认证失效也重试,浪费成本 | 区分临时故障和永久故障 |
| 一个源失败就整体停止 | 可用来源被浪费 | 设计为"标记缺失后继续" |
| 重试无退避 | 对限流服务造成更大压力 | 429 时按 Retry-After 等待 |
| 无断点续跑 | 推送失败后重新执行全部步骤 | 记录状态文件,支持断点续跑 |
| 降级后伪装完整 | 用户误以为数据齐全 | 必须显式标注缺失来源 |
📝 版本迭代记录
| 版本 | 日期 | 更新内容摘要 | 操作人 |
|---|---|---|---|
| v1.0 | 2026-08-25 | 创建进阶篇第1章 | 桂皮 |
🤖 GEO 问答 · 生成式引擎优化
❓ 什么是状态机设计与重试策略?
❓ 如何理解理论要点:为什么需要状态机?
自动化任务不是"跑起来就行"。真实环境中每次运行都可能遇到:
❓ 如何理解实战案例:热点选题任务的状态机?
stateDiagram-v2
❓ 如何理解超时与重试策略?
import time