【问题标题】:Problems with file.path with csv import via sidekiq on heroku通过heroku上的sidekiq导入csv的file.path问题
【发布时间】:2015-08-10 12:37:13
【问题描述】:

我正在使用后台作业将用户数据从 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 的情况下执行此操作时,它可以工作。

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

【问题讨论】:

    标签: ruby-on-rails heroku sidekiq


    【解决方案1】:

    我有同样的经历,你可以在https://github.com/coderaven/datatable-exercise/tree/parallel_processing查看我的类似项目

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

    错误: Errno::ENOENT: No such file or directory @ rb_sysopen 如果您说这适用于 heroku,那么这可能意味着您获得的路径是有效的(在您的示例中,您使用的是 /tmp/ 路径)

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

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

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

    2.) 如果路径正确并且文件已正确保存或处理,那么根据我的经验可能是您使用的是 File.open 而不是 open-uri 的 open 方法。 File.open 不接受远程文件,您需要在工作人员上要求 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
    

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

    【讨论】:

      【解决方案2】:

      您不能将文件对象传递给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

      【讨论】:

        【解决方案3】:

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

        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)
        

        【讨论】:

          猜你喜欢
          • 2018-08-06
          • 2020-01-19
          • 1970-01-01
          • 1970-01-01
          • 2017-05-25
          • 1970-01-01
          • 2020-01-12
          • 2019-03-14
          • 1970-01-01
          相关资源
          最近更新 更多