Do you want to pick up from where you left of?
Take me there

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.

Ρυθμίζοντας το 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!