Прим. перев.: автор этой статьи — engineering manager из Испании, работающий в цифровой торговой площадке Adevinta, представленной в 16 странах, — делится своими наблюдениями о частых проблемах, которые он встречал у разработчиков микросервисов. Об этих вызовах стоит знать заранее, чтобы не столкнуться с ними тогда, когда их решение может оказаться слишком затратным.

Когда пост Мартина Фаулера о микросервисах вышел в 2014 году, команды, в которых я работал, уже занимались SOA-приложениями. Эта статья и последующий хайп коснулись почти каждой команды разработчиков в мире. Стек Open Source-софта от Netflix был самым крутым в то время, поскольку позволял инженерам по всему миру перенимать опыт Netflix в распределенных системах. Если мы взглянем на работу разработчиков программного обеспечения сегодня, более шести лет спустя, большая её часть касается архитектуры микросервисов.

Разработка на волне хайпа

В начале 2010-х годов многие организации столкнулись с проблемами, связанными с циклом разработки ПО. Люди, работавшие вместе с другими 50, 100 или 200 специалистами, испытывали трудности с окружениями для разработки, тяжелыми процессами QA и программируемым развёртыванием. Хотя книга Мартина Фаулера «Непрерывная доставка» (Continuous Delivery) и пролила свет на многие спорные аспекты организации таких команд, они начали понимать, что их же собственные величественные программные монолиты и создают им многочисленные организационные проблемы. Таким образом, микросервисы оказались весьма привлекательными для инженеров-программистов. Гораздо легче сразу начинать разработку на принципах непрерывной интеграции и развертывания (CI/CD), чем внедрять их в уже большой проект.

И команды начали разворачивать по три, десять, сто микросервисов. Большинство из них использовали JSON over HTTP — кто-то мог бы сказать, что RESTful — API для удаленных вызовов между компонентами. Люди были хорошо знакомы с протоколом НТТР, и это казалось относительно простым способом разделить программный монолит на более мелкие составляющие. С этого момента любая команда уже могла начать деплоить код в production менее чем за 15 минут. Больше никаких «Блииин, команда А сломала CI-пайплайн, и я не могу задеплоить свой код» — и это было круто!

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

Реальность наносит ответный удар

Значительные изменения в архитектуре не проходят бесследно. Специалисты начали понимать, что совместное использование базы данных стало единой точкой отказа. Затем они осознали, что разделение функций приложения на сервисы создало целый новый мир, в котором конечная согласованность получила значимую роль. Как насчет того, что сервис, с которого вы получаете данные, не работает?

Количество вопросов и проблем начало накапливаться. Обещания о быстром темпе разработки обернулись поиском багов, непредвиденными ситуациями, проблемами согласованности данных и т.д. Другая проблема заключалась в том, что программистам требовались централизованные логи и средства мониторинга, которые охватили бы десятки сервисов, позволив обнаруживать и исправлять эти дефекты.

Проблема #1: слишком маленькие сервисы

Возможность плодить новые сервисы каждый день позволила раскрыть креативность разработчиков в полной мере. Новая фича? Бам!.. давай-ка запустим новый сервис! И внезапно команды из 20 программистов начали поддерживать по полсотни сервисов. Это больше, чем один сервис на человека!

Проблема с кодом, как правило, в том, что он устаревает. Поддержка каждого сервиса требует вложений. Представьте себе внедрение обновления библиотеки во всём вашем «ассортименте» сервисов. Представьте себе, что эти сервисы были начаты в разные отрезки времени, с разными архитектурами и с некоторым хитросплетением между бизнес-логикой и используемыми фреймворками. Это же с ума сойти! Конечно, есть способы решения этих проблем. Большинство из них не были доступны в те дни, а другие стоили многих дней работы инженеров.

Еще об одном странном явлении я узнал, когда услышал от кого-то, что развертывание новой фичи в сервисе А также нуждается в деплое — в то же время — в сервисе B. Или когда люди начали писать сервисы для генерации CSV. Зачем кому-то вводить дополнительные сетевые хопы для создания всемирно известного формата файлов? Кто будет это поддерживать?

Некоторые команды, похоже, страдали сервиситом (от слова «цервицит» — прим. пер.). Что еще хуже, это вызывало много разногласий во время разработки. Нельзя было просто так заглянуть в проект в их IDE и понять что-то. Нужно было открыть сразу несколько проектов одновременно, чтобы хоть как-то разобраться во всей этой каше.

