我需要从rails应用程序中收集一些数据,将其聚合,然后定期将其发送到远程服务器。我在application.rb中的全局变量(我知道,我知道)中实例化我的聚合类。
在我的聚合类中,我启动一个休眠10秒的线程,然后查看队列,处理数据并发送它。队列是存储在类的实例变量中的哈希。
在rails控制器中,我调用聚合器类中的方法来对哈希中的数据进行排队。当然,这与读取队列的后台任务不同。问题是后台任务永远不会在哈希中看到任何数据。在我的日志中,我在写入它时(从控制器线程)和当我从它(从后台线程)读取时打印出hash的object_id。散列#object_id匹配两个线程,但后台线程永远不会看到数据。
什么杀死我是这在rails之外运行良好。我已经设置了许多线程的测试,这些线程确实很重要,并且它工作正常(有一些线程保护,我没有显示清晰度)。有人知道object_id
的匹配方式,但内容不一致吗?
class Aggregator
def initialize
@q = {}
@timer = nil
end
def start
@timer = Thread.new do
loop do
sleep(10)
flush_q
end
end
end
def flush_q
logger.debug "flush: q.object_id = #{@q.object_id}" # matches what I get below
logger.debug "flush: q.length = #{@q.length}" # always zero!
@q.each_pair do |k,v|
# pack it up and send it
end
@q.clear
end
def add(item)
logger.debug "add: q.object_id = #{@q.object_id}" # matches what I get above
@q[item.name] ||= item
logger.debug "add: q.length = #{@q.length}" # increases with each add
# not actually that simple, but not relevant
end
end
答案 0 :(得分:0)
我将走出困境并假设您的代码是使用分叉应用服务器(例如独角兽或乘客)部署的。
这意味着您的应用程序已加载一次,然后从该主实例分叉新实例。分叉很便宜,这意味着应用程序的新实例可以非常快速地启动/关闭。
我相信您的聚合器实例正在此主进程中创建/启动。当这个分叉时,进程的整个内存空间被复制(所以在新进程中有一个聚合器实例,具有相同的对象id等等。)
但是,当仅分叉当前线程时,所以聚合器刷新仅发生在主进程中,但所有附加都发生在子进程中。您可以通过将Proccess.pid
添加到您记录的内容来确认这一点 - 您应该看到您的日志记录来自2个不同的进程。
解决此问题的一种方法是在子进程分叉后启动/重新启动线程。如何执行此操作取决于应用程序的提供方式。使用独角兽,您可以通过after_fork
方法在独角兽配置中执行此操作。有乘客你做
PhusionPassenger.on_event(:starting_worker_process) do |forked|
if forked
...
end
end