You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django+FFmpeg视频转码服务优化:并发与数据库同步问题问询

优化Django+FFmpeg视频转码的并发稳定性与流程可靠性方案

嘿,咱们来一步步解决这个问题——你的代码在单用户场景下能跑,但高并发和可靠性这块确实有不少可以优化的地方,我给你拆解下需要改的关键点:

一、用异步任务队列解耦转码与请求响应

当前代码是在请求处理线程里同步执行FFmpeg,多用户并发时会把所有请求都堵在转码环节,Django的worker线程被占满后新请求就会超时。必须把转码逻辑放到异步任务队列里,比如用Celery(配合Redis/RabbitMQ当消息中间件),让请求快速返回,转码任务在后台异步执行。

示例Celery任务代码

# tasks.py
from celery import shared_task
import os
import subprocess
from django.conf import settings
from your_app.models import Convert, Video  # 替换成你实际的模型

@shared_task(bind=True, max_retries=3)
def transcode_video_task(self, video_id, input_path, output_folder):
    # 定义要转码的分辨率参数
    resolutions = [
        ("240p", "426x240"),
        ("360p", "640x360"),
        ("480p", "854x480")
    ]
    base_output_path = os.path.join(settings.MEDIA_ROOT, output_folder)
    
    try:
        # 获取主视频记录
        rec = Video.objects.get(id=video_id)
        
        for res_name, res_size in resolutions:
            output_file = f"{res_name}.mp4"
            output_path = os.path.join(base_output_path, output_file)
            
            # 用列表形式传FFmpeg命令,避免shell注入,同时兼容带空格的路径
            command = [
                "ffmpeg",
                "-i", input_path,
                "-s", res_size,
                "-c:v", "libx264",  # 明确指定H.264编码器,保证兼容性
                "-crf", "23",       # 画质与文件大小的平衡值(23为默认,越小画质越好)
                "-preset", "medium",# 编码速度与压缩率的平衡选项
                "-y",               # 覆盖已存在的输出文件
                output_path
            ]
            
            # 执行命令并严格检查返回状态
            subprocess.run(command, check=True, capture_output=True, text=True)
            
            # 只有转码成功,才写入数据库
            Convert.objects.get_or_create(
                vid=rec,
                name=output_file,
                video=os.path.join(output_folder, output_file)  # 存相对路径,配合MEDIA_URL使用
            )
            
    except Video.DoesNotExist:
        # 视频记录不存在时重试
        self.retry(exc=Exception(f"Video {video_id} not found"), countdown=60)
    except subprocess.CalledProcessError as e:
        # 转码失败,记录错误日志并重试
        print(f"FFmpeg转码失败: {e.stderr}")
        self.retry(exc=e, countdown=120)
    except Exception as e:
        print(f"任务执行出错: {str(e)}")

视图中调用异步任务

# views.py
from django.shortcuts import render
from django.conf import settings
import os
import uuid
from .tasks import transcode_video_task
from .models import Video

def handle_video_upload(request):
    if request.method == "POST":
        video_file = request.FILES["video"]
        # 生成唯一文件夹名,避免重名冲突
        folder = f"uploads/{video_file.name.split('.')[0]}_{uuid.uuid4().hex[:8]}"
        upload_path = os.path.join(settings.MEDIA_ROOT, folder)
        os.makedirs(upload_path, exist_ok=True)
        input_path = os.path.join(upload_path, video_file.name)
        
        # 保存用户上传的原始视频
        with open(input_path, "wb+") as f:
            for chunk in video_file.chunks():
                f.write(chunk)
        
        # 创建主视频记录
        rec = Video.objects.create(name=video_file.name, path=os.path.join(folder, video_file.name))
        # 提交异步转码任务,立即返回响应
        transcode_video_task.delay(rec.id, input_path, folder)
        
        return render(request, "upload_success.html", {"video_id": rec.id})

二、修复FFmpeg命令的安全与可靠性问题

  • 禁用shell=True:直接用列表形式传参,既避免shell注入攻击,又能兼容带空格的文件路径
  • 明确编码参数:添加-c:v libx264指定编码器,-crf和-preset平衡画质与性能,-y自动覆盖已存在的输出文件
  • 捕获转码状态:用subprocess.run()代替subprocess.call(),并设置check=True,转码失败时会抛出异常,方便我们做重试或错误处理

三、确保数据库操作仅在转码成功后执行

之前的代码不管FFmpeg是否成功都会执行get_or_create,现在我们把数据库操作放到subprocess.run()成功执行的分支里(try块内,转码命令未抛出异常的情况下),这样只有转码真正完成且成功时,才会往数据库里添加记录。

四、其他优化细节

  • 路径规范化:用settings.MEDIA_ROOT和settings.MEDIA_URL管理媒体路径,不要硬编码media/uploads/
  • 任务重试机制:给Celery任务设置重试次数和间隔,避免临时系统资源不足导致的转码失败
  • 日志记录:把转码的成功/失败日志写入Django的日志系统,方便后续排查问题
  • 重复任务防护:可以给任务添加唯一ID,避免同一个视频被多次提交转码任务

内容的提问来源于stack exchange,提问作者varunj6v1k9

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 15:27:36