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

如何用Shrine+Sidekiq实现5GB以上大文件异步上传?

实现Sidekiq异步大文件上传的方案

Sidekiq 的 perform_async 仅支持序列化基础数据类型(字符串、数字、哈希等),无法直接传递 ActionDispatch::Http::UploadedFile 对象,你可以通过以下两种思路解决这个问题:

方案一:临时文件中转 + Sidekiq 异步上传

核心思路是先将上传的大文件保存到服务器临时目录,再把文件路径等元数据传给 Worker,由 Worker 完成最终上传并清理临时文件。

1. 控制器中保存临时文件

# 上传接口控制器
def create
  uploaded_file = params[:file]
  # 生成唯一临时文件路径,避免文件名冲突
  temp_dir = Rails.root.join('tmp', 'uploads')
  FileUtils.mkdir_p(temp_dir) # 确保目录存在
  temp_path = temp_dir.join("#{SecureRandom.uuid}_#{uploaded_file.original_filename}")

  # 用流复制文件,避免大文件占用过多内存
  File.open(temp_path, 'wb') do |dest_file|
    IO.copy_stream(uploaded_file.open, dest_file)
  end

  # 将临时路径、文件名、内容类型传给Worker
  FileUploadWorker.perform_async(temp_path.to_s, uploaded_file.original_filename, uploaded_file.content_type)
  
  # 立即返回响应,无需等待上传完成
  head :accepted
end

2. Worker 中完成上传与清理

# app/workers/file_upload_worker.rb
class FileUploadWorker
  include Sidekiq::Worker
  sidekiq_options retry: 3 # 配置重试次数

  def perform(temp_path, original_filename, content_type)
    begin
      # 替换为你的实际上传逻辑(比如上传到云存储)
      # 示例:AWS S3上传
      # s3_client = Aws::S3::Client.new
      # s3_client.put_object(
      #   bucket: 'your-bucket-name',
      #   key: original_filename,
      #   body: File.open(temp_path),
      #   content_type: content_type
      # )

      # 上传成功后清理临时文件
      File.delete(temp_path) if File.exist?(temp_path)
    rescue StandardError => e
      Rails.logger.error("文件上传失败: #{e.message}")
      # 保留临时文件用于排查,或定时清理
      raise e # 触发Sidekiq自动重试
    end
  end
end

注意事项

  • 临时文件清理:可通过 Sidekiq Cron 定时清理 tmp/uploads 目录中超过24小时的文件,避免磁盘占用过高。
  • 资源限制:调整 Sidekiq 并发数(默认5),避免大文件上传占用过多带宽和CPU,影响其他任务。
  • 权限配置:确保 Sidekiq 进程对 tmp/uploads 目录有读写权限。

方案二:预签名URL直传(更适合大文件)

如果你的文件最终要上传到云存储(如S3、阿里云OSS),推荐用预签名URL让客户端直接上传到云存储,无需经过服务器中转,效率更高:

1. 控制器生成预签名URL

def presign
  filename = params[:filename]
  content_type = params[:content_type]
  
  # 生成有效期1小时的预签名PUT URL
  s3_object = Aws::S3::Resource.new.bucket('your-bucket').object(filename)
  presigned_url = s3_object.presigned_url(:put, content_type: content_type, expires_in: 3600)

  render json: { presigned_url: presigned_url, filename: filename }
end

2. 客户端直接上传

客户端拿到预签名URL后,直接通过HTTP PUT请求将文件上传到云存储,上传完成后再通知服务器更新数据库或执行后续逻辑(可通过另一接口触发Sidekiq任务)。

这种方式完全避免了服务器存储和带宽压力,是大文件上传的最优解。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 20:09:23