В функциях Azure и Durable Functions говорится, что «функции должны быть идемпотентными». Однако что это означает для контекста функций Azure?

Что такое идемпотентность?

Согласно Википедии,

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

Я могу представить себе идемпотентность инфраструктуры как инструментов кода и функционального программирования. Однако что это с функциями Azure?

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

Почему важна идемпотенция?

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

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

REST-API

Эта запись в блоге с фильмом - полезный ресурс для идемпотентности REST-API.



База данных

Чтение идемпотентно. Upsert или Update также идемпотентны. Удалить не идемпотентно, однако, если ресурса нет, просто верните ошибку. Вы можете пойти на компромисс. Создать не идемпотентно. Однако, если база данных поддерживает Upsert, это идемпотентно. Иногда состояние базы данных могло быть изменено во время звонка. Однако это нормально. В обновлении вы можете использовать оптимистичную стратегию блокировки, чтобы преодолеть это.

Отправить электронное письмо

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

Очередь

Мы часто используем очередь с функциями Azure. Представьте, что если вы используете HTTP-триггер с привязками вывода Queue, как мы можем сделать его идемпотентным? Если вы вызываете HTTP-триггер несколько раз с одним и тем же параметром, очевидно, что он повторно отправляет очередь.

Мы не можем достичь идемпотентности для одной функции. Однако мы можем сделать это во всей системе. Если триггер очереди получает очередь с тем же идентификатором, ничего не делать.

Пример кода

Я создаю для этого образец кода. Это функции узла с HTTP и триггером очереди. Я использую привязки таблиц хранения, чтобы убедиться, что выполнение происходило в прошлом.

Триггер HTTP

Триггер очереди

использование

С помощью почтальона отправьте в это тело запрос POST. Вы можете изменить идентификатор.

{ “id”: 8,
 “name”: “ushio”
}

Функция триггера HTTP отправляет это сообщение функции триггера очереди. Вторая функция что-то выполняет, если идентификатор для них новый.

2018–05–02T07:58:01 Welcome, you are now connected to log-streaming service.
2018–05–02T07:58:22.866 [Info] Function started (Id=4e8ac2da-773e-4b8c-b0ef-7e527073c52a)
2018–05–02T07:58:23.162 [Info] JavaScript queue trigger function processed work item: { RowKey: 8, name: ‘ushio’ }
2018–05–02T07:58:23.162 [Info] new id
2018–05–02T07:58:23.303 [Info] Function completed (Success, Id=4e8ac2da-773e-4b8c-b0ef-7e527073c52a, Duration=444ms)
2018–05–02T07:58:37.414 [Info] Function started (Id=46d4b3b1–51f0–4927-bd52–750ca849966e)

Если вы отправите такой же запрос, вы увидите

2018–05–02T07:58:37.445 [Info] JavaScript queue trigger function processed work item: { RowKey: 8, name: ‘ushio’ }
2018–05–02T07:58:37.445 [Info] already done it
2018–05–02T07:58:37.445 [Info] Function completed (Success, Id=46d4b3b1–51f0–4927-bd52–750ca849966e, Duration=28ms)

Что произойдет, если триггер очереди вызовет исключение?

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

См. Эту статью



Я также включаю логику в доказательство этого.

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

2018–05–02T08:02:03.799 [Info] Function started (Id=6d04eed3–048e-440d-bb1b-09e04868563b)
2018–05–02T08:02:03.799 [Info] JavaScript queue trigger function processed work item: { RowKey: 8, name: ‘ushio’ }
2018–05–02T08:02:03.946 [Error] Exception while executing function: Functions.QueueTriggerJS1. mscorlib: Something happens exception.
2018–05–02T08:02:04.009 [Error] Function completed (Failure, Id=6d04eed3–048e-440d-bb1b-09e04868563b, Duration=206ms)
2018–05–02T08:02:04.135 [Info] Function started (Id=a6438c66–05b9–4c5f-90b6–7b84328b6f40)
2018–05–02T08:02:04.151 [Info] JavaScript queue trigger function processed work item: { RowKey: 8, name: ‘ushio’ }
2018–05–02T08:02:04.291 [Error] Exception while executing function: Functions.QueueTriggerJS1. mscorlib: Something happens exception.
2018–05–02T08:02:04.434 [Error] Function completed (Failure, Id=a6438c66–05b9–4c5f-90b6–7b84328b6f40, Duration=294ms)

Детальная логика

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



Вывод

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

Ретроспективы

Однако, поскольку этот код очень прост, я боролся пару часов. Я не могу найти правильный способ настроить привязки ввода таблицы хранения.

Ответ такой. «RowKey» было очень сложно.

{
      "type": "table",
      "name": "inputTable",
      "tableName": "inTable",
      "partitionKey": "Test",
      "rowKey": "{rowKey}",
      "take": 50,
      "connection": "AzureWebJobsDashboard",
      "direction": "in"
    },

Я пытаюсь найти ответ в этом документе, но не могу четко понять спецификацию.



В конце концов, я сбросил объект контекста и понял, что параметр. В спецификации сказано, что напрямую указываем данные. однако RowKey не работал. :)

context.log(context);

Это помогло мне найти решение. Результат такой

{ 
invocationId: '73e4503a-f033-41ab-9155-0f58e1955371',   executionContext:
     { invocationId: '73e4503a-f033-41ab-9155-0f58e1955371',
        functionName: 'QueueTriggerJS1',
      functionDirectory: 'D:\\home\\site\\wwwroot\\QueueTriggerJS1' },
   bindings:
     { myQueueItem: { RowKey: 8, name: 'ushio' },
      inputTable: { Name: 'ushio', PartitionKey: 'Test', RowKey: '8' } },
   log:
     { [Function]
      error: [Function],
      warn: [Function],
      info: [Function],
      verbose: [Function],
      metric: [Function] },
   bindingData:
     { queueTrigger: '{"RowKey":8,"name":"ushio"}',      dequeueCount: 3,
      expirationTime: '5/9/2018 8:48:35 AM +00:00',
      id: 'c7b3c3af-6979-4cda-aaff-79845ede184f',
      insertionTime: '5/2/2018 8:48:35 AM +00:00',
      nextVisibleTime: '5/2/2018 8:58:36 AM +00:00',
      popReceipt: 'AgAAAAMAAAAAAAAAgv50wfPh0wE=',
      sys:
        { methodName: 'QueueTriggerJS1',
         utcNow: 2018-05-02T08:48:36.273Z },
      rowKey: '8',
      name: 'ushio',
      invocationId: '73e4503a-f033-41ab-9155-0f58e1955371' },   done: [Function] }

Я должен был бросить с самого начала.