Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/ruby-on-rails/61.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby on rails 导轨3中的拉/推状态_Ruby On Rails_Ruby_Ruby On Rails 3_Delayed Job_Sidekiq - Fatal编程技术网

Ruby on rails 导轨3中的拉/推状态

Ruby on rails 导轨3中的拉/推状态,ruby-on-rails,ruby,ruby-on-rails-3,delayed-job,sidekiq,Ruby On Rails,Ruby,Ruby On Rails 3,Delayed Job,Sidekiq,我在后台有一个运行时间较长的任务,我应该如何从后台任务中提取状态,或者如何更好地将任务完成情况传达给前端 背景: 基本上,我的应用程序使用第三方服务来处理数据,所以我希望这个外部web服务工作负载不会阻止对我网站的所有传入请求,所以我将这个调用放在后台作业中(我使用sidekiq)。因此,当这个任务完成时,我想向某个控制器发送一个webhook,它会通知前端任务已经完成 我该怎么做?有更好的解决办法吗 更新: 我的应用程序托管在heroku上 更新II: 我对这个主题做了一些研究,发现我可以在h

我在后台有一个运行时间较长的任务,我应该如何从后台任务中提取状态,或者如何更好地将任务完成情况传达给前端

背景:

基本上,我的应用程序使用第三方服务来处理数据,所以我希望这个外部web服务工作负载不会阻止对我网站的所有传入请求,所以我将这个调用放在后台作业中(我使用sidekiq)。因此,当这个任务完成时,我想向某个控制器发送一个webhook,它会通知前端任务已经完成

我该怎么做?有更好的解决办法吗

更新:

我的应用程序托管在heroku上

更新II:

我对这个主题做了一些研究,发现我可以在heroku上创建一个单独的应用程序来处理这个问题,发现了这个例子:


这个长时间运行的任务将在流量很大的网站上按用户运行,这是个好主意吗

我将使用诸如或之类的系统来实现这一点。这背后的想法是,将长时间运行的作业的状态发布到一个通道,然后将导致该通道的所有订户收到状态更改的通知

例如,在您的job runner中,您可以通过以下方式通知Faye状态更改:

client = Faye::Client.new('http://localhost:9292/')
client.publish('/jobstatus', {id: jobid, status: 'in_progress'})
然后在前端,您可以使用javascript订阅该频道:

var client = new Faye.Client('http://localhost:9292/');

client.subscribe('/jobstatus', function(message) {
  alert('the status of job #' + message.jobid + ' changed to ' + message.status);
});
通过这种方式使用发布/订阅系统,您可以独立于主应用程序扩展实时页面事件-您可以在另一台服务器上运行Faye。你也可以选择像Pusher这样的托管(付费)解决方案,让他们负责扩展你的基础设施


还值得一提的是,Faye使用,这意味着它将在可用的地方使用WebSocket,在不可用的地方使用长轮询。

我们有这种模式,并使用两种不同的方法。在这两种情况下,后台作业都与一起运行,但您可能会使用或执行类似的操作

轮询

在轮询方法中,页面上有一个javascript对象,该对象通过从rails HTML视图传递给它的URL设置轮询超时

这将导致对提供的URL进行Ajax(“脚本”)调用,这意味着Rails将查找JS模板。因此,我们使用它来响应state,并在对象可用或不可用时触发事件进行响应

这有点复杂,我现在不推荐这样做

插座

我们找到的更好的解决方案是使用WebSocket(带垫片)。在我们的例子中,我们使用PubNub,但是有许多服务来处理这个问题。这样可以使轮询/打开连接远离web服务器,并且比运行处理这些连接所需的服务器更具成本效益

您已经声明您正在寻找前端解决方案,并且可以使用PubNub的客户端JavaScript库处理所有前端

下面是我们如何从后端通知PubNub的大致情况

