AWS Lambda
开发人员指南
AWS 服务或AWS文档中描述的功能,可能因地区/位置而异。点 击 Getting Started with Amazon AWS to see specific differences applicable to the China (Beijing) Region.

基于 Lambda 的应用程序问题排查

基于 Lambda 的典型应用程序包含一个或多个通过事件 (如将对象上传到 Amazon S3、Amazon SNS 通知和 API 操作) 触发的函数。这些函数在触发后通常会调用下游资源,如 DynamoDB 表或 Amazon S3 存储桶,或执行其他 API 调用。AWS Lambda 针对函数的所有调用,利用 Amazon CloudWatch 自动发送指标和日志。但是,此机制可能无法方便地跟踪调用您的 Lambda 函数的事件源,或跟踪函数进行的下游调用。有关跟踪的工作方式的完整概述,请参阅 AWS X-Ray

利用 AWS X-Ray 跟踪基于 Lambda 的应用程序

AWS X-Ray 是一种 AWS 服务,使您可以通过 AWS Lambda 应用程序检测、分析和优化性能问题。X-Ray 从 Lambda 服务和组成您的应用程序的所有上游或下游服务中收集元数据。X-Ray 使用这些元数据生成详细的服务图形,用于说明性能瓶颈,延迟峰值,以及影响 Lambda 应用程序性能的其他问题。

使用 AWS X-Ray 服务地图上的 Lambda 识别出有问题的资源或组件后,您可以进行放大,查看请求的可视化形式。此可视化形式涵盖的时间范围从事件源触发 Lambda 函数开始,直到函数执行完成。X-Ray 可以为您提供函数操作的分析结果,如 Lambda 函数对其他服务进行的下游调用的信息。此外,X-Ray 与 Lambda 的集成还便于您了解 AWS Lambda 服务开销。它通过显示请求的停留时间和调用数量等具体信息来实现此功能。

注意

只有目前与 X-Ray 集成的服务可在您的 Lambda 跟踪之外显示为独立跟踪。有关当前支持 X-Ray 的服务列表,请参阅将 AWS X-Ray 与其他 AWS 服务集成

利用 Lambda 设置 AWS X-Ray

以下是利用 Lambda 设置 X-Ray 的详细信息。

开始前的准备工作

要使用 Lambda CLI 启用对 Lambda 函数的跟踪,您必须首先对函数的执行角色添加跟踪权限。要实现此目的,请执行以下步骤:

  • 登录 AWS 管理控制台 并通过以下网址打开 IAM 控制台 https://console.amazonaws.cn/iam/

  • 查找您的 Lambda 函数的执行角色。

  • 附加以下托管策略:AWSXrayWriteOnlyAccess

要了解有关这些策略的更多信息,请参阅 AWS X-Ray

如果您要使用 Lambda 控制台将跟踪模式更改为“活跃”,将如下节所述,自动添加跟踪权限。

跟踪

通过跟踪 ID 来跟踪请求在您的应用程序中传输的路径。跟踪会收集单个请求 (通常是 HTTP GET 或 POST 请求) 生成的所有分段。

Lambda 函数的跟踪有两种模式:

  • 传递:如果您为函数的执行角色添加了跟踪权限,这是所有 Lambda 函数的默认设置。此方法意味着,只有上游服务 (例如 AWS Elastic Beanstalk) 启用 X-Ray 后才会跟踪 Lambda 函数。

  • 活跃:如果 Lambda 函数具有此设置,Lambda 会自动根据 X-Ray 指定的采样算法对调用请求进行采样。

    注意

    X-Ray 应用采样算法确保跟踪有效,同时为应用程序所服务的请求提供代表性样本。默认的采样算法是每分钟 1 个请求,超过此限制的请求采样 5%。但是,如果函数的流量不大,采样率可能会增大。

您可以使用 Lambda 管理控制台或 Lambda CreateFunctionUpdateFunctionConfiguration API 操作,更改 Lambda 函数的跟踪模式。

如果您使用 Lambda 控制台,则适用以下规则:

  • 如果您将函数的跟踪模式更改为“活跃”,跟踪权限会自动附加到该函数的执行角色。如果收到错误信息,说明 Lambda 无法将 AWSXrayWriteOnlyAccess 策略添加到函数的执行角色,请登录 IAM 控制台 https://console.amazonaws.cn/iam/,手动添加策略。

  • 要启用活动跟踪,请转到 Configuration 选项卡,然后选择 Enable active tracing 框。

