南京邮电大学移动互联网俱乐部

Puma: 一个为并发构建的Ruby服务器

本文翻译自Puma官方文档,同时发布于Github

译者:林翔宇

概述

Puma是一个简单,快速,基于线程,并且高并发的面向Ruby/Rack程序的 HTTP 1.1 服务器。Puma同时面向开发和产品环境。为了达到最高的效率,推荐使用一个实现了真正线程的Ruby实现,比如Rubinius或者JRuby。

为速度和并发构建。

Puma是一个简单,快速,基于线程,并且高并发的面向Ruby/Rack程序的 HTTP 1.1 服务器。它可以用于任何支持Rack的程序,可以取代Webrick或者Mongrel。它是为了Rubinius而设计的服务器,但是同样可以工作在JRuby和MRI下。Puma同时面向开发和产品环境。

Puam使用了一个继承自Mongrel的C优化的Ragel扩展,来快速并且精确地解析HTTP1.1请求(并且便于移植)。之后,Puma将这些请求放到一个你可以控制的内部线程池的线程之中,所以Puma为你的web应用提供了一个真实的并发环境。

在 Rubinius2.0 中,Puma会用真实的线程利用你所有的CPU核心,这意味着你不用使用多个进程来增加吞吐量。你可以看到在JRuby上类似的优点。

在 MRI中, 因为有全局解释器锁的存在(GIL),同时只能有一个真实的线程在运行。但是当你在做许多IO阻塞的事情的时候(比如HTTP调用像 Twitter的外部API的时候),Puma仍然会通过运行并发运行阻塞的IO调用增加MRI的吞吐量。( 基于 EventMachine的服务器,如Thin会关掉这个功能,要求你只能使用特别的库)。每个人的选择因实际的应用而异。为了达到最大的吞吐量,强烈推荐使用一个实现了真实线程的Ruby实现,比如Rubinius 或者 JRuby

快速入门

最简单的使用Puma的方式是使用 RubyGems:

$ gem install puma

现在puma命令已经加入到你的环境变量里了,在你的应用程序根目录下运行下面命令启动你的Rack应用:

$ puma app.ru

高级步骤

Sinatra

你可以用下面的命令用Puma运行你的Sinatra应用

$ ruby app.rb -s Puma

或者在你的应用中设置总是使用Puma

require 'sinatra'
configure { set :server, :puma }

如果你使用 Bundler,确保你把Puma加入了你的Gemfile中(步骤见下面)。

Rails

首先,确保你把Puma加入了你的Gemfile中。

gem 'puma'

然后用Rails命令启动Puma

$ rails s Puma

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, 大胆的尝试吧,但是不要把最大线程数量设置的太大,这会耗尽系统资源。

集群模式

Puma 2 提供了集群模式 , 允许你使用 fork出来的进程处理并发处理多个请求。 你可以用-w(or—workers`) 参数调整运行的work进程数量。

$ puma -t 8:32 -w 3

在提供真实线程的Ruby实现中,你应该把这个数字调整到和CPU核心数量相同。

注意集群模式下仍然是使用线程的, -t 选项设置每个 worker的进程数量,所以 -w 2 -t 16:16 可能开启了32个线程。

如果你在并发模式你可以选择在启动worker进程前预加载你的应用。如果想利用MRI Ruby 2.0推出的Copy on Write 的优点的话,这是必要的。只需要调用的时候指定一个 --preload 参数:

# CLI invocation
$ puma -t 8:32 -w 3 --preload

如果你在使用配置文件,使用preload_app!方法,并且使用-C参数指定配置文件:

$ puma -C config/puma.rb

# config/puma.rb
threads 8,32
workers 3
preload_app!

此外,你可以在配置文件里面定义一个代码块,来在每个工作线程启动的时候运行:

# config/puma.rb
on_worker_boot do
  # configuration here
end

这些代码可以用来在启动应用的时候初始化进程, 允许你做一些和Puma服务器有关,但是不和应用结合的事情。比如,你可以发送一个日志通知Puma启动了。

你可以多次增加钩子调用。

如果你使用ActiveRecord预加载你的应用, 推荐你在这里这样初始化你的连接池:

# config/puma.rb
on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end

绑定 TCP / Sockets

相比较其他需要很多配置参数的服务器,Puma仅仅通过-b (或者 --bind)选项使用一个URI参数:

$ puma -b tcp://127.0.0.1:9292

想通过使用UNIX Sockets来取代TCP么(这能提高5-10%的性能),没问题!

$ puma -b unix:///var/run/puma.sock

如果你需要改变Unix socket的权限,只需要加入一个umask参数:

$ puma -b 'unix:///var/run/puma.sock?umask=0777'

希望更加安全?使用 SSL sockets!

$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert'

控制/状态服务

Puma提供了一个内置的控制/状态服务app来提供对Puam自身的控制,这是一个打开Puma控制服务器的样例:

$ puma --control tcp://127.0.0.1:9293 --control-token foo

它会在localhost的9293端口打开Puma配置服务器。此外,所有的到控制服务器的请求需要包括一个token=foo 作为查询参数,作为简单的认证。更多app用法参见status.rb

配置文件

你可以同时使用一个-C (或 --config) 参数提供一个哦诶之文件:

$ puma -C /path/to/config

请参考 示例配置 或者查询 configuration.rb 查看所有的配置项目

重启

Puma有能力在更新版本的时候重启自己,当平台允许的时候(MRI, Rubinius, JRuby),Puma 进行热重启。这是和 unicornnginx 在重启的时候保持服务器sockets连接相同的。它可以保证重启的服务器替代旧的服务器的时候请求已经被处理。

Puma有2个内建的重启机制:

  • 发送SIGUSR2 信号到puma 进程
  • 使用 status server 和 issue /restart

当前和重启的进程不会共享代码,所以手动停止Puma并且关闭它也是安全的。

如果新的进程不能加载,它会简单的退出。你应该在生产环境下用一个监控程序运行Puma。

Cleanup Code

Puma不能理解所有你的App使用的资源,所以它在你用-C提供的配置文件里面提供了一个叫on_restart的钩子。传递给on_restart 会被Puma在重启自己的时候调用。

你应该在这个代码块里面关闭全局日志文件,redis连接等,这样它们的文件描述符不会在重启的进程里面泄漏。否则会导致文件描述符打开太多,当服务器重启次数很多的时候会导致应用崩溃 。

平台限制

不同的平台有差异,下列是Puma在不同平台上有差异的地方

  • JRuby, Windows: 服务器的Socket不能被无缝重启, 这些平台不能通过Ruby传递信息到新的进程
  • JRuby, Windows: 不支持集群模式 ,因为没有fork fork(2)
  • Windows: 不支持daemon mode ,因为没有fork(2)

pumactl

pumactl 是一个简单的CLI前端来控制或者查看上述的app状态。 请参考 pumactl --help

维护多个Puma / init.d / upstart 脚本

如果你想马上得到一个维护多个程序的脚本,请查看用于init.d 和 upstart脚本的tools/jungle

Capistrano 部署

Puma 已经被包括进 Capistrano deploy script, 你只要 require :

config/deploy.rb

1
require 'puma/capistrano'

然后

1
2
3
4
$ bundle exec cap puma:start
$ bundle exec cap puma:restart
$ bundle exec cap puma:stop
$ bundle exec cap puma:phased_restart

协议

Puma 的版权属于Evan Phoenix 和 贡献者. 它基于BSD 协议. 详见LICENSE文件。

Comments