你可以使用内部扩展来修改运行时进程。内部扩展不是独立的进程,它们作为运行时进程的一部分运行。
Lambda提供了特定于语言的环境变量,你可以设置这些变量来为运行时添加选项和工具。Lambda还提供了包装脚本,它允许Lambda将运行时的启动委托给你的脚本。你可以创建一个包装脚本来定制运行时的启动行为。
Lambda支持纯配置的方式,通过以下特定语言的环境变量,使代码在函数初始化过程中被预先加载:
JAVA_TOOL_OPTIONS - 在 Java 11 和 Java 8 (java8.al2) 上,Lambda 支持这个环境变量来设置 Lambda 中的额外命令行变量。这个环境变量允许你指定工具的初始化,特别是使用agentlib或javaagent选项启动本地或Java编程语言代理。
NODE_OPTIONS - 在Node.js 10x及以上版本,Lambda支持这个环境变量。
DOTNET_STARTUP_HOOKS - 在.NET Core 3.1及以上版本中,该环境变量指定了Lambda可以使用的程序集(dll)的路径。
使用特定语言的环境变量是设置启动属性的首选方式。
Java虚拟机(JVM)试图找到用javaagent参数指定给JVM的类,并在应用程序的入口点之前调用其premain方法。
下面的例子使用了Byte Buddy,这是一个在Java应用程序运行期间创建和修改Java类的库,无需编译器的帮助。Byte Buddy提供了一个额外的API用于生成Java代理。在这个例子中,代理类拦截了对RequestStreamHandler类的handleRequest方法的每次调用。这个类在运行时内部使用,以包住处理程序的调用。
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
.with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager())
.type(ElementMatchers.isSubTypeOf(RequestStreamHandler.class))
.transform((builder, typeDescription, classLoader, module) -> builder
.method(ElementMatchers.nameContains("handleRequest"))
.intercept(Advice.to(TimerAdvice.class)))
.installOn(inst);
}
}
前面例子中的代理使用TimerAdvice方法。TimerAdvice测量方法调用花了多少毫秒,并记录方法时间和细节,如名称和传递的参数。
import static net.bytebuddy.asm.Advice.AllArguments;
import static net.bytebuddy.asm.Advice.Enter;
import static net.bytebuddy.asm.Advice.OnMethodEnter;
import static net.bytebuddy.asm.Advice.OnMethodExit;
import static net.bytebuddy.asm.Advice.Origin;
public class TimerAdvice {
@OnMethodEnter
static long enter() {
return System.currentTimeMillis();
}
@OnMethodExit
static void exit(@Origin String method, @Enter long start, @AllArguments Object[] args) {
StringBuilder sb = new StringBuilder();
for (Object arg : args) {
sb.append(arg);
sb.append(", ");
}
System.out.println(method + " method with args: " + sb.toString() + " took " + (System.currentTimeMillis() - start) + " milliseconds ");
}
}
上面的TimerAdvice方法有以下依赖性。
*'com.amazonaws'*, *name*: *'aws-lambda-java-core'*, *version*: *'1.2.1'*
*'net.bytebuddy'*, *name*: *'byte-buddy-dep'*, *version*: *'1.10.14'*
*'net.bytebuddy'*, *name*: *'byte-buddy-agent'*, *version*: *’1.10.14'*
在你创建了一个包含代理JAR的层后,你可以通过设置环境变量将JAR名称传递给运行时的JVM。
JAVA_TOOL_OPTIONS=-javaagent:”/opt/ExampleAgent-0.0.jar"
在用{key=lambdaInput}调用函数后,你可以在日志中找到以下一行。
public java.lang.Object lambdainternal.EventHandlerLoader$PojoMethodRequestHandler.handleRequest
(java.lang.Object,com.amazonaws.services.lambda.runtime.Context) method with args:
{key=lambdaInput}, lambdainternal.api.LambdaContext@4d9d1b69, took 106 milliseconds
当一个扩展在Shutdown事件中被注册时,运行时进程会得到最多500毫秒的时间来处理优雅的关闭。你可以钩住运行时进程,当JVM开始其关机过程时,它将启动所有注册的钩子。要注册一个关机钩子,你必须注册为一个扩展。你不需要明确地注册关机事件,因为那会自动发送到运行时。
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
// Register the extension.
// ...
// Register the shutdown hook
addShutdownHook();
}
private static void addShutdownHook() {
// Shutdown hooks get up to 500 ms to handle graceful shutdown before the runtime is terminated.
//
// You can use this time to egress any remaining telemetry, close open database connections, etc.
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// Inside the shutdown hook's thread we can perform any remaining task which needs to be done.
}));
}
}
@OnMethodEnter
static long enter() {
String invokedFunctionArn = null;
for (Object arg : args) {
if (arg instanceof Context) {
Context context = (Context) arg;
invokedFunctionArn = context.getInvokedFunctionArn();
}
}
}
你可以创建一个封装脚本来定制Lambda函数的运行时启动行为。封装脚本使你能够设置无法通过特定语言环境变量设置的配置参数。
以下Lambda运行时支持封装脚本:
Node.js 14.x
Node.js 12.x
Node.js 10.x
Python 3.9
Python 3.8
Ruby 2.7
Java 11
Java 8 (java8.al2)
.NET Core 3.1
当你为你的函数使用包装脚本时,Lambda会使用你的脚本启动运行时。Lambda会向你的脚本发送解释器的路径以及标准运行时启动的所有原始参数。你的脚本可以扩展或改变程序的启动行为。例如,脚本可以注入和改变参数,设置环境变量,或捕获指标、错误和其他诊断信息。
你通过设置AWS_LAMBDA_EXEC_WRAPPER环境变量的值作为可执行二进制文件或脚本的文件系统路径来指定该脚本。
在下面的例子中,你创建了一个包装脚本,用-X importtime选项启动Python解释器。当你运行该函数时,Lambda会生成一个日志条目,显示每次导入的时间长度。
用Python 3.8创建并使用一个封装器脚本
1.要创建包装器脚本,请将以下代码粘贴到一个名为importtime_wrapper的文件中。
#!/bin/bash
# the path to the interpreter and all of the originally intended arguments
args=("$@")
# the extra options to pass to the interpreter
extra_args=("-X" "importtime")
# insert the extra options
args=("${args[@]:0:$#-1}" "${extra_args[@]}" "${args[@]: -1}")
# start the runtime with the extra options
exec "${args[@]}"
2.要给脚本以可执行的权限,在命令行中输入chmod +x importtime_wrapper。
3.将该脚本部署为Lambda层。
4.使用Lambda控制台创建一个函数。
打开Lambda控制台。
选择创建函数。
在基本信息下,对于函数名称,输入 wrapper-test-function。
在Runtime中,选择Python 3.8。
选择 Create function。
5.将该层添加到你的函数中。
选择你的函数,然后选择代码,如果它还没有被选中。
选择添加一个图层。
在Choose a layer下,选择你先前创建的兼容层的名称和版本。
选择添加。
6.将代码和环境变量添加到你的函数中。
在函数代码编辑器中,粘贴以下函数代码:
import json
def lambda_handler(event, context):
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
选择保存。
在环境变量下,选择编辑。
选择添加环境变量。
对于键,输入AWS_LAMBDA_EXEC_WRAPPER。
对于值,输入/opt/importtime_wrapper。
选择保存。
7.要运行该功能,请选择测试。
因为你的包装脚本用 -X importtime 选项启动了 Python 解释器,日志显示了每次导入所需的时间。比如说:
...
2020-06-30T18:48:46.780+01:00 import time: 213 | 213 | simplejson
2020-06-30T18:48:46.780+01:00 import time: 50 | 263 | simplejson.raw_json
...