3、异步导出业务模型:导出任务生命周期、任务状态机设计、前端轮询与回调机制
异步导出是解决大数据量导出超时、阻塞用户体验的核心模式。本章将深入剖析导出任务的完整生命周期、严谨的状态机设计,以及前端如何通过轮询与回调机制感知任务进展。理解这些模型是后续实现幂等性校验与超时处理的基础。
3.1 导出任务生命周期
一个标准的异步导出任务,从创建到最终交付或失败,通常经历以下五个核心阶段:
| 阶段 | 状态 | 说明 | 关键操作 |
|---|---|---|---|
| 1. 提交 | PENDING | 用户发起导出请求,系统校验参数后生成任务记录,返回任务ID。 | 写入任务表,返回 taskId |
| 2. 排队 | QUEUED | 任务进入消息队列或线程池等待调度,尚未开始执行。 | 入队,更新状态 |
| 3. 执行 | PROCESSING | 工作节点消费任务,执行数据查询、文件生成、上传存储等操作。 | 更新进度,处理数据 |
| 4. 完成 | SUCCESS / FAILED | 任务执行结束。成功时生成下载链接;失败时记录错误原因。 | 生成文件 / 记录错误 |
| 5. 过期 | EXPIRED | 任务超时(如超过30分钟)或文件保留期已过,系统自动清理。 | 标记过期,清理文件 |
设计要点: 生命周期中的每个阶段都应记录时间戳(created_at, queued_at, started_at, completed_at, expired_at),便于后续监控、超时判定和幂等性校验。
3.2 任务状态机设计
状态机是确保任务流转正确、避免状态混乱的核心。以下状态图展示了导出任务的合法状态转换路径:
状态转换规则:
- 正常路径: PENDING → QUEUED → PROCESSING → SUCCESS
- 失败路径: PROCESSING → FAILED(业务异常或系统错误)
- 超时路径: PROCESSING → EXPIRED(超过最大执行时间阈值)
- 取消路径: PENDING / QUEUED → CANCELLED(用户主动取消)
- 重试路径: FAILED → QUEUED(配置了自动重试策略时)
3.3 前端轮询与回调机制
前端需要实时感知任务状态,以便展示进度、提供下载入口或处理错误。主流方案有两种:轮询和回调。实际项目中常将两者结合使用。
3.3.1 轮询机制
前端以固定间隔(如2秒、5秒)主动调用查询接口,获取任务状态。适用于短任务或回调不可靠的场景。
// 前端轮询示例(JavaScript + Promise)
async function pollTaskStatus(taskId, maxRetries = 30, interval = 2000) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(`/api/v1/export/tasks/${taskId}`);
const task = await response.json();
switch (task.status) {
case 'SUCCESS':
return { success: true, downloadUrl: task.downloadUrl };
case 'FAILED':
case 'EXPIRED':
return { success: false, error: task.errorMessage };
case 'CANCELLED':
return { success: false, error: '任务已被取消' };
default:
// PENDING / QUEUED / PROCESSING:继续等待
await new Promise(resolve => setTimeout(resolve, interval));
}
}
// 轮询超时
return { success: false, error: '轮询超时,请稍后重试' };
}
轮询设计要点:
- 指数退避: 初始间隔1秒,失败后逐步增加至5秒,减少服务端压力。
- 最大轮询次数: 根据任务超时时间(如30分钟)和间隔计算,避免无限轮询。
- 幂等性: 查询接口本身必须幂等,多次调用不影响任务状态。
3.3.2 回调机制
服务端在任务状态变更时,主动通知前端。常见实现方式包括 WebSocket、Server-Sent Events (SSE) 或 Webhook。
方案选择建议:
- 短任务(<30秒): 优先使用轮询,实现简单,无需维护长连接。
- 长任务(>30秒): 推荐使用 SSE 或 WebSocket,减少无效请求,提升实时性。
- 混合策略: 先轮询3次(快速感知),若任务未完成则切换为 SSE 监听,兼顾速度与资源。
3.4 轮询与回调的幂等性保障
无论采用哪种机制,都需要确保前端重复请求不会导致任务重复执行或状态错乱。核心原则:
- 查询接口幂等: GET /tasks/{taskId} 始终返回当前状态,不产生副作用。
- 回调去重: 服务端推送状态时携带唯一事件ID,前端根据ID去重,避免重复处理。
- 状态机校验: 后端状态变更必须遵循状态机规则,拒绝非法转换(如从 SUCCESS 回退到 PROCESSING)。
最佳实践: 在任务表中增加
version 字段(乐观锁),每次状态变更时校验版本号,防止并发更新导致状态覆盖。这是后续实现幂等性校验的关键基础。