AWS Lambda, кажется, выходит до завершения

У меня есть очень простая лямбда-функция (nodeJS), которая помещает полученное событие в поток kinesis. Вот исходный код:


    'use strict';

    const AWS = require('aws-sdk');
    const kinesis = new AWS.Kinesis({apiVersion: '2013-12-02'});

    exports.handler = async (event, context, callback) => {
        let body = JSON.parse(event.body);
        let receptionDate = new Date().toISOString();
        let partitionKey = "pKey-" + Math.floor(Math.random() * 10);

        // Response format needed for API Gateway
        const formatResponse = (status, responseBody) => {
            return {
                statusCode: status,
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(responseBody)
            }
        }

        // body.events is an array of events. Just add the reception date in each events.
        for(let e of body.events) {
            e.reception_date = receptionDate;
        }

        console.log("put In kinesis stream");
        let kinesisParams = {
            Data: new Buffer(JSON.stringify(body) + "\n"),
            PartitionKey: partitionKey,
            StreamName: 'event_test'
        };

        kinesis.putRecord(kinesisParams, (err, res) => {
            console.log("Kinesis.putRecord DONE");
            if(err) {
                console.log("putRecord Error:", JSON.stringify(err));
                callback(null, formatResponse(500, "Internal Error: " + JSON.stringify(err)));
            } else {
                console.log("putRecord Success:", JSON.stringify(res));
                callback(null, formatResponse(200));
            }
        });
    };

Когда этот код выполняется, вот журналы в cloudwatch:

START RequestId: 5d4d7526-1a40-401f-8417-06435f0e5408 Version: $LATEST
2019-01-11T09:39:11.925Z    5d4d7526-1a40-401f-8417-06435f0e5408    put In kinesis stream
END RequestId: 5d4d7526-1a40-401f-8417-06435f0e5408
REPORT RequestId: 5d4d7526-1a40-401f-8417-06435f0e5408  Duration: 519.65 ms Billed Duration: 600 ms     Memory Size: 128 MB Max Memory Used: 28 MB  

Кажется, kinesis.putRecord не вызывается... Я ничего не вижу в логах kinesis stream. Я конечно где-то ошибся, но не знаю где!


person Adagyo    schedule 11.01.2019    source источник


Ответы (3)


kinesis.putRecord — это асинхронная операция, которая вызывает обратный вызов (второй параметр) по завершении (успешно или с ошибкой).

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

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

Таким образом, у вас есть 2 варианта здесь:

  1. Поскольку в вашем коде нет await, просто удалите async. В этом случае Lambda ожидает, пока цикл событий не станет пустым (Если вы явно не изменили context.callbackWaitsForEmptyEventLoop)
  2. Измените kinesis.putRecord на что-то вроде:
let result;

try {
  result = await kinesis.putRecord(kinesisParams).promise();
} catch (err) {
  console.log("putRecord Error:", JSON.stringify(err));
  throw Error(formatResponse(500, "Internal Error: " + JSON.stringify(err));
}

console.log("putRecord Success:", JSON.stringify(result));
return formatResponse(200);

Во втором варианте лямбда будет работать до тех пор, пока не завершится kinesis.putRecord.

Для получения дополнительной информации о поведении Lambda в этом случае вы можете увидеть основной код, который выполняет ваш обработчик под /var/runtime/node_modules/awslambda/index.js в контейнере lambda.

person Bar Schwartz    schedule 13.01.2019

@ttulka, не могли бы вы объяснить немного больше? Дайте советы или примеры кода? – Адажио

Речь идет об эволюции асинхронной обработки в JavaScript.

Сначала все делалось с обратным вызовом, это самый старый подход. Повсеместное использование обратных вызовов приводит к «аду обратных вызовов» (http://callbackhell.com).

Затем были введены Promises. Работа с промисами немного похожа на работу с монадами, все упаковано в "коробку" (промис), так что вам нужно связать все ваши вызовы в цепочку:

thisCallReturnsPromise(...)
  .then(data => ...)
  .then(data => ...)
  .then(data => ...)
  .catch(err => ...)

Что немного неестественно для человека, поэтому ECMAScript 2017 предложил синтаксический сахар в асинхронных функциях (async/await) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Заявления/async_function

Синтаксис Async/await позволяет вам работать с асинхронными промисами, как с обычным кодом синхронизации:

const data = await thisCallReturnsPromise(...)

Не забывайте, вызов await должен быть внутри асинхронной функции:

async () => {
  const data = await thisCallReturnsPromise(...)
  return await processDataAsynchronouslyInPromise(data)
}

AWS Lambda поддерживает Node.js версии 8.10, которая полностью реализует этот синтаксис.

person ttulka    schedule 29.01.2019

Только что нашел решение: удаление ключевого слова «async» заставит его работать!

    exports.handler = (event, context, callback) => { ... }
person Adagyo    schedule 11.01.2019
comment
Это старый подход - person ttulka; 27.01.2019
comment
@ttulka, не могли бы вы объяснить немного больше? Дайте советы или примеры кода? - person Adagyo; 29.01.2019
comment
Я не понимаю, почему за это минусуют. Это старый подход, но он по-прежнему актуален. Могут ли люди, которые проголосовали против, хотя бы дать конструктивный отзыв о том, как можно улучшить ситуацию? - person Edward; 08.07.2019