为什么Resque在处理队列中的每个作业时要使用子进程?

3

我们在大部分项目中都使用了Resque,并且对其感到满意。

在最近的一个项目中,我们遇到了这样一种情况:我们需要从Twitter的实时流API建立连接。由于我们需要保持连接,我们将每个来自流API的行都存入到一个Resque队列中,以防止连接丢失。然后我们会在之后处理这个队列。

我们发现插入速率大约是每秒30-40个,而弹出速率仅有每秒3-5个。因此,队列总是在增加。当我们查找原因时,发现Resque有一个父进程,对于队列中的每个作业,它都会派生一个子进程来处理该作业。由于我们的rails环境相当繁重,子进程派生需要花费时间。

因此,我们目前实现了另一个类似的rake任务:

rake :process_queue => :environment do
  while true
    begin
      interaction = Resque.pop("process_twitter_resque")
      if interaction
        ProcessTwitterResque.perform(interaction)
      end
    rescue => e
      puts e.message
      puts e.backtrace.join("\n")
    end
  end
end

并且像这样开始任务:

nohup bundle exec rake process_queue --trace >> log/workers/process_queue/worker.log 2>&1 &

这并没有处理失败的作业等情况。
但是,我的问题是为什么Resque要实现一个子进程来处理队列中的作业。这些作业明确不需要并行处理(因为它是一个队列,我们期望它按顺序一个接一个地处理,我相信Resque也一次只fork一个子进程)。
我相信Resque肯定有某个目的。这种父/子进程架构背后的确切目的是什么?
2个回答

2
在Redis中监听作业的Ruby进程并不是最终运行perform方法中编写的作业代码的进程。它是“主”进程,其唯一职责是监听作业。当它接收到一个作业时,它会fork另一个进程来运行代码。这个“子”进程完全由它的主进程管理。用户不需要使用rake任务启动或与之交互。当子进程完成运行作业代码后,它就退出并将控制权返回给它的主进程。现在主进程继续监听Redis以获取下一个作业。
这种主-子进程组织的优点——以及Resque进程相对于线程的优点——在于作业代码的隔离。Resque假设您的代码存在缺陷,并且包含内存泄漏或其他错误,这些错误会导致异常行为。子进程声明的任何内存都将在退出时释放。这消除了随时间不受管制的内存增长的可能性。它还为主进程提供了从子进程中恢复任何错误的能力,无论错误有多严重。例如,如果需要使用kill -9终止子进程,则不会影响主进程继续从Redis队列处理作业的能力。
在早期版本的Ruby中,Resque的主要批评是它可能消耗大量内存。创建新进程意味着为每个进程创建单独的内存空间。随着Ruby 2.0的发布,通过写时复制减轻了部分开销。但是,与使用线程的解决方案相比,Resque始终需要更多的内存,因为主进程不是fork的。它是使用rake任务手动创建的,因此必须从一开始就将所有需要加载的内容加载到内存中。当然,在具有潜在大量作业的生产应用程序中手动管理每个工作进程很快变得不可行。幸运的是,我们有池管理器来处理这个问题。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接