Puma在Rails应用程序的引导时睡了一个重要的线程



我正在运行带有Postgresql的Puma上的Ruby 2.3.3的Rails 3。我有一个initializer/twitter.rb文件,该文件启动了带有Twitter的流API在启动上的线程。当我使用rails server启动应用程序时,Twitter流媒体可以正常地到达我的网站。(如果我不将流媒体放在另一个线程上,则流媒体可行,但由于线程被Twitter流阻止,因此无法在浏览器中查看我的应用程序(。但是,当我使用puma -C config/puma.rb启动我的应用程序时,我会收到以下消息,这些消息告诉我我的线程是在启动时发现的,并且被入睡。我该如何告诉PUMA让我在启动的背景中运行此线程?

initializer/twitter.rb

### START TWITTER THREAD ### if production
if Rails.env.production?
  puts 'Starting Twitter Stream...'
  Thread.start {
    twitter_stream.user do |object|
      case object
        when Twitter::Tweet
          handle_tweet(object)
        when Twitter::DirectMessage
          handle_direct_message(object)
        when Twitter::Streaming::Event
          puts "Received Event: #{object.to_yaml}"
        when Twitter::Streaming::FriendList
          puts "Received FriendList: #{object.to_yaml}"
        when Twitter::Streaming::DeletedTweet
          puts "Deleted Tweet: #{object.to_yaml}"
        when Twitter::Streaming::StallWarning
          puts "Stall Warning: #{object.to_yaml}"
        else
          puts "It's something else: #{object.to_yaml}"
      end
    end
  }
end

config/puma.rb

workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5)
threads threads_count, threads_count
preload_app!
rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'

on_worker_boot do
  # Valid on Rails up to 4.1 the initializer method of setting `pool` size
  ActiveSupport.on_load(:active_record) do
    config = ActiveRecord::Base.configurations[Rails.env] ||
    Rails.application.config.database_configuration[Rails.env]
    config['pool'] = ENV['RAILS_MAX_THREADS'] || 5
    ActiveRecord::Base.establish_connection(config)
  end
end

启动上的消息

2017-04-19T23:52:47.076636+00:00 app[web.1]: Connecting to database specified by DATABASE_URL
2017-04-19T23:52:47.115595+00:00 app[web.1]: Starting Twitter Stream...
2017-04-19T23:52:47.229203+00:00 app[web.1]: Received FriendList: --- !ruby/array:Twitter::Streaming::FriendList []
2017-04-19T23:52:47.865735+00:00 app[web.1]: [4] * Listening on tcp://0.0.0.0:13734
2017-04-19T23:52:47.865830+00:00 app[web.1]: [4] ! WARNING: Detected 1 Thread(s) started in app boot:
2017-04-19T23:52:47.865870+00:00 app[web.1]: [4] ! #<Thread:0x007f4df8bf6240@/app/config/initializers/twitter.rb:135 sleep> - /app/vendor/ruby-2.3.3/lib/ruby/2.3.0/openssl/buffering.rb:125:in `sysread'
2017-04-19T23:52:47.875056+00:00 app[web.1]: [4] - Worker 0 (pid: 7) booted, phase: 0
2017-04-19T23:52:47.865919+00:00 app[web.1]: [4] Use Ctrl-C to stop
2017-04-19T23:52:47.882759+00:00 app[web.1]: [4] - Worker 1 (pid: 11) booted, phase: 0
2017-04-19T23:52:48.148831+00:00 heroku[web.1]: State changed from starting to up

事先感谢您的帮助。我查看了提到WARNING: Detected 1 Thread(s) started in app boot的其他几篇文章,但是答案说明线程并不重要的情况下忽略了警告。就我的情况而而不是一个睡眠线...我猜这可能是由于某些事情被错误地命名的事实而引起

在服务器世界中,"工人"是指执行服务器相关任务的fork ED进程,通常接受新的连接并处理Web请求。

但是 - fork 不重复线程! - 新过程(工人(仅以一个单个线程开头,该线程是该线程的副本,称为fork

这是因为进程不共享内存(通常(。无论您在一个过程中拥有的全局数据都是私人的(即,如果将连接的Websocket端列在数组中,则每个"工作"的数组都不同(。

这是无法帮助的,这是OS和fork的设计的一部分。

因此,警告不是您可以规避的 - 这是应用程序中设计缺陷的指示(!(。

例如,在您当前的设计中(假设线程没有睡觉(,handle_tweet方法仅用于原始服务器过程,并且不会为任何工作过程调用。

如果您使用的是Pub/sub,则只需要一个twitter_stream连接整个应用程序(无论您应用多少服务器或工人有多少个( - 也许twitter_stream流程(或背景应用程序(都比线程更好。

但是,如果您要以特定方式实现handle_tweet - 即,通过向保存在数组中的每个连接客户端发送消息,您需要确保每个"工作者"启动twitter_stream线程(!(。p>当我编写碘(与PUMA不同的服务器(时,我使用Iodine.run方法处理了这些用例,该方法为稍后的任务进行了辩护。仅在初始化工人并开始运行事件循环后才执行"保存"任务,因此在每个过程中都执行(允许您在每个Proccess中启动新的胎面(。

即。

Iodine.run do
   Thread.start do
    twitter_stream.user do |object|
    # ...
    end
   end
end

我假设PUMA具有类似的解决方案。据我了解PUMA群集模式文档,将以下块添加到您的config/puma.rb可能会有所帮助:

# config/puma.rb
on_worker_boot do
  Thread.start do
   twitter_stream.user do |object|
   # ...
   end
  end
end

祝你好运!


edit :使用ActivereCord

twitter_stream的评论有关

从我收集的评论中,twitter_stream回调将数据存储在数据库中以及处理"推"事件或通知。

尽管这两个问题已连接,但它们彼此截然不同。

例如,twitter_stream回调仅应将数据存储在数据库曾经>中。即使您的应用程序增长到十亿用户,您也只需要将数据保存在。

这意味着twitter_stream回调应具有仅运行一次的专用过程,可能与主应用程序分开。

首先,只要您将应用程序限制为单个(仅运行一台服务器/应用程序(,就可以将forkinitializer/twitter.rb脚本一起使用...即:

### START TWITTER PROCESS ### if production
if Rails.env.production?
  puts 'Starting Twitter Stream...'
  Process.fork do
    twitter_stream.user do |object|
      # ...
    end
  end
end

另一方面,应通过特定过程在特定连接拥有的特定连接上的特定用户。

因此,通知应与twitter_stream数据库更新是一个单独的关注点,并且应该使用上述on_worker_boot(或Iodine.run(在每个过程的背景中运行。

为了实现这一目标,您可能让on_worker_boot启动一个背景线程,该线程将收听redis等酒吧/sub服务,而twitter_stream回调"发布"更新到Pub/sub Service。

这将允许每个过程查看更新并检查其"拥有"的任何连接是否属于应通知更新的客户端。

我正在阅读您的问题的方式,这看起来不像一个问题。A 睡觉线程与A Dead 线程不同。睡眠只是意味着线程正在等待空闲,而不是消耗任何CPU。如果其他所有内容都正确连接,那么一旦Twitter API检测到事件,它应该唤醒线程,运行您定义的任何处理程序,然后直接回到睡眠状态。睡觉不是"在后台运行",而是"在等待某些事情发生(例如,有人推文@ME。(,所以我可以在后台运行。"

一个简单的例子来证明这一点:

2.4.0 :001 > t = Thread.new { TCPServer.new(1234).accept ; puts "Got a connection! Dying..." }
 => #<Thread:0x007fa3941fed90@(irb):1 sleep> 
2.4.0 :002 > t
 => #<Thread:0x007fa3941fed90@(irb):1 sleep> 
2.4.0 :003 > t
 => #<Thread:0x007fa3941fed90@(irb):1 sleep> 
2.4.0 :004 > TCPSocket.new 'localhost', 1234
 => #<TCPSocket:fd 35> 
2.4.0 :005 > Got a connection! Dying...
t
 => #<Thread:0x007fa3941fed90@(irb):1 dead> 

睡觉只是"等待行动"。


PUMA是基于线程的服务器,非常特别地是在其启动过程中旋转线程,因此在App Boot上启动有关线程的警告。

对于它的价值,让线程收听Web服务器中的API的更新是很奇怪的。也许您应该考虑让工人使用Resque之类的东西处理Twitter事件?或者也许与您的用例有关?

最新更新