Громоздкие веб-запросы: часть 1

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

Контекст

Недавно я захотел изучить технологии веб-сервисов. Я решил провести это исследование путем моделирования и обработки огромного количества веб-запросов.

В рамках этого упражнения одной из задач было:

Реализуйте веб-клиент, который принимает URL-адрес и положительное целое число N в качестве аргументов командной строки и выдает N одновременных HTTP-запросов GET к URL-адресу.

Клиент также сообщает о затраченном времени (задержке) для каждого HTTP-запроса GET на URL-адрес U вместе с разбивкой того, сколько из N запросов выполнено / не выполнено.

Выбор технологий

Я выбрал четыре технологии для реализации четырех вариантов клиента.

На этот выбор повлияли две общие причины:

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

Помимо этого, на выбор повлияло несколько незначительных причин, специфичных для технологий.

  1. Erlang: Я не использовал Erlang и хотел попробовать.
  2. Elixir + HTTPoison: после того, как я построил клиент на Erlang с использованием его встроенных библиотек, у меня возник соблазн посмотреть, будет ли использование Elixir отличаться от использования Erlang с точки зрения опыта разработки и производительности. Выбор HTTPoison был довольно произвольным.
  3. Go: Я использовал Go в прошлом, и я хотел сделать с ним больше.
  4. Kotlin + Vert.x: я использовал обе эти технологии в прошлом, и я хотел иметь клиент, основанный на широко используемой технологии, то есть JVM.

Варианты реализации

Я хотел, чтобы исходные артефакты и процесс использования исходных артефактов были простыми и легкими, преодолевая необходимые «препятствия» при использовании технологии. Итак, учитывая простоту клиента, я подошел к разработке вариантов как к хакерскому упражнению с минимальным использованием методов разработки программного обеспечения вне кода. В частности, если вышеуказанные условия не были нарушены,

  1. Вариант был закодирован как один файл без соблюдения правил сообщества, например пакетов, расположения папок.
    Например, клиент Go был закодирован в основной пакет, который хранился в папка верхнего уровня. Клиент Kotlin аналогичным образом не был встроен ни в пакет, ни в папки.
  2. Если вариант можно было закодировать как сценарий и выполнить в скомпилированном режиме, то он был закодирован как сценарий без использования сценариев сборки.
    Например, клиент Erlang был закодирован как отдельный файл сценария, который можно было скомпилировать и выполнить с помощью инструмента Erlang escript. Точно так же клиент Kotlin был написан как отдельный файл сценария, который можно было скомпилировать и запустить с помощью KScript.
  3. Если для варианта требовались зависимости, то использовался инструмент для получения зависимостей.
    Например, поскольку клиент Elixir зависел от библиотеки HTTPoison, использовался инструмент сборки Mix. Зависимость была указана через файл mix.exs (build), и Mix использовался для загрузки и компиляции зависимости перед сборкой клиента.

Наблюдения

Язык

Впервые писать на Erlang было интересно. Объявление функции в Erlang органично охватывает сопоставление с образцом для разделения регистра на основе значений (в отличие от условных выражений на основе флагов / параметров). Он также поддерживает использование сопоставления с образцом для деконструкции (проецирования) составных значений, таких как структуры и списки, на их компоненты с возможностью выбора только интересующих компонентов. Я считаю, что это одновременно элегантно и интуитивно понятно. Сопоставление с образцом можно использовать с аналогичным эффектом при объявлении функции в Elixir.

Параллелизм

Поскольку и в Erlang, и в Go были встроенные базовые функции, позволяющие легко запускать параллельные вычисления и использовать обмен сообщениями, диспетчеризация одновременных запросов была простой. То же самое относится и к Elixir, поскольку он нацелен на виртуальную машину Erlang.

Что касается варианта Kotlin на основе Vert.x, это было еще проще, поскольку его API принимает обратный вызов, который будет вызываться после завершения запроса.

Библиотеки и документация

Встроенная библиотека Erlang была достаточно самодостаточной для этого упражнения. В итоге я использовал основную библиотеку httpc. Поскольку я впервые работал с Erlang, мне потребовалось время, чтобы разобраться со схемой документации Erlang. Хотя дополнительные гиперссылки на термины, используемые в документации, могут облегчить навигацию и поиск, мне очень понравилась структура документации Erlang, поскольку она имитирует структуру кода / API и данных в Erlang.

Хотя я мог бы использовать библиотеку httpc из Erlang с Elixir, я решил не использовать ее только ради изучения библиотеки Elixir. Хотя документация для HTTPoison была хорошей, я чувствовал, что включение полного примера было бы более полезным. Это ограничение могло быть связано с тем, что я был новичком в Elixir. В целом мне понравилась идея разместить документацию по библиотекам Elixir (шестнадцатеричные пакеты) в центральном месте (https://hexdocs.pm).

Как и Erlang, встроенная библиотека Go была достаточно самодостаточной для этого упражнения. В итоге я использовал основной пакет http. Мне очень понравился раздел документации "Примеры", поскольку он помогает быстро оценить возможности пакета и способы их объединения.

Хотя Vert.x был самодостаточным с точки зрения возможностей, я обнаружил, что его документация (как и другие библиотеки в экосистеме JVM) ограничивает. В частности, хотя документация о том, «как использовать возможность X» была хорошей, она казалась «отделенной» от документации задействованного API. Кроме того, учитывая количество различных API и модулей (например, ядро ​​и сеть), определение соответствующего API при его поиске в документации по API на основе имен было непростым и непростым делом. Полагаю, это больше связано с документацией в стиле javadoc. [Если вам интересно, сравните, как httpc пакет и httpc::request методы документированы в Erlang, с тем, как Web модуль и sendXXX методы документированы в Vert.x.]

Независимо от языка, я был приятно удивлен доступной поддержкой для простой работы с веб-сервисами. Было много вариантов с точки зрения библиотек, и большинством из них было довольно легко пользоваться. Полагаю, это могло быть причиной бума микросервисов :)

Инструменты

Хотя инструменты для создания клиента были простыми в Go (например, go build), мне очень понравилась поддержка в Kotlin (через KScript) и Erlang (через escript) для выполнения скриптов в скомпилированном режиме. KScript поддерживает управление зависимостями с помощью аннотаций. Это устраняет нагрузку на файлы сборки во многих ситуациях, в которых используются сценарии, характеризуемые такими прилагательными, как простой, быстрый и непонятный и недолговечный. Подобной поддержки в escript я не нашел. Итак, если бы я использовал внешние библиотеки в клиенте Erlang, то мой опыт работы с Erlang был бы другим.

Напротив, поскольку клиент Elixir зависел от HTTPoison, я использовал инструмент Mix вместе с файлом «сборки», который фиксировал зависимости. Хотя файл сам по себе не является проблемой, использование Mix требует больше работы, как и любой другой инструмент сборки, например, создание проекта, понимание актуальности различных файлов, связанных со сборкой, внесение соответствующих изменений в эти файлы; подробнее об этом в следующем посте. Я считаю, что в простых ситуациях это ненужное препятствие.

[Если есть способ использовать скрипт для управления зависимостями и выполнения скрипта (например, KScript), то расскажите, пожалуйста, как это сделать.]

Длина кода

С точки зрения LOC, Kotlin был самой короткой реализацией, за ней следовали Erlang, Go и Elixir. Хотя я ожидал, что клиент Go будет длинным, я был удивлен, что клиент Elixir был почти таким же длинным. Я не уверен, связано ли это с тем, как я реализовал клиент Elixir, или с дизайном и экосистемой Elixir.

Сложность кода

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

Исходный код

Код для всех клиентов доступен на GitHub.

Следующий

Мой следующий пост будет о наблюдениях за внедрением веб-сервисов с использованием различных технологий.

Примечания

20 августа - 2019: Обнаружение проблемы HTTP-клиента в Erlang

Во время оценки серверов, использующих клиентов, клиент erlang временами зависал. Хотя реализация обрабатывала успешные и неудачные HTTP-запросы и аварийный отказ порожденных процессов, выдающих HTTP-запросы, похоже, что было условие, которое не было рассмотрено. Благодаря вкладу сообщества Erlang (через Slack) я расширил клиент с помощью универсального обработчика. Интересно, что этот обработчик не запускался во время зависания.

Итак, на данный момент я думаю, что либо библиотека httpc в Erlang в некоторых редких случаях не работает, либо библиотеку необходимо соответствующим образом настроить для обработки ситуаций, когда клиент может выдавать множество одновременных запросов, например, 3200. В любом случае я не буду используйте клиент Erlang для оценки серверов.