1 什麼是 Active Job?
Active Job 是一個聲明工作並使其在各種排隊後端上運行的框架。這些工作可以是定期排程的清理、計費收費或郵件發送等等。實際上,任何可以被分解成小單位並且可以並行運行的工作都可以使用 Active Job。
2 Active Job 的目的
主要目的是確保所有的 Rails 應用程式都有一個工作基礎架構。這樣,我們就可以在其上建立框架功能和其他寶石,而不必擔心不同工作運行器(如 Delayed Job 和 Resque)之間的 API 差異。選擇排隊後端變成了一個操作上的考慮,你可以在不重寫工作的情況下切換它們。
注意:Rails 預設提供了一個異步排隊實現,它使用一個進程內的執行緒池來運行工作。工作將以異步方式運行,但在重新啟動時,任何在排隊中的工作都將被丟棄。
3 創建一個工作
本節將提供一個逐步指南來創建一個工作並將其排隊。
3.1 創建工作
Active Job 提供了一個 Rails 生成器來創建工作。以下命令將在 app/jobs
中創建一個工作(並在 test/jobs
下創建一個相應的測試案例):
$ bin/rails generate job guests_cleanup
invoke test_unit
create test/jobs/guests_cleanup_job_test.rb
create app/jobs/guests_cleanup_job.rb
你也可以創建一個在特定排隊中運行的工作:
$ bin/rails generate job guests_cleanup --queue urgent
如果你不想使用生成器,你可以在 app/jobs
中創建自己的文件,只需確保它繼承自 ApplicationJob
。
以下是一個工作的範例:
class GuestsCleanupJob < ApplicationJob
queue_as :default
def perform(*guests)
# 執行某些操作
end
end
請注意,你可以定義 perform
方法帶有任意數量的參數。
如果你已經有一個抽象類,並且它的名稱與 ApplicationJob
不同,你可以使用 --parent
選項來指定你想要一個不同的抽象類:
$ bin/rails generate job process_payment --parent=payment_job
class ProcessPaymentJob < PaymentJob
queue_as :default
def perform(*args)
# 執行某些操作
end
end
3.2 將工作排隊
使用 perform_later
和(可選)set
將工作排隊。例如:
# 將一個工作排隊,以便在排隊系統空閒時立即執行。
GuestsCleanupJob.perform_later guest
# 將一個工作排隊,以便在明天中午執行。
GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
# 將一個工作排隊,以便在一周後執行。
GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
# `perform_now` 和 `perform_later` 會在底層調用 `perform`,所以你可以傳遞與後者中定義的參數數量相同的參數。
GuestsCleanupJob.perform_later(guest1, guest2, filter: 'some_filter')
就是這樣!
4 工作執行
要在生產環境中排隊和執行工作,你需要設置一個排隊後端,也就是說,你需要選擇一個 Rails 應該使用的第三方排隊庫。Rails 本身只提供了一個進程內的排隊系統,它只將工作保存在 RAM 中。如果進程崩潰或機器重啟,則所有未完成的工作都會丟失。這對於較小的應用程式或非關鍵性的工作可能沒有問題,但大多數生產應用程式需要選擇一個持久的後端。
4.1 後端
Active Job 內建了多個排隊後端的適配器(如 Sidekiq、Resque、Delayed Job 等)。要獲取最新的適配器列表,請參閱 ActiveJob::QueueAdapters
的 API 文件。
4.2 設置後端
你可以使用 config.active_job.queue_adapter
輕鬆設置你的排隊後端:
# config/application.rb
module YourApp
class Application < Rails::Application
# 確保在你的 Gemfile 中有適配器的 gem,
# 並遵循適配器的特定安裝和部署說明。
config.active_job.queue_adapter = :sidekiq
end
end
你也可以根據每個工作單獨配置你的後端:
class GuestsCleanupJob < ApplicationJob
self.queue_adapter = :resque
# ...
end
# 現在你的工作將使用 `resque` 作為其後端排隊適配器,覆蓋了在 `config.active_job.queue_adapter` 中配置的設置。
4.3 啟動後端
由於作業在Rails應用程式中並行執行,大多數佇列庫需要您啟動特定於該庫的佇列服務(除了啟動Rails應用程式)以使作業處理正常運作。請參閱庫的文件以獲取有關啟動佇列後端的指示。
這是一份非全面性的文件列表:
5 佇列
大多數適配器支援多個佇列。使用Active Job,您可以使用queue_as
來安排作業在特定佇列上運行:
class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
# ...
end
您可以使用config.active_job.queue_name_prefix
在application.rb
中為所有作業的佇列名稱添加前綴:
# config/application.rb
module YourApp
class Application < Rails::Application
config.active_job.queue_name_prefix = Rails.env
end
end
# app/jobs/guests_cleanup_job.rb
class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
# ...
end
# 現在,您的作業將在生產環境的production_low_priority佇列上運行,
# 並在暫存環境的staging_low_priority佇列上運行
您還可以根據每個作業單獨配置前綴。
class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
self.queue_name_prefix = nil
# ...
end
# 現在,您的作業的佇列名稱將不帶前綴,覆蓋了在`config.active_job.queue_name_prefix`中配置的內容。
默認的佇列名稱前綴分隔符是'_'。您可以通過在application.rb
中設置config.active_job.queue_name_delimiter
來更改它:
# config/application.rb
module YourApp
class Application < Rails::Application
config.active_job.queue_name_prefix = Rails.env
config.active_job.queue_name_delimiter = '.'
end
end
# app/jobs/guests_cleanup_job.rb
class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
# ...
end
# 現在,您的作業將在生產環境的production.low_priority佇列上運行,
# 並在暫存環境的staging.low_priority佇列上運行
如果您想更精確地控制作業將在哪個佇列上運行,可以將:queue
選項傳遞給set
:
MyJob.set(queue: :another_queue).perform_later(record)
要從作業層面控制佇列,可以將塊傳遞給queue_as
。該塊將在作業上下文中執行(因此可以訪問self.arguments
),並且必須返回佇列名稱:
class ProcessVideoJob < ApplicationJob
queue_as do
video = self.arguments.first
if video.owner.premium?
:premium_videojobs
else
:videojobs
end
end
def perform(video)
# 執行視頻處理
end
end
ProcessVideoJob.perform_later(Video.last)
注意:請確保您的佇列後端“監聽”您的佇列名稱。對於某些後端,您需要指定要監聽的佇列。
6 回調
Active Job提供了在作業生命週期中觸發邏輯的鉤子。與Rails中的其他回調一樣,您可以將回調實現為普通方法,並使用宏風格的類方法將它們註冊為回調:
class GuestsCleanupJob < ApplicationJob
queue_as :default
around_perform :around_cleanup
def perform
# 執行某些操作
end
private
def around_cleanup
# 在執行之前執行某些操作
yield
# 在執行之後執行某些操作
end
end
宏風格的類方法也可以接收一個塊。如果您的塊內的代碼非常簡短,可以使用此風格。例如,您可以為每個入隊的作業發送指標:
class ApplicationJob < ActiveJob::Base
before_enqueue { |job| $statsd.increment "#{job.class.name.underscore}.enqueue" }
end
6.1 可用的回調
7 動作郵件
在現代Web應用程序中,最常見的工作之一是在請求-響應週期之外發送電子郵件,以便用戶無需等待。Active Job與Action Mailer集成,因此您可以輕鬆地異步發送電子郵件:
# 如果要立即發送電子郵件,請使用#deliver_now
UserMailer.welcome(@user).deliver_now
# 如果要通過Active Job發送電子郵件,請使用#deliver_later
UserMailer.welcome(@user).deliver_later
注意:從Rake任務使用異步佇列(例如,使用.deliver_later
發送電子郵件)通常不起作用,因為Rake可能會在任何/所有.deliver_later
電子郵件處理之前結束,導致在處理之前刪除處理中的線程池。為了避免此問題,在開發中使用.deliver_now
或運行持久佇列。
8 國際化
每個作業使用創建作業時設置的I18n.locale
。如果您異步發送電子郵件,這很有用:
I18n.locale = :eo
UserMailer.welcome(@user).deliver_later # 電子郵件將以世界語本地化。
9 支持的參數類型
ActiveJob 支援以下預設的參數類型:
- 基本類型(
NilClass
、String
、Integer
、Float
、BigDecimal
、TrueClass
、FalseClass
) Symbol
Date
Time
DateTime
ActiveSupport::TimeWithZone
ActiveSupport::Duration
Hash
(鍵應該是String
或Symbol
類型)ActiveSupport::HashWithIndifferentAccess
Array
Range
Module
Class
9.1 GlobalID
Active Job 支援 GlobalID 作為參數。這使得您可以將活動的 Active Record 物件傳遞給工作,而不是類別/ID 對,然後您需要手動反序列化。以前,工作會像這樣:
class TrashableCleanupJob < ApplicationJob
def perform(trashable_class, trashable_id, depth)
trashable = trashable_class.constantize.find(trashable_id)
trashable.cleanup(depth)
end
end
現在您可以簡單地這樣做:
class TrashableCleanupJob < ApplicationJob
def perform(trashable, depth)
trashable.cleanup(depth)
end
end
這適用於任何混入 GlobalID::Identification
的類別,預設已混入 Active Record 類別。
9.2 序列化器
您可以擴展支援的參數類型列表。您只需要定義自己的序列化器:
# app/serializers/money_serializer.rb
class MoneySerializer < ActiveJob::Serializers::ObjectSerializer
# 檢查參數是否應由此序列化器序列化。
def serialize?(argument)
argument.is_a? Money
end
# 使用支援的物件類型將物件轉換為較簡單的表示形式。
# 推薦的表示形式是具有特定鍵的 Hash。鍵只能是基本類型。
# 您應該調用 `super` 將自訂的序列化器類型添加到哈希中。
def serialize(money)
super(
"amount" => money.amount,
"currency" => money.currency
)
end
# 將序列化的值轉換為正確的物件。
def deserialize(hash)
Money.new(hash["amount"], hash["currency"])
end
end
並將此序列化器添加到列表中:
# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer
請注意,初始化期間不支援重新載入可重新載入的程式碼。因此,建議僅加載一次序列化器,例如通過修改 config/application.rb
如下:
# config/application.rb
module YourApp
class Application < Rails::Application
config.autoload_once_paths << Rails.root.join('app', 'serializers')
end
end
10 例外狀況
在工作執行期間引發的例外狀況可以使用 rescue_from
來處理:
class GuestsCleanupJob < ApplicationJob
queue_as :default
rescue_from(ActiveRecord::RecordNotFound) do |exception|
# 處理例外狀況
end
def perform
# 執行某些操作
end
end
如果工作的例外狀況未被捕獲,則該工作被稱為「失敗」。
10.1 重試或丟棄失敗的工作
除非另有配置,否則失敗的工作不會重試。
可以使用 retry_on
或 discard_on
來重試或丟棄失敗的工作。例如:
class RemoteServiceJob < ApplicationJob
retry_on CustomAppException # 預設為 3 秒等待,5 次嘗試
discard_on ActiveJob::DeserializationError
def perform(*args)
# 可能會引發 CustomAppException 或 ActiveJob::DeserializationError
end
end
10.2 反序列化
GlobalID 允許序列化傳遞給 #perform
的完整 Active Record 物件。
如果在工作入隊之後但在調用 #perform
方法之前,傳遞的記錄被刪除,Active Job 將引發 ActiveJob::DeserializationError
例外狀況。
11 工作測試
您可以在測試指南中找到有關如何測試工作的詳細說明。
12 除錯
如果您需要幫助找出工作來自何處,您可以啟用詳細日誌記錄。
回饋
歡迎協助提升本指南的品質。
如果您發現任何錯別字或事實錯誤,請貢獻您的力量。 開始之前,您可以閱讀我們的 文件貢獻 部分。
您也可能會發現不完整的內容或過時的資訊。 請為主要的文件補充任何遺漏的內容。請先檢查 Edge 指南,以確認問題是否已經修復或尚未在主分支上修復。 請參考 Ruby on Rails 指南指引 以了解風格和慣例。
如果您發現需要修復但無法自行修補的問題,請 開啟一個問題。
最後但同樣重要的是,關於 Ruby on Rails 文件的任何討論都非常歡迎在 官方 Ruby on Rails 論壇 上進行。