视频转码系统的架构设计:从任务调度到分布式转码的性能优化 视频转码系统的架构设计从任务调度到分布式转码的性能优化一、背景与问题定义视频转码是内容平台的基础设施级能力。用户上传一个源文件可能是 iPhone 拍摄的 HEVC 编码 4K 视频也可能是 Android 设备的 H.264 1080P平台需要将其转换为多档清晰度4K/1080P/720P/480P和多封装格式HLS-DASH才能覆盖不同网络环境和终端设备的播放需求。转码系统的核心挑战有三个第一转码是计算密集型任务单机 GPU 吞吐有上限必须分布式第二转码任务有优先级差异——头部创作者的内容必须优先处理而长尾内容可以排队第三转码进度的实时感知直接影响创作者体验——上传后 转码中 状态持续太久会挫伤发布意愿。本文复盘一套日均处理 50 万转码任务的分布式系统架构设计涵盖任务生命周期管理、调度策略、进度通知和不同清晰度的差异化处理。二、转码任务的生命周期与整体架构2.1 任务生命周期一个转码任务从创建到完成经历五个阶段PENDING → QUEUED → RUNNING → COMPLETING → COMPLETED ↘ FAILED (可重试) → PENDINGPENDING任务刚创建尚未入队。此时可编辑优先级和转码参数。QUEUED任务已入优先级队列等待调度器分配执行节点。RUNNING转码节点已领取任务FFmpeg 进程正在执行。COMPLETING转码完成进行后处理——分片上传、M3U8 生成、CDN 预热。COMPLETED / FAILED终态。FAILED 状态支持自动重试最多 3 次。2.2 整体架构图三、分布式调度的核心设计3.1 优先级队列任务优先级由三因子加权决定priority α × creator_level_score β × video_hotness_score γ × wait_time_bonuscreator_level_score头部创作者 100 分腰部 60 分长尾 20 分保证核心内容供给者的体验。video_hotness_score预处理阶段的 AI 模型预估视频热度0~100热度越高越优先。wait_time_bonus防饥饿因子每等待 5 分钟 5 分确保长尾任务不会被无限期推迟。α、β、γ 的典型值为 0.4、0.3、0.3。Redis Sorted Set 天然支持这个模型score 即为 priority 值。Service public class TranscodeScheduler { private static final String QUEUE_KEY transcode:queue; private final StringRedisTemplate redisTemplate; public void enqueueTask(TranscodeTask task) { double priority calculatePriority(task); redisTemplate.opsForZSet().add(QUEUE_KEY, task.getTaskId(), priority); task.setState(TaskState.QUEUED); } public TranscodeTask dequeueTask() { // ZPOPMAX 原子操作取最高优先级任务 SetZSetOperations.TypedTupleString result redisTemplate.opsForZSet().popMax(QUEUE_KEY, 1); if (result null || result.isEmpty()) return null; String taskId result.iterator().next().getValue(); return taskRepository.findById(taskId) .map(task - { task.setState(TaskState.RUNNING); return taskRepository.save(task); }) .orElse(null); } }3.2 资源匹配与 Worker 管理Worker 节点启动时向调度器注册自身能力GPU 型号T4/A10/A100、可用显存、最大并发任务数。调度器维护 Worker 的能力视图将不同清晰度的任务分配到合适的 Worker4K 转码高计算量优先分配到 A100 节点1080P/720P 分配到 T4 节点即可H.265 编码计算量比 H.264 高 4 倍需要更多 GPU 资源Worker 以心跳5 秒间隔上报当前负载调度器据此决策。心跳超时 30 秒则将 Worker 标记为离线其正在执行的任务由调度器重新分配到其他 Worker。3.3 失败重试与幂等性转码失败的原因多样源文件损坏、GPU OOM、网络抖动导致输出上传失败。重试策略Transactional public void handleTaskFailure(String taskId, String errorMessage) { TranscodeTask task taskRepository.findByIdWithLock(taskId); task.incrementRetryCount(); task.setLastError(errorMessage); if (task.getRetryCount() 3) { // 指数退避 随机抖动避免重试风暴 long delaySeconds (long) Math.pow(2, task.getRetryCount()) ThreadLocalRandom.current().nextInt(10); task.setState(TaskState.PENDING); task.setNextRetryAt(Instant.now().plusSeconds(delaySeconds)); taskRepository.save(task); } else { task.setState(TaskState.FAILED); taskRepository.save(task); notifyService.pushTranscodeFailed(task.getUserId(), task.getVideoId()); } }幂等性的保证在 Worker 侧Worker 在领取任务后先检查输出存储中是否已存在对应清晰度的完整文件通过文件大小校验若存在则跳过转码直接进入 COMPLETING 阶段。四、不同清晰度的转码策略与进度通知4.1 差异化转码策略清晰度编码格式码率范围GOP大小优先级策略4KH.26515-25 Mbps2s仅头部创作者1080PH.264/H.2654-8 Mbps2s全部视频720PH.2642-4 Mbps3s全部视频480PH.2641-2 Mbps4s全部视频值得注意的设计4K 转码仅对头部创作者开放——因为 4K 的转码成本是 1080P 的 4~6 倍而长尾内容的 4K 播放量极低ROI 不合理。对于长尾内容平台只生成 1080P 及以下清晰度。4.2 FFmpeg 参数模板# 1080P H.264 模板 ffmpeg -i input.mp4 \ -c:v libx264 -preset faster -crf 23 \ -vf scale1920:1080:force_original_aspect_ratiodecrease,pad1920:1080:(ow-iw)/2:(oh-ih)/2 \ -c:a aac -b:a 128k -ar 44100 \ -g 48 -keyint_min 48 -sc_threshold 0 \ -f hls -hls_time 4 -hls_list_size 0 -hls_segment_filename \ output_1080p_%05d.ts output_1080p.m3u8关键参数解释-g 48设置 GOP 为 2 秒24fps × 2在拖动体验和编码效率之间取平衡。-sc_threshold 0禁用自适应场景检测确保固定的 GOP 间隔对 HLS 分片一致性很重要。-preset faster在编码速度和质量之间取折中。4.3 进度实时通知转码进度通过 FFmpeg 输出的time字段解析实时推送到前端public class TranscodeProgressTracker { public void monitorFfmpegOutput(Process ffmpegProcess, String taskId, long totalDurationSeconds) { try (BufferedReader reader new BufferedReader( new InputStreamReader(ffmpegProcess.getErrorStream()))) { String line; while ((line reader.readLine()) ! null) { OptionalDouble currentTime parseTimeFromFfmpegLine(line); currentTime.ifPresent(time - { int progress (int) (time / totalDurationSeconds * 100); // 限流每 2 秒最多推送一次进度 if (shouldPush(taskId, progress)) { TranscodeProgressEvent event new TranscodeProgressEvent( taskId, Math.min(progress, 99), TranscodePhase.TRANSCODING); websocketService.push(taskId, event); } }); } } catch (IOException e) { log.error(Failed to monitor ffmpeg output, e); } } }后处理阶段上传 CDN 预热也按 20% 的权重计入进度条最终在全部完成时推送 100%。五、总结转码系统的设计本质上是资源调度问题——如何在有限的 GPU 算力约束下最小化用户等待时间。优先级队列用 Redis Sorted Set 实现天然支持高并发入队和原子出队Worker 能力注册 心跳让调度器始终掌握全局负载视图分清晰度的差异化策略在成本和体验之间取得平衡。未来的方向探索 Serverless GPU如 AWS Lambda GPU实现弹性扩缩将长尾任务迁移到成本更低的竞价实例引入 AV1 编码进一步降低带宽成本尽管编码时间更长以及构建跨数据中心的转码调度能力利用不同区域的 GPU 资源池做全局负载均衡。