描述
Puma是一个简单,快速,线程式,并且支持HTTP1.1高并发的Ruby/Rack服务器。在开发环境和生产环境中都可以使用Puma。
为速度和并发而生
Puma是一个应用于Ruby Web应用,简单,快速,支持高并发的HTTP1.1服务器。它可以和任何支持Rack的应用配合使用,是Webrick
或Mongrel
的替代选择。它最初被设计为Rubinius的服务器,但是配合JRuby
和MRI
使用性能同样出色。在开发环境和生产环境中都可以使用Puma。
在面纱之下,Puma使用经过C语言优化的Ragel扩展(继承自Mongrel)来解析请求,用一种轻便的方式进行快速,精确的HTTP 1.1协议解析。然后使用内部线程池的线程来处理请求。这使得Puma可以为你的Web应用提供真正的并发。
Rubinius 2.0后,Puma将使用真正的线程来利用CPU的所有核,因此不用再使用多进程来提高性能。可以期待我们将获得和JRuby相似的性能。
对于MRI,因为全局锁(GIL)的限制,每次只能运行一个线程。但是对于大量阻塞IO的操作(例如调用Twitter API的HTTP请求),Puma还是通过允许阻塞IO操作并行运行提高了MRI的性能(像Thin这种以EventMachine为基础的服务器关闭了这个特性,需要自己使用第三方库)。根据你的使用情况,为了获得最佳性能,强烈建议使用实现了真正多线程的Ruby版本,例如 Rubinius或JRuby。
快速开始
开始使用Puma最简单的方式是通过RubyGems安装它。简单的运行以下命令即可:
$ gem install puma
现在PATH中应该有了puma命令,可以试着在你的Rack应用根目录中运行:
$ puma app.ru
插件
Puma 3.0增加了对插件的支持,可以使用这些插件来增强配置项。
来看两个核心插件
- tmp_restart: 如果tmp/restart.txt被touch,则重启服务器。
- heroku:封装了在Heroku上使用Puma的默认配置。
插件可以通过在puma配置文件(例如config/puma.rb)中添加plugin "插件名"来指定,例如plugin "heroku"。
也可以通过引用路径来指定,要使用heroku插件只需要引用puma/plugin/heroku即可。这样一来gem可以提供多个插件(或者由不相关的gem提供puma插件)。
Puma内置了tmp_restart
插件,可以直接使用。
可以通过将puma-heroku
添加到Gemfile中来安装使用heroku插件。
API
目前,插件可以使用两个钩子方法:start
和config
。
start
在服务器启动之后运行,可以支持插件启动一些其他的服务来增强puma。
config
在服务器被配置时执行,并且会被传递一个Puma::DSL对象用来添加增强的配置项。
在Puma::Plugin
中的所有公共方法都是插件可以使用的公共API。
未来,会加入更多的钩子方法和API。
高级配置
Sinatra
可以通过命令行使用Puma运行Sinatra应用
$ ruby app.rb -s Puma
或者配置应用默认使用Puma运行
require 'sinatra'
configure { set :server, :puma }
如果使用Bundler的话,请将Puma加入到Gemfile当中(见下文)。
Rails
首先,确定Puma在Gemfile中
gem 'puma'
然后使用rails
命令启动服务
$ rails s Puma
Rails 5.0之后puma是默认服务器,直接执行rails s
即可。
Rackup
将Puma作为参数传递给rackup
来启动应用
$ rackup -s Puma
也可以通过将下面的命令加到config.ru
文件的第一行,使应用默认以Puma启动
#\ -s puma
配置
Puma提供了多种选项来控制服务器的操作,执行puma -h
(或puma --help
)了解详情。
线程池
Puma使用可配置的动态线程池。可以通过配置-t
(或--threads
)来指定可用的最小和最大线程数。
$ puma -t 8:32
Puma会基于流量自动调节线程数,范围从最小值到最大值。当前默认设置是0:16
。你可以修改这个设置,但是当心不要将最大线程数设置的过大,这样有可能会耗尽系统资源。
集群模式
Puma2提供了集群模式,支持使用fork出的进程配合已有的多线程功能来并发的处理请求。可以通过配置-w
(或--workers
)来指定workers数量:
$ puma -t 8:32 -w 3
在提供真正多线程的Ruby实现中,应该将worker数设置为可用的核数。注意多线程仍然在集群模式下可用,使用-t
来为每一个worker设置线程数,所以-w 2 -t 16:16
就是32个线程。
在集群模式下,�Puma可以"预加载"应用。在fork之前加载整个项目代码。预加载通过利用操作系统的copy-on-write特性降低了内存的使用量(Ruby2.0以上)。指定--preload
启动puma:
puma -w 3 --preload
如果使用配置文件,则在文件中指定preload_app!
方法:
# config/puma.rb
workers 3
preload_app!
另外可以在配置文件中指定每个worker启动时需要执行的代码块:
# config/puma.rb
on_worker_boot do
# configuration here
end
这段代码用于在应用启动之前设置进程,避免将puma相关的设置嵌入应用代码中。比如,可以在这里设置worker启动的日志信息。
如果你需要预加载项目并且在使用ActiveRecord
,那么建议你在这里设置连接池:
# config/puma.rb
on_worker_boot do
ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.establish_connection
end
end
在其上还可以指定fork之前需要执行的代码块:
# config/puma.rb
before_fork do
# configuration here
end
预加载不能和分步重启一起使用,因为分步重启需要一个个杀死worker再一个个重启,而preload_app是将master加载的代码拷贝到其他workers。
Puma的启动方式
With Rubinius 2.0, Puma will utilize all cores on your CPU with real threads
Global Interpreter Lock (GIL)
puma app.ru
puma
rails s Puma
puma -C config/puma.rb
puma -b tcp://127.0.0.1:9292
puma -b unix:///var/run/puma.sock
puma --control tcp://127.0.0.1:9293 --control-token foo #通过控制服务器启动
pumactl restart
使用touch重启puma不生效?
touch tmp/restart.txt
设置线程数(动态线程池)
Puma will automatically scale the number of threads, from the minimum until it caps out at the maximum, based on how much traffic is present. The current default is 0:16. Feel free to experiment, but be careful not to set the number of maximum threads to a very large number, as you may exhaust resources on the system (or hit resource limits).
除了线程之外,可以通过配置worker开启集群模式
worker的数量应该与计算机核数相等
如果使用集群模式,可以选择在启动worker之前预加载应用。为了有效利用copy on write功能,这一步是必须的。
只需要在invocation中指定--preload即可。
如果使用配置文件,使用preload_app!方法
If you're preloading your application and using ActiveRecord, it's recommended that you setup your connection pool here
When you use preload_app, all of your new code goes into the master process, and is then copied into the workers
需要快速启动
General rule is to use preload_app when your workers die often and need fast starts. If you don’t have many workers, you probably should not use preload_app.
Note that preload_app can’t be used with phased restart, since phased restart kills and restarts workers one-by-one, and preload_app is all about copying the code of master into the workers.
控制/状态服务器
puma有一个内置的状态/控制服务器用来查询管理puma。
puma --control tcp://127.0.0.1:9293 --control-token foo
This directs Puma to start the control server on localhost port 9293. Additionally, all requests to the control server will need to include token=foo as a query parameter. This allows for simple authentication.
指定配置文件启动
By default, if no configuration file is specified, Puma will look for a configuration file at config/puma.rb
puma -e staging
重启
Puma includes the ability to restart itself allowing easy upgrades to new versions. When available (MRI, Rubinius, JRuby), Puma performs a "hot restart". This is the same functionality available in unicorn and nginx which keep the server sockets open between restarts. This makes sure that no pending requests are dropped while the restart is taking place.
重启的两种方式:
- 向puma进程发送SIGUSR2信号
- 使用状态服务器并且发送/restart指令
No code is shared between the current and restarted process, so it should be safe to issue a restart any place where you would manually stop Puma and start it again.
If the new process is unable to load, it will simply exit. You should therefore run Puma under a process monitor (see below) when using it in production.
If you perform a lot of database migrations, you probably should not use phased restart and use a normal/hot restart instead (pumactl restart). That way, no code is shared while deploying (in that case, preload_app might help for quicker deployment, see below).
pumactl
pumactl is a simple CLI frontend to the control/status app described above. Please refer to pumactl --help for available commands.
app_root = ‘/home/hashtagbe/app/current’
pidfile “#{app_root}/tmp/pids/puma.pid”
state_path “#{app_root}/tmp/pids/puma.state”
bind ‘unix:///home/hashtagbe/app/shared/tmp/sockets/puma.sock’
daemonize true
environment ‘staging’
port 9291
workers 2
threads 1,4
preload_app!
on_worker_boot do
ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.establish_connection
end
end
before_fork do
ActiveRecord::Base.connection_pool.disconnect!
end