Проблема #2: окружения для разработки

Я уже сбился со счёта, сколько раз ко мне подходили и говорили:

Эй, João. Есть минутка? Нам нужно пофиксить наши окружения для разработки! Люди постоянно на них жалуются, и ничего не работает!

Эта проблема затрагивала различные аспекты. Мобильные разработчики не могли разрабатывать фичу до её появления в окружении для разработки. Или у бэкенд-разработчиков не получалось опробовать свой сервис, не нарушив никаких бизнес-процессов. Также было проблематично, если кто-то хотел протестировать весь рабочий цикл в мобильном приложении перед production.

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

  1. Сколько стоит запустить 200 сервисов у облачного провайдера? Справитесь с этим? Сможете ли также развернуть инфраструктуру, необходимую для их работы?

  2. Каких временных затрат всё это стоит? Что будет, если на момент начала разработки мобильным программистом новой фичи есть набор сервисов в одной версии, а когда он завершает работу над ней, уже есть десяток других (новых) версий, развернутых в production?

  3. Что насчет тестовых данных? У вас есть тестовые данные для всех сервисов? Согласованы ли они все между собой — так, чтобы пользователи и другие объекты совпадали?

  4. Если вы разрабатываете мультитенантное приложение для нескольких регионов, как насчет флагов конфигурации и функций? Как поддерживать согласованность с production? Что, если значения по умолчанию изменятся?

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

Проблема #З: end-to-end-тестирование

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

С микросервисами, даже если мы сможем решить все вышеуказанные проблемы настройкой окружения, уже точно не скажешь, что система работает. В лучшем случае мы можем зафиксировать, что система с конкретными версиями запущенных сервисов и данной конфигурацией работает в конкретный момент времени. Это огромная разница!

Было крайне трудно убедить людей, что у нас не может быть больше двух таких тестов. И что недостаточно запустить их в потоке непрерывной интеграции. Они должны работать непрерывно. И они должны запускаться перед production и выдавать соответствующие предупреждения. Я много раз делился статьей Cindy Sridharan «Тестирование в production, безопасный способ» (Testing in production, the safe way), чтобы люди поняли мою точку зрения.

Проблема #4: огромные общие базы данных

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

Помимо того, что это очевидная единая точка отказа, разрушающая некоторые из принципов SOA, есть и другие минусы. Вы создаете пользователя для каждого сервиса? Имеется ли у вас такое тонкое разграничение доступа, чтобы сервис А мог читать или записывать только с конкретных таблиц? Что, если кто-то непреднамеренно удалит индекс? Откуда мы знаем, сколько сервисов используют разные таблицы? Что насчёт масштабирования?

Распутывание всего этого само по себе становится совершенно новой проблемой. И процесс нетривиален в техническом смысле, учитывая, что базы данных имеют тенденцию переживать программное обеспечение. Решение проблемы с помощью репликации данных — будь то Kafka, AWS DMS или что-либо еще — приведет к необходимости для ваших команд разработчиков понимать специфику базы данных и как поступать с дублирующимися событиями, и так далее.

Проблема #5: API gateways

API gateways — типичный паттерн в сервис-ориентированной архитектуре. Они помогают отделить бэкенд- от фронтенд-консьюмера. Они также полезны, когда дело доходит до реализации агрегирования endpoint’ов, ограничения скорости или аутентификации в вашей системе. В последнее время индустрия склоняется к архитектуре backend-for-frontend, где эти шлюзы разворачиваются для каждого единственного фронтенд-консьюмера — iOS, Android, web или десктопных приложений, — что позволяет развивать их независимо друг от друга.

Как это часто бывает, у такого подхода со временем появляются новые, нестандартные варианты использования. Иногда это небольшой хак, чтобы сделать мобильное приложение обратно совместимым. И вот внезапно ваш «API-шлюз» становится единой точкой отказа — потому что людям проще обработать аутентификацию в одном месте — и с некоторой неожиданной бизнес-логикой внутри него. Вместо монолита, забирающего весь трафик, теперь у вас самодельный Spring Boot-сервис, получающий весь трафик за него!

Что может пойти не так? Инженеры быстро осознают, что это ошибка, но, поскольку сделано множество кастомизаций, иногда у них не получается воспользоваться хорошей (stateless, легко масштабируемой) заменой.

