Puma在rails应用程序启动时睡眠很重要

时间:2017-04-20 00:15:14

标签: ruby-on-rails ruby puma

我使用postgresql在puma上使用Ruby 2.3.3运行Rails 3。我有一个初始化器/ twitter.rb文件,它启动一个启动时的线程,带有一个用于twitter的流式api。当我使用rails server启动我的应用程序时,Twitter流式传输工作正常,我可以像平常一样访问我的网站。 (如果我不将流媒体放在不同的线程上,则流式传输有效但我无法在浏览器中查看我的应用程序,因为线程被twitter流阻止)。但是,当我使用puma -C config/puma.rb启动我的应用程序时,我收到以下消息,告诉我我的线程是在启动时找到的并且已经进入睡眠状态。我怎么能告诉puma让我在启动时在后台运行这个帖子?

初​​始化/ 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

配置/ 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的帖子,但是如果帖子不重要,答案就是忽略警告。在我的情况下,线程非常重要,我需要这个线程不能睡觉。

2 个答案:

答案 0 :(得分:0)

从你的代码中我认为你手上有一个比睡眠线程更大的问题...我想这可能是因为某些事情被错误命名而其他事情在依赖网络框架时通常不会被考虑

在服务器领域,“工作者”指的是执行服务器相关任务的fork进程,通常接受新连接和处理Web请求。

但是 - fork 不会重复线程! - 新进程(worker)只以一个线程开始,该线程是调用fork 的线程的副本。

这是因为进程不共享内存(通常)。无论您在进程中拥有的全局数据都是该进程的私有数据(即,如果将连接的websocket客户端保存在数组中,则该数组对于每个“工作者”都是不同的)。

这无法提供帮助,这是操作系统和fork的设计方式的一部分。

所以,警告不​​是你可以规避的事情 - 它表明了应用程序中的设计缺陷(!)。

例如,在您当前的设计中(假设线程没有休眠),只会为原始服务器进程调用handle_tweet方法,并且不会为任何工作进程调用它。

如果您使用的是pub / sub,则整个应用只需要一个twitter_stream连接(无论您的应用有多少服务器或工作者) - 也许是twitter_stream进程(或背景) app)比线程更好。

但是,如果您以特定于进程的方式实现handle_tweet - 即通过向保存在数组中的每个连接的客户端发送消息 - 您需要确保每个“worker”都启动{{1} } thread(!)。

当我写Iodine(与Puma不同的服务器)时,我使用Iodine.run method来处理这些用例,这会延迟任务。只有在初始化工作程序并且事件循环开始运行后才应执行“已保存”任务,因此它在每个进程中执行(允许您在每个进程中启动新的步骤)。

twitter_stream

我认为Puma有类似的解决方案。根据我对Puma Clustered-Mode Documentation的理解,将以下块添加到Iodine.run do Thread.start do twitter_stream.user do |object| # ... end end end 可能有所帮助:

config/puma.rb
祝你好运!

编辑:与使用ActiveRecord的# config/puma.rb on_worker_boot do Thread.start do twitter_stream.user do |object| # ... end end end 评论有关

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

尽管这两个问题是相互关联的,但它们彼此之间存在很大差异。

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

这意味着twitter_stream回调应该有自己的专用进程,只运行一次,可能与主应用程序分开。

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

initializer/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启动一个后台线程来监听发布/订阅服务(如Redis),而on_worker_boot回调“发布”发布/发布服务的更新

这将允许每个进程检查更新并检查它“拥有”的任何连接是否属于应该通知更新的客户端。

答案 1 :(得分:0)

我在阅读你的问题的方式,这看起来不是一个问题。 sleep 线程与 dead 线程不同。 Sleep只是意味着线程正在等待空闲,而不是消耗任何cpu。如果其他所有内容都正确连接,那么只要twitter api检测到一个事件,它就应该唤醒线程,运行你已定义的任何处理程序,然后再回到睡眠状态。睡觉不是在后台跑,&#34;但它是等待发生的事情(例如有人推特@me。)所以我可以在后台运行。&#34;

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

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> 

睡觉意味着等待行动。&#34;

<小时/> Puma是一个基于线程的服务器,非常特别关注在启动过程中启动线程,因此在应用程序启动时启动了线程警告。

但是,对于它的价值,让一个线程在网络服务器中监听来自api的更新是有点奇怪的。也许你应该考虑让一个工人使用像Resque这样的东西来处理Twitter事件?或者ActionCable可能与您的用例相关?