7.2 KiB
7.2 KiB
转码任务恢复实现方案(已完成 ✅)
实现状态
✅ 已完成实现并部署到影子系统(2024-12-20)
实现的功能
-
自动恢复中断的转码任务
- 懒加载方式:第一次访问转码API时自动触发
- 查询所有
processing状态的视频,清理残留文件并重置为pending - 查询所有
pending状态的视频,按时间顺序重新入队
-
清理残留文件
- 清理S3上的预览视频(videoPreview540Url)
- 清理S3上的预览片段(videoPreviewClipUrl)
- 智能清理封面图:仅删除自动生成的封面(文件名包含
video-cover-)
-
转码队列管理
- 支持并发转码(默认2个并发)
- 自动重试机制(最多3次)
- 进度回调和日志记录
实现文件
lib/video-transcode-queue.ts- 转码队列和恢复逻辑app/api/materials/transcode/route.ts- 转码API(含懒加载恢复)lib/video-processor.ts- 视频处理工具(FFmpeg转码)
您的方案分析
方案概述
- 部署后自动恢复:所有
processing状态的视频重置为pending - 清理残留文件:删除这些视频的转码残留文件(S3 + 本地)
- 队列重建:按时间顺序查询所有
pending状态的视频,重新入队
优点 ✅
- 实现简单:不需要持久化队列,不需要 Redis
- 自动恢复:部署后自动处理,无需手动干预
- 清理彻底:避免残留文件占用存储空间
- 保持顺序:按
createdAt排序,保持原始队列顺序
需要注意的点 ⚠️
-
封面图判断
- 问题:如何区分用户上传的封面和自动生成的封面?
- 方案:如果 URL 包含
video-cover-,认为是自动生成的,可以删除 - 否则认为是用户上传的,不删除
-
队列重建时机
- 问题:什么时候重建队列?
- 方案 A:应用启动时立即执行(可能影响启动速度)
- 方案 B:懒加载,第一次访问转码 API 时执行(推荐)
- 方案 C:创建独立的恢复 API,手动触发
-
性能考虑
- 如果有大量 pending 任务,恢复时可能需要一些时间
- 建议:清理文件可以异步执行,不阻塞恢复流程
实现细节
1. 残留文件识别
S3 上的文件(需要清理)
videoPreview540Url- 720P 预览视频(文件名通常包含video-540p-或video-preview-)videoPreviewClipUrl- 10 秒预览片段(文件名通常包含video-clip-)videoCoverUrl- 封面图(仅当文件名包含video-cover-时删除)
本地临时文件
- 通常在
tmpdir()目录下,文件名包含materialId - 转码失败时可能残留,但通常会在
finally中清理 - 可选:扫描并清理超过一定时间的临时文件
2. 清理逻辑
// 清理策略
1. videoPreview540Url → 如果存在且是 S3 URL,删除
2. videoPreviewClipUrl → 如果存在且是 S3 URL,删除
3. videoCoverUrl → 如果存在且是 S3 URL 且文件名包含 "video-cover-",删除
(避免误删用户上传的封面)
3. 队列重建
// 查询顺序
1. 查询所有 pending 状态的视频
2. 条件:
- videoProcessingStatus = 'pending'
- videoSourceUrl IS NOT NULL(必须有源视频)
- deletedAt IS NULL(未删除)
3. 排序:按 createdAt ASC(最早上传的优先)
4. 重新入队:按顺序调用 transcodeQueue.add()
实现代码结构
文件结构
lib/video-transcode-queue.ts
- 添加 recoverInterruptedTasks() 方法
- 添加 cleanupTranscodeResidues() 辅助函数
app/api/materials/transcode/route.ts
- 在第一次调用时触发恢复(懒加载)
- 或创建独立的恢复 API
关键实现点
-
恢复函数:
- 查询所有
processing状态的视频 - 清理残留文件
- 重置状态为
pending - 查询所有
pending状态的视频,按时间排序 - 重新入队
- 查询所有
-
清理函数:
- 判断文件类型(预览视频、片段、封面)
- 判断封面是否自动生成(文件名包含
video-cover-) - 批量删除 S3 文件
-
调用时机:
- 懒加载:第一次访问转码 API 时检查并恢复
- 或:应用启动时执行(需要异步初始化)
具体实现建议
方案 A:懒加载恢复(推荐)
优点:
- 不影响应用启动速度
- 只在需要时才执行恢复
- 实现简单
实现:
- 在转码 API 的
POST方法中,第一次调用时检查是否需要恢复 - 使用一个标志位
recoveryChecked确保只执行一次
方案 B:启动时恢复
优点:
- 部署后立即恢复,用户看到的状态更准确
缺点:
- 可能影响启动速度(如果有大量任务)
- 需要异步初始化机制
实现:
- 在
lib/video-transcode-queue.ts的初始化时调用 - 或创建独立的初始化脚本
需要确认的问题
-
封面图判断:
- 使用文件名判断(包含
video-cover-就删除)? - 还是需要更精确的判断方式?
- 使用文件名判断(包含
-
恢复时机:
- 懒加载(第一次访问转码 API 时)?
- 还是启动时立即执行?
-
本地临时文件:
- 是否需要清理本地临时文件?
- 还是只清理 S3 上的文件?
-
日志记录:
- 是否需要记录哪些任务被恢复了?
- 方便后续排查问题
我的建议
推荐实现方式:
-
恢复时机:懒加载(第一次访问转码 API 时)
- 不影响启动速度
- 实现简单
-
清理策略:
- S3 文件:清理预览视频、片段、自动生成的封面
- 本地临时文件:可选,通常已自动清理
-
封面判断:
- 简单判断:URL 包含
video-cover-就删除 - 如果后续发现不够准确,再优化
- 简单判断:URL 包含
-
日志记录:
- 记录恢复的任务数量
- 记录清理的文件数量
- 方便排查问题
实现后的效果
部署前:
- 队列:[1, 2, 3, 4, 5, 6...]
- 正在转码:3(状态
processing)
部署后:
- 自动检测到 3 的状态是
processing - 清理 3 的残留文件(S3 上的预览视频、片段等)
- 重置 3 的状态为
pending - 查询所有
pending状态的视频:[3, 4, 5, 6...](按时间排序) - 重新入队,3 会重新开始转码
用户体验:
- 用户看到的状态会从"转码中"变为"待转码"
- 然后自动开始重新转码
- 不会"卡住"在转码中状态
总结
您的方案非常好,实现简单且实用。主要优势:
- ✅ 不需要持久化队列:利用数据库状态重建队列
- ✅ 自动恢复:部署后自动处理,无需手动干预
- ✅ 清理彻底:避免残留文件占用存储
- ✅ 保持顺序:按时间排序,保持原始队列顺序
建议的实现方式:
- 懒加载恢复(第一次访问转码 API 时)
- 简单判断封面图(文件名包含
video-cover-) - 只清理 S3 文件(本地临时文件通常已自动清理)
如果您同意这个方案,我可以开始实现。有什么需要调整的地方吗?