Aws lambda 从SQS队列读取的Lambda-瓶颈?

Aws lambda 从SQS队列读取的Lambda-瓶颈?,aws-lambda,amazon-sqs,Aws Lambda,Amazon Sqs,因此,我实施了一个电子邮件系统,就像这里的系统: 流程如下 结束电子邮件的http请求->api网关->HttpRequestLambda->SQS SQSMessageConsumerLambda(计划)->MessageWorkerLambda(通过电子邮件服务提供商发送电子邮件) 我的SQSMessageConsumerLambda计划每分钟运行一次 我将SQS使用者更改为在接近超时时递归调用自己,而不是仅仅结束。这样做意味着SQS队列更有可能不会堆积太多的消息 到目前为止,这似乎很有效,

因此,我实施了一个电子邮件系统,就像这里的系统:

流程如下

结束电子邮件的http请求->api网关->HttpRequestLambda->SQS SQSMessageConsumerLambda(计划)->MessageWorkerLambda(通过电子邮件服务提供商发送电子邮件)

我的SQSMessageConsumerLambda计划每分钟运行一次

我将SQS使用者更改为在接近超时时递归调用自己,而不是仅仅结束。这样做意味着SQS队列更有可能不会堆积太多的消息

到目前为止,这似乎很有效,但我有几个问题:

1.如果函数timeout,则从队列读取的消息可能仍在其可见性超时期间内,因此递归调用lambda意味着在其可见性超时到期之前无法从队列中重新读取消息,而递归调用后可能不会立即如此。那么将这些消息传递到递归调用本身是一个好主意吗?然后以某种方式检查消费者lambda开头的这些“传入消息”,并在这种情况下直接发送给工人

2.SQSMessageConsumerLambda仍然有点瓶颈,不是吗?因为为每个要委派的消息调用MessageWorkerLambda大约需要40-50毫秒。或者,“async.parallel”是否缓解了这一问题

3.如果我们可以根据一些CloudWatch警报(即检查队列上是否有超过X条消息持续X分钟的警报)以某种方式弹性地增加SQSMessageConsumerLambda的数量,这会更好吗

var AWS = require('aws-sdk');

var sqs = new AWS.SQS();

var async = require("async");

var lambda = new AWS.Lambda();

var QUEUE_URL = `https://sqs.${process.env.REGION}.amazonaws.com/${process.env.ACCOUNT_ID}/${process.env.STAGE}-emailtaskqueue`;

var EMAIL_WORKER = `${process.env.SERVICE}-${process.env.STAGE}-emailWorker`


var THIS_LAMBDA = `${process.env.SERVICE}-${process.env.STAGE}-emailTaskConsumer`

function receiveMessages(callback) {

    var numMessagesToRead = 10;

    //console.log('in receiveMessages, about to read ',numMessagesToRead);
    //WaitTimeSeconds : The duration (in seconds) for which the call waits for a message to arrive in the queue before returning
    var params = {
        QueueUrl: QUEUE_URL,
        MaxNumberOfMessages: numMessagesToRead,
        WaitTimeSeconds: 20
    };
    sqs.receiveMessage(params, function(err, data) {
        if (err) {
            console.error(err, err.stack);
            callback(err);
        } else {
            if (data.Messages && data.Messages.length > 0) {
                console.log('Got ',data.Messages.length, ' messages off the queue' );
            }else{
                console.log('Got no messages from queue');
            }
            callback(null, data.Messages);
        }
    });
}


function invokeWorkerLambda(task, callback) {

    console.log('Need to invoke worker for this task..',task);

    //task.Body is a json string
    var payload =  {
        "ReceiptHandle" : task.ReceiptHandle,
        "body" : JSON.parse(task.Body)
    };

    console.log('payload:',payload);

    //using 'Event' means use async (http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#invoke-property)
    //TODO need variable here
    var params = {
        FunctionName: EMAIL_WORKER,
        InvocationType: 'Event',
        Payload: JSON.stringify(payload)
    };
    var millis = Date.now();
    lambda.invoke(params, function(err, data) {
        millis =  Date.now() - millis;
        console.log('took ', millis, ' to invoke ', EMAIL_WORKER, ' asynchronously');
        if (err) {
            console.error(err, err.stack);
            callback(err);
        } else {
            callback(null, data)
        }
    });

}

function handleSQSMessages(context, callback) {
    //console.log('in handleSQSMessages');
    receiveMessages(function(err, messages) {
        if (messages && messages.length > 0) {
            var invocations = [];
            messages.forEach(function(message) {
                invocations.push(function(callback) {
                    invokeWorkerLambda(message, callback)
                });
            });
            async.parallel(invocations, function(err) {
                if (err) {
                    console.error(err, err.stack);
                    callback(err);
                } else {
                    if (context.getRemainingTimeInMillis() > 20000) {
                        console.log('there is more time to read more messages for this run of the cron')
                        handleSQSMessages(context, callback);
                    } else {



                        console.log('remaining time in millis:',context.getRemainingTimeInMillis(),' No more time here, invoking this lambda again')

                        lambda.invoke({FunctionName: THIS_LAMBDA, InvocationType: 'Event',Payload: '{"recursiveMarker":true}' }, function(err, data) {

                            if (err) {
                                console.error(err, err.stack);
                                callback(err);
                            } else {
                                console.log('data from the invocation:', data);
                                callback(null, 'Lambda was just called recursively');
                            }
                        });


                    }
                }
            });
        } else {
            callback(null, "DONE");
        }
    });
}

module.exports.emailTaskConsumer = (event, context, callback) => {


    console.log('in an emailTaskConsumer. Was this a recursive call ?', event);
    handleSQSMessages(context, callback);

}
1) 可见性超时是SQS的一个重要功能,它允许您构建弹性系统。找不到自己尝试处理失败的原因

2) 您可以将从队列读取的所有消息批处理到Worker Lambda,同时处理它们


3) 您可以添加额外的CloudWatch事件规则来触发使用者Lambda以增加读取吞吐量。

使用SNS来触发Lambda。这是使用Lambda函数的正确方法。您的HttpRequestLambda将触发SNS通知,并立即触发另一个Lambda函数以响应该事件。实际上,如果您在HttpRequestLambda中不做任何其他事情,您也可以用AWSAPI代理替换它。你可以看到关于通过API网关公开SNS API的完整教程。

re 1)如果我将超时设置得太低,那么它将导致重复2)可能是最好的选择,但不是很好,因为我真的希望工作人员执行单个任务3)最大读取吞吐量为10,所以这不好@AndreasI我本来想使用SNS,但后来读到它会导致重复复制消息需要另一个级别的重复数据消除逻辑来处理复制消息是非常偶然的。我不会花那么多精力来减轻这种风险。使用Lambda来消费SQS只会让设计变得糟糕。这里有一些关于SQS和Lambda的讨论: