edge
Daugiau informacijos rubyonrails.org: Daugiau apie Ruby on Rails

Aktyvios užduoties pagrindai

Šis vadovas suteiks jums visą informaciją, kurią reikia pradėti kurti, įtraukti ir vykdyti fono užduotis.

Po šio vadovo perskaitymo, jūs žinosite:

1 Kas yra Aktyvi užduotis?

Aktyvi užduotis yra karkasas, skirtas deklaruoti užduotis ir paleisti jas įvairiose eilių užpakaliniuose. Šios užduotys gali būti viskas, nuo reguliariai planuojamų valymo procedūrų iki sąskaitų apmokestinimo ar siuntimo. Iš tikrųjų, tai gali būti bet kas, kas gali būti suskaldyta į mažas darbo vienetas ir vykdoma lygiagrečiai.

2 Aktyvios užduoties tikslas

Pagrindinis tikslas yra užtikrinti, kad visos „Rails“ programos turėtų užduočių infrastruktūrą vietoje. Tada galėsime turėti karkaso funkcijas ir kitas juvelyrikas, kurios bus paremtos tuo, nereikės rūpintis API skirtumais tarp įvairių užduočių vykdytojų, tokių kaip „Delayed Job“ ir „Resque“. Eilių užpakalio pasirinkimas tampa daugiau operacinės svarbos klausimu. Be to, galėsite tarp jų perjungti, nereikės persirašyti jūsų užduočių.

PASTABA: „Rails“ pagal numatytuosius nustatymus yra asinchroninės eilių įgyvendinimo versija, kuri vykdo užduotis su vidinės gijos baseinu. Užduotys bus vykdomos asinchroniškai, bet bet kokie užduotys eilėje bus prarandamos paleidus iš naujo.

3 Užduoties kūrimas

Šiame skyriuje pateikiamas palaipsniui vadovas, kaip sukurti užduotį ir įtraukti ją.

3.1 Sukurkite užduotį

Aktyvi užduotis teikia „Rails“ generatorių, skirtą kurti užduotis. Šis kodas sukurs užduotį „app/jobs“ aplanke (su pridėtu testo atveju „test/jobs“ aplanke):

$ bin/rails generate job guests_cleanup
invoke  test_unit
create    test/jobs/guests_cleanup_job_test.rb
create  app/jobs/guests_cleanup_job.rb

Taip pat galite sukurti užduotį, kuri bus vykdoma tam tikroje eilėje:

$ bin/rails generate job guests_cleanup --queue urgent

Jei nenorite naudoti generatoriaus, galite sukurti savo failą „app/jobs“ aplanke, tik įsitikinkite, kad jis paveldi iš „ApplicationJob“.

Taip atrodo užduotis:

class GuestsCleanupJob < ApplicationJob
  queue_as :default

  def perform(*guests)
    # Padarykite kažką vėliau
  end
end

Atkreipkite dėmesį, kad galite apibrėžti „perform“ su tiek argumentų, kiek norite.

Jei jau turite abstrakčią klasę ir jos pavadinimas skiriasi nuo „ApplicationJob“, galite perduoti „--parent“ parinktį, kad nurodytumėte, kad norite kitos abstrakčios klasės:

$ bin/rails generate job process_payment --parent=payment_job
class ProcessPaymentJob < PaymentJob
  queue_as :default

  def perform(*args)
    # Padarykite kažką vėliau
  end
end

3.2 Įtraukite užduotį

Įtraukite užduotį naudodami perform_later ir, pagal poreikį, set. Taip:

# Įtraukite užduotį, kuri bus vykdoma, kai tik eilių sistema bus
# laisva.
GuestsCleanupJob.perform_later guest
# Įtraukite užduotį, kuri bus vykdoma rytoj vidurdienį.
GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
# Įtraukite užduotį, kuri bus vykdoma po 1 savaitės.
GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
# `perform_now` ir `perform_later` iškvies `perform` viduje, todėl
# galite perduoti tiek argumentų, kiek apibrėžta antrame.
GuestsCleanupJob.perform_later(guest1, guest2, filter: 'some_filter')

Tai viskas!

4 Užduoties vykdymas