Причина бед API-шлюзов появляется тогда, когда они задействуют endpoint’ы, которые не разбиты на страницы или возвращают массивные ответы. Или когда вы создаете агрегацию без fallback-механизмов, а один единственный вызов API уничтожает ваш шлюз.

Проблема #6: время ожидания, повторные попытки и устойчивость

Распределенные системы постоянно находятся в режиме частичного отказа. Что происходит, когда сервис А не может связаться с сервисом В? Мы можем повторить запрос, верно? Но это быстро приводит к неожиданным последствиям.

Я видел, как некоторые команды использовали circuit breaker’ы, а затем увеличивали время ожидания НТТР-запроса к downstream’у сервиса. Хотя это и может быть нормальным способом выиграть время для исправления проблемы, но также создает эффекты второго порядка. Теперь все запросы, которые отменял circuit breaker, потому что они слишком долгие, «висят» больше времени. В случае увеличения трафика, все больше и больше запросов будет поставлено в очередь, что приведет к более плохой ситуации, чем та, которую вы хотели исправить.

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

Хитрость в том, что по завершении восстановления после сбоя сложно понять, что не все из этих сбоев одинаковы. Мы можем ожидать, что потребитель в некоторых случаях будет идемпотентным. Но это означает, что нужно проактивно решать, что делать в каждом из возможных сценариев. Идемпотентен ли потребитель? Можно повторить запрос? Я видел, как многие инженеры игнорировали сбои, потому что это «крайний случай», впоследствии осознавая, что у них масштабная проблема целостности данных.

Повторные попытки еще хитрее, даже если вы настроили fallback-механизмы. Представьте, что у вас есть 5 миллионов пользователей в мобильном приложении и что шина сообщений, которая обновляет настройки пользователей, перестала работать на некоторое время. Вы установили fallback-механизм для этого случая, а он в свою очередь обращается к сервису с настройками пользователей через НТТР API. Думаю, понятно, к чему я клоню…

А теперь этот сервис внезапно получил огромный всплеск трафика и, вероятно, не сможет справиться с ним. Что ещё хуже, сервис может получать все эти новые запросы, но если механизм повторных попыток не реализует экспоненциальное откладывание и джиттер, вы можете столкнуться с распределенным отказом в обслуживании (DDoS) из-за мобильных приложений.

Зная обо всех этих ужасах, вы все еще любите распределенные системы?

А что, если я скажу, что написал только о малой части всех бед, с которыми столкнулся?.. Распределенные системы трудно понять, и в последнее время большинству разработчиков регулярно приходится иметь с ними дело.

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

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

P.S. от переводчика

Читайте также в нашем блоге:

