使用 Puma Web 服務器部署 Rails 應用

n342 9年前發布 | 15K 次閱讀 Rails

使用 Puma Web 服務器部署 Rails 應用

相對于一次只處理一個請求的 Web 應用程序,并發處理請求的 Web 應用程序,能夠更高效的使用動態資源。Puma 是和 Unicorn 相競爭的 Web 服務器,它能夠處理并發請求。

Puma 使用線程,以及工作者進程,能夠更多的利用可用的 CPU。在 Puma 中,如果整個基礎代碼是線程安全的,那么你可用利用線程。否則,在使用 Puma 的時候,你只能使用工作者進程進行拓展。

這個指南會簡單介紹,使用 Puma Web 服務器部署新的 Rails 應用程序到 Heroku 平臺。 對于基本的Rails 安裝,請參看 Rails 入門.

在部署到生產環境之前,請先在準生產環境(staging environment)測試。

將 Puma 加入應用程序

Gemfile

首先,將 Puma 添加到應用程序的 Gemfile 文件:

gem 'puma'

Procfile

設置 Puma 為應用程序 Procfile 文件中Web 進程的服務器。你可以在一行中設置大多數值:

web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-development}

但是,我們推薦生成一個配置文件:

web: bundle exec puma -C config/puma.rb

請確保 Procfile 大寫正確,并且簽入到 git。

Config

在 config/puma.rb 中為 Puma 創建一個配置文件,或者任選一個目錄。對于一個簡單的 Rails 應用程序,我們推薦下面的基本配置:

workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['MAX_THREADS'] || 5)
threads threads_count, threads_count

preload_app!

rackup      DefaultRackupport        ENV['PORT']     || 3000environment ENV['RACK_ENV'] || 'development'on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connectionend

你同樣必須保證,Rails 應用程序,為所有的線程和工作者(這將在后續介紹),提供足夠的連接池中的可用數據庫連接。

如果你的應用程序不是線程安全的,你將只能使用工作者(worker)。設置最小和最大線程數為1:

$ heroku config:set MAX_THREADS=1

查看下面關于線程安全的部分,以獲取更多信息。

工作者(Worker)

workers Integer(ENV['WEB_CONCURRENCY'] || 2)

環境變量WEB_CONCURRENCY 可能被設置為基于dyno大小的默認值。手動設置可以使用 heroku 配置:set WEB_CONCURRENCY。

Puma 在每個dyno內部生成多個操作系統進程,以允許 Rails應用程序支持多個并發請求。 用Puma的術語來說,它們稱為工作者進程(不要和Heroku工作者進程相混淆,Heroku工作者進程是在它們自己的dyno里面運行的)。工作者進程處于操作系統級別的相互隔離,因此,無需保持線程安全。

如果你在使用JRuby或者Windows,多進程模式無法工作,因為JVM和Windows不支持多進程。如果你在使用JRuby或者Windows,請從配置中忽略這行。

每個工作者進程使用額外的內存。這限制了在一個dyno中運行的進程數。依據典型的Rails內存占用,在1x dyno中,你大概可以運行2-4個Puma工作者進程。我們推薦在配置變量中指定這個數值,以便于應用程序更快的調整。請監控應用程序日志的R14錯誤(內存配額溢出),使用 附加日志(logging addons)或者 heroku日志.

線程

threads_count = Integer(ENV['MAX_THREADS'] || 5)
threads threads_count, threads_count

Puma 可以一個線程服務一個請求,這些線程來自內部的線程池。對于Web應用程序來說,這讓Puma獲得了額外的并發性。寬泛的說,工作者消耗更多的RAM,線程消耗更多的CPU,兩者都提供了更高的并發性。

在MRI之中,存在一個全局解釋鎖(Global Interpreter Lock (GIL)),它確保,在任意時刻,只有一個線程在運行。IO操作,如數據庫調用,文件系統交互,或者外部HTTP調用,都不會鎖定GIL。大多數Rails應用程序大量使用IO,因此,增加額外的線程將允許Puma處理多線程,獲得更大的吞吐量。JRuby和Rubinius也從Puma受益。這些Ruby實現并沒有GIL,無論什么情況下,它們都會以并行的方式運行所有的線程。