class BackgroundJob
  @queue = :some_queue

  def perform
    // Do some action
  end

  def after_perform
    publish some_state, client_channel
  end

  private

  def publish some_state, client_channel
    Pubnub.new(
        publish_key: Settings.pubnub.publish_key,
        subscribe_key: Settings.pubnub.subscribe_key,
        secret_key: Settings.pubnub.secret_key
    ).publish(
        channel: client_channel,
        message: some_state.to_json,
        http_sync: true
    )
  end
end

我能想到的最简单的方法是,当任务完成时,在数据库中设置一个标志,前端(视图)定期发送一个ajax请求以检查数据库中的标志状态。如果设置了标志,则在视图中采取适当的操作。以下是代码示例:

由于您建议此长时间运行的任务需要按用户运行,因此让我们向
用户
表添加一个布尔值-
任务_complete
。将作业添加到sidekiq时,可以取消设置标志:

# Sidekiq worker: app/workers/task.rb

class Task
include Sidekiq::Worker
  def perform(user_id)
    user = User.find(user_id)
    # Long running task code here, which executes per user
    user.task_complete = true
    user.save!
  end
end

# When adding the task to sidekiq queue
user = User.find(params[:id])
# flag would have been set to true by previous execution
# In case it is false, it means sidekiq already has a job entry. We don't need to add it again
if user.task_complete?
  Task.perform_async(user.id)
  user.task_complete = false
  user.save!
end
在视图中,您可以定期检查标志是否使用ajax请求设置:

<script type="text/javascript">
var complete = false;
(function worker() {
  $.ajax({
    url: 'task/status/<%= @user.id %>', 
    success: function(data) {
      // update the view based on ajax request response in case you need to
    },
    complete: function() {
      // Schedule the next request when the current one's complete, and in case the global variable 'complete' is set to true, we don't need to fire this ajax request again - task is complete.
      if(!complete) {
      setTimeout(worker, 5000);  //in miliseconds
      }
    }
  });
})();
</script>

# status action which returns the status of task
# GET /task/status/:id
def status
  @user = User.find(params[:id])
end

# status.js.erb - add view logic based on what you want to achieve, given whether the task is complete or not

<% if @user.task_complete? %>
  $('#success').show();
  complete = true;
<% else %>
  $('#processing').show();
<% end %>

var complete=false;
(职能工作人员(){
$.ajax({
url:“任务/状态/”,
成功:功能(数据){
//如果需要,请基于ajax请求响应更新视图
},
完成:函数(){
//在当前请求完成时安排下一个请求,如果全局变量“complete”设置为true,我们不需要再次触发此ajax请求-任务完成。
如果(!完成){
setTimeout(worker,5000);//以毫秒为单位
}
}
});
})();
#返回任务状态的状态操作
#获取/任务/状态/:id
def状态
@user=user.find(参数[:id])
结束
#status.js.erb-根据任务是否完成,根据您想要实现的内容添加视图逻辑
$(“#成功”).show();
完整=正确;
$(“#处理”).show();
您可以根据任务的平均执行时间设置超时。假设你的任务平均需要10分钟,所以他们没有必要以5秒的频率检查

此外,如果任务执行频率比较复杂(而不是每天1次),您可能需要在处添加时间戳
task\u completed\u,并将您的逻辑基于标志和时间戳的组合

关于这一部分: “这个长时间运行的任务将在流量很大的网站上按用户运行,这是个好主意吗?”


我不认为这种方法有什么问题,尽管在单独的硬件上执行作业(sidekiq workers)等体系结构更改会有所帮助。这些是轻量级ajax调用,javascript中内置的一些智能(如全局完成标志)将避免不必要的请求。如果您有巨大的流量,并且DB读/写是一个问题,那么您可能希望将该标志直接存储到
redis
中(因为您已经为sidekiq提供了该标志)。我相信这将解决您的读/写问题,我不认为这会导致问题。这是我能想到的最简单、最干净的方法,不过你可以尝试通过WebSocket实现同样的效果,大多数现代浏览器都支持WebSocket(虽然在旧版本中可能会导致问题)。

使用HTML5套接字io会很棘手,所以我建议你使用Javascript(比如jQuery)并使用