Вы знаете это чувство, когда архитектура выглядит абсолютно потрясающе на диаграмме на доске? Архитектура, управляемая событиями, — это архитектурный эквивалент той девушки, которая выглядит потрясающе в Instagram, но опустошит ваш кошелёк, рассудок и график сна. Не поймите меня неправильно — я не говорю, что EDA плоха. Я говорю, что то, о чём не упоминают в докладах на конференциях, — это принятие EDA, по сути, означает запись на мастер-класс по отладке распределённых систем в 3 часа ночи в воскресенье.
Обещание: почему мы все влюбляемся в EDA
Позвольте мне начать с признания того, почему архитектура, управляемая событиями (EDA), действительно привлекательна. Согласно основополагающим принципам, архитектура, управляемая событиями, — это шаблон проектирования программного обеспечения, который позволяет создавать масштабируемые и слабо связанные системы. События, представляющие собой возникновения или изменения в системе, управляют потоком, и они генерируются различными источниками, публикуются в шине событий или брокерской службе сообщений и асинхронно потребляются заинтересованными компонентами.
Преимущества звучат как фантастический роман:
- Слабая связанность: компоненты взаимодействуют через асинхронные сообщения событий, что позволяет их разрабатывать, развертывать и масштабировать независимо. Теоретически можно добавлять новые компоненты, не нарушая существующие. Теоретически.
- Масштабируемость: поскольку компоненты не связаны между собой, их можно добавлять или удалять без влияния на текущую конфигурацию. Горизонтальное масштабирование становится «тривиальным», или так говорят.
- Обработка в реальном времени: события обрабатываются по мере их возникновения, что позволяет системе эффективно управлять задачами, чувствительными ко времени.
- Устойчивость: вашу систему можно восстановить до согласованного состояния, воспроизведя события, что обеспечивает надёжные возможности восстановления.
Это рекламное предложение. Это то, что ваш CTO услышал на конференции и внезапно стал одержим. Это то, что заставило вас подумать: «Да, мы определённо должны перестроить нашу платформу вокруг этого».
Реальность: где мечты встречаются с выходами из системы в 2 часа ночи
Здесь я должен быть предельно честен: те же характеристики, которые делают EDA элегантной, также создают эксплуатационную сложность, которая заставит вас усомниться в своём выборе карьеры.
Проблема №1: наблюдаемость — это ложь, которую вы говорите себе
В традиционной монолитной системе вы можете отследить запрос от входа до выхода. Вы ударяете по конечной точке, проходите по пути кода, возможно, ударяете по базе данных и возвращаете ответ. Отладка последовательна. Это почти приятно, правда.
В системе, управляемой событиями, одно действие пользователя может запустить каскад событий, который протекает через несколько сервисов, возможно, с временными промежутками, повторами и асинхронной обработкой. Событие может:
- Быть опубликованным в брокерскую службу.
- Посидеть в очереди некоторое время.
- Быть потреблённым сервисом A.
- Заставить сервис A опубликовать другое событие.
- Быть потреблённым сервисом B с задержкой.
- Сбойнуть на полпути.
- Быть повторенным через 5 минут.
- Возможно, удастся, возможно, снова сбойнёт.
- Оказаться в очереди мёртвых писем.
И вы должны соединить все эти точки, когда что-то ломается в 3 часа ночи? Удачи в поиске места фактического сбоя, когда журналы разбросаны по четырем разным сервисам, а время не совпадает, потому что у сервиса B была небольшая пауза GC.
Проблема №2: порядок событий — ваш новый лучший друг (и новый злейший враг)
Сторонники EDA умалчивают об этом, но проблемы с порядком событий являются реальной эксплуатационной проблемой. Вот сценарий, который не даст вам уснуть:
У вас есть система электронной коммерции. Клиент публикует событие order.created. Затем сразу публикует событие order.cancelled. Оба события попадают в брокерскую службу. Из-за задержки в сети, задержек обработки или просто из-за жестокости вселенной событие order.cancelled обрабатывается раньше order.created.
Теперь у вас есть заказ, который был отменён до его создания. Ваша система инвентаризации уменьшила количество товара. Ваша платёжная система попыталась списать деньги с клиента. Ваша служба уведомлений отправила им электронное письмо «ваш заказ готовится» до того, как они получили уведомление об отмене заказа.
Результаты поиска упоминают, что системы, управляемые событиями, используют конечную согласованность, что является корпоративным жаргоном для «всё в конечном итоге будет правильно, наверное». Это нормально, пока не наступит 2 часа ночи и ваш CEO не спросит, почему клиенты получают счета за отменённые заказы.
Проблема №3: распределённые транзакции — это кошмар
В монолитной системе с транзакцией базы данных вы получаете гарантии ACID. В системе, управляемой событиями и распределённой по микросервисам? Вам приходится реализовывать компенсационную логику, проверки идемпотентности и молиться, чтобы ничего больше не сломалось.
Рассмотрим этот поток:
Действие пользователя: перевести 100 долларов с счёта A на счёт B
Событие: account.transfer_initiated
├─> Сервис 1: Списывает средства со счёта A → Событие: account.debited
│ └─> Событие опубликовано успешно ✓
└─> Сервис 2: Зачисляет средства на счёт B → Событие: account.credited
└─> СЕРВИС 2 КРУШИТСЯ ДО ПУБЛИКАЦИИ СОБЫТИЯ ✗
Результат: 100 долларов списаны со счёта A, но никогда не зачислены на счёт B.
Клиент счёта B в ярости. Вы реализуете компенсационную логику в 3 часа ночи.
Результаты поиска упоминают, что системы справляются с проблемами согласованности данных, используя такие приёмы, как версионирование событий, идемпотентность и компенсирующие действия, но реализовать их правильно в распределённой системе значительно сложнее, чем указано в документации.
Проблема №4: отладка — это археология
Когда что-то ломается, вы не отлаживаете одну систему — вы расследуете место преступления в нескольких сервисах. Брокеры сообщений не всегда воспроизводят сообщения. Очереди мёртвых писем могут устареть. Журналы вращаются. А ваша настройка распределённого трассирования была создана инженером, который больше здесь не работает и не оставил никаких документов.
Я потратил 45 минут, пытаясь выяснить, почему данные конкретного пользователя не были обработаны, только чтобы обнаружить, что их событие попало не в ту секцию в Kafka из-за несоответствия хэшей. Событие было в порядке. Обработка была в порядке. Логика маршрутизации имела незначительный баг, который проявлялся один раз каждые 10 000 событий.
В 3 часа ночи поиск этого не доставляет удовольствия.
Архитектура, которая выглядела так хорошо
Позвольте мне показать вам, как обычно выглядит система, управляемая событиями:
(Kafka/RabbitMQ)"] end subgraph Consumers["Потребители событий"] C1["Сервис уведомлений"] C2["Аналитический сервис"] C3["Сервис инвентаризации"] C4["Сервис выполнения"] end P1 -->|Публикует события| EB P2 -->|Публикует события| EB P3 -->|Публикует события| EB EB -->|Маршрутизирует события| C1 EB -->|Маршрутизирует события| C2 EB -->|Маршрутизирует события| C3 EB -->|Маршрутизирует события| C4 C1 -.->|Публикует события| EB C3 -.->|Публикует события| EB C4 -.->|Публикует события| EB
Выглядит элегантно, правда? Теперь представьте эту систему в масштабе, с повторами, предохранителями, несколькими разделами, перебалансировкой групп потребителей и случайными сетевыми сбоями. Внезапно эта красивая диаграмма начинает выглядеть как стена заговора с красными нитями, соединяющими всё со всем.
Понимание компонентов (и их подвохов)
Согласно основополагающим принципам, система, управляемая событиями, состоит из нескольких ключевых компонентов:
Брокер событий/шина событий: центральный хаб для управления обменом событиями. Он получает события от издателей, фильтрует их и направляет соответствующим подписчикам. Подвох? Когда этот единственный компонент выходит из строя или перегружен, вся ваша система испытывает каскадные сбои, потому что всё зависит от него.
Издатели: отвечают за отправку событий в шину событий. Они преобразуют действия или изменения системы в события и отправляют их асинхронно, не зная, кто их будет потреблять. Подвох? Издатели часто не видят, были ли их события успешно обработаны. Они отправляют и забывают. Это хорошо для ослабления связей, но плохо для отладки.
Потребители/слушатели: активно отслеживают шину событий на предмет определённых событий, обнаруживают интересующие события и инициируют обработку. Подвох? Если потребитель падает после чтения события, но до его обработки, вам нужно обработать повторную обработку. Неправильно реализуйте идемпотентность, и у вас будет дублирование выполнения бизнес-логики.
Диспетчер: контролирует, как события доставляются в системе, направляет события
