edge
เพิ่มเติมที่ rubyonrails.org: เพิ่มเติมเกี่ยวกับ Ruby on Rails

พื้นฐานของ Active Job

เอกสารนี้จะให้คุณทราบทุกสิ่งที่คุณต้องการเพื่อเริ่มต้นในการสร้างงานพื้นหลัง (background jobs) และการเรียกใช้งาน

หลังจากอ่านเอกสารนี้คุณจะรู้:

1 Active Job คืออะไร?

Active Job เป็นเฟรมเวิร์กสำหรับการประกาศงานและการทำงานของงานในหลายรูปแบบของการเก็บงานในคิว (queuing backends) งานเหล่านี้สามารถเป็นทั้งการทำความสะอาดเป็นประจำ, การเรียกเก็บเงิน, การส่งจดหมาย อย่างไรก็ตามทุกอย่างที่สามารถแบ่งออกเป็นหน่วยงานเล็ก ๆ และทำงานพร้อมกันได้จริง ๆ

2 วัตถุประสงค์ของ Active Job

จุดสำคัญคือการให้แน่ใจว่าแอปพลิเคชัน Rails ทุกตัวจะมีโครงสร้างงานพื้นฐานอยู่ในที่ จากนั้นเราสามารถใช้คุณสมบัติของเฟรมเวิร์กและเจมอื่น ๆ สร้างขึ้นบนโครงสร้างนั้นได้โดยไม่ต้องกังวลเรื่องความแตกต่างของ API ระหว่างตัวรันงานต่าง ๆ เช่น Delayed Job และ 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)
    # ทำบางอย่างในภายหลัง
  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)
# เพิ่มงานในคิวเพื่อทำงานในอีก 1 สัปดาห์
GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
# `perform_now` และ `perform_later` จะเรียกใช้ `perform` ภายใน ดังนั้นคุณสามารถส่งอาร์กิวเมนต์ได้เท่าที่กำหนดไว้ใน `perform`
GuestsCleanupJob.perform_later(guest1, guest2, filter: 'some_filter')

4 การดำเนินการงาน

สำหรับการเรียกคิวและการดำเนินการงานงานในการใช้งานจริงคุณต้องติดตั้งระบบคิวที่เหมาะสม กล่าวคือคุณต้องตัดสินใจเลือกไลบรารีคิวที่เป็นของบุคคลที่สามที่ Rails ควรใช้ Rails เองมีระบบคิวในกระบวนการภายในเท่านั้น ซึ่งเก็บงานไว้ใน RAM เท่านั้น หากกระบวนการล้มเหลวหรือเครื่องถูกรีเซ็ต งานที่ค้างอยู่ทั้งหมดจะหายไปกับ backend แบบ async ที่ตั้งค่าเริ่มต้น สำหรับแอปขนาดเล็กหรืองานที่ไม่สำคัญ อาจเป็นไปได้ แต่แอปในการใช้งานจริงส่วนใหญ่จะต้องเลือก backend ที่มีความทนทาน

4.1 แบ็กเอนด์

Active Job มีตัวอย่างแบ็กเอนด์สำหรับหลาย backend คิว (Sidekiq, Resque, Delayed Job, และอื่น ๆ) เพื่อให้ได้รายการอัปเดตล่าสุดของแอ็ดเอปเตอร์ ดูที่เอพีไอเอกสารประกอบ ActiveJob::QueueAdapters

4.2 การตั้งค่าแบ็กเอนด์

คุณสามารถตั้งค่า backend ของคิวได้อย่างง่ายดายด้วย config.active_job.queue_adapter:

# config/application.rb
module YourApp
  class Application < Rails::Application
    # ตรวจสอบให้แน่ใจว่ามี gem ของแอ็ดเอปเตอร์ใน Gemfile ของคุณ
    # และปฏิบัติตามคำแนะนำการติดตั้งและการใช้งานของแอ็ดเอปเตอร์นั้น
    config.active_job.queue_adapter = :sidekiq
  end
end

คุณยังสามารถกำหนดค่า backend ของคุณในแต่ละงานได้:

class GuestsCleanupJob < ApplicationJob
  self.queue_adapter = :resque
  # ...
end

# ตอนนี้งานของคุณจะใช้ `resque` เป็น backend ของคิว แทนที่จะใช้
# ค่าที่ตั้งค่าไว้ใน `config.active_job.queue_adapter`.

4.3 เริ่ม backend

เนื่องจากงานทำงานแบบพร้อมกันกับแอปเว็บ Rails ของคุณ หลายไลบรารีคิว ต้องการให้คุณเริ่มบริการคิวที่เฉพาะเจาะจงสำหรับไลบรารีนั้น (นอกเหนือจาก การเริ่มแอป Rails ของคุณ) เพื่อให้การประมวลผลงานทำงานได้ ดูเอกสารของไลบรารี สำหรับคำแนะนำในการเริ่ม backend คิวของคุณ

นี่คือรายการเอกสารที่ไม่ครอบคลุมทั้งหมด:

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 ในสภาพแวดล้อม
# production และในคิว staging_low_priority
# ในสภาพแวดล้อม staging

คุณยังสามารถกำหนดคำนำหน้าชื่อในแต่ละงานได้

class GuestsCleanupJob < ApplicationJob
  queue_as :low_priority
  self.queue_name_prefix = nil
  # ...
end

# ตอนนี้คิวของงานของคุณจะไม่มีคำนำหน้า แทนที่จะใช้ค่าที่ตั้งค่าไว้ใน `config.active_job.queue_name_prefix`.

ตัวคั่นคำนำหน้าชื่อคิวเริ่มต้นคือ '_' สามารถเปลี่ยนได้โดยการตั้งค่า config.active_job.queue_name_delimiter ใน application.rb:

# 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 ในสภาพแวดล้อม
# production และในคิว staging.low_priority
# ในสภาพแวดล้อม staging

หากคุณต้องการควบคุมคิวที่งานจะถูกเรียกใช้คุณสามารถส่ง :queue option ไปยัง set:

MyJob.set(queue: :another_queue).perform_later(record)

หากต้องการควบคุมคิวจาก job level คุณสามารถส่ง block ไปยัง queue_as โดย block จะถูกเรียกใช้ใน context ของ job (ดังนั้นจึงสามารถเข้าถึง self.arguments ได้) และ block ต้องส่งคืนชื่อคิว:

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)

หมายเหตุ: ตรวจสอบให้แน่ใจว่า queuing backend ของคุณ "ฟัง" ชื่อคิวของคุณ สำหรับบาง backend คุณจำเป็นต้องระบุคิวที่จะ "ฟัง"

6 Callbacks

Active Job มี hooks เพื่อเรียกใช้ตรรกะในระหว่าง lifecycle ของ job เช่นเดียวกับ callbacks อื่น ๆ ใน Rails คุณสามารถสร้าง callbacks เป็นเมธอดธรรมดาและใช้ macro-style class method เพื่อลงทะเบียนเป็น callbacks:

class GuestsCleanupJob < ApplicationJob
  queue_as :default

  around_perform :around_cleanup

  def perform
    # ดำเนินการอื่น ๆ ในภายหลัง
  end

  private
    def around_cleanup
      # ดำเนินการก่อน perform
      yield
      # ดำเนินการหลัง perform
    end
end

Macro-style class methods ยังสามารถรับ block ได้ คิดจะใช้รูปแบบนี้หาก code ภายใน block สั้นมากพอที่จะพอดีในบรรทัดเดียว ตัวอย่างเช่น คุณสามารถส่ง metrics สำหรับทุก job ที่ enqueue:

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

6.1 Callbacks ที่มีอยู่

7 Action Mailer

หนึ่งในงานที่สำคัญที่สุดในแอปพลิเคชันเว็บที่ทันสมัยคือการส่งอีเมลภายนอกของรอบการตอบสนอง ดังนั้นผู้ใช้ไม่ต้องรอในการตอบสนอง Active Job ได้รวมกับ Action Mailer เพื่อให้คุณสามารถส่งอีเมลแบบ asynchronous ได้ง่าย:

# หากคุณต้องการส่งอีเมลทันทีให้ใช้ #deliver_now
UserMailer.welcome(@user).deliver_now

# หากคุณต้องการส่งอีเมลผ่าน Active Job ให้ใช้ #deliver_later
UserMailer.welcome(@user).deliver_later

หมายเหตุ: การใช้คิว asynchronous จาก Rake task (ตัวอย่างเช่นการส่งอีเมลโดยใช้ .deliver_later) มักจะไม่ทำงานเนื่องจาก Rake จะจบลง ทำให้เส้นทางการทำงานในกระบวนการถูกลบออกก่อนที่อีเมลที่ .deliver_later จะถูกประมวลผลทั้งหมด เพื่อหลีกเลี่ยงปัญหานี้ให้ใช้ .deliver_now หรือเรียกใช้คิวที่ยังคงอยู่ในการพัฒนา

