代码之家  ›  专栏  ›  技术社区  ›  vincent

AWS Fifo停止使用Lambda油门

  •  0
  • vincent  · 技术社区  · 5 年前

    我们使用a Fifo Queue 配置了a Lambda Function 作为一个 processor .我们使用 MessageGoupId BatchSize 乐观地删除冗余消息。为了限制处理速率,我们使用 reserved concurrency .我们的职能 timeout 必须很高。排队 maximum receives 设置为10。

    观察

    当队列中有很多消息时,Lambda函数会放大。一旦它扩展到足以节流,队列处理就会完全停止,在几分钟过去之前,不会再处理任何消息。

    我假设这是因为节流,因为停止总是与节流同时发生,当增加保留的并发性时,处理停止需要更长的时间。

    我假设队列再次启动之前的时间与lambda有关 retry limit ,功能 超时 和队列 visibility timeout 但由于我不知道具体发生了什么,所以这只是猜测。

    问题

    没有记录错误,最终所有消息都会被处理,但由于处理对时间和吞吐量敏感,队列暂停数分钟是不可接受的。

    问题

    发生了什么,我们如何解决这个问题?如果需要更多信息,我很乐意进一步调试。


    编辑: 找到这个: To allow your function time to process each batch of records, set the source queue's visibility timeout to at least 6 times the timeout that you configure on your function. The extra time allows for Lambda to retry if your function execution is throttled while your function is processing a previous batch. 我们当然违反了规定,但我不确定这如何/是否解释了观察到的行为。

    0 回复  |  直到 5 年前
        1
  •  1
  •   vincent    5 年前

    如何繁殖

    在这个答案的末尾,有一个完整的、最小的例子,可以很容易地重现这个问题。

    要部署,请创建所有文件并填写aws profile 并期望 region 进入所有 sh 文件夹。

    然后跑

    .deploy-stack.sh

    创建包含所有必要资源的cloudformation堆栈。

    然后打开AWS web界面(SQS)并运行

    生成消息.sh

    在队列中生成消息。

    然后可以看到,在函数节流和队列完全停止之前,大约有一半的消息被处理。

    在所有调试完成后删除cloudformation堆栈 remove-stack.sh

    解决方案

    AWS文档 contains a note

    为了让您的函数有时间处理每一批记录,请将源队列的可见性超时设置为您在函数上配置的超时的至少6倍。如果您的函数在处理前一批时执行受阻,额外的时间允许Lambda重试。

    更改 timeout 关于lambda函数 600 100 重新部署堆栈允许所有消息正确处理,即使lambda函数节流。

    我无法解释为什么会观察到这种行为,非常感谢对此的反馈。然而,上述内容确实解决了所描述的问题。

    文件夹

    stack.yaml

    AWSTemplateFormatVersion: '2010-09-09'
    Transform: AWS::Serverless-2016-10-31
    Description: Debug Stack for Fifo with Lambda Processor
    Resources:
      MyLambdaRole:
        Type: AWS::IAM::Role
        Properties:
          RoleName:
            Fn::Sub: lambda-role
          AssumeRolePolicyDocument:
            Version: 2012-10-17
            Statement:
              - Action:
                  - sts:AssumeRole
                Effect: Allow
                Principal:
                  Service:
                    - lambda.amazonaws.com
          ManagedPolicyArns:
            - arn:aws:iam::aws:policy/AWSLambdaExecute
            - arn:aws:iam::aws:policy/AmazonSqsFullAccess
          Path: /
      MySqsQueue:
          Type: 'AWS::SQS::Queue'
          Properties:
            FifoQueue: true
            VisibilityTimeout: 600
      MySQSQueueFunction:
        Type: AWS::Lambda::Function
        Properties:
          Handler: index.handler
          Role: !GetAtt MyLambdaRole.Arn
          Runtime: nodejs12.x
          Timeout: 600
          ReservedConcurrentExecutions: 5
          Code:
            ZipFile: |
              exports.handler = (event, context) => new Promise((resolve) => {
                setTimeout(resolve, 1000);
              });
      MySQSLambdaEventSource:
        Type: AWS::Lambda::EventSourceMapping
        Properties:
          BatchSize: 1
          Enabled: false
          EventSourceArn: !GetAtt MySqsQueue.Arn
          FunctionName: !Ref MySQSQueueFunction
    Outputs:
      QueueUrl:
        Value:
          Ref: MySqsQueue
      EventSource:
        Value:
          Ref: MySQSLambdaEventSource
    

    deploy-stack.sh

    #!/bin/bash
    
    profile=local
    region=us-east-1
    
    # -----------------
    
    aws cloudformation deploy \
    --profile $profile \
    --region $region \
    --template-file stack.yaml \
    --stack-name fifo-lambda-debug \
    --capabilities CAPABILITY_NAMED_IAM
    

    generate-messages.sh

    #!/bin/bash
    
    profile=local
    region=us-east-1
    
    # -----------------
    
    function genGroupId {
      echo $(shuf -i 1-10 -n 1)
    }
    function genRndStr {
      echo $(openssl rand -hex 12)
    }
    function entry {
      echo "{\"Id\":\"$(genRndStr)\",\"MessageBody\":\"$(genRndStr)\",\"MessageGroupId\":\"$(genGroupId)\",\"MessageDeduplicationId\":\"$(genRndStr)\"}"
    }
    
    # -----------------
    
    echo "Getting Subscription UUID..."
    eventSource=$(aws cloudformation describe-stacks \
    --query "Stacks[0].Outputs[?OutputKey=='EventSource'].OutputValue" \
    --output text \
    --profile $profile \
    --region $region \
    --stack-name fifo-lambda-debug)
    
    echo "Getting Queue Url..."
    queueUrl=$(aws cloudformation describe-stacks \
    --query "Stacks[0].Outputs[?OutputKey=='QueueUrl'].OutputValue" \
    --output text \
    --profile $profile \
    --region $region \
    --stack-name fifo-lambda-debug)
    
    echo "Disabling Subscription"
    aws lambda update-event-source-mapping \
    --profile $profile \
    --region $region \
    --uuid $eventSource \
    --no-enabled \
    > /dev/null
    
    while : ; do
        echo "Waiting until Subscription disabled..."
        [[ $(aws lambda get-event-source-mapping \
          --profile $profile \
          --region $region \
          --uuid $eventSource \
          --query "State") != '"Disabled"' ]] || break
        sleep 10
    done
    
    echo "Queueing Messages..."
    for i in {1..30}
    do
      aws sqs send-message-batch \
      --profile $profile \
      --region $region \
      --queue-url "$queueUrl" \
      --entries "[$(entry),$(entry),$(entry),$(entry),$(entry),$(entry),$(entry),$(entry),$(entry),$(entry)]" \
      > /dev/null
      echo "Done: $i / 30"
    done
    
    echo "Re-Enabling Subscription..."
    aws lambda update-event-source-mapping \
    --profile $profile \
    --region $region \
    --uuid $eventSource \
    --enabled \
    > /dev/null
    

    remove-stack.sh

    #!/bin/bash
    
    profile=local
    region=us-east-1
    
    # -----------------
    
    aws cloudformation delete-stack \
    --profile $profile \
    --region $region \
    --stack-name fifo-lambda-debug