Блог Amazon Web Services

Используем AWS Lambda: что такое событийно-управляемая архитектура – Часть 1

Оригинал статьи: ссылка (James Beswick, Principal Developer Advocate)

В серии “Используем AWS Lambda” я затрону несколько тем, важных для разработчиков, архитекторов и системных администраторов, которые работают с приложениями, использующими AWS Lambda. Эта серия из трёх статей познакомит вас с событийно-управляемой архитектурой, и покажет, как она связана с бессерверными приложениями.

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

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

Как лямбда вписывается в событийную парадигму

AWS Lambda – это сервис, который выполняет пользовательский код в ответ на возникновение определенного события. Большинство сервисов AWS генерируют события, и многие из них можно использовать для запуска пользовательского кода в AWS Lambda. Внутри Lambda ваш код, обрабатывающий события хранится в виде программного пакета. Все взаимодействия с кодом происходят через Lambda API, напрямую к нему обратиться нельзя. Основное назначение функций в Lambda – обработка событий.

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

Вызвать функцию лямбда может практически любое событие: HTTP-запрос через Amazon API Gateway, вызов по расписанию, заданному в Amazon EventBridge, или уведомление от Amazon S3. Даже самое простое приложение, использующее лямбда, должно быть связано по крайней мере с одним событием.

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

Событие также может быть сгенерировано сервисом AWS, таким как Amazon SQS, когда новое сообщение доступно в очереди:

В обоих случаях событие будет передано обработчику в качестве первого параметра:

  1. Код за пределами функции-обработчика, так же называемый кодом инициализации, выполняется перед выполнением функции-обработчика. В этом месте можно разместить код для импорта библиотек или объявление и инициализацию глобальных переменных.
  2. Сам обработчик события – это функция, которой передается объект-событие. Независимо от языка, на котором написана лямбда, объект-событие всегда передается в формате JSON.

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

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

Создавая приложения на основе лямбда-функций, разработчики опираются на наработанные практики построения событийно-ориентированной архитектуры. Был создан ряд подходов к разработке, которые упрощают создание событийно-управляемых архитектур. Эвентсторминг (Event Storming – по аналогии с брейнстормингом) – одна из популярных методик, представляющая собой интерактивный подход к предметно-ориентированному проектированию (Domain Driven Design). Распределив события, которые возникают в приложении, по логическим группы, можно разделить его на отдельные микросервисы.

Чтобы узнать больше об архитектуре, управляемой событиями, прочтите статьи “What is an Event-Driven Architecture?” и “What do you mean by Event-Driven?

Преимущества событийно-ориентированной архитектуры

Замена опросов и обратных вызовов на обмен событиями

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

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

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

Уменьшение сложности

Микросервисы позволяют разработчикам и архитекторам декомпозировать сложные рабочие процессы. Например, монолитное приложение интернет-магазина может быть разделено на микросервис приема заказов и микросервис оплаты заказов, отдельные микросервисы для работы со складом, комплектации заказа и бухгалтерского учета. Цельные бизнес-процессы, которые сложно реализовать и поддерживать в монолитной архитектуре, заменяются набором самостоятельных микросервисов, которые могут взаимодействовать между собой асинхронно при помощи событий.

Этот подход так же позволяет интегрировать сервисы, которые обрабатывают данные с различной скоростью. Например, микросервис приема заказов может использовать очередь Amazon SQS в качестве буфера для хранения входящих заказов, поступающих с высокой скоростью.

Микросервис обработки платежей обычно работает медленнее из-за более сложных алгоритмов проведения платеже. Он сможет выбирать информацию о заказах из этой очереди с подходящей скоростью. Микросервис так же может реализовать сложную логику повторной обработки событий и обработки ошибок, при помощи AWS Step Functions и координировать активные процессы обработки платежей для сотен тысяч заказов.

Повышение масштабируемости и расширяемости

Микросервисы отправляют порожденные события в службы обмена сообщениями вроде Amazon SNS и SQS. Эти службы создают эластичный буфер между микросервисами и позволяют обеспечить масштабирование при росте трафика. Службы вроде EventBridge могут затем фильтровать и распределять события в зависимости от содержания и заданных правил. В результате событийно-управляемые приложения проще масштабировать и проще обеспечить избыточность.

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

Чтобы узнать больше, прочтите “How event-driven architecture solves modern web app problems” и “How to Use Amazon EventBridge to Build Decoupled, Event-Driven Architectures”.

Сложности событийно-ориентированных архитектур

Плавающая задержка

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

Сервисы AWS для бессерверных вычислений спроектированы, чтобы обеспечивать доступность, что означает, что они используют несколько зон доступности (Availability Zones) в регионе. В случае сбоя, сервис автоматически переключается на другую зону доступности и повторяет попытку обработки вызова. В результате вместо сбоя вызов может быть успешно обработан, однако задержка будет выше.

Событийно-управляемая архитектура не подходит приложениям, требующим предсказуемой низкой задержки, таким, как высокочастотный трейдинг в банках, или управление складскими роботами, требующее задержек в доли миллисекунд.

Согласованность в конечном счете

Событие сообщает об изменении состояния приложения. Поскольку в моменте в процессе обработки находится множество событий, приложение может достичь согласованности только в конечном счете (eventual consistency). Это усложняет обработку транзакций, работу с дубликатами и определение точного состояния всей системы.

Некоторые задачам не очень подходит событийно-управляемая архитектура из-за необходимости обеспечить свойства ACID. В некоторых случаях задачи требуют обеспечить сочетание возможной согласованности (например, общее количество заказов в текущий момент) и строгой согласованности (например, количество товара на складе). Для случаев, когда требуется строгая согласованность, разработаны подходящие архитектурные шаблоны.

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

Возврат результатов

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

Большинство вызовов лямбда-функций в эксплуатируемых системах являются асинхронными – они отвечают на события, поступающие сервисов S3 или SQS. В этих случаях знать, завершилась ли обработка успехом или неудачей, важнее, чем вернуть результат. AWS Lambda реализует функционал вроде очереди ошибок обработки (dead letter queues – DLQ), который позволяет обнаруживать закончившиеся неудачей попытки обработки событий и повторять их вновь без необходимости уведомлять о неудаче вызывающую функцию.

В интерактивных приложениях, например веб и мобильных приложениях, пользователь обычно ожидает увидеть результат своего действия. Для таких приложений разработан набор шаблонов архитектур (см. several design patterns), который создает поток событий, возвращающих значения вызывающей функции. Однако их реализация более сложна, чем использование традиционного асинхронного возврата.

Отладка в условиях взаимодействия служб и функций

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

Чтобы узнать больше, прочтите “Challenges with distributed systems” и “Implementing Microservices on AWS”.

Заключение

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

Во второй части этой серии обсуждаются принципы проектирования и лучшие практики разработки приложений на основе лямбды.