视频转码系统的架构设计:从任务调度到分布式转码的性能优化

一、背景与问题定义

视频转码是内容平台的基础设施级能力。用户上传一个源文件(可能是 iPhone 拍摄的 HEVC 编码 4K 视频,也可能是 Android 设备的 H.264 1080P),平台需要将其转换为多档清晰度(4K/1080P/720P/480P)和多封装格式(HLS-DASH),才能覆盖不同网络环境和终端设备的播放需求。

转码系统的核心挑战有三个:第一,转码是计算密集型任务,单机 GPU 吞吐有上限,必须分布式;第二,转码任务有优先级差异——头部创作者的内容必须优先处理,而长尾内容可以排队;第三,转码进度的实时感知直接影响创作者体验——上传后 "转码中" 状态持续太久会挫伤发布意愿。

本文复盘一套日均处理 50 万转码任务的分布式系统架构设计,涵盖任务生命周期管理、调度策略、进度通知和不同清晰度的差异化处理。

二、转码任务的生命周期与整体架构

2.1 任务生命周期

一个转码任务从创建到完成经历五个阶段:

PENDING → QUEUED → RUNNING → COMPLETING → COMPLETED
                                     ↘ FAILED (可重试) → PENDING
  • PENDING:任务刚创建,尚未入队。此时可编辑优先级和转码参数。
  • QUEUED:任务已入优先级队列,等待调度器分配执行节点。
  • RUNNING:转码节点已领取任务,FFmpeg 进程正在执行。
  • COMPLETING:转码完成,进行后处理——分片上传、M3U8 生成、CDN 预热。
  • COMPLETED / FAILED:终态。FAILED 状态支持自动重试(最多 3 次)。

2.2 整体架构图

三、分布式调度的核心设计

3.1 优先级队列

任务优先级由三因子加权决定:

priority = α × creator_level_score + β × video_hotness_score + γ × wait_time_bonus
  • creator_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 原子操作,取最高优先级任务
        Set<ZSetOperations.TypedTuple<String>> 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 的能力视图,将不同清晰度的任务分配到合适的 Worker:

  • 4K 转码(高计算量)优先分配到 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 "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920: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) {
                Optional<Double> 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 资源池做全局负载均衡。

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