Fork me on GitHub

Poolboy

Ίσως υπάρχει περιεχόμενο σε αυτή τη μετάφραση που δεν είναι ενημερωμένο.
Υπάρχουν αρκετές μικρές αλλαγές στο αρχικό μάθημα μετά την τελευταία ενημέρωση.

Μπορείτε πολύ εύκολα να εξαντλήσετε τους πόρους τους συστήματός σας αν επιτρέψετε σε παράλληλες διεργασίες να τρέχουν ανεξέλεγκτα. Το Poolboy αποτρέπει την ύπαρξη του overhead δημιουργώντας μια λίστα με εργάτες για να περιορίσουν τον αριθμό των ταυτόχρονων διεργασιών.

Πίνακας περιεχομένων

Γιατί να χρησιμοποιήσουμε το Poolboy;

Ας δούμε ένα συγκεκριμμένο παράδειγμα για λίγο. Σας έχει ανατεθεί να χτίσετε μια εφαρμογή αποθήκευσης πληροφοριών προφίλ σε μια βάση δεδομένων. Αν έχετε δημιουργήσει μια διεργασία για κάθε εγγραφή χρήστη, θα χρησιμοποιούσατε απεριόριστο αριθμό συνδέσεων. Και σε κάποιο σημείο αυτές οι συνδέσεις αρχίζουν να ανταγωνίζονται για τους περιορισμένους πόρους που είναι διαθέσιμοι στο διακομιστή της βάσης δεδομένων. Τελικά η εφαρμογή σας αρχίζει να εμφανίζει σφάλματα ορίου χρόνου και διάφορες εξαιρέσεις εξ’ αιτίας του overhead από αυτό τον ανταγωνισμό.

Η λύση σε αυτό το πρόβλημα είναι η χρήση ενός σετ εργατών (διεργασίες) για να περιορίσουμε τον αριθμό συνδέσεων αντί να δημιουργούμε μια διεργασία για κάθε εγγραφή χρήστη. Τότε μπορείτε εύκολα να αποτρέψετε την εξάντληση των πόρων του συστήματος σας.

Εδώ έρχεται το Poolboy. Δημιουργεί μια λίστα εργατών που διαχειρίζονται από έναν Supervisor χωρίς καμμία ενέργεια από μέρους σας για να γίνει χειροκίνητα. Για την ακρίβεια υπάρχουν πολλές βιβλιοθήκες που χρησιμοποιούν το Poolboy στο εσωτερικό τους. Για παράδειγμα: η λίστα συνδέσεων της postgrex (η οποία με τη σειρά της αξιοποιείται από την Ecto όταν χρησιμοποιούμε την PostgreSQL), η redis_poolex (Λίστα συνδέσεων Redis) είναι μερικές από τις πιο δημοφιλείς βιβλιοθήκες που χρησιμοποιούν το Poolboy.

Εγκατάσταση

Η εγκατάσταση είναι πανεύκολη με το mix. Το μόνο που πρέπει να κάνουμε είναι να προσθέσουμε το Poolboy σαν εξάρτηση στο mix.exs μας.

Ας δημιουργήσουμε μια εφαρμογή πρώτα:

$ mix new poolboy_app --sup
$ mix deps.get

Ας προσθέσουμε το Poolboy σαν εξάρτηση στο mix.exs μας.

defp deps do
  [{:poolboy, "~> 1.5.1"}]
end

Και ας προσθέσουμε το Poolboy στην εφαρμογή OTP μας:

def application do
  [applications: [:logger, :poolboy]]
end

Οι επιλογές ρύθμισης

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

Ρυθμίζοντας το Poolboy

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

Ας ορίσουμε τις επιλογές ρύθμισης του Poolboy και ας το προσθέσουμε σαν εργάτης παιδί μέρος της εκκίνησης της εφαρμογής μας.

defmodule PoolboyApp do
  use Application

  defp poolboy_config do
    [{:name, {:local, :worker}},
      {:worker_module, Worker},
      {:size, 5},
      {:max_overflow, 2}]
  end

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      :poolboy.child_spec(:worker, poolboy_config, [])
    ]

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

Το πρώτο πράγμα που ορίσαμε είναι οι επιλογές ρυθμίσεων για τη λίστα. Καθορίσαμε ένα μοναδικό :name για τη λίστα μας, ορίσαμε την :scope σε τοπική, και το :size της λίστας να είναι 5 εργάτες. Επίσης, σε περίπτωση που οι εργάτες μας είναι απασχολημένοι, ρυθμίζουμε τη δημιουργία δύο επιπλέον εργατών για να βοηθήσουν με το φόρτο, χρησιμοποιώντας την επιλογή :max_overflow. (οι overflow εργάτες παύουν να υπάρχουν μόλις ολοκληρώσουν την εργασία τους.)

Στη συνέχεια, προσθέσαμε τη συνάρτηση poolboy.child_spec/3 στον πίνακα των παιδιών ώστε η λίστα εργατών να ξεκινάει με την εκκίνηση της εφαρμογή.

Η συνάρτηση child_spec/3 δέχεται τρία ορίσματα: το όνομα της λίστας, τις ρυθμίσεις της λίστας και το τρίτο όρισμα που περνάμε στη συνάρτηση worker.start_link. Στην περίπτωσή μας είναι απλά μια άδεια λίστα.

Δημιουργία Εργάτη

Η ενότητα εργάτη θα είναι ένας απλός GenServer υπολογισμού της τετραγωνικής ρίζας ενός αριθμού, που κοιμάται για ένα δευτερόλεπτο και τυπώνει το pid του εργάτη:

defmodule 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}"
    :timer.sleep(1000)
    {:reply, :math.sqrt(x), state}
  end
end

Χρήση του Poolboy

Τώρα που έχουμε τον Worker, μπορούμε να δοκιμάσουμε το Poolboy. Ας δημιουργήσουμε μια απλή ενότητα που δημιουργεί ταυτόχρονες διεργασίες χρησιμοποιώντας τη συνάρτηση :poolboy.transaction:

defmodule Test do
  @timeout 60000

  def start do
     tasks = Enum.map(1..20, fn(i) ->
        Task.async(fn -> :poolboy.transaction(:worker,
          &(GenServer.call(&1, {:square_root, i})), @timeout)
        end)
     end)
     Enum.each(tasks, fn(task) -> IO.puts(Task.await(task, @timeout)) end)
  end
end

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

Παρόλο που προσπαθούμε να δημιουργήσουμε πολλαπλές διεργασίες (είκοσι στο σύνολό τους στο παραπάνω παράδειγμα), η συνάρτηση :poolboy.transaction θα περιορίσει το σύνολο των δημιουργημένων διεργασιών σε πέντε (συν δύο εργάτες υπερχείλισης αν απαιτούνται) όπως το ορίσαμε στις ρυθμίσεις μας. Όλες οι αιτήσεις θα χειριστούν από τη λίστα εργατών μας αντί να δημιουργείται μια νέα διεργασία για κάθε μια αίτηση.



Share This Page