Это дословная копия статьи, которую я опубликовал на Medium.
Когда я читал эту замечательную статью Another Way to Trigger a Lambda Function Every 5-10 Seconds, она показалась мне немного знакомой. В проекте, над которым я работал несколько лет назад, я использовал похожий подход к использованию пошаговых функций, чтобы позволить функциям запускаться так быстро, как будто они выполняются каждую секунду. Но вместо фиксированной скорости выполнения функции Lambda, эта скорость должна была быть регулируемой и мгновенно применяться через панель администратора. В этой статье я расскажу, как это было достигнуто еще в 2018 году и как я улучшил это в процессе написания данной статьи.
Почему
Я подумал, что сначала имеет смысл объяснить, почему моему проекту понадобилась подобная функциональность, поскольку в противном случае это может быть бессмысленно. Это требование возникло в результате создания бессерверного интернет-магазина для нашего клиента ID&T, организатора ведущих мировых танцевальных фестивалей, таких как Defqon.1, Mysteryland и Qlimax. Спрос на билеты очень высок, и когда открывается продажа билетов, десятки тысяч нетерпеливых фанатов стоят в очереди, чтобы купить билеты. Во время, как мы это называем, «режима пиковой продажи» мы хотим, чтобы определенные процессы, такие как статистика и очистка, происходили каждые несколько секунд. Вне пика продаж эти процессы менее важны, и мы можем запускать их раз в несколько часов. Перевод веб-магазина в режим пиковых продаж может быть выполнен самим клиентом и должен немедленно изменить скорость процессов. Мы могли бы сделать так, чтобы эти процессы всегда выполнялись так же быстро, как в режиме пиковых продаж, но, очевидно, это стоило бы гораздо больше денег для вещей, которые нам нужно выполнять так быстро всего несколько раз в год. Самое замечательное в использовании Шаговых функций в Standard Workflow — это то, что вам не нужно платить за время ожидания. Я планирую написать больше статей о бессерверном интернет-магазине в будущем, так как мы сделали еще несколько вещей для этого проекта, которые могут быть полезны другим.
Концепция
Она сводится к передаче скорости в секундах и функции в качестве входа для выполнения Шаговой функции. Шаговая функция вызовет лямбду, используя состояние Task, а затем подождет указанное количество секунд, используя состояние Wait. Когда скорость изменится, просто начните новое выполнение и уничтожьте текущее выполнение (если оно есть), используя API пошаговых функций, и продолжайте как обычно. Таким образом, вы получаете бесконечный цикл выполнения, который не должен останавливаться, пока вы не прикажете ему остановиться.
В обоих случаях входные данные одинаковы: вы задаете скорость и имя или ARN лямбды:
{“task”: “lambda-name-or-arn”, “rate”: 10}
Следует помнить, что Step Functions имеет максимальное время выполнения (1 год) и максимальное количество шагов (25.000), как указано в документации. Чтобы преодолеть риск того, что выполнение просто прервется по времени или достигнет большего количества шагов, чем разрешено, нам нужно отслеживать, сколько раз была вызвана функция. Когда мы рискуем дойти до максимального количества шагов, рабочий процесс создаст новый экземпляр, используя текущие входные параметры, и позволит текущему завершить выполнение. Теперь мы создали простую основу для выполнения фоновых задач с любой скоростью без необходимости управления инфраструктурой.
Подводя итог, мы получаем следующие шаги:
- Передать имя лямбды или ARN в качестве задачи и частоту в секундах, как часто мы хотим ее вызывать.
- Убиваем все существующие выполнения, запущенные для той же задачи, чтобы убедиться, что в одно и то же время запущен только один экземпляр.
- Выполнить фактическую задачу
- Подождите определенное количество времени
- Пока нас устраивает время и количество шагов, повторите два вышеуказанных шага.
- Если нам больше не комфортно, создайте новое выполнение с теми же параметрами
- Завершить текущее выполнение
Решение в 2018 году
Когда я столкнулся с этой задачей, это был 2018 год, я нашел учебник Continuing as a New Execution от AWS, и эта концепция легла в основу моего решения. По сути, вам нужен счетчик и увеличивать его на 1 каждый раз, когда задача была выполнена. Я ввел шаг ожидания, который ждал определенное количество секунд. Затем перехожу к шагу Choice, где проверяю, превышает ли число определенное (жестко заданное) количество допустимых итераций. Если нет, я запускаю функцию снова, увеличиваю счетчик, жду и так далее. Если мы достигли определенного количества допустимых итераций, то выходим из цикла и запускаем задачу, которая запускает Lambda для создания нового выполнения с теми же параметрами и позволяет завершить текущее выполнение.
Решение в 2021 году
Во время написания этой статьи я понял, что у вышеописанного подхода есть существенный недостаток. Лямбда, которую я хотел периодически выполнять, должна увеличивать счетчик, что делает ее тесно связанной, а это совсем нежелательно. Также я не хотел, как в руководстве AWS, создавать Lambda для простого увеличения счетчика, поскольку это привело бы к дополнительным затратам и задержкам. Вдохновившись статьей, на которую я ссылался ранее, я решил использовать тип Map (функция, недоступная в 2018 году). Когда начинается новое выполнение, я просто создаю пустой массив с количеством итераций, которые я хочу запустить, и устанавливаю MaxConcurrency в конфигурации Map равным 1, чтобы Step Functions выполняла внутреннюю машину состояний для вызова Lambda и ждала только по одной за раз. Это устраняет сложность хранения счетчика и позволяет Lambda запускаться без ведома cron, идеально! Дополнительными бонусами являются отсутствие задержки при увеличении счетчика через Lambda и то, что теперь я могу учитывать время ожидания при расчете количества запусков.
Для вашего удобства я сделал пример проекта на GitHub с использованием Serverless Framework, чтобы вы могли попробовать сами.
Повторные попытки и обработка ошибок
Очевидно, что есть вещи, которые могут пойти не так. Самое худшее, что может случиться, это то, что по какой-то причине выполнение пошаговой функции остановится или запуск нового выполнения не удастся, и лямбда больше не будет запущена. К счастью, пошаговые функции обеспечивают достойную обработку ошибок и позволяют нам повторить выполнение наших состояний, сводя количество неудач к минимуму. В качестве второй линии защиты вы можете действовать на основе событий CloudWatch и запускать какое-либо уведомление, когда выполнение завершается нежелательным образом.
Администрирование
Было бы очень здорово иметь какой-то инструмент администрирования, с помощью которого я мог бы отслеживать, какие задачи должны выполняться с какой скоростью, иметь возможность изменять скорость и вручную запускать и останавливать их. Я думаю, что хранение этих данных в DynamoDB было бы отличным вариантом, поскольку она проста в использовании и может быть настроена на режим тарификации по требованию, так что вам не придется беспокоиться о емкости и стоимости.
Большое спасибо Заку Чарльзу за то, что он дал мне свежий взгляд на то, как улучшить мое первоначальное решение. Спасибо Майклу Гунтенаару за потрясающий проект.