如何在 Rails 5.2.4 中从 _Sidekiq Worker 调用控制器操作?

How to call controller action from _Sidekiq Worker in Rails 5.2.4?

我的 RoR 应用程序从 Linux 操作系统触发数据处理脚本(SAS 技术)。 Scheduler::ProductionExecutionsController 中的 execute 方法驱动与脚本的交互。

Scheduler::ProductionExecutionsController

  # POST /production_executions/1/execute
  def execute
    @production_execution = ProductionExecution.find(params[:id])
    @production_execution.update_attributes(started_at: Time.now, 
                                            status_id: statuses.find { |x| x["code"] == "RUNNING" }.id)

--- Scripts interaction ---

  @production_execution.update_attributes(ended_at: Time.now,
                                          status_id: statuses.find { |x| x["code"] == "FINISHED" }.id,
                                          source_records_count: global_input_count,
                                          processed_count: global_output_count,
                                          error_message: nil
                                          )

  respond_to do |format|
    format.html { redirect_back fallback_location: @production_execution, notice: @msg }
    format.js
  end

此方法使用以下语法调用:link_to "Go!", execute_scheduler_production_execution_path(execution)。路线已定义并且按预期工作。

由于某些脚本可能需要一分钟以上的时间才能执行,因此我需要在专门的作业中执行脚本,有时还要安排它们。所以我安装了 Sidekiq 并定义了一个 Scheduler::ScriptWorker :

class Scheduler::ScriptWorker
  include Sidekiq::Worker
  sidekiq_options queue: :default, tags: ['script']

  def perform(execution_id)
    puts "Sidekiq job start"
    puts execution_id
    redirect_to execute_scheduler_production_execution_path(execution_id) and return
  end

end

执行排队 Scheduler::ScriptWorker.perform_async(@execution.id)

Sidekiq 运行正常,但每次调用 worker 时,都会引发以下错误:

WARN: NoMethodError: undefined method `execute_scheduler_production_execution_path' for #<Scheduler::ScriptWorker:0x000000000b15ded8>

这样做是否正确,我该如何解决这个问题? 感谢您的帮助!

简短的回答是:你不能

您无法从异步 Sidekiq 工作程序执行重定向。当用户单击“开始!”时,将向您的 Rails 网络应用程序发出 HTTP 请求。 Rails 将请求传递给控制器​​操作,在您的情况下会产生一个异步 sidekiq 作业。控制器操作逻辑继续并且 Rails 使用 HTTP 响应完成 HTTP 请求,即使您的 sidekiq worker 仍然是 运行。您不能将消息从 sidekiq worker 广播回发起任务的用户

很抱歉这不能解决您的问题。您需要采取不同的方法。

正如 Sean Huber 所说,将前端作业转换为后端作业需要一些架构。通过重构,实验性的 execute 方法被移动到 helper 并重命名为 execute_ssh。控制器中的新 run_once 方法会触发助手的 execute_ssh 方法并重新加载页面。

script_worker.rb

class Scheduler::ScriptWorker
  include Sidekiq::Worker
  include SchedulerExecutionHelper
  include ParametersHelper
  sidekiq_options queue: :default, tags: ['script'], retry: false

  def perform(execution_id)
    puts "Sidekiq job start"
    puts execution_id
    execute_ssh execution_id
  end
end

scheduler_execution_helper.rb

  def execute_ssh(execution_id)
    puts "--- Switched to helper"
    @production_execution = ProductionExecution.find(execution_id)
    puts @production_execution.id

    @production_execution.update_attributes(started_at: Time.now, status_id: statuses.find { |x| x["code"] == "RUNNING" }.id)

--- Scripts interaction ---

  @production_execution.update_attributes(ended_at: Time.now,
                                          status_id: statuses.find { |x| x["code"] == "FINISHED" }.id,
                                          source_records_count: global_input_count,
                                          processed_count: global_output_count,
                                          error_message: nil
                                          )

  end

production_schedules_controller

  def run_once
    @job = @production_schedule.parent
    @execution = @job.production_executions.build(playground_id: @job.playground_id,
                                                  production_job_id: @job.id,
                                                  environment_id: @production_schedule.environment_id,
                                                  owner_id: current_user.id,
                                                  status_id: options_for('Statuses', 'Scheduler').find { |x| x["code"] == "READY" }.id || 0)
    if @execution.save
      @job.production_events.where(production_execution_id: nil).each do |event|
        execution_event = event.dup
        execution_event.production_execution_id = @execution.id
        execution_event.return_value = 0
        execution_event.status_id = statuses.find { |x| x["code"] == "READY" }.id
        execution_event.save
      end
      Scheduler::ScriptWorker.perform_async(@execution.id)
      redirect_to scheduler_production_job_path(@job)
    else
    end
  end

这样,controller 仍然很瘦,worker 可以很容易地重用,逻辑在 helper 模块中。