Puma允許配置線程池中的線程數,通過設置min和max來控制每個Puma實例使用的線程數。當處于非負載狀態下,最小線程可以讓應用程序降低資源消耗。這個特性在Heroku上并不需要,因為應用程序可以消耗給定的dyno上的所有資源。我們推薦將min和max設置為相同的值。

每個Puma工作者都將可以生成指定數目的線程。

預加載應用程序(Preload app)

preload_app!

預加載應用程序減少了Puma工作者進程啟動的時間,同時,可以使用on_worker_boot調用,管理每個單一工作者的外部連接。在上面的配置中,這些調用,用來為每個工作者進程和Postgres正常建立連接。

關于工作者引導(On worker boot)

這里的on_worker_boot塊,是在工作者生成后運行的,但是,是在它開始接受請求之前。這個塊對于連接到不同的服務非常有用,因為在多個進程之間,連接不能共享。這和Unicorn的after_fork塊非常類似。它只在使用多進程模式(也就是,有指定的工作者)的情況下才需要。

如果你正在使用Rails 4.1+,你可以使用database.yml 來設置連接池的大小 ,按照下面的來做即可:

on_worker_boot do
  # Valid on Rails 4.1+ using the `config/database.yml` method of setting `pool` size
  ActiveRecord::Base.establish_connection
end

否則,你必須使用重連代碼:

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['MAX_THREADS'] || 5
    ActiveRecord::Base.establish_connection(config)  
  end
end

如果你正在使用初始化器(initializer),你應該盡快切換到database.yml方法。如果在Puma中采用混合模式(hybrid mode),使用初始化器需要復制代碼。關于正在發生什么,以及源頭在何處,初始化器方法可能會導致混亂。

我們在默認配置里設置數據庫數據池尺寸。需要更多信息請閱讀在Ruby中的并發與數據庫連接ActiveRecord(Concurrency and Database Connections in Ruby with ActiveRecord)。我們也要確定創建的新連接連到數據庫哪里。

很多數據存儲諸如 Postgres, Redis 或者 memcache,都需要你重連接。在預加載那一節,我們會展示如何重連接 Active Record。如果你使用 Resque,它連接到 Redis 時,你將需要重連接:

on_worker_boot do
  # ...
  if defined?(Resque)     
    Resque.redis = ENV["<redis-uri>"] || "redis://127.0.0.1:6379"
  end
end

如果在你啟動應用時沒有成功連接,可以查閱 gem 文檔,了解如何在這個模塊上重新連接。

Rackup

rackup      DefaultRackup

使用rackup命令,告知Puma如何啟動rack應用程序。這應該在應用程序的config.ru文件中,這個文件是新建一個項目時,由Rails自動生成的。

在新版本的Puma中,這行是不需要的。

端口(Port)

port        ENV['PORT']     || 3000

這是Puma將會綁定的端口。當Web進程啟動時,HeroKu會設置ENV['PORT']。本地默認設置端口為3000,以匹配Rails的默認值。

環境(Environment)

environment ENV['RACK_ENV'] || 'development'

這是設置Puma的環境。在Heroku之中,ENV['RACK_ENV']會默認設置為'production'。

超時(Timeout)

在Puma之中,沒有請求超時。 Heroku 路由將 超過30秒的請求設為超時。 盡管會返回錯誤給客戶端,但是Puma仍會繼續處理這個請求,就像路由沒有采用任何方式,提前告知Puma,此請求已經終止了一樣。

增加 Rack 超時 到項目的gem,然后在初始化器(initializer)中設置低于30的值:

# config/initializers/timeout.rbRack::Timeout.timeout = 20  # seconds

現在,任何超過20秒的請求都會被終止,同時棧跟蹤信息會輸出到日志之中。棧跟蹤信息,有助于查明應用程序的哪個部分會導致超時,以便修復。

