Сокет с Accept=yes: на каждое соединение свой экземпляр службы
Сокет умеет работать двумя способами: передать слушающий сокет одной службе или принимать соединения сам и запускать по экземпляру службы на каждое. Второй способ подходит редким обращениям и губителен для нагруженных служб: на тысячу соединений придётся тысяча запусков.
Вероятные причины
По порядку: сверху то, что встречается чаще.
-
Выбран режим приёма соединений сокетом
При этом режиме systemd создаёт экземпляр шаблонной службы на каждое соединение. Для нагруженной службы это огромные накладные расходы.
-
Число одновременных экземпляров упирается в предел
Предел одновременных соединений ограничивает число экземпляров. При его достижении новые соединения отвергаются.
-
Служба не написана под шаблон
В этом режиме нужна шаблонная служба, получающая соединение на стандартном вводе. Обычная служба так работать не будет.
Диагностика
Команды идут в том порядке, в котором их стоит выполнять: каждая следующая проверяет то, что осталось после предыдущей.
Режим сокета и число соединений.
systemctl show myapp.socket -p Accept -p MaxConnections -p NConnectionsСколько экземпляров службы создано.
systemctl list-units "myapp@*" --no-legend | headЖурнал экземпляров.
journalctl -u "myapp@*" -n 20 --no-pagerРешение
Решение своё для каждой причины. Сначала определите, какая из них ваша, — иначе правки наложатся друг на друга.
- Почему происходит
- При этом режиме systemd создаёт экземпляр шаблонной службы на каждое соединение. Для нагруженной службы это огромные накладные расходы.
- Как проверить
-
Посмотрите режим сокета и число экземпляров.
systemctl show myapp.socket -p Accept systemctl list-units "myapp@*" --no-legend | wc -l
- Как исправить
-
Для нагруженных служб выбирайте передачу слушающего сокета: служба запускается один раз и обслуживает все соединения сама.
[Socket] ListenStream=8080 Accept=no
- Почему происходит
- Предел одновременных соединений ограничивает число экземпляров. При его достижении новые соединения отвергаются.
- Как проверить
-
Посмотрите предел и текущее число.
systemctl show myapp.socket -p MaxConnections -p NConnections
- Как исправить
- Поднимите предел, если режим по экземпляру на соединение выбран осознанно, либо перейдите на передачу слушающего сокета.
- Почему происходит
- В этом режиме нужна шаблонная служба, получающая соединение на стандартном вводе. Обычная служба так работать не будет.
- Как проверить
-
Посмотрите, есть ли шаблонная служба.
systemctl cat "myapp@.service" 2>/dev/null | head -12
- Как исправить
- Напишите шаблонную службу, читающую соединение со стандартного ввода, либо откажитесь от этого режима.
Пример вывода
На каждое соединение создан отдельный экземпляр. Пример показательный: он собран на тестовой машине специально для этой страницы, а не взят из чужого журнала.
systemd[1]: Created slice system-myapp.slice.
systemd[1]: Started myapp@1-192.0.2.10:8080-192.0.2.55:41522.service.
systemd[1]: Started myapp@2-192.0.2.10:8080-192.0.2.55:41523.service.
systemd[1]: myapp.socket: Too many incoming connections (128), dropping connection.
Связанные ошибки
- Сокет отклоняет соединения: достигнут MaxConnections При Accept=yes число одновременных экземпляров ограничено. Как поднять предел и когда режим по соединению не подходит.
- Служба не получает сокет при сокет-активации Сокет активен, служба запускается, но не находит переданный дескриптор. Разбор: Accept, FileDescriptorName, соответствие имён.
- Сокет отключён: сработало ограничение частоты активаций Сообщение о слишком частых срабатываниях сокета: служба падает сразу после запуска и снова вызывается соединением.
- Сокет с ReusePort: соединения распределяются неравномерно Несколько экземпляров службы на одном порту: как работает ReusePort и почему нагрузка неравномерна.
- Bad file descriptor в журнале службы Ошибка 9 при операции с файлом: дескриптор закрыт или не тот. Разбор для служб и сокет-активации.
- File exists при создании файла или сокета Ошибка 17: объект уже существует. Разбор для сокетов, файлов блокировки и каталогов службы.
- Gunicorn: WORKER TIMEOUT в журнале службы Рабочие процессы Gunicorn убиваются по таймауту: медленные запросы, блокирующие вызовы, неверный тип обработчика.
- MySQL: Can't connect to local server through socket Клиент не находит unix-сокет MySQL: сервер не запущен, путь к сокету другой, каталог в /run не создан.
Источники
-
systemd.socket(5)
Accept=, MaxConnections= и шаблонные службы для соединений. -
Воспроизведено на тестовой машине, systemd 255 (Ubuntu 24.04)
Проверено на systemd 255: в режиме приёма создаётся экземпляр на соединение.