У меня есть парк серверов Java Vertx за балансировщиком нагрузки, который обрабатывает пиковый трафик. В одну минуту он может обрабатывать 150 000 об / мин, в следующую минуту он может обрабатывать 2 мм об / мин, а затем сразу же снова снижается до 150 000 об / мин. Я обнаружил, что во время этих всплесков весь парк может перестать отвечать на запросы в течение нескольких минут и обрывать соединения, в то время как давление процессора и памяти на любой из блоков едва достигает 50% использования.
Чтобы проверить, что именно вызывает сбой, я настраиваю один тестовый сервер, который соответствует спецификациям одного в моем производственном парке, чтобы посмотреть, сколько я могу бросить на него, прежде чем он сработает. Мой тест включает использование 10 других машин, каждая из которых открывает 500 https-соединений с сервером и отправляет 1-миллиметровые запросы размером около 2 КБ на полезную нагрузку запроса. Это составляет 5 тыс. Одновременных открытых подключений, отправляющих в общей сложности 10 мм запросов, что составляет примерно 20 ГБ данных.
После открытия соединений я могу отправлять около 700 тысяч запросов в минуту. Я отслеживаю доступность серверов, просто отправляя запрос в конечную точку работоспособности и записывая время ответа. Время отклика быстрое, десятки миллисекунд. Я доволен этими результатами.
Но прежде чем начнется поток данных, эти 10 машин должны сначала установить 5 000 подключений. В это время сервер не отвечает и может даже выйти из строя, когда я пытаюсь проверить конечную точку работоспособности. Я считаю, что именно это вызывает перебои в работе моего производственного парка - внезапное увеличение количества новых подключений. После того, как соединения установлены, у сервера нет проблем с обработкой всех входящих данных.
Я обновил nofile ulimit, net.core.netdev_max_backlog, net.ipv4.tcp_max_syn_backlog и net.core.somaxconn, но он по-прежнему зависает при получении пакета из 5 тыс. Новых запросов на соединение с интервалом в несколько секунд.
Что я могу сделать, чтобы быстрее установить новые связи?
Редактировать:
Фактический сервер работает в контейнере докера. Мои сетевые настройки не применяются к контейнеру. Собираюсь попробовать это в следующий раз и посмотреть, имеет ли это значение.
Редактировать Редактировать:
Все дело в SSL. Создание такого количества соединений, которые быстро выполняются через простой HTTP, практически мгновенно. Итак, мне нужно выяснить, как быстрее устанавливать TLS-соединения.
Редактировать Редактировать Редактировать:
Я обнаружил, что узким местом был собственный ssl-обработчик безопасности Java. Переход на netty-tcnative
(также известный как собственный OpenSSL) в значительной степени решил мою проблему с HTTPS.
Спасибо @MichaelHampton за вашу помощь.
Я нашел решение своей проблемы, и, надеюсь, это может помочь другим (особенно, если вы используете Java).
Я слышал много предложений просто увеличить nofiles
чтобы разрешить больше подключений, но я хотел бы начать с повторения, что проблема не в том, что сервер не может устанавливать больше подключений, а в том, что он не может устанавливать подключения достаточно быстро и разрывать подключения.
Моя первая попытка решить эту проблему заключалась в увеличении очереди подключений через net.ipv4.tcp_max_syn_backlog
, net.core.somaxconn
и снова в конфигурации сервера приложения, где это необходимо. Для vertx это server.setAcceptBacklog(...);
. Это привело к принятию большего количества соединений в очереди, но не ускорило установление соединений. С точки зрения подключающегося клиента, они больше не сбрасывали соединения из-за переполнения, просто установление соединений занимало гораздо больше времени. По этой причине увеличение очереди подключений не было реальным решением и просто заменяло одну проблему другой.
Пытаясь сузить круг проблем в процессе подключения, я попробовал те же тесты с HTTP вместо HTTPS и обнаружил, что проблема полностью исчезла. Моя конкретная проблема была с самим TLS-рукопожатием и способностью серверов удовлетворить его.
Еще немного покопавшись в моем собственном приложении, я обнаружил, что замена стандартного SSLHandler Javas на собственный (OpenSSL) значительно увеличила скорость подключения через HTTPS.
Вот изменения, которые я внес для своего конкретного приложения (с использованием Vertx 3.9.1).
<!-- https://mvnrepository.com/artifact/io.netty/netty-tcnative -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId>
<version>2.0.31.Final</version>
<classifier>osx-x86_64</classifier>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-tcnative -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId>
<version>2.0.31.Final</version>
<classifier>linux-x86_64-fedora</classifier>
<scope>compile</scope>
</dependency>
Первая зависимость - это проверка osx во время выполнения. Второй - для centos linux при компиляции. linux-x86_64
также доступен для других вкусов. Я пытался использовать boringssl
так как openssl
не поддерживает ALPN
но через много часов я не смог заставить его работать, поэтому я решил пока жить без http2. Поскольку большинство соединений отправляют только 1-2 небольших запроса перед отключением, это действительно не проблема для меня. Если бы вы могли использовать boringssl
вместо этого это, вероятно, предпочтительнее.
RUN yum -y install openssl
RUN yum -y install apr
httpServerOptions.setOpenSslEngineOptions(new OpenSSLEngineOptions());
io.netty.handler.ssl.openssl.useTasks=true
вариант для Java. Это указывает обработчику ssl использовать задачи при обработке запросов, чтобы не блокировать.java -Dio.netty.handler.ssl.openssl.useTasks=true -jar /app/application.jar
После этих изменений я могу устанавливать соединения намного быстрее с меньшими накладными расходами. То, что раньше занимало десятки секунд и приводило к частому сбросу соединения, теперь занимает 1-2 секунды без сброса. Могло быть лучше, но это большое улучшение по сравнению с тем, где я был.
Хорошее исправление !.
Так что, похоже, это уровень SSL, он определенно должен выполнять гораздо больше обработки с точки зрения сетевых рукопожатий и криптографических преобразований, требующих ресурсов. Если ваш SSL не может переложить часть обработки на оборудование, SSL определенно может увеличить нагрузку на ваши серверы, и, как вы узнали, не все библиотеки SSL созданы равными!
Эти проблемы - отличный кандидат на использование обратного прокси-сервера переднего плана. В идеале это может быть размещено перед вашим приложением и обрабатывать все SSL-соединения с клиентами, а затем выполнять http для вашей серверной части.
Вашему исходному приложению нужно сделать немного меньше, поскольку ваш обратный прокси-сервер переднего плана может поглотить всю работу SSL и управление TCP-соединением.
Apache и NGNIX могут это сделать, и у них есть несколько вариантов для балансировки нагрузки этих подключений к наименее загруженному внутреннему серверу.
Вы обнаружите, что NGNIX может выполнять завершение SSL намного быстрее, чем Java, и даже если Java может, вы распределяете обработку управления подключением между машинами, тем самым уменьшая нагрузку (память / cpu / disk io) на ваш внутренний сервер. Вы получаете побочный эффект упрощения конфигурации серверной части.
Обратной стороной является использование http между вашим прокси и приложениями, что в некоторых сверхбезопасных средах нежелательно.
Удачи!