Не все сервисы умеют обрабатывать большой поток HTTP запросов.
В том числе, однопоточные сервисы на python, nodejs, ...
Данный скрипт умеет поднимать пул таких сервисов, назначать им
рандомные порты и распределять нагрузку между ними.
Для запуска требуется Elixir >= 1.12.0-otp-22 и Erlang >= 22.2.0 Установить можно с помощью asdf:
asdf plugin add erlang && \
asdf plugin add elixir && \
asdf install erlang 22.2.4 && \
asdf install elixir 1.12.0-otp-22
После установки обязательно нужно проверить версию текущего elixir.
Сделать это можно выполнив команду iex
.
Версии в выводе должны совпадать, вывод будет примерно такой:
Erlang/OTP 22 [erts-10.6.2] [source] [64-bit] [smp:32:32] [ds:32:32:10] [async-threads:1] [hipe]
Interactive Elixir (1.12.0) - press Ctrl+C to exit (type h() ENTER for help)
Для запуска через systemd одним из вариантов может быть asdf, сделать это проще всего через скрипт:
#!/bin/bash
source /root/.bashrc
source /root/.asdf/asdf.sh
asdf global elixir 1.12.0-otp-22
asdf global erlang 22.2.4
elixir multiplexer config.exs
И его уже запускаем как UNIT, пример:
[Unit]
Description=Muliplexer
[Service]
Type=simple
ExecStart=/start_script.sh
TimeoutStopSec=2
KillMode=control-group
User=root
Group=root
WorkingDirectory=⚠️ Не забываем поменять WorkingDirectory на директорию где находиться multiplexer.exs
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=multiplexer
[Install]
WantedBy=multi-user.target
Перед запуском сервис пытается остановить все ранее запущенные
воркеры, для этого выполнется команда kill с параметром service_kill_pattern
из конфига.
После запуска поднимается pool_size
сервисов, запускает они как Порты (смотреть Erlang Port)
с командой service_start_process_cmd
и указание http порта, http порт выбирается из пула портов,
пулл реализован на структуре кольцо/ring.
От сервисов ожидается выхлоп в stdout либо прошествие service_start_delay_ms
времени.
После этого сервис считается запущенным и готовым к работе.
В случае если сервис падает, Порт отлавливает это событие и перезапускает воркера.
Данный скрипт начинает слушать http_port
.
На каждый запрос происходит проксирование на случайный воркер из пула свободных.
Проксирование отрабатывает как reverse_proxy, воркер не знает что запрос проксировался.
Если запросов приходит больше, чем запущено воркеров, начинают использоваться временные.
Их число можно задать в pool_max_overflow
.
Например pool_size: 1, pool_max_overflow: 2.
В сервис пришло одновременно 2 запроса, один запрос сразу же попадет в 1 воркера, а для
второго запроса, будет запущен еще один воркер, после отработки запроса, один из воркеров
будет убит. Кто будет убит рулиться параметром pool_strategy
, есть fifo и lifo.
По хорошему следует использовать fifo, так как сервисы могут течь по памяти, а в случае fifo
оставаться в пуле всегда будут последние поднятые.
import Config
config :multiplexer,
pool_size: 5,
pool_max_overflow: 15,
# ------------------------------
http_port: 3000,
http_proxy_timeout: 5 * 60 * 1000,
# ------------------------------
service_http_base_url: "http://localhost",
service_port_range: 10300..10350,
service_start_process_cmd: "node index.js %PORT%",
# Можно установить в :wait_first_output
# Тогда сервис будет считаться запущеным, если он
# отправил что либо в stdout, зачастую сервисы когда запускаются пишут туда порт на котором запущены
# это может быть хорошим индикатором запущенности
service_start_delay_ms: :wait_first_output,
# Паттерн для убийства процессов, будет подставлен в строку:
# System.shell("ps aux | grep ⚠️СЮДА⚠️ | tr -s ' ' | cut -d ' ' -f 2 | xargs kill -9")
# Вызывается при старте сервиса, полезен в случае ненормального завершения процесса
service_kill_pattern: "index.js"