铁路大量下载

时间:2017-01-18 22:28:13

标签: javascript ruby-on-rails ruby

我使用paperclip和AWS S3进行文件存储。

我有一个Car模型和一个Image模型。 一辆汽车has_many :images。 图片has_attachment :file

汽车可以拥有我想要的任意数量的图像。

我想要的是一种同时下载所有这些汽车图像的方法。

我有工作代码:

def download
  @images = @car.images

  compressed_filestream = Zip::OutputStream.write_buffer do |zos|
    @images.each do |img|
      zos.put_next_entry img.file_file_name
      zos.print open(img.file.url).read
    end
  end

  compressed_filestream.rewind
  send_data compressed_filestream.read, filename: "#{@car.name}.zip"
end

请求/cars/1/download时,上述控制器操作将运行。 它有效,但我发现它真的很慢。 我现在想要的是一个更快的大规模下载解决方案。 我觉得下载时间每兆字节需要6秒。

我想要更快的方式。 我知道您可以访问任何网页,右键单击,然后"另存为..."为了保存该特定页面。 当页面有图像时,它们会在下载完成后显示在新文件夹中。 下载也非常快。 我想这是因为浏览器已经下载了这些图像,所以它只是将它们提供给我的计算机而不是再次获取图像。 如果浏览器可以下载HTML文件和资产文件夹,我们应该能够让浏览器只下载一个图像文件夹吗?

我有一些想法,我会继续努力,但我想知道是否有人有更快的解决方案或至少对当前的想法输入。

思路:

  1. 每次有人想下载时,都不要起草新的.zip文件,而是每次更新汽车的图片时编辑.zip文件。这样,当用户请求所有图像时,该文件已经存在,并且他们只是下载它。但是这些.zip文件应该放在哪里?我们在哪里以及如何保存它们?

  2. 在JavaScript中,您可以使用某个图片网址创建blob文件。我们可以在页面加载后加载所有图像吗?这样页面加载速度很快,但是在后台,当用户正在查看页面时,浏览器正在后台下载图像。如果用户决定下载它们,则下载时间很快。

  3. 也许我的控制器操作可以更快地创建一个临时的.zip文件。

  4. 想点什么?

2 个答案:

答案 0 :(得分:0)

这里最好的选择是将原始缓存系统与aws s3 gem结合使用。

首先,您在S3中创建一个名为car_image_zips的存储桶。当有人点击下载时,你会联系到这个桶,看看那里是否存在汽车图片zip。如果是,请下载它。如果没有,请下载所有文件并创建zip并上传。这里要注意的一件事是,如果您的实现使用Sidekiq之类的东西作为后台作业,您可以通过使后续上传成为后台作业来进行优化。

所以说: 我认为@car有一个id。这也假设您已正确配置AWS S3 gem。所以下载看起来像这样:

def download
 car_id = @car.id
 s3 = AWS::S3.new #should be added as constant somewhere
 bucket = s3.buckets['car_image_zips']
 if buckets.object["#{car_id}_zip"].exists? #Sample naming scheme
  send_data s3.get_object(bucket:'car_image_zips', key:"#{car_id}_zip").body.read
 else
   # Zip up files like you have
   @images = @car.images
   compressed_filestream = Zip::OutputStream.write_buffer do |zos|
    @images.each do |img|
      zos.put_next_entry img.file_file_name
      zos.print open(img.file.url).read
    end
  end
  compressed_filestream.rewindsend_data 
  s3_obj = s3.bucket('car_image_zips').object("#{@car.name}.zip")
  s3_obj.upload_file("#{@car.name}.zip")
  send_data compressed_filestream.read, filename: "#{@car.name}.zip"
end

当然,我还没有对此进行过测试,但是这应该可以让您大致了解如何使用基本缓存进行此操作。它并不完美,因为您需要下载并处理一次,但对于相对简单的解决方案来说,这是一个巨大的收获。

如果您真的想要优化,那么每次将文件上传到S3并使其可供下载时,您都可以使用类似AWS Lambda函数的内容来创建zip。

答案 1 :(得分:0)

以下是选项3的答案。在原始代码中,.each方法等待每个循环完成,然后继续下一个循环。如果将图片从互联网下载到服务器平均需要一秒钟,那么下载40张图像需要大约40秒。而是,同时下载所有文件。为此,请使用Threads

class CarsController < ApplicationController
  def download
    images = load_images

    filestream = write_file images

    send_data filestream.read, filename: "#{@car.name}.zip"
  end

  def load_images
    threads = []
    images = []

    @car.images.each do |f|
      threads << Thread.new do
        images << { name: f.file_file_name, file: open(f.file.url).read }
      end
    end

    threads.each(&:join)

    images
  end

  def write_file(images)
    require 'zip'

    Zip.default_compression = Zlib::NO_COMPRESSION

    stream = Zip::OutputStream.write_buffer do |zos|
      images.each do |img|
        zos.put_next_entry img[:name]
        zos.print img[:file]
      end
    end

    stream.rewind

    stream
  end
end

使用线程将图像及其名称铲成数组。将该信息数组传递给zipfile编写器方法。编写zipfile后,将其发送给用户。

这会将控制器动作运行时间从40秒减少到1..2秒。