Norint įtraukti ir vykdyti užduotis gamyboje, turite nustatyti eilių užpakalį, tai yra, turite nuspręsti, kurią trečiosios šalies eilių biblioteką „Rails“ turėtų naudoti. „Rails“ pats suteikia tik vidinę eilių sistemą, kuri laiko užduotis tik atmintyje. Jei procesas sutrinka arba mašina iš naujo paleidžiama, tada visos neišspręstos užduotys prarandamos su numatytuoju asinchroniniu užpakalio adapteriu. Tai gali būti tinkama mažesnėms programoms arba nesvarbioms užduotims, bet dauguma gamintojo programų turės pasirinkti nuolatinį užpakalį.

4.1 Užpakalniai

Aktyvioje užduotyje yra įdiegti adapteriai daugeliui eilių užpakalinių („Sidekiq“, „Resque“, „Delayed Job“ ir kt.). Norėdami gauti naujausią adapterių sąrašą, žiūrėkite API dokumentaciją ActiveJob::QueueAdapters.

4.2 Nustatykite užpakalnį

Galite lengvai nustatyti savo eilių užpakalnį naudodami config.active_job.queue_adapter:

# config/application.rb
module YourApp
  class Application < Rails::Application
    # Įsitikinkite, kad adapterio juvelyras yra jūsų Gemfile
    # ir laikykitės adapterio konkretaus diegimo
    # ir diegimo instrukcijų.
    config.active_job.queue_adapter = :sidekiq
  end
end

Taip pat galite konfigūruoti savo užpakalnį pagal užduotį:

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

# Dabar jūsų užduotis naudos „resque“ kaip savo užpakalnio eilės adapterį, pakeisdama tai,
# kas buvo sukonfigūruota „config.active_job.queue_adapter“.

4.3 Pradedant Backend

Kadangi darbai vyksta lygiagrečiai su jūsų „Rails“ programa, dauguma eilės bibliotekų reikalauja, kad paleistumėte bibliotekos specifinę eilės paslaugą (be jūsų „Rails“ programos paleidimo), kad darbų apdorojimas veiktų. Norėdami sužinoti, kaip paleisti eilės backend'ą, žiūrėkite bibliotekos dokumentaciją.

Čia yra nebaigtas dokumentacijos sąrašas:

5 Eilės

Dauguma adapterių palaiko kelias eiles. Su „Active Job“ galite suplanuoti darbą paleisti tam tikroje eilėje naudodami queue_as:

class GuestsCleanupJob < ApplicationJob
  queue_as :low_priority
  # ...
end

Galite pridėti eilės pavadinimo priešdėlį visiems savo darbams naudodami config.active_job.queue_name_prefix application.rb faile:

# 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

# Dabar jūsų darbas bus paleistas eilėje production_low_priority jūsų
# produkcinėje aplinkoje ir eilėje staging_low_priority
# jūsų staging aplinkoje

Taip pat galite konfigūruoti priešdėlį pagal darbą.

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

# Dabar jūsų darbo eilės pavadinimas nebus priešdėlis, pakeisdamas tai,
# kas buvo sukonfigūruota `config.active_job.queue_name_prefix`.

Numatytasis eilės pavadinimo priešdėlio skyriklis yra '_'. Tai galima pakeisti nustatant config.active_job.queue_name_delimiter application.rb faile:

# 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

# Dabar jūsų darbas bus paleistas eilėje production.low_priority jūsų
# produkcinėje aplinkoje ir eilėje staging_low_priority
# jūsų staging aplinkoje

Jei norite turėti daugiau kontrolės, kuriame darbe bus paleistas darbas, galite perduoti :queue parametrą į set:

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

Norėdami kontroliuoti eilę iš darbo lygio, galite perduoti bloką į queue_as. Blokas bus vykdomas darbo kontekste (todėl jis gali pasiekti self.arguments), ir jis turi grąžinti eilės pavadinimą:

class ProcessVideoJob < ApplicationJob
  queue_as do
    video = self.arguments.first
    if video.owner.premium?
      :premium_videojobs
    else
      :videojobs
    end
  end

  def perform(video)
    # Daryti kažką su vaizdo įrašu
  end
