Επιτηρητές OTP

Οι Επιτηρητές είναι εξειδικευμένες διεργασίες με ένα σκοπό: την επισκόπηση άλλων διεργασιών. Αυτοί οι επιτηρητές μας επιτρέπουν να δημιουργούμε ανεκτικές στα σφάλματα εφαρμογές με το να επανεκινούν τις διεργασίες παιδιά όταν αποτυγχάνουν.

Ρύθμιση

Η μαγεία των επιτηρητών είναι στη συνάρτηση Supervisor.start_link/2. Επιπρόσθετα με την εκκίνηση του επιτηρητή και των παιδιών, μας επιτρέπει να ορίσουμε τη στρατηγική που χρησιμοποιεί ο επιτηρητής για τη διαχείριση των διεργασιών παιδιών.

Ας αρχίσουμε χρησιμοποιώντας την ενότητα SimpleQueue από το μάθημα Συγχρονισμός OTP:

Δημιουργήστε ένα νέο project χρησιμοποιόντας την εντολή mix new simple_queue --sup για να δημιουργήσετε ένα project με δέντρο επιτήρησης. Ο κώδικας για την ενότητα SimpleQueue θα πρέπει να μπει στο lib/simple_queue.ex και ο κώδικας επιτήρησης που θα προσθέσουμε θα μπει στο lib/simple_queue/application.ex

Οι διεργασίες παιδιά ορίζονται με τη χρήση μιας λίστας, ειδάλως με μια λίστα ονομάτων ενοτήτων:

defmodule SimpleQueue.Application do
  use Application

  def start(_type, _args) do
    children = [
      SimpleQueue
    ]

    opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

ή μια λίστα από τούπλες αν θέλετε να συμπεριλάβετε επιλογές διαμόρφωσης:

defmodule SimpleQueue.Application do
  use Application

  def start(_type, _args) do
    children = [
      {SimpleQueue, [1, 2, 3]}
    ]

    opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Αν τρέξουμε την εντολή iex -S mix θα δούμε ότι η ενότητα SimpleQueue εκκινείται αυτόματα:

iex> SimpleQueue.queue
[1, 2, 3]

Αν η διεργασία της SimpleQueue κράσαρε ή τερματιζόταν ο επιτηρητής μας θα την επανεκκινούσε αύτοματα σαν να μην είχε συμβεί τίποτε.

Στρατηγικές

Αυτή τη στιγμή οι επιτηρητές μας έχουν διαθέσιμες τρείς διαφορετικές στρατηγικές επανεκκίνησης:

Προδιαγραφή Παιδιού

Ο επιτηρητής μετά την εκκίνηση του θα πρέπει να γνωρίζει πως να εκκινήσει/σταματήσει/επανεκκινήσει τις διεργασίες παιδιά του. Κάθε ενότητα παιδιού θα πρέπει να έχει μια συνάρτηση child_spec/1 η οποία θα ορίζει αυτές τις συμπεριφορές. Οι μακροεντολές use GenServer, use Supervisor και use Agent προσδιορίζουν αυτόματα αυτή την μέθοδο για εμάς ( Η SimpleQueue έχει use Genserver, οπότε δεν χρειάζεται να τροποποιήσουμε την ενότητα), αλλά αν θέλετε να την καθορίσετε μόνοι σας, η συνάρτηση child_spec/1 θα πρέπει να επιστρέφει έναν χάρτη επιλογών:

def child_spec(opts) do
  %{
    id: SimpleQueue,
    start: {__MODULE__, :start_link, [opts]},
    shutdown: 5_000,
    restart: :permanent,
    type: :worker
  }
end

Αν η διεργασία είναι τύπου :worker, η προκαθορισμένη τιμή είναι 5000.

Προκαθορισμένο για όλες τις διεργασίες

DynamicSupervisor

Η εκκίνηση των επιτηρητών συνήθως συνοδεύεται από μια λίστα παιδιών που εκκινούν με την εκκίνηση της εφαρμογής. Παρ’όλ’αυτά, μερικές φορές τα επιτηρούμενα παιδιά δεν θα είναι γνωστά με την εκκίνηση της εφαρμογής μας (για παράδειγμα, μπορεί να έχουμε μια εφαρμογή web που εκκινεί μια νέα διεργασία για να χειριστεί την σύνδεση ενός χρήστη στην σελίδα μας. Για αυτές τις περιπτώσεις θα θέλουμε έναν επιτηρητή του οποίου οι διεργασίες παιδιά θα εκκινούνται κατά παραγγελία. Ο DynamicSupervisor χρησιμοποιείται για να χειριστεί αυτήν την περίπτωση.

Από την στιγμή που δεν θα προσδιορίσουμε διεργασίες παιδιά, χρειάζεται μόνο να καθορίσουμε τις επιλογές εκτέλεσης για τον επιτηρητή. Ο DynamicSupervisor υποστηρίζει μόνο στρατηγικές επιτήρησης :one_for_one:

options = [
  name: SimpleQueue.Supervisor,
  strategy: :one_for_one
]

DynamicSupervisor.start_link(options)

Κατόπιν, για να εκκινήσουμε δυναμικά μια νέα SimpleQueue θα χρησιμοποιήσουμε την start_child/2 που παίρνει έναν επιτηρητή και τις προδιαγραφές του παιδιού (επαναλαμβάνουμε, η SimpleQueue χρησιμοποιεί την use GenServer άρα οι προδιαγραφές του παιδιού είναι ήδη ορισμένες):

{:ok, pid} = DynamicSupervisor.start_child(SimpleQueue.Supervisor, SimpleQueue)

Επιτηρητής Εργασίας

Οι εργασίες έχουν τον δικό τους εξειδικευμένο Επιτηρητή, τον Task.Supervisor. Σχεδιασμένος για δυναμικά δημιουργημένες εργασίες, ο επιτηρητής χρησιμοποιεί την :simple_one_for_one.

Εγκατάσταση

Η συμπερίληψη του Task.Supervisor δεν έχει καμμία διαφορά από τους άλλους επιτηρητές:

children = [
  {Task.Supervisor, name: ExampleApp.TaskSupervisor, restart: :transient}
]

{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

Η μεγάλη διαφορά μεταξύ Supervisor και Task.Supervisor είναι ότι η προκαθορισμένη του στρατηγική επανεκίνησης είναι η :temporary (οι εργασίες δεν θα επανεκινούνται ποτέ).

Επιτηρούμενες Εργασίες

Με τον επιτηρητή να έχει ξεκινήσει, μπορούμε να χρησιμοποιήσουμε τη συνάρτηση start_child/2 για να δημιουργήσουμε μια επιτηρούμενη εργασία:

{:ok, pid} = Task.Supervisor.start_child(ExampleApp.TaskSupervisor, fn -> background_work end)

Αν η εργασία μας κρασάρει πρόωρα, θα επανεκκινηθεί. Αυτό μπορεί να αποδειχθεί ιδιαίτερα χρήσιμο όταν δουλεύουμε με εισερχόμενες συνδέσεις ή επεξεργαζόμαστε δεδομένα στο παρασκήνιο.

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!