Блог Amazon Web Services

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

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

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

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

Введение

Многие из лучших практик, используемых при разработке программного обеспечения и распределённых систем, применимы и к разработке бессерверных приложений. В целом они соответствуют рекомендациям по проектированию архитектуры Well-Architected Framework. Основная цель заключается в разработке приложений, которые:

  1. Надёжны: обеспечивают высокий уровень доступности. Бессерверные сервисы AWS надёжны, потому что спроектированы с учётом возможности сбоев.
  2. Долговечны: обеспечивают сохранность информации в соответствии с заданными требованиями.
  3. Безопасны: соответствуют лучшим практикам, используют надлежащие инструменты для организации безопасного доступа, ограничивают периметр поражения в случае инцидентов.
  4. Производительны: эффективно используют вычислительные ресурсы и обеспечивают производительность согласно заданным требованиям.
  5. Экономичны: спроектированы, чтобы избегать ненужных затрат при масштабировании; позволяют, при необходимости, освобождать ресурсы без значительных накладных расходов.

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

Не переписывайте существующие сервисы

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

Категория Сервис AWS
Вычисления AWS Lambda
Хранение данных Amazon S3
Amazon DynamoDB
Amazon RDS
API Amazon API Gateway
Интеграция приложений Amazon EventBridge
Amazon SNS
Amazon SQS
Оркестрация AWS Step Functions
Потоковый обмен данными и аналитика Amazon Kinesis Data Firehose

Для распределенных систем существует множество устоявшихся архитектурных шаблонов, которые можно реализовать самостоятельно или с помощью сервисов AWS. Для большинства пользователей затрата на самостоятельную реализацию «с нуля» не приносят большой коммерческой пользы. Если в вашем приложении требуется реализация какого-либо из подобных шаблонов, проще воспользоваться соответствующим сервисом AWS:

Шаблон Сервис AWS
Очередь Amazon SQS
Шина событий Amazon EventBridge
Публикация/подписка (разветвление) Amazon SNS
Оркестрация AWS Step Functions
API Amazon API Gateway
Поток событий Amazon Kinesis

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

Учитывайте уровень абстракции

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

Аналогично AWS управляет интеграцией других сервисов с Lambda: для конфигурации предоставляется только ограниченный набор параметров. Например, при интеграции API Gateway и Lambda нет необходимости настраивать балансировку нагрузки, она реализуется сервисами самостоятельно. У вас также нет непосредственного контроля над тем, какие Зоны доступности используются сервисами при вызове функций в любой момент времени, или как и когда масштабируется или освобождается среда выполнения лямбда-функции.

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

Откажитесь от хранения состояния в коде функций

При написании лямбда-функций следует исходить из того, что среда выполнения создаётся только на время единственного вызова. Функция должна инициализировать требуемое состояние, например, получать корзину покупок из таблицы DynamoDB, в начале выполнения. Перед завершением выполнения она должна сохранять любые постоянные изменения данных в подходящем хранилище вроде S3, DynamoDB или SQS. Она не должна ожидать существования каких-либо структур данных или временных файлов, или любой другой информации, которая бы сохранялась между вызовами (например, счётчики или другие вычисляемые агрегатные значения).

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

Архитектура лямбда-функции

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

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

Обрабатывайте события по мере поступления

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

Хотя и в бессерверных приложениях можно выполнять задачи по расписанию, используя расписания Amazon EventBridge, как это делается с помощью cron, рекомендуется делать это нечасто, в крайнем случае. Для любой задачи, выполняемой по расписанию и обрабатывающей транзакции в пакетном режиме, существует вероятность того, что количество транзакций превысит то, что может быть обработано в течение 15-минутного ограничения для выполнения лямбда-функций. Если ограничения внешних систем вынуждают вас использовать планировщик, как правило, следует планировать вызовы через минимальные промежутки времени.

Например, не рекомендуется использовать пакетную обработку для вызова лямбда-функции для получения списка новых объектов S3. Это связано с тем, что при очередном вызове сервис может получить больше новых объектов, чем сможет обработать за 15 минут.

Вызов лямбда-функции по расписанию EventBridge

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

Корзина S3 отправляет событие лямбда-функции

Оркестрация процессов

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

С помощью AWS Step Functions можно описать конечный автомат для управления оркестрацией. Это позволит отделить логику обработки ошибок, маршрутизации и ветвления от вашего кода, заменив её конечными автоматами, объявленными с использованием JSON. Помимо повышения надёжности и сопровождаемости приложения, можно будет использовать систему контроля версий и хранить описание конечного автомата в виде кода в репозитории.

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

Обрабатывайте дублирующиеся вызовы и ошибки

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

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

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

Лямбда-функция обычно обеспечивает идемпотентность, используя таблицу DynamoDB для отслеживания недавно обработанных идентификаторов, чтобы определить, была ли транзакция обработана ранее. Таблица DynamoDB обычно использует Срок жизни (TTL) для удаления устаревших элементов, чтобы ограничить используемое для хранения пространство.

Реализация идемпотентности с помощью таблицы DynamoDB

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

Заключение

В этом посте обсуждаются принципы проектирования, которые помогут разрабатывать бессерверные приложения в соответствии с рекомендациями по проектированию архитектуры Well-Architected Framework. Я рассказал, почему использование существующих сервисов вместо переписывания «с нуля» может повысить гибкость и масштабируемость вашего приложения. Я также показал, как отказ от хранения состояния и правильная декомпозиция функций способствуют созданию хорошей архитектуры приложений. Я затронул вопрос, как использование событий вместо пакетной обработки по расписанию способствует разработке бессерверных приложений и как проектировать приложения для обработки дублирующихся событий и ошибок.

Часть 3 этой серии рассматривает общие антипаттерны в событийно-управляемой архитектуре и способы избежать их появления в ваших микросервисах.

Больше учебных материалов по теме бессерверных вычислений можно найти на сайте Serverless Land.