在heroku上通过sidekiq导入csv导入的file.path问题

时间:2015-08-10 12:37:14

标签: ruby-on-rails heroku sidekiq

我正在使用后台作业,以便将用户数据从csv文件导入到我的数据中。首先我做了这个"很难"在我的用户模型中,只需调用我的用户模型中的方法,并传递通过表单file_field传输的文件路径:

User.import_csv(params[:file].path)

在本地和生产(heroku)工作得很好。

现在谈到巨大的CSV文件,我明白我需要一份工作才能在后台执行此导入。我熟悉redis和sidekiq,所以工作很快就建好了。

CsvImportJob.perform_async(URI.parse(params[:file].path))

在我的工作人员中:

def perform(file_path)

  User.import_csv(file_path)

end

嗯,这在本地也很完美,但是一旦我在制作时遇到这个,我在日志中看到以下错误:

» 10 Aug 2015 13:56:26.596 2015-08-10 11:56:25.987726+00:00 app worker.1 - - 3 TID-oqvt6v1d4 ERROR: Actor crashed!
» 10 Aug 2015 13:56:26.596 2015-08-10 11:56:25.987728+00:00 app worker.1 - - Errno::ENOENT: No such file or directory @ rb_sysopen - /tmp/RackMultipart20150810-6-14u804c.csv
» 10 Aug 2015 13:56:26.596 2015-08-10 11:56:25.987730+00:00 app worker.1 - - /app/vendor/ruby-2.2.2/lib/ruby/2.2.0/csv.rb:1256:in `initialize'

这是file_path变量。 当我将文件传递给sidekiq作业时,Heroku无法找到该文件。当我没有sidekiq这样做时,它可以工作。

我真的不知道如何解决这个问题,所以感谢任何帮助。

3 个答案:

答案 0 :(得分:1)

我有同样的经历,您可以在https://github.com/coderaven/datatable-exercise/tree/parallel_processing

查看我的类似项目

(基本上只关注object_record.rb模型和作业:import_csv_job.rb和process_csv_job.rb)

错误: Errno :: ENOENT:没有这样的文件或目录@ rb_sysopen 如果你说这可以在heroku上运行那么可能这意味着你获得它的路径是有效的(在你的例子中你使用的是/ tmp / path)

所以这里有两个可能的问题及其解决方案:

1。)您已将未知的Heroku路径(或不可访问的路径)保存到应用程序运行时无法访问或打开。因为,当处理导入csv时没有sidekiq - 您上传的文件暂时保存在内存中,直到您完成处理csv为止 - 但是,在作业调度程序(或sidekiq)中,路径不应该在内存中,并且应该是应用程序可访问的现有路径。

解决方案:将文件保存到某个存储位置(heroku有一个短暂的文件系统,因此您无法通过正在运行的Web应用程序保存文件)来解决这个问题,您必须使用类似Amazon S3的服务(您也可以使用Google像我那样开车)将文件保存在那里,然后给你的sidekiq工作者提供路径 - 这样它就可以在以后访问和处理它。

2。)如果路径正确并且文件保存或处理正确,那么从我的经验可能是您使用File.open而不是open-uri的open方法。 File.open不接受远程文件,你需要在你的worker上使用open-uri然后使用open方法来处理远程文件。

离。

require 'open-uri'

class ProcessCsvJob < ActiveJob::Base
  queue_as :default

  def perform(csv_path)
    csv_file = open(csv_path,'rb:UTF-8')

    SmarterCSV.process(csv_file) do |array|
        .... code here for processing ...
    end
  end

end

我完全清楚这个问题已经过去了将近一年,所以如果你已经解决了这个问题,或者这个答案有效,那么它也可以作为文档存档帮助那些可能会遇到同样问题的人。

答案 1 :(得分:0)

您无法将文件对象传递给perform方法。

修复是事先按摩数据并直接传递你需要的参数。

像...一样的东西。

def import_csv(file)
  CSV.foreach(file.path, headers: true) do |row|
    new_user = { email: row[0], password: row[1] }
    CsvImportJob.perform_async(new_user)
  end
end

注意:您可以使用ActiveJob和Rails 5为Sidekiq调用CsvImportJob.perform_later

答案 2 :(得分:0)

您收到此错误,是因为在生产/登台和sidekiq上运行在不同的服务器上。 使用我的解决方案:将csv上传到Google云存储

class Services::Downloader
  require 'fog'
  StorageCredentials = YAML.load_file("#{::Rails.root}/config/g.yml")[Rails.env]


  def self.download(file_name, local_path)
    storage = Fog::Storage.new(
        provider: "Google",
        google_storage_access_key_id: StorageCredentials['key_id'],
        google_storage_secret_access_key: StorageCredentials['access_key'])
    storage.get_bucket(StorageCredentials['bucket'])
    f = File.open(local_path)
    storage.put_object(StorageCredentials['bucket'], file_name, f)
    storage.get_object_https_url(StorageCredentials['bucket'], file_name, Time.now.to_f + 24.hours)
  end
end

班级用户

class User < ApplicationRecord
  require 'csv'
  require 'open-uri'


  def self.import_data(file)
    load_file =  open(file)
    data = CSV.read(load_file, { encoding: "UTF-8", headers: true, header_converters: :symbol, converters: :all})
...

工人

class ImportWorker
  include Sidekiq::Worker
  sidekiq_options queue: 'workers', retry: 0

  def perform(filename)

    User.import_data(filename)
  end
end

和启动工作者代码

--
path = Services::Downloader.download(zip.name, zip.path)
      ImportWorker.perform_async(path)