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

时间:2017-02-23 13:05:22

标签: aws-lambda amazon-sqs

所以我实施了一个类似于此处的电子邮件系统:https://cloudonaut.io/integrate-sqs-and-lambda-serverless-architecture-for-asynchronous-workloads/

流程如下

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

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

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

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

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

2.SQSMessageConsumerLambda仍然有点瓶颈不是吗?因为它想要委派的每条消息调用MessageWorkerLambda大约需要40-50毫秒。或者,'async.parallel'是否可以减轻这种影响?

3.如果我们能够根据某些CloudWatch警报以某种方式弹性增加SQSMessageConsumerLambda的数量,那就更好了,即在X分钟内检查队列中是否有超过X条消息的警报?

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);

}

2 个答案:

答案 0 :(得分:1)

1)可见性超时是SQS的一项重要功能,允许您构建弹性系统。无法找到尝试自行处理故障的理由。

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

3)您可以添加其他CloudWatch事件规则,触发Consumer Lambda以增加读取吞吐量。

答案 1 :(得分:0)

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