Распределенные приложения: типичные проблемы и решения

Небольшая заметка о построении распределённых приложений, как сохранять эффективность маленькой команды несмотря на рост сложности на проекте.

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

Мотивация создания распределённого приложения:

  1. модульность, некоторые части должны быть относительно автономны;
  2. безопасность, приложение должно взаимодействовать как с сервисами из Internet так и с внутренними корпоративными, в связи с чем некоторые его части могут находиться в различных сегментах сети, например в DMZ;
  3. максимально эффективная утилизация ресурсов оборудования из-за серьезных ограничений на цену транзакции, в противном случае создание и использование приложения может терять экономический смысл.
  4. требования определённого уровня отказоустойчивости.

Теперь можно перейти к списку проблем, которые я постараюсь описать в порядке их возникновения и о том, как их можно преодолеть.

Управление конфигурационными параметрами П: Каждый сервис обычно имеет некоторый набор конфигурационных параметров, настраивать каждый по отдельности нудно и требует много времени.

Р: Использовать REST-сервис, который раздаёт конфигурационные параметры другим сервисам и оповещает об их изменении через Message Broker. Сервис-клиент при старте имеет лишь адрес сервиса конфигурации и bearer-токен, с помощью которого идентифицируется клиент и отдаются его параметры. В случае необходимости сервис подписывается на обновление конфигурационных параметров через MB. Для облегчения тестирования и возможности использовать каждый сервис в автономном режиме имеет смысл использовать под одной абстракцией два типа клиента: для использования локальных конфигурационных параметров и для получения конфигурационных параметров с удалённого сервиса.

Логирование

П: Много сервисов — много логов и на разных серверах, также следует помнить, что интенсивное логирование может создать конкуренцию за ресурсы сервера.

Р: Для логов следует организовывать два таргета: централизованный и локальный. В первом случае логи собираются и используются когда инфраструктура функционирует в штатном режиме, во втором - когда проблемы с сетью.

В качестве примера предлагаю рассмотреть следующий вариант из собственной практики: Так как сервисы уже использовали RabbitMQ, для получения нотификаций от других сервисов, было решено использовать его, дописав для него таргет для NLog. Таким образом, мы получаем логи в JSON-формате и отправляем через RabbitMQ. Основным потребителем логов является Logstash c плагином к RabbitMQ, который их индексирует и отправляет в Elasticsearch. Графически схему логирования можно представить следующим образом:

Пример организации логирования в распределённом приложении

Конечно можно было бы обойтись и без RabbitMQ, отправляя логи напрямую в Logstash, но это бы создало две следующие проблемы:

  • Конкуренция за ресурсы между сервисом и Logstash, а он очень прожорлив;
  • Каждый environment приходилось настраивать с учётом необходимости дополнительно разворачивать Logstash и организовывать доступ к Elasticsearch

Тестирование

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

Р: В ситуациях когда невозможно зафиксировать требования и спецификации, юнит-тесты становятся очень хрупкими, а как следствие очень дорогими для бюджета, но при этом не решают проблему комплексно. В таких случаях стоит сфокусироваться на интеграционных тестах, когда можно тестом покрыть use case или вообще бизнес-процесс с конкретным business value, при этом не вдаваясь в детали реализации, (продаём не код, а решение). Решая эту проблему мы отчасти создаём себе две следующие.

Сборка и развёртывание

П: Увеличение количества собираемых приложений, ресурсов, инсталляторов неминуемо влечёт увеличение времени на компиляцию, сборку, развёртывание и тестирование. Особенно если основная статья расходов — фонд заработной платы.

Р: Билды не должны строиться долго, чтобы у инженера не было повода выходить из рабочего контекста. Если проекты становятся слишком большие, имеет смысл их дробить. Чем быстрее будет собран проект - тем быстрее его можно запустить и протестировать, тем больше эффективность инженеров и тестеров. Разработчик должен иметь возможность запустить юнит и интеграционные тесты одной - двумя командами. Continuos Integration и Continuous Delivery решения отличные помощники, но для автоматизации прежде всего стоит думать с позиции расходов времени персоналом и без создания отдельных environment для билд-сервера. Грубо говоря, приложение должно собираться тем же набором инструментов и на компьютере разработчика, и на билд-сервере.

Избегайте мыслей: “Наймём DevOps инженера и сразу заживём хорошо!” Хорошо не заживёте, каждый инженер и тестер должен обладать знанием, что и как собирается, где взять различные версии билдов, где билд может быть развёрнут. Большим плюсом будет возможность разворачивать систему для Nighltly-билда автоматически, либо по потребности, чтобы для всех заинтересованных лиц на проекте была возможность видеть приложение в действии. Для того, чтобы не путаться в билдах имеет смысл использовать semantic versioning.

Программно-аппаратное обеспечение

П: Большие затраты времени на сборку, запуск, развёртывание, невозможно запустить все компоненты приложения, падение эффективности персонала. Р: Не стоит недооценивать значимость программно-аппаратного обеспечения для людей вовлеченных в разработку распределённых приложений. Прежде всего стоит разобраться с тем, какие есть текущие показатели (цифры), что даст апгрейд или дополнительное ПО и как это повлияет на экономические показатели проекта и удовлетворённость персонала. Когда час работы инженера стоит $10–20, а 500GB SSD и 32GB RAM стоят ~$450, которые могут увеличить его производительность хотя бы на 1/4, то подобное вложение может окупить себя уже через месяц - полтора, а потом увеличивать доход из-за сэкономленных человеко-часов. Хорошее вложение в 25% годовых, не правда ли?

Мониторинг

П: Отсутствие информации о состоянии приложения ведёт к недоступности информации для принятия обоснованных решений и фактической потерей контроля над ним.

Р: Мониторинг состояния приложения является отличным элементом оперативного контроля и управления. Позволяет уменьшить время на выявление внештатных ситуаций, уменьшить затраты времени на их устранение, а также даёт возможность анализировать поведение программных агентов и пользователей. В качестве примера можно упомянуть Open Monitoring Distribution (OMD) со следующим набором инструментов: Icinga2, Thruk, Grafana, InfluxDB.

Avatar
Alexander Salimonov
Engineering Manager

Distributed and data-intensive sytems, databases, data processing, cloud computing.