Проблема Laravel + PHP FPM + MYSQL: “Не удается назначить адрес” под высокой нагрузкой

Вопрос или проблема

У нас есть 3 дроплета Ubuntu от Digital Ocean (*исключая фронтенд, который на nextjs):

БЭКЕНД:
16 vCPU | 32GB RAM

БД MYSQL:
8 vCPU | 16GB RAM

REDIS:
4 vCPU | 32GB RAM

  • Мы используем laravel -> через php-fpm -> за nginx
  • Мы настроили драйверы сессий и вещания на REDIS
  • Мы используем IP-адреса сервера для подключения к MYSQL и REDIS (без TLS)

Мы управляем веб-сайтом, который время от времени получает большое количество пользователей одновременно.

Нагрузка может мгновенно прыгнуть с 0 до 90, но сервер справляется, несмотря на ограничения конфигурации системы, которые мы испытываем на каждом шагу.

После недели отладки мы сделали следующее, решая узкие места и переходя к следующему:

  • Мы начали использовать opcache
  • Мы настроили php-fpm в статическом режиме с максимальным количеством подключений, установленным на 2000, чтобы у нас было 2000 процессов fpm, ожидающих обработки запросов (перезапуск процесса после выполнения 1000 запросов)
  • Мы столкнулись с проблемой подключений рабочих процессов NGINX, когда они были низкими и сбрасывали запросы, так что мы увеличили это значение до 7500
  • Затем мы столкнулись с проблемой лимита nofile и установили его на 50_000
  • Мы достигли лимита MAX_CONNECTIONS в MYSQL и увеличили его с 151 до 2000

После всего этого все, казалось, работало хорошо. Хотя у нас началась другая проблема, связанная с максимальным количеством открытых TCP-подключений на порт, которые сервер использует для подключения к MYSQL и REDIS.

  • Эта проблема вызывала следующую ошибку: “Не удается назначить запрашиваемый адрес”

Мы провели простой стресс-тест, отправив запросы к единой конечной точке, которая считывает данные как из mysql, так и из redis
1000 одновременных запросов, отправленных 50 раз, в общей сложности 50k последовательных запросов

Около отметки 35k запросов запросы начали прерываться с вышеупомянутой ошибкой. При дальнейшей проверке и запуске netstat -anlp | grep :3306 | grep TIME_WAIT -wc во время стресс-теста мы заметили, что число продолжается расти с каждым входящим запросом, быстро достигая 35k, после чего возникает ошибка. Эти соединения TIME_WAIT, похоже, закрываются через 60 секунд. После исследований мы попытались уменьшить это число до 3 секунд с помощью net.ipv4.tcp_fin_timeout = 3 & sysctl -p, но без результата :/ Соединения остаются открытыми на 60 секунд всегда, и это вызывает большой узкий проход под тяжелой нагрузкой.

Что нам делать?

PS: RAM и CPU работали нормально под простым стресс-тестом.

Ответ или решение

Ваша ситуация описывает серьезную проблему с терминами подключения и ограничениями на уровне системы, возникающую под высоким нагрузкой. Ошибка "Cannot assign requested address" обычно связана с исчерпанием доступных адресов для новых TCP-соединений. Вот несколько направлений, которые можно рассмотреть для решения вашей проблемы:

1. Увеличение порта TCP и настройка net.ipv4.ip_local_port_range

Проверьте и увеличьте диапазон локальных портов, чтобы повысить количество доступных адресов. Вы можете изменить этот диапазон следующим образом:

echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range

Этот диапазон позволит использовать больше портов для ваших TCP-соединений. Не забудьте перезагрузить или применить изменения, чтобы они вступили в силу.

2. Увеличение значения net.ipv4.tcp_max_syn_backlog и net.core.somaxconn

Проверьте и увеличьте лимиты на очередь ожидающих соединений, чтобы избежать потери соединений при высокой нагрузке:

echo "2048" > /proc/sys/net/ipv4/tcp_max_syn_backlog
echo "2048" > /proc/sys/net/core/somaxconn

3. Настройка временем ожидания TCP

Вы уже пытались настраивать tcp_fin_timeout, однако, другие параметры могут помочь решить вашу проблему:

  • Попробуйте установить:
echo "5" > /proc/sys/net/ipv4/tcp_fin_timeout
echo "30" > /proc/sys/net/ipv4/tcp_keepalive_time
echo "10" > /proc/sys/net/ipv4/tcp_keepalive_intvl
echo "5" > /proc/sys/net/ipv4/tcp_keepalive_probes

4. Настройка клиентских соединений MySQL и Redis

Сконфигурируйте MySQL и Redis для работы с большим количеством соединений. Для MySQL вы уже увеличили max_connections, но также проверьте и установите параметры wait_timeout и interactive_timeout, чтобы они были установлены на более высокие значения после нагрузки.

5. Использование соединений с пулы

Рассмотрите возможность использования соединений с пулы для управления соединениями с MySQL и Redis. Это позволяет уменьшить количество открываемых соединений и избежать перегрузки, так как соединения могут быть повторно использованы. Для PHP вы можете использовать PDO с опцией ATTR_PERSISTENT.

$pdo = new PDO('mysql:host=localhost;dbname=test', $user, $pass, [
    PDO::ATTR_PERSISTENT => true
]);

6. Рассмотрите нагрузочное тестирование

Используйте инструменты нагрузочного тестирования (например, Apache JMeter или Siege), чтобы симулировать высокие нагрузки и находить bottlenecks в системе.

7. Мониторинг и алертинг

Используйте инструменты мониторинга, такие как Prometheus и Grafana, чтобы отслеживать состояние системы, соединения, использование памяти и другие важные метрики. Это поможет вам вовремя реагировать на любые потенциальные проблемы.

Заключение

Ваше приложение должно быть оптимизировано в соответствии с высоким уровнем нагрузки, и правильная конфигурация системы и программного обеспечения может предоставить необходимую устойчивость. Следуя этим шагам и проводя регулярные тесты, вы сможете избежать проблемы "Cannot assign requested address" и обеспечить стабильную работу вашей системы под нагрузкой.

Оцените материал
Добавить комментарий

Капча загружается...