Επιτηρητές 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
κράσαρε ή τερματιζόταν ο επιτηρητής μας θα την επανεκκινούσε αύτοματα σαν να μην είχε συμβεί τίποτε.
Στρατηγικές
Αυτή τη στιγμή οι επιτηρητές μας έχουν διαθέσιμες τρείς διαφορετικές στρατηγικές επανεκκίνησης:
-
:one_for_one
- Επανεκκίνηση μόνο της αποτυχημένης διεργασίας παιδί. -
:one_for_all
- Επανεκκίνηση όλων των διεργασιών παιδί σε περίπτωση αποτυχίας. -
:rest_for_one
- Επανεκκίνηση της αποτυχημένης διεργασίας και κάθε διεργασίας που ξεκίνησε μετά από αυτή.
Προδιαγραφή Παιδιού
Ο επιτηρητής μετά την εκκίνηση του θα πρέπει να γνωρίζει πως να εκκινήσει/σταματήσει/επανεκκινήσει τις διεργασίες παιδιά του.
Κάθε ενότητα παιδιού θα πρέπει να έχει μια συνάρτηση 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
-
id
- Απαιτούμενο κλειδί. Χρησιμοποιείται απο τον επιτηρητή ώστε να αναγνωρίσει τις προδιαγραφές της διεργασίας παιδιού. -
start
- Απαιτούμενο κλειδί. Η Ενότητα/Συνάρτηση/Ορίσματα που θα κληθούν όταν γίνει εκκίνηση απο τον επιτηρητή. -
shutdown
- Προεραιτικό κλειδί. Προσδιορίζει τον τρόπο λειτουργίας ενός παιδιού κατά τον τερματισμό.
Οι επιλογές είναι:
-
:brutal_kill
- Η διεργασία παιδί τερματίζεται αμέσως. -
0
ή ένας θετικός ακέραιος - ο χρόνος σε χιλιοστά του δευτερολέπτου που θα περιμένει ο επιτηρητής πριν τερματήσει την διεργασία παιδί.
Αν η διεργασία είναι τύπου :worker
, το shutdown
ορίζεται από προεπιλογή στα 5000
.
-
:infinity
- Ο επιτηρητής θα περιμένει επ άπειρον πριν τερματήσει την διεργασία παιδί.
Προκαθορισμένο για διεργασίες τύπου :supervisor
.
Δεν συνιστάται για διεργασίες τύπου :worker
.
-
restart
- Προαιρετικό κλειδί.Υπάρχουν διάφορες προσεγγίσεις για το χειρισμό κρασαρισμάτων διεργασιών παιδιών:
-
:permanent
- Η διεργασία παιδί πάντα επανεκκινείται. Προκαθορισμένο για όλες τις διεργασίες -
:temporary
- Η διεργασία παιδί δεν επανεκκινείται ποτέ. -
:transient
- Η διεργασία παιδί επανεκκινείται μόνο αν τερματιστεί απρόβλεπτα.
-
-
type
- Προαιρετικό κλειδί. Οι διεργασίες μπορεί να είνα είτε τύπου:worker
είτε τύπου:supervisor
. Ο προκαθορισμένος τύπος είναι:worker
.
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)
Αν η εργασία μας κρασάρει πρόωρα, θα επανεκκινηθεί. Αυτό μπορεί να αποδειχθεί ιδιαίτερα χρήσιμο όταν δουλεύουμε με εισερχόμενες συνδέσεις ή επεξεργαζόμαστε δεδομένα στο παρασκήνιο.
Έπιασες λάθος ή θέλεις να συνεισφέρεις στο μάθημα; Επεξεργαστείτε αυτό το μάθημα στο GitHub!