как реализовать семафоры для конечных автоматов

1656576250 kak realizovat semafory dlya konechnyh avtomatov

Янь Цуй

ZdgcEsHbAfD7C35ryumJG2-2Itw34Blg0xcs
Фото Никито Татеиси на Unsplash

Здесь, в DAZN, мы переходим от нашей устаревшей платформы к удивительному новому миру микрофронтендов и микросервисов. По дороге мы также узнали о прелестях, которые могут предложить AWS Step Functions. Например…

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

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

Мы придумали несколько решений, которые делятся на две общие категории:

  1. Контролируйте количество исполнения, которое вы можете начать
  2. Разрешить начать одновременное выполнение, но заблокировать выполнение от входа в критический путь, пока оно не сможет получить семафор (т.е. сигнал для продолжения)

Контролируйте количество одновременных исполнений

Вы можете контролировать MAX количество одновременных исполнений, введя очередь SQS. Расписание CloudWatch запускает функцию Лямбда, чтобы:

  1. проверить, сколько есть одновременных выполнения
  2. если есть N исполнения, то мы можем запустить MAX-N исполнение
  3. опрашивать SQS для MAX-N сообщений и начать новое исполнение для каждого
HEltXnpPctxNoQlmZPgqWmqFKAExcaRJMBst

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

Кроме того, вы должны использовать очередь FIFO, чтобы задачи обрабатывались в том же порядке, в каком они добавлены в очередь.

Блочное исполнение с помощью семафоров

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

fJaX03lK7RvYFecLc9CeJn0hZ5dj3Kpt4BMc

Возьмем для примера следующий конечный автомат.

35CfjuZqHsXqiEx5fXvkAeYBov9zvyDMA3ol

The OnlyOneShallRunAtOneTime государство вызывает one-shall-pass Лямбда-функция и возвращает a proceed флаг. The Пройдет? состояние затем разветвляет поток этого исполнения на основе proceed флаг.

OnlyOneShallRunAtOneTime:  Type: Task  Resource: arn:aws:lambda:us-east-1:xxx:function:one-shall-pass  Next: Shall Pass?Shall Pass?:  Type: Choice  Choices:    - Variable: $.proceed  # check if this execution should proceed                      BooleanEquals: true      Next: SetWriteThroughputDeltaForScaleUp  Default: WaitToProceed   # otherwise wait and try again later          WaitToProceed:  Type: Wait  Seconds: 60  Next: OnlyOneShallRunAtOneTime

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

const name = uuid().replace(/-/g, '_')const input = JSON.stringify({ name, bucketName, fileName, mode })   const req = { stateMachineArn, name, input }const resp = await SFN.startExecution(req).promise()

Когда one_shall_pass функция запускается, она может использовать выполнение name от входа. Затем он сможет сопоставить вызов с исполнениями, возвращенными ListExecutions.

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

module.exports.handler = async (input, context) => {  const executions = await listRunningExecutions()  Log.info(`found ${executions.length} RUNNING executions`)
const oldest = _.sortBy(executions, x => x.startDate.getTime())[0]       Log.info(`the oldest execution is [${oldest.name}]`)
if (oldest.name === input.name) {    return { ...input, proceed: true }  } else {    return { ...input, proceed: false }  }}

Сравните подходы

Давайте сравним два подхода по следующим критериям:

  • Масштабируемость. Насколько хорошо этот подход справляется с увеличением количества одновременных исполнений?
  • Простота. Сколько движущихся частей добавляет подход?
  • Стоимость. Сколько дополнительных затрат прибавляет этот подход?

Масштабируемость

Подход 2 (блокировка исполнения) имеет две проблемы, когда у вас есть большое количество одновременных исполнений.

Во-первых, вы можете достичь регионального лимита регулирования ListExecutions Вызов API.

WeuQ0JcWEx5FoUQlIkfuj1pYh3fcHkfXn9x2

Во-вторых, если вы настроили тайм-аут на вашем конечном автомате (и вы должны!), то они тоже могут тайм-аут. Это создает возвратное давление на систему.

c69Jh5yX3TpcLhmDBfHfwzmUn8d8nMsx1k56

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

Простота

Подход 1 вводит новые элементы в инфраструктуру – SQS, расписание CloudWatch и Lambda. Кроме того, это заставляет меняться и производителей.

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

Стоимость

Подход 1 предусматривает минимальные базовые затраты, даже если исполнение не выполняется. Однако здесь речь идет о центах…

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

Выводы

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *