当抛出未捕获的异常时,任何亚马逊AWS Lambda Java函数都会花费不合理的长时间来完成,这正常吗?请注意,这是一个关于Java中AmazonLambdas的一般问题,因为我用一个非常简单的基本函数以一种非常通用的方式测试它。
例如,考虑下面的函数,该函数用于验证PIN。如果PIN有效,则返回文本:PIN is OK: "A"
。否则,它抛出一个IOException
:
public class Hello implements RequestStreamHandler {
private static final int BUFFER_SIZE = 65_536;
private static final int MAX_SIZE = 262_144;
private static final String CHARSET_UTF8 = "UTF-8";
private static final byte[] buffer = new byte[BUFFER_SIZE];
private static final ByteArrayOutputStream baos = new ByteArrayOutputStream();
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
String input = readInputStreamToString(inputStream);
// PIN is valid.
if (input.equals(""A""))
writeStringToOutputStream(outputStream, "PIN is OK: " + input);
// PIN is not valid.
else
throw new IOException("PIN is wrong: " + input);
}
private String readInputStreamToString(InputStream inputStream) throws IOException {
baos.reset();
int length, total = 0;
while ((length = inputStream.read(buffer)) != -1) {
total += length;
if (total > MAX_SIZE) throw new IllegalStateException("InputStream bigger than " + MAX_SIZE + ".");
baos.write(buffer, 0, length);
}
return baos.toString(CHARSET_UTF8);
}
private void writeStringToOutputStream(OutputStream outputStream, String info) throws IOException {
byte[] chars = info.getBytes(CHARSET_UTF8);
outputStream.write(chars, 0, chars.length);
}
}
测试上述代码:
对于有效的PIN,请使用
"A"
作为测试数据。对于无效的PIN,请使用任何其他输入,例如:
"B"
。
内存大小为128 MB,使用的最大内存为48 MB。当PIN有效时,该功能非常快,并且在不到1毫秒的时间内退出。然而,当PIN无效时,该函数在3秒内超时,我得到了这个:
{
"errorMessage": "2017-10-15T21:35:58.744Z *** Task timed out after 3.00 seconds",
"errorType": "java.lang.RuntimeException"
}
然后我把超时时间增加到10秒,现在它实际上完成了7.5秒左右,给了我一个堆叠比赛:
{
"errorMessage": "PIN is wrong: "B"",
"errorType": "java.io.IOException",
"stackTrace": [ "example.Hello.handleRequest(Hello.java:83)" ]
}
我的问题:
1) lambda函数中的异常需要亚马逊花那么多时间来处理,这正常吗?为什么?如果没有,为什么我会有这个问题?
2) 处理异常的建议方法是什么?我应该永远不让函数以异常结束吗?
1)不要介意使用的最大内存。根据我的经验,128 MB对于Java函数来说通常是很小的,不仅仅是JVM开销引起的例外。你应该把它增加到至少4倍。但也要记住,例外情况并非免费:
请参阅前面的问题:
Java异常的速度有多慢?
抛出异常的哪个部分比较昂贵?
请注意,增加资源对您来说并不一定意味着更多的成本,尤其是如果您的函数是CPU绑定的。你需要做实验。
2) 您可以根据应用程序可能引发的异常返回特定的HTTP状态代码。但是这种灵活性将为您的代码添加一些样板。参见:
http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html?shortFooter=true#api-网关代理集成lambda函数java
我似乎发现了问题。AmazonLambda似乎会进行某种内部"异常初始化",只有在第一次它在容器中获得异常时(要明确的是,我指的是用户的处理程序,并且允许冒泡到内部AmazonLambda代码)。
因此,假设您有一些代码很少发出异常,并且它在1秒内运行,超时时间为3秒。如果此代码抛出未捕获的异常(由于错误或设计原因),Lambda将初始化其内部异常处理,对于128MB的最低内存配置,这需要大约7秒。由于超时时间为3秒,因此没有时间完成,初始化也不会完成。下次它抛出异常时,初始化将再次启动并再次超时。
如果增加内存,异常初始化将运行得更快,并且可能在超时之前完成。另一种可能性是将超时限制提高到超过异常初始化完成所需的时间。一旦Lambda能够完成这个异常初始化,它就不必再次初始化(在这个特定的容器中)。后续的异常将非常迅速。
所有这一切的含义是,你永远不能允许异常冒泡到亚马逊(可能是通过在try/catch中包装句柄代码),否则超时时间应该足以完成异常初始化(比通常需要的时间多7秒,128MB)。