Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[RU] Add the 'libraries/poolboy' lesson translation (#892)
- Loading branch information
Showing
2 changed files
with
141 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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` ограничит общее количество созданных процессов до пяти *(плюс два процесса для обработки перегрузки)*, как мы и указали в настройках. Все запросы будут обработаны пулом процессов вместо создания по процессу на каждый запрос. |