AWS Lambda 重试与死信队列(DLQ)

AWS Lambda 允许设置 Debugging and error handling, 在 Lambda 出现异常,达到最大的重试次数后,把以下信息放到选择的 SNS 或 SQS 主题作为死信队列(DLQ - Dead Letter Queue),包括

  1. 原始 Lambda 接收到的消息(基于 SNS 和 SQS 消息的总大小,可能会被截取,本人猜测,尤其是 Kinesis 的消息会比较大)
  2. 原始 Lambda 的 RequestId
  3. ErrorCode(三位数字的 HTTP 错误码)
  4. ErrorMessage, 即原 Lambda 抛出 Exception 的 getMessage() 信息,截取 1 KB 字符串

并且 Lambda 要使用 DLQ 的话还必须设置当前 Lambda 的 IAM role 有对于 SNS/SQS 主题相应的 sns:Publish 和 sqs:SendMessage 权限。

AWS Lambda 基本重试规则:对于 Kinesis 消息会无限重试直至消息过期,对于 SNS 或 SQS 的消息出现异常后会再重试两次。参考:AWS Lambda Retry Behavior

而在重试次数用完后仍然失败,并且设置了 DLQ 的话就会发送消息到 DLQ 中去。

最感观的理解就是来做一个测试,创建一个 Lambda test-dlq-lambda, 该 Lambda 由 SNS topic sns-test-topic 触发,并且设置 Lambda 的 DLQ 为另一个 SNS topic sns-test-dlq-topic。为方便测试,我们用同样的 Lambda 来接收 sns-test-dlq-topic 中的消息验证 DLQ 中的内容。

Lambda 的代码

以上代码打包成一个可部署的 Lambda jar 包,并且部署为两个  Lambda

  1. test-dlq-lambda: 监听 SNS topic sns-test-topic, 并且设置该 Lambda 的  DLQ 为 SNS topic sns-test-dlq-topic
  2. test-dlq-receiver-lambda: 监听  SNS topic sns-test-dlq-topic

发送消息给 test-dlq-lambda

发送消息的代码

消息中含有 dql,所以 test-dlq-lambda 将会触发 RuntimeException 异常,异常消息是 test dlq

Lambda test-dlq-lambda  的日志

从以上日志解读到的是

  1. 三次消费同一消息的时间点为 03:52:38, 03:53:35, 03:55:26,大概是 Lambda 第一次失败后, 一分钟后重试一次,第二次失败两分钟后再重试一次
  2. 三次 Lambda 执行都是相同的 RequestId:  7029fce0-c52d-11e8-a386-0dc76ede9b27
  3. 最多执行三次(针对 SNS 消息),最后把错误相关的信息送到所设定的 DLQ 中去
  4. 因为只发送了一条 SNS 消息,所以上面三次执行都是同一个 Lambda 实例,并发高的时候不确定重试是否也是由同一个 Lambda 实例

这里 Lambda 接收到的 SNS 消息是

DLQ sns-test-dlq-topic 中的消息

我们通过 test-dlq-receiver-lambda 的日志来了解前面 Lambda 三次重试后送到 DLQ 中的内容。它所收到的消息是

sns-test-dlq-topic 中的 SNS 消息条目包含以下关键内容(前面说过,在此重复一遍)

  1. 原始 Lambda 接收到的完整 sns 记录内容作为新 SNS 消息的 message 字段的字符串内容
  2. 原始 Lambda 的 RequestId,放在新 SNS 消息的 messageAttributes 中,键为 RequestID
  3. ErrorCode(三位数字的 HTTP 错误码),放在新 SNS 消息的 messageAttributes 中,键为 ErrorCode。不知如何设置不同的 ErrorCode。
  4. ErrorMessage, 即原 Lambda 抛出 Exception 的 getMessage() 信息,截取 1 KB 字符串,也是放在新 SNS 消息的 messageAttributes 中,键为 ErrorMessage

从这里发现从 DLQ 中的消息一个能用于追踪原 Lambda 的信息是 RequestId,还有就是 ErrorMessage,不过它只是取到原 Lambda 的异常的 getMessage() 消息,过于简单。但我们可以用 ErrorMessage 携带更有用的错误信息,看接下来

利用 DLQ 的 ErrorMessage 传递异常栈信息

从前面了解到 DLQ 中信息的 ErrorMessage 字段只是取了原 Lambda 异常的 getMessage() 消息,可能并不太助于定位错误点,所以我们也许希望用它来展示完整的异常栈信息。这需要在原始 Lambda 捕获异常后作些文章

把异常栈的信息转换为一个字符串作为新 RuntimeException 的 message, 现在来看 DLQ 中的消息的 ErrorMessage 部分的内容就是

"ErrorMessage": {
"type": "String",
"value": "Function: yanbin-test-dlq\njava.lang.RuntimeException: dlq\n\tat com.serverless.Handler.process(Handler.java:41)\n\tat com.serverless.Handler.handleRequest(Handler.java:29)\n\tat com.serverless.Handler.handleRequest(Handler.java:18)\n\tat lambdainternal.EventHandlerLoader$PojoHandlerAsStreamHandler.handleRequest(EventHandlerLoader.java:178)\n\tat lambdainternal.EventHandlerLoader$2.call(EventHandlerLoader.java:888)\n\tat lambdainternal.AWSLambda.startRuntime(AWSLambda.java:286)\n\tat lambdainternal.AWSLambda.<clinit>(AWSLambda.java:64)\n\tat java.lang.Class.forName0(Native Method)\n\tat java.lang.Class.forName(Class.java:348)\n\tat lambdainternal.LambdaRTEntry.main(LambdaRTEntry.java:94)\n"
}

原 Lambda 函数名也有了,并且带上了完整的异常信息。唯有不足的地方就是原始 Lambda 在输出该异常信息日志时重复了一遍异常栈

不过呢,应该不太碍事,每次 Lambda 失败只有一次重复出现。

上面测试的是被 SNS 触发的 Lambda,DLQ 也是设置的 SNS。可以对下面几种组合进行测试

  1. Trigger: Kinesis  -> DLQ:  SNS
  2. Trigger: Kinesis -> DLS: SQS
  3. Trigger: SNS -> DLQ: SQS
  4. Trigger: SQS  -> DLQ: SNS
  5. Trigger: SQS  -> DLQ: SQS

Kinesis 也不容易测试,因为 Kinesis 消息的重试是直至消息过期,可简单看下 DLQ 是 SQS 的话消息是如何包装的(直接从 AWS 的 SQS 控制台)

Message Body 内容:

就是原始的 SNS 消息内容

Message Attributes 内容:

同样包含以上列举的三个字段。

对本文有必要小结一下:

  1. SNS 触发的 Lambda 重试总共为三次,间隔时间分别为 1 分钟后,两分钟后
  2. Lambda 对 SNS 消息的多次重试所记录的  RequestId 是一样的
  3. DLQ 中的消息含有原 Lambda 执行时的 RequestId
  4. DLQ 中消息的 message 是原始 Lambda 接收到的消息内容
  5. DLQ 中消息的 ErrorMessage 字段是原始 Lambda 抛出异常的 getMessage() 内容,需要更丰富的信息自行包裹,再长不过 1KB

类别: AWS. 标签: , . 阅读(15). 订阅评论. TrackBack.

Leave a Reply

avatar
trackback

[…] 同时此篇也是作为上文 AWS Lambda 重试与死信队列(DLQ) 的一个很重要的补充。在此也会验证 SQS 触发的 Lambda 的重试机制以及 DLQ 相的内容。 […]