Poolboy
Μπορείτε πολύ εύκολα να εξαντλήσετε τους πόρους τους συστήματός σας αν δεν περιορίσετε τον μέγιστο αριθμό ταυτόχρονων διεργασιών που μπορεί να ξεκινήσει το πρόγραμμά σας. Το Poolboy είναι μια ευρέως διαδεδομένη, ελαφριά βιβλιοθήκη δημιουργίας σετ για την Erlang η οποία αντιμετωπίζει αυτό το ζήτημα.
Γιατί να χρησιμοποιήσουμε το Poolboy
Ας δούμε ένα συγκεκριμένο παράδειγμα για λίγο. Σας έχει ανατεθεί να χτίσετε μια εφαρμογή αποθήκευσης πληροφοριών προφίλ σε μια βάση δεδομένων. Αν έχετε δημιουργήσει μια διεργασία για κάθε εγγραφή χρήστη, θα χρησιμοποιούσατε απεριόριστο αριθμό συνδέσεων. Σε κάποιο σημείο αυτές οι συνδέσεις μπορούν να ξεπεράσουν τους διαθέσιμους πόρους του διακομιστή της βάσης δεδομένων. Τελικά η εφαρμογή σας μπορεί να αρχίσει να εμφανίζει σφάλματα ορίου χρόνου και διάφορες εξαιρέσεις.
Η λύση σε αυτό το πρόβλημα είναι η χρήση ενός σετ εργατών (διεργασίες) για να περιορίσουμε τον αριθμό συνδέσεων αντί να δημιουργούμε μια διεργασία για κάθε εγγραφή χρήστη. Τότε μπορείτε εύκολα να αποτρέψετε την εξάντληση των πόρων του συστήματος σας.
Εδώ έρχεται το Poolboy.
Σας επιτρέπει να δημιουργήσετε εύκολα μια λίστα εργατών που διαχειρίζονται από έναν Supervisor
χωρίς μεγάλη προσπάθεια από μέρους σας.
Υπάρχουν πολλές βιβλιοθήκες που χρησιμοποιούν το Poolboy στο εσωτερικό τους.
Για παράδειγμα: η redis_poolex
(Λίστα συνδέσεων Redis) είναι μια δημοφιλής βιβλιοθήκη που χρησιμοποιεί το Poolboy.
Εγκατάσταση
Η εγκατάσταση είναι πανεύκολη με το mix.
Το μόνο που πρέπει να κάνουμε είναι να προσθέσουμε το Poolboy σαν εξάρτηση στο mix.exs
μας.
Ας δημιουργήσουμε μια εφαρμογή πρώτα:
mix new poolboy_app --sup
Ας προσθέσουμε το Poolboy σαν εξάρτηση στο mix.exs
μας.
defp deps do
[{:poolboy, "~> 1.5.1"}]
end
Τότε ας κατεβάσουμε τις εξαρτήσεις, συμπεριλαμβανομένου του Poolboy:
mix deps.get
Οι επιλογές ρύθμισης
Πρέπει να γνωρίζουμε μερικά πράγματα για τις διάφορες επιλογές ρύθμισης ώστε να ξεκινήσουμε να χρησιμοποιούμε το Poolboy.
-
:name
- το όνομα της λίστας. Το πεδίο δράσης μπορεί να είναι:local
,:global
ή:via
. -
:worker_module
- η ενότητα που αναπαριστά τον εργάτη. -
:size
- το μέγιστο μέγεθος της λίστας. -
:max_overflow
- ο μέγιστος αριθμός προσωρινών εργατών που δημιουργούνται αν η λίστα είναι άδεια. (προεραιτικό) -
:strategy
-:lifo
ή:fifo
, ορίζουν αν οι επιστρεφόμενοι εργάτες ορίζονται πρώτοι ή τελευταίοι στη σειρά των διαθέσιμων εργατών. Η προεπιλογή είναι:lifo
.(προαιρετικό)
Ρυθμίζοντας το Poolboy
Για αυτό το παράδειγμα, θα δημιουργήσουμε μια λίστα εργατών που είναι υπέυθυνοι για το χειρισμό αιτήσεων υπολογισμού της τετραγωνικής ρίζας ενός αριθμού. Θα διατηρήσουμε το παράδειγμά μας απλό ώστε να εστιάσουμε στο Poolboy.
Ας ορίσουμε τις επιλογές ρύθμισης του Poolboy και ας το προσθέσουμε σαν εργάτης παιδί μέρος της εκκίνησης της εφαρμογής μας.
Αλλάξτε το lib/poolboy_app/application.ex
:
defmodule PoolboyApp.Application do
@moduledoc false
use Application
defp poolboy_config do
[
name: {:local, :worker},
worker_module: PoolboyApp.Worker,
size: 5,
max_overflow: 2
]
end
def start(_type, _args) do
children = [
:poolboy.child_spec(:worker, poolboy_config())
]
opts = [strategy: :one_for_one, name: PoolboyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Το πρώτο πράγμα που ορίσαμε είναι οι επιλογές ρυθμίσεων για τη λίστα.
Ονομάσαμε τη λίστα μας :worker
και ορίσαμε το πεδίο δράσης σε :local
.
Στη συνέχεια προσδιορίσαμε την ενότητα PoolboyApp.Worker
σαν την :worker_module
που θα χρησιμοποιεί αυτή η λίστα.
Επίσης ορίσαμε το :size
της λίστας να είναι 5
εργάτες.
Επίσης, σε περίπτωση που όλοι οι εργάτες είναι απασχολημένοι, ρυθμίζουμε τη δημιουργία δύο επιπλέον εργατών για να βοηθήσουν με το φόρτο, χρησιμοποιώντας την επιλογή :max_overflow
.
(οι overflow
εργάτες παύουν να υπάρχουν μόλις ολοκληρώσουν την εργασία τους.)
Στη συνέχεια, προσθέσαμε τη συνάρτηση poolboy.child_spec/2
στον πίνακα των παιδιών ώστε η λίστα εργατών να ξεκινάει με την εκκίνηση της εφαρμογής.
Δέχεται δύο ορίσματα: το όνομα της λίστας και τις ρυθμίσεις της.
Δημιουργία Εργάτη
Η ενότητα εργάτη θα είναι ένας απλός GenServer υπολογισμού της τετραγωνικής ρίζας ενός αριθμού, που κοιμάται για ένα δευτερόλεπτο και τυπώνει το pid του εργάτη.
Δημιουργήστε το lib/poolboy_app/worker_ex
:
defmodule PoolboyApp.Worker do
use GenServer
def start_link(_) do
GenServer.start_link(__MODULE__, nil)
end
def init(_) do
{:ok, nil}
end
def handle_call({:square_root, x}, _from, state) do
IO.puts("process #{inspect(self())} calculating square root of #{x}")
Process.sleep(1000)
{:reply, :math.sqrt(x), state}
end
end
Χρήση του Poolboy
Τώρα που έχουμε τον PoolboyApp.Worker
μας, ας δοκιμάσουμε το Poolboy.
Ας γράψουμε μια απλή ενότητα που δημιουργεί ταυτόχρονες διεργασίες χρησιμοποιώντας το Poolboy.
Η :poolboy.transaction/3
είναι η συνάρτηση που μπορείτε να χρησιμοποιείτε για τη διεπαφή με τη λίστα εργατών.
Δημιουργήστε το lib/poolboy_app/test.ex
:
defmodule PoolboyApp.Test do
@timeout 60000
def start do
1..20
|> Enum.map(fn i -> async_call_square_root(i) end)
|> Enum.each(fn task -> await_and_inspect(task) end)
end
defp async_call_square_root(i) do
Task.async(fn ->
:poolboy.transaction(
:worker,
fn pid ->
# Ας καλύψουμε το κάλεσμα του genserver σε μια ενότητα try - catch.
# Αυτό μας δίνει την δυνατότητα να παγιδεύσουμε κάποιες εξαιρέσεις
# που μπορούν να δημιουργηθούν και να γυρίσουμε τον εργάτη πίσω στο poolboy με ένα καθαρό τρόπο.
# Επίσης επιτρέπει στον προγραματιστή να επικαλεστεί το σφάλμα και να το διορθώσει.
try do
GenServer.call(pid, {:square_root, i})
catch
e, r -> IO.inspect("poolboy transaction caught error: #{inspect(e)}, #{inspect(r)}")
:ok
end
end,
@timeout
)
end)
end
defp await_and_inspect(task), do: task |> Task.await(@timeout) |> IO.inspect()
end
Τρέξτε τη δοκιμαστική συνάρτηση για να δείτε το αποτέλεσμα.
iex -S mix
iex> PoolboyApp.Test.start()
process #PID<0.182.0> calculating square root of 7
process #PID<0.181.0> calculating square root of 6
process #PID<0.157.0> calculating square root of 2
process #PID<0.155.0> calculating square root of 4
process #PID<0.154.0> calculating square root of 5
process #PID<0.158.0> calculating square root of 1
process #PID<0.156.0> calculating square root of 3
...
Αν δεν υπάρχει διαθέσιμος εργάτης στη λίστα, το Poolboy θα έχει μια λήξη ορίου χρόνου μετά το τέλος της περιόδου λήξης ορίου χρόνου (πέντε δευτερόλεπτα) και δεν θα δέχεται νέες αιτήσεις.
Στο παράδειγμά μας, αυξήσαμε το προκαθορισμένο όριο λήξης χρόνου στο ένα λεπτό ώστε να επιδείξουμε πως μπορούμε να αλλάξουμε την προκαθορισμένη τιμή λήξης ορίου χρόνου.
Στην περίπτωση αυτής της εφαρμογής, μπορείτε να δείτε το σφάλμα αλλάζοντας την τιμή της @timeout
σε λιγότερο από 1000.
Παρόλο που προσπαθούμε να δημιουργήσουμε πολλαπλές διεργασίες (είκοσι στο σύνολό τους στο παραπάνω παράδειγμα), η συνάρτηση :poolboy.transaction/2
θα περιορίσει το σύνολο των δημιουργημένων διεργασιών σε πέντε (συν δύο εργάτες υπερχείλισης αν απαιτούνται) όπως το ορίσαμε στις ρυθμίσεις μας.
Όλες οι αιτήσεις θα χειριστούν από τη λίστα εργατών αντί να δημιουργείται μια νέα διεργασία για κάθε μια αίτηση.
Έπιασες λάθος ή θέλεις να συνεισφέρεις στο μάθημα; Επεξεργαστείτε αυτό το μάθημα στο GitHub!