Распределенные системы. Паттерны проектирования. Обзор книги
Здравствуйте, коллеги. Сегодня мы публикуем перевод очередного обзора с сайта Бена Нейдела — этот сайт наверняка заинтересует вас и в оригинале. На сей раз мы поговорим о книге "Распределенные системы. Паттерны проектирования", которая дополняет вышедшую у нас в начале этого года книгу "Осваиваем Kubernetes" и, в сущности, является аналогом GoF для проектирования распределенных систем.
Приятного чтения.
Всего за выходные я прочел книгу «Распределенные системы. Паттерны проектирования», принадлежащую перу Брендана Бёрнса. Книга мне весьма понравилась, хотя, должен признать, я ожидал встретить в ней немного иной материал. Так, в описании книги контейнеры упоминаются лишь вскользь. При этом, хотя паттерны, описанные в книге, применимы не только при контейнеризации, практически все шаблоны, изложенные здесь, даются именно в контейнерном контексте, а после этого рассматриваются в конвейере развертывания Kubernetes. Это оказалось кстати. Я только начинаю знакомиться с контейнер-ориентированными разработкой и развертыванием, и обсуждение архитектурных паттернов именно с такой «контейнерной» точки зрения было для меня откровением. Такой подход помог мне хорошо сориентироваться в том, как в микросервисном ландшафте правильно разграничиваются мелкие сервисы, в каждом из которых реализована конкретная возможность.
Автор книги Брендан Бёрнс – сооснователь опенсорсного проекта Kubernetes. Поэтому неудивительно, что все его прикладные примеры выстроены вокруг развертывания контейнеров на основе конфигурационных файлов Kubernetes. На момент чтения книги я слегка разбирался в Docker и ничего не знал о Kubernetes. Таким образом, я пытался уяснить «назначение» конфигурационных файлов Kubernetes, просто читая их. Однако, я считаю, что книга будет полезнее, если читатель имеет хотя бы какой-нибудь опыт работы с Kubernetes.
Читая книгу, я не мог избавиться от ассоциаций с работой «Шаблоны интеграции корпоративных приложений» Грегора Хопа и Бобби Вулфа. Многие паттерны, рассматриваемые у Бёрнса, весьма напоминают шаблоны очередей сообщений, обсуждаемые у Хопа и Вулфа. На самом деле, должен отметить, что многие паттерны даже называются в обеих книгах одинаково (например, Scatter/Gather). Это логично, так как тема обеих книг – разбиение сложных монолитных систем на совокупности мелких, плотно подогнанных многоразовых сервисов. Думаю, даже можно утверждать, что Бёрнс излагает конкретные подходы, которые пригодятся при реализации сервисов-Производителей и сервисов-Потребителей, участвующих в функционировании рабочих потоков на основе сообщений, тех самых, что описаны в книге «Шаблоны интеграции корпоративных приложений».
Опять-таки, полагаю, что параллели, прослеживаемые между двумя этими книгами, свидетельствуют о силе паттернов проектирования. Как в том, насколько удачно они ведут нас к проверенным решениям, так и в том, что паттерны проектирования облегчают техническую коммуникацию, поскольку позволяют нам рассуждать на общем языке.
При этом, один из наиболее понравившихся мне подходов, описанных в книге «Распределенные системы. Паттерны проектирования» как раз связан с потреблением очереди сообщений. Бёрнс советует не заставлять рабочий контейнер вытягивать сообщения непосредственно из системы (подобно тому, как это делается в системе Simple Queue Service (SQS) от Amazon), а создавать «посла» (паттерн Ambassador). Такой контейнер-«посол» будет развертываться вместе с рабочим контейнером и предоставлять обобщенный API для манипуляций над очередями. Контейнер Ambassador позволяет абстрагировать детали реализации, связанные с постоянным хранением элементов в очереди сообщений, благодаря чему рабочий контейнер совершенно не зависит ни от каких конкретных технологических решений.
«Посол контейнера-источника рабочей очереди» — всего один пример из сквозной темы, красной нитью проходящей через всю книгу: используйте совокупности небольших контейнеров, чтобы каждый отдельный контейнер мог максимально сосредоточиться на конкретной задаче и получился как можно более многоразовым. Бёрнс исследует эту концепцию на уровне отдельных контейнеров, рассказывая о параметризации контейнеров при помощи аргументов командной строки и переменных окружения. Затем он поднимается на уровень выше, говорит о многоконтейнерных паттернах side-car и ambassador в одноузловом контексте. Наконец, он показывает, как при помощи многоконтейнерных паттернов создать мощную микросервисную архитектуру.
Мне думается, что Бёрнсу удалось отлично сформулировать «мечту» всего микросервисного ландшафта:
Микросервисный подход имеет немало преимуществ, многие из которых связаны с надежностью и гибкостью. Микросервисы делят приложение на небольшие части, каждая из которых отвечает за предоставление определенной услуги. За счет сужения области действия сервисов каждый сервис в состоянии разрабатывать и поддерживать команда, которую можно накормить двумя пиццами.
Уменьшение размера команды также снижает расходы на поддержание ее деятельности.
Кроме того, появление формального интерфейса между микросервисами ослабляет взаимозависимость команд и устанавливает надежный контракт между сервисами. Такой формальный контракт снижает потребность в тесной синхронизации команд, поскольку команда, предоставляющая API, понимает, в каком объеме необходимо обеспечивать совместимость, а команда, потребляющая API, может рассчитывать на стабильное обслуживание, не заботясь о деталях реализации потребляемого сервиса. Такая декомпозиция позволяет командам независимо управлять темпом разработки и графиком выпуска новых версий, что дает им возможность выполнять итерации, улучшая тем самым код сервиса.
Наконец, разделение на микросервисы повышает масштабируемость. Поскольку каждый компонент выделен в отдельный сервис, его можно масштабировать независимо от остальных
(с. 79-80 в русском переводе)
Я говорю о «мечте», поскольку, как признает и сам Бёрнс, сложно проектировать микросервисную систему и проектировать ее архитектуру. А мониторинг и отладка такой системы дается куда сложнее, нежели мониторинг и отладка монолитного аналога. Как раз с этим я легко могу согласиться. По собственному ограниченному опыту работы с микросервисами подтверждаю, что разделенные сервисы быстро становятся сильно связанными, а «надежные контракты» между сервисами быстро превращаются в подвижную мишень, подвергаясь все новым и новым изменениям.
В заключение, я хотел бы коснуться концепции FaaS – «функции как сервисы». Системы вроде Lambda Service от Amazon вызывают у меня смешанные ощущения. В предельно абстрактном смысле такие системы мне нравятся, но я не имею ни малейшего понятия о том, как они проявляют себя в конкретном приложении. Бёрнс затрагивает FaaS в части II о «Паттернах проектирования обслуживающих систем», но, к сожалению, и она не до конца проясняет для меня проблему Faas.
Мне очень понравилось, что Бёрнс рекомендует использовать FaaS для решения лишь некоторого подмножества известных проблем:
Как и в случае с другими инструментами разработки распределенных систем, может возникнуть желание использовать частное решение (например, событийно-ориентированную обработку) в качестве универсального инструмента. Правда в том, что частное решение обычно решает частные задачи. В определенном контексте оно окажется мощным инструментом, но притягивание его за уши для решения общих проблем порождает сложные, хрупкие архитектуры.
(c. 135 в русском переводе)
Причем, большое ему спасибо, что упомянул и сложности, возникающие при использовании FaaS:
Как было сказано в предыдущем разделе, разработка систем с использованием подхода FaaS вынуждает делать компоненты системы слабосвязанными. Каждая функция независима по определению. Все взаимодействие осуществляется по сети. Экземпляры функций не имеют собственной памяти, следовательно, они требуют наличия общего хранилища для хранения состояния. Принудительное ослабление связности элементов системы может повысить гибкость и скорость разработки сервисов, но в то же время может существенно осложнить ее поддержку.
В частности, довольно сложно увидеть исчерпывающую структуру сервиса, определить, как функции интегрируются друг с другом, понять, что и почему пошло не так в случае отказа. Кроме того, запросно-ориентированная и бессерверная природа функций означает, что некоторые проблемы будет сложно обнаружить.
(c. 136-137 в русском переводе)
Опять же, меня удивил тот факт, что большинство FaaS-систем не слишком хороши для решения задач, требующих активной обработки:
… кроме того, в силу бессерверной реализации сервисов время выполнения экземпляра функции обычно ограничено. Это значит, что подход FaaS обычно не годится для приложений, требующих длительной фоновой обработки данных. (С. 138 в русском переводе)
Наконец, меня порадовало замечание о том, что FaaS становятся экономически нецелесообразными, если невозможно обеспечить долгую бесперебойную работу процессора:
Но если у вас запросов столько, что функция постоянно активна, вероятно, вы переплачиваете за количество обрабатываемых запросов.
… по мере роста сервиса количество обрабатываемых запросов вырастает до такого уровня, что процессор все время занят их обработкой. В этот момент плата за количество запросов начинает становиться невыгодной. Затраты на единицу процессорного времени облачных виртуальных машин сокращаются по мере добавления ядер, а также за счет резервирования ресурсов и скидок за длительное пользование. Затраты на оплату количества запросов обычно повышаются с ростом количества запросов.
(c. 139-140 в русском переводе)
В результате я так и не понял: когда же лучше использовать «функции как сервисы»? Отмечу, что далее Бёрнс вкратце описывает работу с событийно-ориентированными краткосрочными задачами, не сильно нагружающими процессор, такими, как двухфакторная аутентификация (2FA). Однако, учитывая, что речь идет о мелких непродолжительных задачах с малыми издержками, возникает вопрос: а зачем их независимо масштабировать? Почему бы просто не включить эти функции в другой контейнерный сервис, тесно связанный с первым?
Рассчитываю лучше разобраться с этими вопросами, когда перейду к использованию FaaS-технологий на практике.
Не считая некоторой путаницы с FaaS, книга мне очень понравилась. Читается быстро, воспринимается легко. Лишний раз напоминает, каким огромным потенциалом обладает ослабление связи между компонентами на всех уровнях разработки приложений. Наконец, теперь мне будет гораздо проще поддержать разговор с коллегами на такие темы как «Sidecar-контейнеры.»