如果您使用 Lambda CreateFunctionUpdateFunctionConfiguration API 操作:

  • 如果您希望跟踪模式为“活跃”,请将 TracingConfig 参数的 Mode 属性设置为 Active. 请注意,任何新函数的默认跟踪模式都是 PassThrough

  • 任何新建或更新的 Lambda 函数的 $LATEST 版本均会设置为您指定的值。

    注意

    如果您没有为函数的执行角色添加跟踪权限,将收到一条错误消息。有关更多信息,请参阅 开始前的准备工作

AWS X-Ray 服务地图上的 Lambda

针对 Lambda 处理的请求,X-Ray 在服务地图上显示三种类型的节点:

  • Lambda 服务 (AWS::Lambda) – 此类节点表示请求用于 Lambda 服务的时间。Lambda 首次接收请求时开始计时,直到请求离开 Lambda 服务时计时结束。

  • Lambda 函数 (AWS::Lambda::Function) – 此类节点表示 Lambda 函数的执行时间。

  • 下游服务调用 – 在这种类型中,Lambda 函数发出的每个下游服务调用均表示为一个单独的节点。

下图中的节点 (从左到右) 依次代表:Lambda 服务、用户函数和对 Amazon S3 的下游调用:

有关更多信息,请参阅查看服务地图

Lambda 作为 AWS X-Ray 跟踪

您可以在服务地图中进行放大,查看您的 Lambda 函数的跟踪视图。跟踪将显示与函数调用相关的深度信息,以分段和子分段表示。

  • Lambda 服务分段 – 此分段表示不同的信息,具体取决于调用函数的事件源:

    • 同步和流事件源 – 此服务分段计算从 Lambda 服务接收请求/事件,直到请求离开 Lambda 服务 (完成请求的最终调用) 所经历的时间。

    • 异步 – 此服务分段表示响应时间,即 Lambda 服务向客户端返回 202 响应所需的时间。

    Lambda 服务分段可以包含两种类型的子分段:

    • 停留时间 (仅限异步调用) – 表示函数在被调用之前在 Lambda 服务中花费的时间。这个子分段在 Lambda 服务接收请求/事件时开始,在首次调用 Lambda 函数时结束。

    • 尝试 – 表示单次调用尝试,包括 Lambda 服务引起的任何开销。开销示例包括初始化函数代码所花费的时间和函数执行时间。

  • Lambda 函数分段 – 表示函数针对给定调用尝试的执行时间。该分段于函数处理程序启动时开始,函数终止时结束。该分段可以包含三种类型的子分段:

    • 初始化 - 运行函数 initialization 代码 (定义为 Lambda 函数处理程序或静态初始化程序的外部代码) 所花费的时间。

    • 下游调用 - Lambda 函数的代码对其他 AWS 服务的调用。

    • 自定义子分段 - 自定义子分段或用户注释,可以使用 X-Ray 开发工具包添加到 Lambda 函数分段中。

注意

对于每个跟踪调用,Lambda 会发送 Lambda 服务分段及其所有子分段。发送这些分段无需考虑运行时,也不需要更改代码。

从 Lambda 函数发送跟踪分段

对于每个跟踪调用,Lambda 会发送 Lambda 服务分段及其所有子分段。此外,Lambda 将发送 Lambda 函数分段和 init 子分段。发送这些分段无需考虑函数的运行时,也不需要更改代码或其他库。如果您希望 Lambda 函数的 X-Ray 跟踪包含自定义分段、注释或来自下游调用的子分段,可能需要包含其他库并对代码添加注释。请注意,任何分析都必须在 Lambda 函数处理程序内而不是在初始化代码中实现。您只能为自己创建的子分段添加注释,不能为根 Lambda 函数分段添加注释。

注意

目前,这些额外功能 (自定义分段、注释和下游调用子分段) 适用于 Node.js、Python 和 Java 运行时。请注意,任何分析都必须在 Lambda 函数处理程序内而不是在初始化代码中实现。

Node.js

在 Node.js 中,您可以让 Lambda 向 X-Ray 发送子分段,显示您的函数对其他 AWS 服务进行的下游调用的相关信息。要执行此操作,首先需要在部署程序包中包含适用于 Node.js 的 AWS X-Ray 开发工具包。此外,请按如下方式封装您的 AWS 开发工具包 require 语句:

Copy
var AWSXRay = require('aws-xray-sdk-core'); var AWS = AWSXRay.captureAWS(require('aws-sdk'));

然后,使用前例中定义的 AWS 变量初始化 X-Ray 需要跟踪的所有服务客户端,例如:

Copy
s3Client = AWS.S3();

完成这些步骤后,您的函数使用 s3Client 进行的任何调用都会生成代表该调用的 X-Ray 子分段。您可以运行如下 Node.js 函数示例,了解跟踪在 X-Ray 中的样子:

Copy
var AWSXRay = require('aws-xray-sdk-core'); var AWS = AWSXRay.captureAWS(require('aws-sdk')); s3 = new AWS.S3({signatureVersion: 'v4'}); exports.handler = (event, context, callback) => { var params = {Bucket: BUCKET_NAME, Key: BUCKET_KEY, Body: BODY}; s3.putObject(params, function(err, data) { if (err) { console.log(err) } else { console.log('success!') } }); };

以下是上述代码发送的跟踪的样子 (同步调用):

Java

在 Java 中,您可以让 Lambda 向 X-Ray 发送子分段,显示您的函数对其他 AWS 服务进行的下游调用的相关信息。要利用此功能,请在您的部署程序包中包括适用于 Java 的 AWS X-Ray 开发工具包。无需更改代码。只要您使用的 AWS 开发工具包版本为 1.11.48 或更早版本,则不需要添加任何其他代码行,就可以跟踪您的函数的下游调用。

AWS 开发工具包将动态导入 X-Ray 开发工具包,针对您的函数进行的下游调用发送子分段。您可以使用适用于 Java 的 X-Ray 开发工具包检测代码,从而发送自定义子分段和/或在 X-Ray 分段中添加注释。

以下示例使用适用于 Java 的 X-Ray 开发工具包检测 Lambda 函数,以发送自定义子分段并将自定义注释发送到 X-Ray:

Copy
package uptime; import java.io.IOException; import java.time.Instant; import java.util.HashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import com.amazonaws.regions.Regions; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.xray.AWSXRay; import com.amazonaws.xray.proxies.apache.http.HttpClientBuilder; public class Hello { private static final Log logger = LogFactory.getLog(Hello.class); private static final AmazonDynamoDB dynamoClient; private static final HttpClient httpClient; static { dynamoClient = AmazonDynamoDBClientBuilder.standard().withRegion(Regions.US_EAST_1).build(); httpClient = HttpClientBuilder.create().build(); } public void checkUptime(Context context) { AWSXRay.createSubsegment("makeRequest", (subsegment) -> { HttpGet request = new HttpGet("https://aws.amazon.com/"); boolean is2xx = false; try { HttpResponse response = httpClient.execute(request); is2xx = (response.getStatusLine().getStatusCode() / 100) == 2; subsegment.putAnnotation("responseCode", response.getStatusLine().getStatusCode()); } catch (IOException ioe) { logger.error(ioe); } Map<String, AttributeValue> item = new HashMap<>(); item.put("Timestamp", new AttributeValue().withN("" + Instant.now().getEpochSecond())); item.put("2xx", new AttributeValue().withBOOL(is2xx)); dynamoClient.putItem("amazon-2xx", item); }); } }

以下是上述代码发送的跟踪的样子 (同步调用):

Python

在 Python 中,您可以让 Lambda 向 X-Ray 发送子分段,显示您的函数对其他 AWS 服务进行的下游调用的相关信息。要执行此操作,首先需要在部署程序包中包含适用于 Python 的 AWS X-Ray 开发工具包。此外,您还可以修补 boto3 (或 botocore,如果您使用的是会话),以便您为了访问其他 AWS 服务而创建的任何客户端都将自动被 X-Ray 跟踪。

Copy
import boto3 from aws_xray_sdk.core import xray_recorder from aws_xray_sdk.core import patch patch(['boto3'])

您修补用于创建客户端的模块后,便可以使用它来创建您的被跟踪客户端,在下面的情况中 Amazon S3:

Copy
s3_client = boto3.client('s3')

适用于 Python 的 X-Ray 开发工具包为调用创建子分段,并记录请求和响应中的信息。您可以使用 aws_xray_sdk_sdk.core.xray_recorder 通过装饰 Lambda 函数自动创建子分段,或通过在函数内调用 xray_recorder.begin_subsegment()xray_recorder.end_subsegment() 手动创建子分段,如以下 Lambda 函数中所示。

Copy
import boto3 from aws_xray_sdk.core import xray_recorder from aws_xray_sdk.core import patch patch(['boto3']) s3_client = boto3.client('s3') def lambda_handler(event, context): bucket_name = event['bucket_name'] bucket_key = event['bucket_key'] body = event['body'] put_object_into_s3(bucket_name, bucket_key, body) get_object_from_s3(bucket_name, bucket_key) # Define subsegments manually def put_object_into_s3(bucket_name, bucket_key, body): try: xray_recorder.begin_subsegment('put_object') response = s3_client.put_object(Bucket=bucket_name, Key=bucket_key, Body=body) status_code = response['ResponseMetadata']['HTTPStatusCode'] xray_recorder.current_subsegment().put_annotation('put_response', status_code) finally: xray_recorder.end_subsegment() # Use decorators to automatically set the subsegments @xray_recorder.capture('get_object') def get_object_from_s3(bucket_name, bucket_key): response = s3_client.get_object(Bucket=bucket_name, Key=bucket_key) status_code = response['ResponseMetadata']['HTTPStatusCode'] xray_recorder.current_subsegment().put_annotation('get_response', status_code)