8 Internationalization

แต่ละ job ใช้ I18n.locale ที่ตั้งเมื่อ job ถูกสร้าง สิ่งนี้เป็นประโยชน์หากคุณส่งอีเมลแบบ asynchronous:

I18n.locale = :eo

UserMailer.welcome(@user).deliver_later # อีเมลจะถูกแปลงเป็นภาษา Esperanto

9 Supported Types for Arguments

ActiveJob รองรับประเภทต่อไปนี้ของ arguments โดยค่าเริ่มต้น:

  • ประเภทพื้นฐาน (NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass)
  • Symbol
  • Date
  • Time
  • DateTime
  • ActiveSupport::TimeWithZone
  • ActiveSupport::Duration
  • Hash (Keys ควรเป็นประเภท String หรือ Symbol)
  • ActiveSupport::HashWithIndifferentAccess
  • Array
  • Range
  • Module
  • Class

9.1 GlobalID

Active Job รองรับ GlobalID สำหรับพารามิเตอร์ ซึ่งทำให้เป็นไปได้ที่จะส่งอ็อบเจ็กต์ Active Record สดไปยัง job ของคุณแทนที่จะเป็นคู่คลาส/ID ซึ่งคุณต้องแยกออกมาเอง ก่อนหน้านี้ job จะมีรูปแบบดังนี้: ruby 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 Serializers

คุณสามารถขยายรายการประเภทอาร์กิวเมนต์ที่รองรับได้ คุณเพียงแค่ต้องกำหนดซีรีย์ไลเซอร์ของคุณเอง:

# app/serializers/money_serializer.rb
class MoneySerializer < ActiveJob::Serializers::ObjectSerializer
  # ตรวจสอบว่าอาร์กิวเมนต์ควรถูกซีรีย์ไลซ์โดยซีรีย์ไลเซอร์นี้หรือไม่
  def serialize?(argument)
    argument.is_a? Money
  end

  # แปลงออบเจ็กต์เป็นรูปแบบที่เรียกว่าง่ายกว่าโดยใช้ประเภทออบเจ็กต์ที่รองรับ
  # รูปแบบที่แนะนำคือแฮชที่มีคีย์เฉพาะ คีย์สามารถเป็นประเภทพื้นฐานเท่านั้น
  # คุณควรเรียกใช้ `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

หากงานเกิดข้อยกเว้นแล้วไม่ได้รับการกู้คืน งานนั้นจะถูกเรียกว่า "ล้มเหลว" (failed)

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 ช่วยให้สามารถซีรีย์ไลซ์ออบเจ็กต์ Active Record เต็มรูปแบบที่ส่งผ่านไปยัง #perform ได้

หากบันทึกที่ส่งผ่านถูกลบหลังจากงานถูกเพิ่มลงในคิวแต่ก่อนที่ #perform จะถูกเรียก Active Job จะยกเว้น ActiveJob::DeserializationError

ข้อเสนอแนะ

คุณสามารถช่วยปรับปรุงคุณภาพของคู่มือนี้ได้

กรุณาช่วยเพิ่มเติมหากพบข้อผิดพลาดหรือข้อผิดพลาดทางความจริง เพื่อเริ่มต้นคุณสามารถอ่านส่วน การสนับสนุนเอกสาร ของเราได้

คุณอาจพบเนื้อหาที่ไม่สมบูรณ์หรือเนื้อหาที่ไม่ได้อัปเดต กรุณาเพิ่มเอกสารที่ขาดหายไปสำหรับเนื้อหาหลัก โปรดตรวจสอบ Edge Guides ก่อนเพื่อตรวจสอบ ว่าปัญหาได้รับการแก้ไขหรือไม่ในสาขาหลัก ตรวจสอบ คู่มือแนวทาง Ruby on Rails เพื่อดูรูปแบบและกฎเกณฑ์

หากคุณพบข้อผิดพลาดแต่ไม่สามารถแก้ไขได้เอง กรุณา เปิดปัญหา.

และสุดท้าย การสนทนาใด ๆ เกี่ยวกับ Ruby on Rails เอกสารยินดีต้อนรับที่สุดใน เว็บบอร์ดอย่างเป็นทางการของ Ruby on Rails.