Архитектуры на акторах: фрагментированные системы
Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.
Вступление (Actors / Objects; Complexity; When to Distribute; Kinds of Actors)
Монолиты (Control/Data Flow; Reactor / Proactor / Half-Sync/Half-Async; Request State)
Простые системы (Notation for Diagrams; Sharding; Layers; Services / Pipeline)
Системы с моделью (Hexagonal / MVC; Blackboard / Message Bus; Microkernel / Plug-ins)
Фрагментированные системы (Mesh; SOA; Hierarchy; Design Space)
There’s a method in my madness;
You get nothing from your sadness.
В прошлый раз мы изучали системы с моделью — горизонтальным слоем, проходящим через весь домен. Коснулись трех видов моделей:
- Доменная модель, в которой содержится логика взаимодействия данных и событий — то, ради чего программа написана.
- Модель данных — есть взаимосвязи и вся информация домена, но что и как с этим всем делать — решают внешние модули.
- Модель компонентов — описание и адресация модулей, присутствующих в системе.
В позапрошлый раз — смотрели базовые сценарии разделения системы:
- Шардинг (идентичные копии).
- Слои (по уровню абстрактности логики).
- Субдомены (по функциональности).
Не знаю, откуда тут берется Rule-of-Three, но сейчас завершим тремя раздробленными архитектурами — в которых одновременно произошло разделение на субдомены и на слои, и нет одного куска (слоя или субдомена), проходящего через всю предметную область.
Сеть
Что будет, если нарезать сразу по всем направлениям? Неочевидно, но похоже на сеть (Mesh, Swarm). Нижний асинхронный слой отвечает за связь и топологию сети, в верхнем слое модули с бизнес-логикой делают что-то полезное, не заботясь о ресурсах, транспорте или масштабировании. Поддержка топологии сети — отдельная задача со своими нефункциональными требованиями (реал-тайм), и ее удобно выделить в отдельный актор, чтобы не путаться с работой бизнес-логики. Здесь независимость требований и имплементации вызывает разделение на асинхронные слои.
Отличается от сервисов:
- нестабильностью связей между нодами;
- полным разделением уровней транспорта (общий для всей системы) и бизнес-логики (может содержать несколько сервисов, полиморфных по транспорту);
- малым разделением по домену на уровне транспорта: скорее всего, здесь 2 типа нод: узловые, где в верхнем слое бежит аналитика и управление сетью, и листья — где в верхнем слое пользовательская логика. Возможен даже случай, когда все ноды идентичны (p2p networks: torrent, onion) — тогда структуру можно считать вариантом слоев с сильным шардингом и общением шардов друг с другом;
Это, наверное, единственный вид систем, в которых шарды (инстансы) общаются между собой — на этом общении подход и построен.
Плюсы:
- Толерантность к утере любого компонента благодаря шардингу и слабой связности.
- Идеальное распределение нагрузки по свободным мощностям.
- Легкость масштабирования под нагрузкой.
- Унифицированный интерфейс транспорта (без велосипедов).
Минусы:
- Невозможно построить сложную бизнес-логику, охватывающую несколько нод (слишком много границ и мало стабильности).
- Медленное общение между нодами.
- Высокая вероятность ошибки во время выполнения сценария, что приведет к перезапуску шагов или сценариев и увеличению времени обработки запроса.
- Необходимость отслеживать дупликаты ответов/ сценариев или делать их идемпотентными (чтобы лишние ответы ничего не испортили).
- Сложность транспортного уровня.
- Унифицированный интерфейс транспорта (может быть не оптимальным вариантом для части коммуникаций).
Бэкенд: Service Mesh, Space-Based Architecture [SAP].
В жизни сюда относится собственно инфраструктура WWW, а также, как ни странно — связность в финансовых системах. В обоих случаях сеть (Mesh, Grid) выполняет роль одного из инфраструктурных слоев (middleware — шина передачи данных) и при рассмотрении общей архитектуры системы структура сети обычно опускается.
Варианты:
Service Mesh
Сервисы приложения используют прокси для обмена сообщениями. Прокси разных сервисов могут содержать сервисно-зависимый код, адаптирующий протокол сервиса к протоколу сети (например, конверсия JSON <-> XML <-> Protobuf). Разделение по субдомену будет отличаться для уровня бизнес-логики и для транспортного уровня.
Применение: Быстро написать микросервисы, не задумываясь о транспорте и масштабировании — нужно только заплатить правильным людям.
Space-Based Architecture [SAP]
Насколько я понял, все сервисы одинаковые, а основной продающей фичей будет Data Grid — в оперативке каждой ноды содержится часть общего набора данных системы. Транспортный уровень синхронизирует данные, решает или не допускает конфликты и гарантирует целевую степень репликации (Leaderless Replication [DDIA]). Также, вероятно, в нем может быть логика для направления запросов на обработку тем нодам, которые содержат поднабор данных, необходимых для обработки этого запроса.
Применение: Если данные по объему не влазят ни в какую базу, и можно гарантировать, что все наши ноды не погаснут одновременно (потеряв все данные). Транспортный уровень очень сложный.
Peer-to-Peer Network
Ноды одинаковые и умеют находить друг друга.
Применение: Стрессоустойчивая коммуникация (internet, torrent, onion, bitcoin).
Распределенные модули
В предыдущей архитектуре (сеть) четко выделялись слои (логика, связность и, возможно, хранение данных), шардились вертикальные компоненты, состоящие из всех слоев, и было мало видов компонентов. Сейчас снимем эти ограничения, и получим хаос.
Берем любой ООП проект, каждый его модуль выделяем в актор и выносим на отдельный компьютер. Получаем распределенный монолит [MP] (при синхронных связях) или SOA.
Обычно присутствует общий для всех сервисов распределенный транспортный фреймворк (Middleware / Message Bus), но мы его не будем рисовать, чтобы не путать картинку.
Плюсы:
- Максимальное уменьшение Dev сложности кода (дробим монолит на много частей).
- Каждый модуль бежит на «железе», подобранном индивидуально под его потребности.
- Креш одного модуля полностью не убивает систему, если общение модулей асинхронное.
- Модули можно независимо шардить.
- Модули можно относительно независимо деплоить (при совместимости интерфейса).
Минусы:
- Сильно увеличена Ops сложность, так как модули зависят друг от друга по бизнес-логике и интерфейсам.
- Сильно увеличена сложность координации команд на проекте из-за зависимостей модулей, разрабатываемых разными командами, друг от друга.
- Креш служебного модуля или убивает все (синхронные вызовы между модулями), или парализует высокоуровневые сценарии, в которых затрагиваются многие модули.
- Все сценарии медленные, так как в них вовлечено много распределенных взаимодействий.
- Сценарии невозможно дебажить из-за большого количества вовлеченных независимых компонентов.
То есть, хотели как лучше, а получилось — как всегда. Попробовали избавиться от сложности в коде — получили сумасшедшую сложность в интеграции.
Бекенд: Service Oriented Architecture, Distributed Monolith (синхронный вариант через RPC) [MP].
Фэйл академических архитекторов и рекламы платных курсов повышения ООП квалификации. Практические проблемы вытеснили теоретически красивое решение отовсюду, кроме авто-/авиастроения и уже вложившихся в SOA энтерпрайзов.
Почему осталось в транспортной отрасли? Потому, что сервисы можно раскидать по копеечным чипам, а чипы раскидать в пространстве. Несколько черных ящиков для логирования — остальные компоненты отсылают им логи. Проц для тормозов возле колеса сам ничего себе на флеш не логирует — все отсылает в черный ящик. И даже если черный ящик сломается — тормоз будет работать. Возможно, еще помогает надежная и быстрая связь по шине данных вместо рваного интернета. Думаю, независимость логики (модульность) систем автомобиля друг от друга и близость ООП к моделируемому физическому миру тоже сыграли. Еще — сопротивление комитетов любым изменениям и зарегулированность отрасли. Последние факторы могли сработать и в энтерпрайзе, вместе с тем, что объем кода никак не влазит в монолит.
Почему почти отмерло для бекенда? Народ понял ошибку и перешел на микросервисы. Они намного более независимы (каждый микросервис может обработать большие куски сценариев внутри себя, не обращаясь к соседям), поэтому и система проще и стабильнее для Ops, и команды меньше нужно синхронизировать в плане менеджмента.
Да, код монолита не смогли раздробить настолько сильно, как хотелось теоретикам. Но получается быстрее, надежнее, и веселее. А ошибка была в том, что разбирали в первой части статьи — связную логику нельзя резать асинхронными интерфейсами. Иначе будет сложный тормозной неподдерживаемый трэш.
Иерархия
Последний вариант известных мне составных структур. Сверху — немного абстрактной высокоуровневой логики, ниже — несколько более приземленных вариантов, вероятно, полиморфных для верхнего уровня. Еще ниже — конкретные сервисы или устройства — интерфейс к действительности.
Плюсы:
- Доменная логика разбита на много относительно небольших относительно независимых частей.
- Возможны разные режимы работы для разных компонентов.
- Высокая независимость в разработке и деплое компонентов.
- Система может легко пережить отказ любого компонента, кроме верхнего уровня логики.
- Нижние уровни могут локально обрабатывать часть запросов, не засоряя их логикой вышестоящие компоненты.
- Локальная обработка запросов на нижних уровнях очень быстрая.
- Параллельное выполнение сценариев на нижних уровнях.
- Независимый шардинг всех компонентов.
- Легкость замены нижних компонентов благодаря нескольким слоям интерфейсов.
Минусы:
- Если логика не иерархическая (нельзя выделить ограниченный «центр» или «ядро» домена) — мы не сможем реализовать такую структуру.
- Верхний уровень может быть «бутылочным горлышком» по производительности.
- Отказ верхнего уровня парализует сложные сценарии (но локальные продолжат работать).
- Сценарии, проходящие через несколько уровней, может быть тяжело отлаживать.
- Сценарии, проходящие через несколько уровней, будут медленными.
- Медленный старт разработки из-за громоздкости.
Иерархия уменьшает сложность системы по горизонтали (субдомены), разнося эту сложность по нескольким слоям абстракции. Так, если для гексагоналки основная сложность — монолитная бизнес-логика, то в гексагоналке гексагоналок логика разделена на несколько частей по уровню абстракции и по субдоменам. Если логика согласования состояния системы через message bus становится слишком сложной — делается иерархия сетей. Когда сервисов слишком много, взаимосвязи запутались и тяжелый деплой — делаются сервисы сервисов.
Получается, если мы можем разделить систему на «координатора» и «ведомые компоненты» (в идеале — полиморфные по отношению к координатору) сопоставимых размеров, то стоит подумать об иерархии. Если координатора нет — это (микро-)сервисы. Если координатор забрал в себя всю логику — это гексагоналка.
Общие названия: Иерархия, дерево.Классика: Presentation-Abstraction-Control [POSA1].
Бекенд: Cell-Based Architecture.
Собственно, известные мне варианты:
Гексагоналка гексагоналок
Бизнес-логика каждого уровня опирается на бизнес-логику нижних уровней. То есть, адаптеры верхней гексагоналки являются моделями для нижних. В реальности такое используется, например, для (пожарной) сигнализации зданий. На верхнем уровне — пульт с UI, ниже — контроллеры этажей и лифтов в отдельных коробках, и каждая коробка собирает данные с датчиков своего этажа. Многослойность с полиморфизмом позволяет приспосабливать систему к разным зданиям и железкам без существенных изменений кода.
Шина шин (Presentation-Abstraction-Control [POSA1])
Интернет с роутерами и опорными сетями или телефония с разными видами транспорта от телефона до вышки и между вышками — пример из инфраструктуры. Пример из софта — старый (и сомнительный) паттерн Presentation-Abstraction-Control, создающий по актору для каждого кастомного виджета на экране. Акторы собраны в иерархию, по которой распространяются изменения данных. Каждый актор умеет рисовать себя, знает, куда должны отрисовываться дети, берет данные из корня дерева, и отдает подмножество данных детям. Использовалось для мониторинга древовидных систем — тех же сетей или, может, пожарной сигнализации. Думаю, убит фронтендом.
Сервисы сервисов (Cell-Based Architecture)
Микросервисов развелось сильно много, мы их разбиваем на группы по связности, каждой группе назначаем свой Gateway, и деплоим всю группу целиком. Сами группы друг для друга являются микросервисами, хотя в каждой из них внутри — отдельный мир микросервисов. Сложность деплоя теперь — не количество микросервисов, а количество групп.
Design Space
Пространство архитектурных решений (design space [POSA1, POSA5]) — неведомая воображаемая многомерная хрень, содержащая все возможные архитектуры программ или систем. Многомерная — потому что в архитектуре есть много всякого, что можно менять, то есть — степеней свободы, то есть — измерений в этом самом пространстве решений.
Мы не можем с этим многомерным работать. Но можем взять из него 3 измерения и получить удобоосознаваемую проекцию. И мы как раз это делали в последних трех частях цикла статей, выбрав как измерения гордую аббревиатуру ASS:
* Abstraction (абстрактность логики) — по вертикали.
* Subdomain — по горизонтали.
* Sharding — куда-то в сторону.
Да, мы не различим внутреннего устройства синхронных монолитов — в такой проекции монолиты одинаковы. Но для распределенных систем должны довольно четко просматриваться простые фигуры, лежащие в основе архитектуры.
Вот и сказке конец.
Сделал группу в телеграмме swarchua — заходите поболтать.
Заключение
Мы возвращались и возвращались к тому, что такое «хорошо», и что такое «плохо».
Хорошо: держать связную логику вместе — в одном компоненте.
Плохо: резать связную бизнес-логику на несколько кусков, особенно — если эти куски асинхронны.
Хорошо: держать части, требующие отличных свойств («-ilities») в отдельных асинхронных компонентах (акторах, сервисах).
Плохо: пытаться навесить несовместимые нефункциональные требования на один компонент.
Не всегда возможно следовать простым советам. Но ведь лучше эти советы знать и предвидеть часть проблем при нарушении правил, чем стать очередным первопроходцем грабельного поля. Больше шансов на осмысленный выбор.
Литература
[DDIA] Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems. Martin Kleppmann. O’Reilly Media, Inc. (2017).
[MP] Microservices Patterns: With Examples in Java. Chris Richardson. Manning Publications (2018).
[POSA1] Pattern-Oriented Software Architecture Volume 1: A System of Patterns. Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad and Michael Stal. John Wiley & Sons, Inc. (1996).
[POSA5] Pattern Oriented Software Architecture Volume 5: On Patterns and Pattern Languages. Frank Buschmann, Kevlin Henney, Douglas C. Schmidt. John Wiley & Sons, Ltd. (2007).
[SAP] Software Architecture Patterns. Mark Richards. O’Reilly Media, Inc. (2015).
Вступление (Actors / Objects; Complexity; When to Distribute; Kinds of Actors)
Монолиты (Control/Data Flow; Reactor / Proactor / Half-Sync/Half-Async; Request State)
Простые системы (Notation for Diagrams; Sharding; Layers; Services / Pipeline)
Системы с моделью (Hexagonal / MVC; Blackboard / Message Bus; Microkernel / Plug-ins)
Фрагментированные системы (Mesh; SOA; Hierarchy; Design Space)
2 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів