v7.0.0
更多資訊請前往 rubyonrails.org: 更多在 Ruby on Rails

Action Job 基礎

本指南為您提供了開始建立、 排隊和執行後臺作業。

閱讀本指南後,您將瞭解:

1 什麼是主動工作?

Active Job 是一個框架,用於宣告作業並使它們在各種 排隊後端。這些工作可以是定期安排的一切 清理,計費,郵寄。任何可以切碎的東西 分成小的工作單元並並行執行,真的。

2 主動工作的目的

重點是確保所有 Rails 應用程式都具有作業基礎架構 到位。然後我們可以在其基礎上構建框架功能和其他 gem, 無需擔心各種作業執行程式之間的 API 差異,例如 延遲的工作和 Resque。選擇您的排隊後端變得更具有操作性 那麼關心。而且您將能夠在它們之間切換而無需重寫 你的工作。

預設情況下,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)
    # Do something later
  end
end

請注意,您可以根據需要使用任意數量的引數定義 perform

3.2 將作業入隊

使用 perform_later 和可選的 set 對作業進行排隊。像這樣:

# 排隊系統一啟動就將要執行的作業入隊
 自由。
GuestsCleanupJob.perform_later guest
# 將明天中午要執行的作業排入佇列。
GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
# 將要在 1 周後執行的作業加入佇列。
GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
# `perform_now` 和 `perform_later` 會在引擎蓋下呼叫 `perform` 所以
# 您可以傳遞與後者中定義的一樣多的引數。
GuestsCleanupJob.perform_later(guest1, guest2, filter: 'some_filter')

而已!

4 工作執行

為了在生產中排隊和執行作業,您需要設定一個排隊後端, 也就是說,你需要決定 Rails 應該使用的第 3 方佇列庫。 Rails 本身只提供了一個程序內排隊系統,它只將作業儲存在 RAM 中。 如果程序崩潰或機器被重置,那麼所有未完成的作業都會丟失 預設非同步後端。這對於較小的應用程式或非關鍵工作可能沒問題,但大多數 生產應用程式需要選擇一個持久的後端。

4.1 後端

Active Job 具有用於多個佇列後端的內建介面卡(Sidekiq、 Resque、延遲作業等)。獲取最新的介面卡列表 請參閱 ActiveJob::QueueAdapters 的 API 文件。

4.2 設定後端

您可以輕鬆設定排隊後端:

# 配置/應用程式.rb
module YourApp
  class Application < Rails::Application
    # Be sure to have the adapter's gem in your Gemfile
    # and follow the adapter's specific installation
    # and deployment instructions.
    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

您可以使用字首為所有作業的佇列名稱 application.rb 中的 config.active_job.queue_name_prefix

# 配置/應用程式.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

# 配置/應用程式.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)
    # Do process video
  end
end
ProcessVideoJob.perform_later(Video.last)

確保您的排隊後端“監聽”您的佇列名稱。對於一些 後端您需要指定要偵聽的佇列。

6 Callbacks

Active Job 提供掛載機制以在作業的生命週期內觸發邏輯。喜歡 Rails 中的其他 callbacks,可以將 callbacks 實現為普通方法 並使用宏樣式的類方法將它們註冊為 callbacks:

class GuestsCleanupJob < ApplicationJob
  queue_as :default

  around_perform :around_cleanup

  def perform
    # Do something later
  end

  private
    def around_cleanup
      # Do something before perform
      yield
      # Do something after perform
    end
end

宏風格的類方法也可以接收一個塊。考慮使用這個 如果程式碼塊內的程式碼太短以至於它適合一行。 例如,您可以為每個入隊的作業傳送指標:

class ApplicationJob < ActiveJob::Base
  before_enqueue { |job| $statsd.increment "#{job.class.name.underscore}.enqueue" }
end

6.1 可用 callbacks

7 Action Mailer

現代 Web 應用程式中最常見的工作之一是向外部發送電子郵件 請求-回應週期,所以使用者不必等待它。在職工作 與 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 # Email will be localized to Esperanto.

9 支援的引數型別

預設情況下,ActiveJob 支援以下型別的引數:

  • 基本型別(NilClassStringIntegerFloatBigDecimalTrueClassFalseClass
  • Symbol
  • Date
  • Time
  • DateTime
  • ActiveSupport::TimeWithZone
  • ActiveSupport::Duration
  • Hash(Key 應該是 StringSymbol 型別)
  • ActiveSupport::HashWithIndifferentAccess
  • Array
  • Range
  • Module
  • Class

9.1 全域性 ID

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
  # Checks if an argument should be serialized by this serializer.
  def serialize?(argument)
    argument.is_a? Money
  end

  # Converts an object to a simpler representative using supported object types.
  # The recommended representative is a Hash with a specific key. Keys can be of basic types only.
  # You should call `super` to add the custom serializer type to the hash.
  def serialize(money)
    super(
      "amount" => money.amount,
      "currency" => money.currency
    )
  end

  # Converts serialized value into a proper object.
  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

# 配置/應用程式.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|
    # Do something with the exception
  end

  def perform
    # Do something later
  end
end

如果作業的異常未獲救,則該作業被稱為“失敗”。

10.1 重試或丟棄失敗的作業

除非另有配置,否則不會重試失敗的作業。

可以使用 retry_ondiscard_on,分別。例如:

class RemoteServiceJob < ApplicationJob
  retry_on CustomAppException # defaults to 3s wait, 5 attempts

  discard_on ActiveJob::DeserializationError

  def perform(*args)
    # Might raise CustomAppException or ActiveJob::DeserializationError
  end
end

10.2 反序列化

GlobalID 允許序列化傳遞給 #perform 的完整 Active Record 物件。

如果在作業入隊之後但在 #perform 之前刪除透過的記錄 方法被稱為 Active Job 將引發 ActiveJob::DeserializationError 例外。

11 工作測試

您可以在 測試指南

回饋

我們鼓勵您幫助提高本指南的品質。

如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。

您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。

如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue

關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區