Комментарии (23)


  1. maslyaev
    24.08.2021 12:31
    +14

    HDD это не только hard disk drive, но и hype driven development.


  1. jetcar
    24.08.2021 13:27
    +3

    Ну так это были ожидаемые проблемы. Даже если не переписывать, а писать с нуля ситуация лучше не становиться, за всё надо платить. Стало легче делать одно, сложность перешла на другой уровень, теперь до написания кода надо подумать как оно впишется в кучу микросервисов, ну и стоимость железа возрасла, но программистов можно брать дешевле и больше, ведь мелкие куски делать много уметь не надо, делай по готовому примеру и паблиши.


  1. iwannabeacoder
    24.08.2021 13:56
    +1

    >Зная обо всех этих ужасах, вы все еще любите распределенные системы?

    Только в том случае, когда распределенная система имеет преимущества. К примеру, 100% требуется масштабирование, но в момент разработки еще не совсем понятно где именно. Тогда используются всякие кубернетесы или серверлесс код в облаках.


    1. maslyaev
      24.08.2021 15:11
      +9

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


      1. gandjustas
        25.08.2021 00:30
        +2

        Тут наверное речь о масштабировании процесса разработки, а не самой системы.


    1. gandjustas
      25.08.2021 00:32
      +1

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

      Те же соцсети пишутся огромными коллективами и испытывают огромные нагрузки. Но как-то обходились 10 лет без микросервисов.

      Почему сейчас все считают что им нужны микросервисы?


      1. avengerweb
        25.08.2021 06:04

        Стало много языков и фреймворков. Раньше в команды набирали по знанию технологии/языка к примеру. Сейчас это практически не реально, кто то гуру Java, кто то попивает смузи и пишет бэк на JS или Go, каждый второй знает питон (но он же медленный), кто то влюблён в ruby, а кому то подуше больше php, из какого то интерпрайза сбежала группа C#’перов на вольные берега стартапов, эпплбой сказал что корзина будет на swift, всех этих ребят собеседует бородатый Cишник. Можно нанять всех и заставить писать на чем то одном, но сколько денег на это потребуется? Сильно дороже чем Кластер с кубером и пара девопсов.


        1. gandjustas
          25.08.2021 07:51

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


  1. caballero
    24.08.2021 17:18
    +6

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

    А вообще микросервисы это такая же крайность как и монолит.

    Приложение дллжно быть просто модульным. С одной стороны можно отдельно разрабытывать и тестировать модули с другой стороны в целом это все же одно приложение а не десятки


    1. shurup
      24.08.2021 17:20

      О, про крайности и модули у нас как раз недавно был другой перевод.


    1. gandjustas
      25.08.2021 00:35
      +2

      Не "зачастую", а просто "всегда". Вертикальное масштабирование ВСЕГДА дешевле горизонтального, пока оно вообще возможно.


  1. nick1612
    24.08.2021 18:10
    +4

    С микросервисами как с шардингом - в большинстве случаев нужно избегать любой ценой и внедрять только в местах, где других вариантов нет. А еще перестать читать Фаулера, Мартина и Бека, ибо они только портят программистов)


    1. mvv
      24.08.2021 23:56
      +3

      Фаулеров читать приятнее, чем Кнутов: и не нужно уметь глубоко копать, и тут же оправдание (что ж я не копаю, а розовые сопельки про паттерны и мифические человеко-месяцы размазываю) для себя: "Я ж архитектор, мне систему понимать надо, а кодеров, понимающих, как и когда нужны красно-черные деревья, мы наймем".


      1. nick1612
        25.08.2021 08:56

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


        1. Murtagy
          25.08.2021 10:28

          Хоть кто-то вообще видел эти стереотипы в живую?
          А вообще практически в любой области, когда ты начинаешь руководить или планировать над группой людей или процессов - ты уже перестаешь непосредственно работать. Условно: главный врач больницы может не быть врачом


          1. nick1612
            25.08.2021 11:47
            +1

            Я лично видел как даже изначально неплохие программисты, которые после повышения отстранялись от конкретных деталей работы системы и начинали мыслить и проектировать в шаблонах/диаграмках, через некоторое время доходили до абсурда. Намного печальнее ситуация с начинающими архитекторами начитавшимися Фаулера. После этого оказывается, что проект должен быть platform agnostic, который при необходимости можно легко перевести с Azure на AWS и поменять Mysql на MongoDb настройкой в конфиге. Чем меньше человек начинает разбираться в деталях, тем больше сложные вещи ему начинают казаться простыми, а невозможное возможным.


  1. Interreto
    25.08.2021 02:50
    +1

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


  1. Throwable
    25.08.2021 09:18
    +1

    На волне всеобщего хайпа микросервисы стали уже своего рода карго-культом в IT.

    Автор вскользь упомянул проблему с непротиворечивости данных. В монолитной архитектуре у нас есть контекст выполнения, который "из коробки" привязывается к acid транзакциям. При возникновении любой ошибки откат производится автоматически. В микросервисной архитектуре приходится городить самопальные велосипеды, ввиде шаблона "сага", которые легко заметить, не решают проблему от слова совсем. В большинстве же случаев acid и консистентность данных ни кого по сути уже не заботит.


  1. anonymous
    00.00.0000 00:00


  1. JPEG
    28.08.2021 08:34

    Как раньше бездумно хвалили микросервисы, так теперь бездумно ругают.


  1. DioNNiS
    29.08.2021 08:15
    +1

    "All problems in computer science can be solved by another level of indirection" (the "fundamental theorem of software engineering"). "...except for the problem of too many layers of indirection."


  1. YuryB
    31.08.2021 17:39

    была тут статья пару лет назад с названием типа "вы не гугл", так вот, если вы не гугл, то вам не нужны все эти микросервисы, супер пупер базы и т.д. :) конечно, наверно каждый думает, что лучше всё делать по крутому сразу, когда-нибудь клиентов будет невпроворот, и что тогда - переписывать всё? но как показывает практика и железо будет другое и оптимизация будет за счёт иных вещей, зато не будет всех этих инородных и искусственных проблем


  1. AlexunKo
    06.09.2021 16:10
    +2

    Пришла такая мысль - кто не умеет делать монолит тому нельзя разрешать делать микросервисы.