Фреймворк Camel: сравнение компонентов HTTP и AHC

В данной статье производится сравнение работы простейших сервисов реализованных с помощью фреймворка Camel и двух его компонентов: HTTP и AHC. Углубляться в структуру и работу с самим фреймворком не будем, предполагается что читатель уже немного знаком с ним.

Рассматривать будем простой сервис на Camel, который получает запросы от jetty-компонента, обрабатывает их, например, выводит в лог, вызывает другой сервис через http, и обрабатывает ответ от него, например, пишет в лог.

Для тестирования использовались скрипты JMeter для вызова нашего сервиса в соответствии с задуманной частотой и интервалами, а так же небольшой Http-сервис, играющий роль внешнего по отношению к нашему сервису, и выполняющий задержку 5 секунд. Все взаимодействие происходит по локальной петле (127.0.0.1), так что сетевые задержки не учтены, но для сравнительного анализа они и не нужны.

HTTP-компонент

В данном разделе будет рассматриваться стандартный HTTP-компонент для взаимодействия по HTTP. Код простого сервиса:

from("jetty:http://localhost:8080/test")
     .log("receive request body ${body}")
     .removeHeaders("CamelHttp*")
     .to("http://{{another.url}}")
     .log("finish process body ${body}");

Примечание: удаление заголовков, начинающихся на “CamelHttp” необходимо потому, что они выставляются в Jetty-компоненте и могут повлиять на работу Http-компонента.

Для проверки работы данного сервиса запустим скрипт JMeter, который отправляет 25 одновременных запросов.

Samples

Min

Max

Error %

25

5012

7013

20.000%

В результате видим, что 20% или 5 из 25 запросов обработались с ошибкой(Read timed out). Связано это с тем, что у http-компонента по умолчанию установлено ограничение в 20 соединений к одному хосту. Изменяется это ограничение параметром connectionsPerRoute

from("jetty:http://localhost:8080/test")
     .log("receive request body ${body}")
     .removeHeaders("CamelHttp*")
     .to("http://{{another.url}}?connectionsPerRoute=200")
     .log("finish process body ${body}");

После этого исправления все 25 сообщений обрабатываются без ошибок. Но есть еще одно ограничение – это ограничение пула потоков jetty-компонента, по умолчанию 200. Для проверки этого ограничения запустим следующие 4 сценария JMeter:

  1. 200 одновременных запросов

  2. 300 одновременных запросов

  3. 300 запросов равномерно распределенных в течении 5 секунд, с повтором 5 раз

  4. 200 запросов равномерно распределенных в течении 5 секунд, с повтором 5 раз

После запуска 1 сценария в JVM произошел рост потоков до 214 штук, и далее количество потоков не менялось для всех сценариев.

Результаты выполнения тестовых сценариев:

Процент ошибок

200 запросов единовременно

0%

300 запросов единовременно

34.667%

300 запросов с повтором 5 раз

71.733%

200 запросов с повтором 5 раз

0%

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

Второй сценарий с 300 одновременными запросами демонстрирует резкое превышение возможностей настроенного сервиса, 200 запросов обрабатываются потоками jetty-компонента, а остальные 100 дожидаются в пуле задач jetty, и в результате обрабатываются не 5 секунд, а 10. Соответственно 34% ошибок – это примерно эти 100 запросов.

Третий сценарий демонстрирует продолжительную работу сервиса по нагрузкой, превышающей его возможности – 300 запросов равномерно распределяются в 5 секундный интервал, и каждый из них повторяется 5 раз, т.е. каждую секунду в сервис поступает 60 запросов, а так как сервис не может обрабатывать более 200 запросов в один момент времени лишние запросы хранятся в пуле задач и для клиента обрабатываются дольше положенных 5 секунд, в результате клиенты отваливаются по таймауту.

Четвертый сценарий аналогичен третьему, с тем исключением, что нагрузка допустимая и в сервис не приходит больше запросов, чем он может обработать, очередь задач jetty-компонента пустая.

Для того чтобы увеличить количество одновременно обрабатываемых запросов, можно увеличить пул потоков jetty-компонента, однако следует помнить что каждый поток в JVM по умолчанию потребляет 1 МБ ОЗУ для хранения стэка, и бесконечно плодить потоки в современном мире Docker-контейнеров и микросервисов невозможно, лимиты по памяти не позволят это сделать. Лучше рассмотрим другой подход в следующем разделе.

