Бессерверные задания cron в Azure

TL; DR. Я обсуждаю несколько способов запланировать выполнение функции Azure с помощью триггера таймера и приложения логики. Я также затрагиваю Durable Function и рассказываю о нюансах каждого подхода и некоторых сценариях использования.

Планирование может быть непростым делом. Я работал над проектом, который, помимо прочего, требовал, чтобы я перестроил простое задание cron, которое раньше выполнялось каждую минуту в системе Linux. Задание было написано на node.js, было небольшим, без состояния, без внешних зависимостей и поэтому хорошо подходило для Функций Azure. Или я так думал ...

Для начала возьмем это простое задание cron, которое запускается каждую минуту, добавляет текущее время в /tmp/cron.out и спит в течение 2 минут:

По умолчанию задания cron не являются одиночными - новый экземпляр будет запущен, даже если предыдущий все еще работает. В этом сценарии, хотя для выполнения каждой итерации требуется не менее двух минут, примерно каждую минуту в /tmp/cron.out будет записываться новая строка:

Фактически, в Linux, если вам нужно обеспечить выполнение только одного экземпляра вашего задания, это ваша ответственность - и есть несколько методов на выбор. Но как насчет функций Azure?

Триггер таймера в Функциях Azure

Вы можете думать о триггере таймера в Функциях Azure как о бессерверном кроне. Загвоздка в том, что триггер построен на Webjobs SDK TimerTrigger, который гарантирует, что только единственный экземпляр вашей запущенной функции будет запущен в любой момент времени. Давайте проверим это:

Создайте функцию Azure плана потребления TimerTrigger1 node.js, которая запускается каждую секунду (для простоты я использую интерфейс портала):

… И наблюдайте, как он срабатывает каждую секунду:

Теперь замените код по умолчанию следующим фрагментом, который имитирует очень важную задачу, выполнение которой занимает около 5 секунд (переменная среды COMPUTERNAME будет содержать уникальное имя виртуального хоста, на котором выполняется ваша функция - я нахожусь на «Потребление План"):

Как и ожидалось, поведение синглтона предотвратит запуск нового экземпляра каждую секунду из-за блокировки, созданной в фоновом режиме. Теперь наш код запускается каждые 5 секунд или около того на том же виртуальном хосте RD0003FF02FEFE:

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

Триггер повторения в логических приложениях

Некоторые из вас, возможно, использовали службу Планировщика Azure - она ​​устарела и заменена на Azure Logic Apps.

Сначала отключите функцию TimerTrigger1:

Затем создайте функцию, запускаемую HTTP, с образным названием HTTPTrigger1:

Замените код по умолчанию на слегка измененную версию функции, запускаемой по таймеру, которую мы только что отключили:

Затем создайте пустое приложение логики, добавьте повторяющийся триггер, срабатывающий каждую секунду, и действие, как вы угадали, нашу функцию HTTPTrigger1:

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

Теперь все забавно, если вы точно знаете, сколько времени будет выполняться ваша задача (в данном случае 5 секунд), но что, если это непредсказуемо, и вы хотите иметь возможность ограничить количество очень важных задач, выполняемых параллельно? Имеет смысл делать это всегда, если вы имеете дело с внешней конечной точкой и не хотите подвергать ее DDoS-атаке или быть ограниченными по скорости.

Это еще одна интересная проблема, я изначально пытался применить ее на стороне функций Azure с помощью пары методов:

  • Параметр приложения WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT, который можно использовать для ограничения максимальной степени параллелизма. Это довольно ненадежно, не на 100% пуленепробиваемое и не рекомендуется для значений выше 5.
  • Параметр maxConcurrentRequests HTTP-триггера функции Azure в host.json - такая же ситуация, я хотел бы иметь что-то более надежное.

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

Долговечные функции

Другой способ запланировать действия - использовать функцию Durable Azure с Eternal Orchestrator. Я расскажу об этом в отдельном посте.

Спасибо за чтение и до следующего раза!