Singleton在Rails中分叉独角兽进程时消失了



我有一个运行Ruby 2.4.4的rails应用程序,使用Unicorn作为Web服务器,它利用单例在后台线程中读取Kafka。这个想法是每个独角兽进程有一个单例实例。所以 4 个进程,4 个单例。

我在独角兽配置中的after_fork钩内启动了卡夫卡消费。我可以成功等待历史消息的消耗完成(通过紧接着撬动来验证(。

但是,当我到达提供流量的点时,单例实例是 a( 不同的实例,b( 空 - 之前设置的 ivar 消失了。

我已经确认我在同一个进程和同一个线程中。

设置如下:

# background_foo_consumer.rb
class BackgroundFooConsumer
include Singleton
attr_reader :background_consumer
def add_background_consumer(consumer, topics, options: nil)
@background_consumer ||= BackgroundKafkaConsumer.new(consumer, topics, options: options)
end
def processed_historical_messages?
background_consumer&.consumer&.reached_head
end
end

# config/unicorn.rb
after_worker_ready do |server, worker|
BackgroundFooConsumer.instance.add_background_consumer(nil, ["foos"])
BackgroundFooConsumer.instance.background_consumer.start
BackgroundFooConsumer.instance.background_consumer.consumer.mutex.synchronize {
BackgroundFooConsumer
.instance.background_consumer.consumer.processed_historical_messages.wait(
BackgroundFooConsumer.instance.background_consumer.consumer.mutex
)
}
end
end

我确认我在同一个进程中,甚至是同一个线程,因为我可以通过用自定义实现和 Thread 局部变量替换include Singleton成功地将正确的对象传递给应用程序,如下所示:

# config/unicorn.rb
after_worker_ready do |server, worker|
# ... same as above
Thread.current[:background_foo_consumer] = BackgroundFooConsumer.instance
end

# background_foo_consumer.rb
class BackgroundFooConsumer
attr_reader :background_consumer
def self.instance
@instance ||= begin
Thread.current[:background_foo_consumer] || self.new
ensure
Thread.current[:background_foo_consumer] = nil
end
end
end

在此实现中,当我从我的应用程序提供流量时BackgroundFooConsumer.instance是在after_fork钩中创建的正确实例,并且每个独角兽进程都有一个独立的实例,通过检查对象 ID 来确认。

我不相信这是 GC,至少底层对象不会被清理,我已经通过在 after_fork 钩子中设置 Thread 局部变量来确认这一点,然后在我的消费者类中使用include Singleton。我仍然得到空/新单例,但如果我直接查询它,线程局部变量仍然存在。

我目前的假设是这与写入时复制有关,通过设置线程局部变量,我以某种方式强制 ruby 仅为该过程创建一个单例并将其保存到该变量中。

所以我的问题是单例实例如何在单个线程中像这样消失?我怎样才能阻止它发生?如果我能提供帮助,我宁愿不使用这些线程局部变量。

这个问题的答案最终是由于一个相当小众的轨道配置:cache_classes。我在本地运行我的独角兽服务器,因此没有缓存类。

Rails(在生产模式以外的任何模式下运行时,通常在暂存和生产中都使用,但不在本地使用(如果类级对象在生产中发生其他静态更改,则会重新加载类级对象。

实际上,rails看到了一些变化并重新加载了类,因为这会阻止程序员重新启动服务器。

这是由一个名为cache_classes的配置控制的 - 我以前听说过它,这就是为什么您需要在生产中运行迁移后重新启动服务器的原因,以便可以从 ActiveRecord 对象访问任何更改。我没有把两个和两个放在一起,因为我不知道这些类会被重新加载。我仍然不确定为什么它们会被视为已更改并需要重新加载。

最终,如果我不尝试在本地运行独角兽服务器,我就不会看到这个问题,并且可以通过在development.rb中设置config.cache_classes = true来防止它

文档在这里: https://guides.rubyonrails.org/configuring.html#rails-general-configuration

相关内容

  • 没有找到相关文章

最新更新