end
ProcessVideoJob.perform_later(Video.last)

PASTABA: Įsitikinkite, kad jūsų eilės backend'as „klausosi“ jūsų eilės pavadinimo. Kai kuriems backend'ams reikia nurodyti klausomų eilių sąrašą.

6 Atgalinio iškvietimo funkcijos

„Active Job“ suteikia „hooks“, skirtus aktyvuoti logiką darbo gyvavimo ciklo metu. Kaip ir kiti „Rails“ „callbacks“, galite įgyvendinti „callbacks“ kaip įprastus metodus ir naudoti makro stiliaus klasės metodus, kad juos užregistruotumėte kaip „callbacks“:

class GuestsCleanupJob < ApplicationJob
  queue_as :default

  around_perform :around_cleanup

  def perform
    # Daryti kažką vėliau
  end

  private
    def around_cleanup
      # Daryti kažką prieš vykdymą
      yield
      # Daryti kažką po vykdymo
    end
end

Makro stiliaus klasės metodai taip pat gali priimti bloką. Svarstykite naudoti šį stilių, jei jūsų bloko viduje esantis kodas yra tokio trumpas, kad telpa vienoje eilutėje. Pavyzdžiui, galėtumėte siųsti metrikas kiekvienam užduočiui įtrauktam į eilę:

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

6.1 Galimi „callbacks“

7 Veiksmų laiškai

Vienas iš dažniausių darbų modernioje internetinėje aplikacijoje yra el. laiškų siuntimas išorėje užklausos-atsakymo ciklo, kad vartotojas neturėtų laukti. „Active Job“ yra integruotas su „Action Mailer“, todėl galite lengvai siųsti el. laiškus asinchroniškai:

# Jei norite išsiųsti laišką dabar, naudokite #deliver_now
UserMailer.welcome(@user).deliver_now

# Jei norite išsiųsti laišką per „Active Job“, naudokite #deliver_later
UserMailer.welcome(@user).deliver_later

PASTABA: Naudoti asinchroninę eilę iš „Rake“ užduoties (pvz., norint išsiųsti laišką naudojant .deliver_later) paprastai neveiks, nes „Rake“ greičiausiai baigs darbą, prieš apdorodamas bet kurį/iš visų .deliver_later laiškus. Norint išvengti šios problemos, naudokite .deliver_now arba paleiskite nuolatinę eilę vystymo metu.

8 Tarptautinės funkcijos

Kiekvienas darbas naudoja I18n.locale, nustatytą darbo sukūrimo metu. Tai naudinga, jei siunčiate el. laiškus asinchroniškai:

I18n.locale = :eo

UserMailer.welcome(@user).deliver_later # Laiškas bus lokalizuotas esperanto kalba.

9 Palaikomi argumentų tipai

ActiveJob pagal numatytuosius parametrus palaiko šiuos argumentų tipus:

  • Pagrindiniai tipai (NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass)
  • Symbol
  • Date
  • Time
  • DateTime
  • ActiveSupport::TimeWithZone
  • ActiveSupport::Duration
  • Hash (Raktai turėtų būti String arba Symbol tipo)
  • ActiveSupport::HashWithIndifferentAccess
  • Array
  • Range
  • Module
  • Class

9.1 GlobalID

Active Job palaiko GlobalID parametrams. Tai leidžia perduoti gyvus Active Record objektus į jūsų darbą, o ne klasės/id poras, kurias tada reikia rankiniu būdu deserializuoti. Anksčiau darbai atrodė taip:

class TrashableCleanupJob < ApplicationJob
  def perform(trashable_class, trashable_id, depth)
    trashable = trashable_class.constantize.find(trashable_id)
    trashable.cleanup(depth)
  end
end

Dabar galite tiesiog tai padaryti:

class TrashableCleanupJob < ApplicationJob
  def perform(trashable, depth)
    trashable.cleanup(depth)
  end
end

Tai veikia su bet kuria klase, kuri maišo GlobalID::Identification, kuri pagal numatytuosius nustatymus yra maišoma į Active Record klases.

9.2 Serializeriai

Galite išplėsti palaikomų argumentų tipų sąrašą. Jums tiesiog reikia apibrėžti savo serializerį:

# app/serializers/money_serializer.rb
class MoneySerializer < ActiveJob::Serializers::ObjectSerializer
  # Patikrina, ar argumentas turėtų būti serializuojamas šiuo serializeriu.
  def serialize?(argument)
    argument.is_a? Money
  end

  # Konvertuoja objektą į paprastesnį atstovą, naudojant palaikomus objektų tipus.
  # Rekomenduojamas atstovas yra `Hash` su tam tikru raktu. Raktai gali būti tik pagrindinių tipų.
  # Turėtumėte iškviesti `super`, kad pridėtumėte pasirinktinį serializerio tipą į `Hash`.
  def serialize(money)
    super(
      "amount" => money.amount,
      "currency" => money.currency
    )
  end

  # Konvertuoja serializuotą reikšmę į tinkamą objektą.
  def deserialize(hash)
    Money.new(hash["amount"], hash["currency"])
  end
end

ir pridėkite šį serializerį į sąrašą:

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

Atkreipkite dėmesį, kad inicializacijos metu nepalaikomas automatinis kodų pakrovimas. Todėl rekomenduojama nustatyti, kad serializeriai būtų įkelti tik vieną kartą, pvz., pakeičiant config/application.rb taip:

# config/application.rb
module YourApp
  class Application < Rails::Application
    config.autoload_once_paths << Rails.root.join('app', 'serializers')
  end
end

10 Išimtys

Darbo vykdymo metu iškeltos išimtys gali būti apdorotos naudojant rescue_from:

class GuestsCleanupJob < ApplicationJob
  queue_as :default

  rescue_from(ActiveRecord::RecordNotFound) do |exception|
    # Kažką daryti su išimtimi
  end

  def perform
    # Kažką daryti vėliau
  end
end

Jei darbo išimtis nėra išgelbėta, tuomet darbas vadinamas "nepavykęs".

10.1 Nepavykusių darbų pakartojimas arba atsisakymas

Nepavykusio darbo nebus pakartojama, nebent kitaip konfigūruota.

Galima pakartoti arba atsisakyti nepavykusį darbą naudojant retry_on arba discard_on, atitinkamai. Pavyzdžiui:

class RemoteServiceJob < ApplicationJob
  retry_on CustomAppException # numatytasis laukimo laikas yra 3 sekundės, 5 bandymai

  discard_on ActiveJob::DeserializationError

  def perform(*args)
    # Gali sukelti CustomAppException arba ActiveJob::DeserializationError
  end
end

10.2 Deserializacija

GlobalID leidžia serializuoti visus perduotus Active Record objektus į #perform.

Jei perduotas įrašas yra ištrintas po to, kai darbas yra įtrauktas į eilę, bet prieš tai yra iškviesta #perform metodas, Active Job iškels ActiveJob::DeserializationError išimtį.

11 Darbo testavimas

Išsamią instrukciją, kaip testuoti savo darbus, rasite testavimo vadove.

12 Derinimas

Jei jums reikia pagalbos nustatant, iš kur ateina darbai, galite įjungti išsamų žurnalavimą.

Atsiliepimai

Jūs esate skatinami padėti pagerinti šio vadovo kokybę.

Prašome prisidėti, jei pastebite rašybos klaidų ar faktinių klaidų. Norėdami pradėti, galite perskaityti mūsų dokumentacijos prisidėjimo skyrių.

Taip pat gali būti nepilnos informacijos arba informacijos, kuri nėra atnaujinta. Prašome pridėti bet kokią trūkstamą dokumentaciją pagrindiniam. Patikrinkite Edge vadovus pirmiausia, ar problemas jau išspręsta arba ne pagrindinėje šakoje. Patikrinkite Ruby on Rails vadovų gaires dėl stiliaus ir konvencijų.

Jei dėl kokios nors priežasties pastebite kažką, ką reikia ištaisyti, bet negalite patys tai pataisyti, prašome pranešti apie problemą.

Ir galiausiai, bet ne mažiau svarbu, bet koks diskusijos dėl Ruby on Rails dokumentacijos yra labai laukiamos oficialiame Ruby on Rails forume.