注意

适用于 Python 的 X-Ray 开发工具包能让您修补以下模块:

  • botocore

  • boto3

  • 请求

  • sqlite3

  • mysql

您可以使用 patch_all() 来同时将它们全部修补。

以下是上述代码发送的跟踪的样子 (同步调用):

Lambda 环境中的 AWS X-Ray 守护程序

AWS X-Ray 守护程序是收集原始分段数据并将其中继到 AWS X-Ray 服务的软件应用程序。守护程序与 AWS X-Ray 开发工具包结合使用,使得开发工具包发送的数据可以到达 X-Ray 服务。

当您跟踪 Lambda 函数时,X-Ray 守护程序会自动在 Lambda 环境中运行,收集跟踪数据并发送到 X-Ray。进行跟踪时,X-Ray 守护程序会占用最多 16 MB 或 3% 的函数内存分配。例如,如果为 Lambda 函数分配 128 MB 的内存,X-Ray 守护程序会占用 16 MB 的函数内存分配。如果您为 Lambda 函数分配了 1024 MB 内存,分配给 X-Ray 守护程序的内存为 31 MB (3%)。有关更多信息,请参阅 AWS X-Ray 守护程序

注意

Lambda 将尝试终止 X-Ray 守护程序,以免超出函数的内存限制。例如,假设您为 Lambda 函数分配了 128 MB 内存,这就意味着 X-Ray 守护程序将获得 16 MB。这样您的 Lambda 函数获得的内存分配为 112 MB。但是,如果您的函数超过 112 MB,X-Ray 守护程序将被终止,以避免引发内存不足错误。

使用环境变量与 AWS X-Ray 通信

AWS Lambda 会自动生成三个环境变量,方便与 X-Ray 守护程序进行通信,并设置 X-Ray 开发工具包的配置:

  • _X_AMZN_TRACE_ID:包含跟踪标头,其中包括采样决策、跟踪 ID 和父分段 ID。(要了解有关这些属性的更多信息,请参阅跟踪标头。)如果调用您的函数时 Lambda 收到跟踪标头,该标头将用于填充 _X_AMZN_TRACE_ID 环境变量。如果 Lambda 未收到跟踪标头,将为您生成一个跟踪标头。

  • AWS_XRAY_CONTEXT_MISSING:您的函数尝试记录 X-Ray 数据,但跟踪标头不可用时,X-Ray 开发工具包使用此变量确定其行为。默认情况下,Lambda 将此值设置为 LOG_ERROR

  • AWS_XRAY_DAEMON_ADDRESS:此环境变量公开 X-Ray 守护程序的地址,格式为:IP_ADDRESS:PORT。您可以使用 X-Ray 守护程序的地址,直接将跟踪数据发送到 X-Ray 守护程序,而无需使用 X-Ray 软件开发工具包。

在 AWS X-Ray 控制台中跟踪 Lambda:示例

以下示例对两个不同的 Lambda 函数进行了 Lambda 跟踪。每个跟踪展示不同调用类型的跟踪结构:异步和同步。

  • 异步 - 以下示例展示具有一个成功调用和一个对 DynamoDB 进行下游调用的异步 Lambda 请求。

    Lambda 服务分段封装响应时间,即将响应 (例如 202) 返回至客户端所用的时间。其中包括在 Lambda 服务队列中花费的时间 (停留时间) 的子分段和每次调用尝试的子分段。(以上示例中只出现了一次调用尝试。) 服务分段中的每次尝试子分段具有相应的用户函数分段。在此示例中,用户函数分段包含两个子分段:初始化子分段,表示函数在处理程序之前运行的初始化代码,以及下游调用子分段,表示对 DynamoDB 的 ListTables 调用。

    每个调用子分段和每个下游调用都会显示状态代码和错误消息。

  • 同步 - 以下示例展示对 Amazon S3 进行下游调用的同步请求。

    Lambda 服务分段捕获请求在 Lambda 服务中花费的全部时间。该服务分段将有一个相应的用户函数分段。在此示例中,用户函数分段包含一个表示函数初始化代码 (在处理程序之前运行的代码) 的子分段;以及表示对 Amazon S3 的 PutObject 调用的子分段。