樣例代碼

開源codetriage.com項目使用 Puma 并且你可以在這個配置文件中看 Puma

線程安全

線程安全代碼可以被多線程運行并且無錯誤。并不是多有的 Ruby 代碼是 threadsafe 代碼,而且決定你的代碼和所有的庫文件在多線程上運行是有點困難的。

直到 Rails4 版本,出現了一個伴有疑慮的線程安全互通模式。縱然 Rails 是線程安全,但是它不保證你的代碼能運行在安全線程上。如果你還沒有在線程化的環境下運行你的應用,我們建議部署以及設置 MIN_THREADS 和 MAX_THREADS 二者為1:

$ heroku config:set MIN_THREADS=1 MAX_THREADS=1

你仍可以通過增加工作器來獲得并發。因為工作器運行在不同的進程上,而且不會共享內存,不是安全線程的代碼可以運行在多工作器進程上。

一旦你在工作器上運行了應用,你可以增加在工作臺上的線程數量以及部署為2:

$ heroku config:set MIN_THREADS=2 MAX_THREADS=2

你需要監視異常情況并且查找錯誤,像(致命的)死鎖,競爭條件以及能夠修改全局或者共享變量的位置。

并發程序的bug很難被偵測和修復,因此必須確定你的應用在部署前已經被充分地測試。如果讓你的應用線程安全,是非常值得的,比起單獨使用工作者(worker),使用擴展的Puma線程和工作者(worker)取得了更多的生產力.

一旦你對應用的預期行為有信心,你可以增加你的線程數。

優化線程數,我們被推薦著眼于請求的延遲。如果你的應用是在負載下額外的線程,在一定程度上,將會減少請求的延遲。一旦額外的新線程不再給你的應用提升可測的請求時間,那就不再需要添加額外的線程。

數據庫連接

隨著應用程序并發量的增加,你會需要更多的與數據庫的連接。確定每個應用需要連接數的一個準則是用PUMA_WORKERS乘以MAX_THREADS。這將會確定每個dyno消費多少個連接。

Rails維護自己的數據庫連接池,同時對于每個工作者進程新建一個連接池。一個工作者的線程將會在同一個連接池中操作。確保你的Rails數據庫連接池中有足夠的連接,這樣就有MAX_THREADS個數的連接可以使用了。如果你看見這個錯誤:

ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds

這表明你的Rails連接池太小了。想要深入了解該話題,請閱讀dev center中的文章并發和數據庫連接

慢客戶端

一個慢客戶端發送和接收數據是非常慢的。舉個例子,一個 app 接收用戶上傳的手機圖片網絡類型非 WiFi,4G 或其他網絡類型。這種連接可能導致某些服務器拒絕服務,如 unicorn,整個上傳過程必須等待請求完成。

puma 可以允許多個慢客戶端連接而不需要一個被請求的事件。因此,puma 處理慢客戶端非常快速。你要是有滿客戶端的情況,使用 puma 是不錯的選擇。

Backlog

在Puma中可以設置 “backlog” 的值。這個值是,Puma在開始拒絕HTTP請求之前,socket隊列的請求數。它的默認值為1024,建議不要修改或降低這個值。減小這個值,看起來像是一個好主意,這樣,當一個dyno處于繁忙之中的時候,請求可以發送到不那么繁忙的dyno。當Heroku重新路由(reroute)一個彈回(bounced)請求的時候,它假定整個應用程序已經飽和。此時,每個連接延遲5秒,因此,每個請求懲罰性的自動延遲5秒。你可以獲取關于 路由運轉 的更多信息。另外,當一個dyno開始彈回(bounce)請求的時候,很可能是由于負載增加,所有的dyno都將彈回請求。重復彈回同樣的請求,將導致客戶端更高的錯誤率。

一個隨意的高backlog值允許dyno處理陡然增加的請求。降低這個值,不能明顯加速應用程序,卻更可能導致客戶端更多的失敗請求。Heroku建議 不要 設置backlog的值,而使用其默認值。

 本文由用戶 n342 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!