AHC-компонент

AHC-компонент – это еще один компонент фреймворка Camel для взаимодействия по HTTP. Основан он на библиотеке AsyncHttpClient, позволяющей реализовывать асинхронное (реактивное) взаимодействие. За счет этого компонента попытаемся добиться реактивной работы сервиса – в обычном синхронном режиме с http-компонентом наши потоки просто стояли и ждали, пока внешний сервис нам ответ, т.е. в пустую тратили 5 секунд времени. В асинхронном же компоненте они сразу после отправки запроса освобождаются и готовы принимать новые запросы, а ответы на эти запросы при их получении обрабатываются другим пулом потоков. Изменения в нашем сервисе будут совсем небольшие:

from("jetty:http://localhost:8080/test")
     .log("receive request body ${body}")
     .removeHeaders("CamelHttp*")
     .to("ahc:http://{{another.url}}")
     .log("finish process body ${body}");

Сценарий, в котором 300 запросов запускаются единовременно выполнился без ошибок. Что уже плюс, так как синхронный http-компонент не мог его вообще осилить. Рассмотрим состояние потоков JVM:

Потоков, если сравнить с предыдущим вариантом тоже сравнительно меньше. Рассмотри результаты других сценариев:

Процент ошибок

300 запросов с повтором 5 раз

0%

800 запросов с повтором 5 раз

0%

1200 запросов с повтором 5 раз

1.533%

1600 запросов с повтором 5 раз

15.02%

За счет того, что запросы идут не одновременно, общее количество потоков меньше чем в первом сценарии.

В результате можно сделать выводы, что пропускная способность сервиса выросла в несколько раз, ошибки в сценариях с 1200 и 1600 запросов вероятно связаны с задержкой при получении соединений из пула либо задержкой http-заглушки, либо с чем-то еще, но эта тема для другого исследования.

Возможные проблемы с AHC-компонентом

Если в сервисе используется динамическое создание AHC-эндпоинтов, то это может случайно выстрелить вам ногу. Рассмотрим пример:

from("jetty:http://localhost:8080/test")
     .log("receive request body ${body}")
     .removeHeaders("CamelHttp*") 
     .setHeader("rand", ()->new Random().nextInt(10000) )
     .toD("ahc:http://{{another.url}}?rand=${headers.rand}")
     .log("finish process body ${body}");

После запуска сценария с единовременным стартом 300 запросов состояние потоков в JVM:

Как видим, поток слишком много. Дело в том, что по умолчанию AHC-компонент для каждого эндпоинта создает свою инстанцию объекта AsyncHttpClient, у каждой из которых свой пул соединений и потоков, в результате для каждого запроса создается по 2 потока – один поток ввода/вывода, другой поток-таймер для контроля таймаутов и поддержания соединений в состоянии KeepAlive. Чтобы этого избежать необходимо настроить инстанцию AsyncHttpClient на уровне компонента, которая будет передаваться в эндпоинт при его создании.

AhcComponent ahc = getContext().getComponent("ahc", AhcComponent.class);
ahc.setClient(new DefaultAsyncHttpClient());

После этого создание множества инстанций AsyncHttpClient’a прекратятся.

Let’s block ads! (Why?)

Read More

Recent Posts

VK купила 40% билетной платформы Intickets.ru

VK объявляет о приобретении 40% компании Intickets.ru (Интикетс). Это облачный сервис для контроля и управления продажей билетов на мероприятия. Сумма…

1 день ago

OpenAI готовится запустить поисковую систему на базе ChatGPT

OpenAI готовится запустить собственную поисковую систему на базе ChatGPT. Информацию об этом публикуют западные издания. Ожидается, что новый поисковик может…

1 день ago

Роскомнадзор рекомендовал хостинг-провайдерам ограничить сбор данных с сайтов для иностранных ботов

Центр управления связью общего пользования (ЦМУ ССОП) Роскомнадзора рекомендовал компаниям из реестра провайдеров ограничить доступ поисковых ботов к информации на российских сайтах.…

2 дня ago

Apple возобновила переговоры с OpenAI и Google для интеграции ИИ в iPhone

Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…

1 неделя ago

Российская «дочка» Google подготовила 23 иска к крупнейшим игрокам рекламного рынка

Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…

1 неделя ago

Google завершил обновление основного алгоритма March 2024 Core Update

Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…

1 неделя ago