如何用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
相关产品推荐
相关产品推荐

