From fb991df341dc1e23ab0b665e71932e406c2ac393 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 5 Jan 2017 14:30:48 +0300 Subject: [PATCH] [RU] Add the 'libraries/poolboy' lesson translation (#892) --- GLOSSARY.md | 1 + ru/lessons/libraries/poolboy.md | 140 ++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 ru/lessons/libraries/poolboy.md diff --git a/GLOSSARY.md b/GLOSSARY.md index 24499d3c25..53a07af588 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -37,6 +37,7 @@ Class names and proper nouns do not get translated. - match operator - оператор сопоставления - pin operator - фиксирующий оператор - pipe operator - оператор конвейера +- pool - пул - sigil - строковая метка - schema - структура - tuple - кортеж diff --git a/ru/lessons/libraries/poolboy.md b/ru/lessons/libraries/poolboy.md new file mode 100644 index 0000000000..e6fef40795 --- /dev/null +++ b/ru/lessons/libraries/poolboy.md @@ -0,0 +1,140 @@ +--- +layout: page +title: Poolboy +category: libraries +order: 2 +lang: ru +--- + +Если позволить параллельным процессам выполняться произвольно, то с лёгкостью можно израсходовать все системные ресурсы. Poolboy предотвращает возможность возникновения чрезмерной нагрузки с помощью пула процессов-обработчиков, ограничивающих количество параллельных процессов. + +{% include toc.html %} + +## Зачем использовать Poolboy? + +Возьмём конкретный пример. Вам надо создать приложение для сохранения профилей пользователей в базу данных. Если бы вы создавали по процессу на регистрацию каждого пользователя, вы бы генерировали неограниченное количество соединений. В определённый момент эти соединения начали бы соперничать за ограниченные ресурсы сервера базы данных. В итоге в приложении стали бы возникать таймауты и различные ошибки из-за перегрузки, появившейся вследствие такой конкуренции. + +Решение такой проблемы — использовать пул процессов-обработчиков для ограничения количества одновременных соединений вместо создания процессов для регистрации каждого пользователя. Так можно с лёгкостью избежать истощения системных ресурсов. + +Для этого и нужен Poolboy. Он создаёт пул процессов-обработчиков, управляемых супервизором без необходимости делать что-либо вручную. Очень многие библиотеки используют Poolboy под капотом. Например, так работают пулы соединений в `postgrex` *(который предоставляется Ecto при использовании PostgreSQL)* и `redis_poolex`. + +## Установка + +Благодаря mix установка очень проста. Нужно всего лишь добавить Poolboy в список зависимостей в `mix.exs`. + +Для начала создадим приложение: + +``` +$ mix new poolboy_app --sup +$ mix deps.get +``` + +Добавим Poolboy в список зависимостей `mix.exs`. + +```elixir +defp deps do + [{:poolboy, "~> 1.5.1"}] +end +``` + +Добавим Poolboy в приложение OTP: + +```elixir +def application do + [applications: [:logger, :poolboy]] +end +``` + +## Настройка + +Перед тем как начать пользоваться Poolboy, надо ознакомиться с возможностями его настройки: + +* `:name` — наименование пула. Область видимости может быть `:local`, `:global` или `:via`. +* `:worker_module` — модуль, представляющий рабочий процесс. +* `:size` — максимальный размер пула. +* `:max_overflow` — максимальное количество процессов-обработчиков, создаваемых, если в пуле закончились свободные процессы. (необязательно) +* `:strategy` — `:lifo` или `:fifo`, определяет, в начало или в конец списка доступных процессов-обработчиков должен быть помещён создаваемый процесс. По умолчанию `:lifo`. (необязательно) + +## Настройка Poolboy + +Для примера создадим пул процессов-обработчиков, ответственных за обработку запросов на расчёт квадратного корня числа. Пример намеренно выбран попроще, чтобы мы могли сфокусироваться на Poolboy. + +Опишем модуль настройки Poolboy и добавим его как дочерний рабочий процесс при запуске нашего приложения. + +```elixir +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`) пятью процессами. Также в опции `:max_overflow` мы указали, что в случае, если все процессы-обработчики будут заняты, то можно создавать два дополнительных процесса, чтобы помочь разобраться с нагрузкой. *(`overflow`-процессы завершаются как только они выполнят свою работу.)* + +Затем мы добавили функцию `poolboy.child_spec/3` в список дочерних процессов, чтобы пул запускался при запуске нашего приложения. + +Функция `child_spec/3` принимает три аргумента: имя пула, настройки и третий аргумент, передаваемый в функцию `worker.start_link`. В нашем случае это пустой список. + +## Создание рабочего процесса + +Модуль рабочего процесса будет простым GenServer'ом, который считает квадратный корень числа, затем останавливается на секунду и выводит pid процесса: + +```elixir +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 "процесс #{inspect self} считает квадратный корень из #{x}" + :timer.sleep(1000) + {:reply, :math.sqrt(x), state} + end +end +``` + +## Использование Poolboy + +Теперь, когда у нас есть `Worker`, мы можем тестировать Poolboy. Создадим простой модуль, генерирующий задачи при помощи функции `:poolboy.transaction`: + +```elixir +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` ограничит общее количество созданных процессов до пяти *(плюс два процесса для обработки перегрузки)*, как мы и указали в настройках. Все запросы будут обработаны пулом процессов вместо создания по процессу на каждый запрос. \ No newline at end of file