当抛出未捕获的异常时,Amazon AWS Lambda Java函数花费不合理的长时间完成是否正常?

Is it normal that Amazon AWS Lambda Java functions take an unreasonable long time to finish when an uncaught exception is thrown?

在抛出未捕获的异常时,任何Amazon AWS Lambda Java函数花费不合理的长时间来完成是否正常?请注意这是关于Java中的Amazon Lambdas的一般性问题,因为我以非常通用的方式测试它,具有非常简单的裸骨功能。

例如,请考虑下面的函数,验证PIN。如果PIN有效,则返回文本:PIN is OK:"A"。否则,它会抛出IOException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 ms的时间内退出。但是,当PIN无效时,该功能在3秒内超时,我得到了这个:

1
2
3
4
{
 "errorMessage":"2017-10-15T21:35:58.744Z *** Task timed out after 3.00 seconds",
 "errorType":"java.lang.RuntimeException"
}

然后我将超时时间增加到10秒,现在它实际上完成了大约7.5秒并给了我一个堆栈跟踪:

1
2
3
4
5
{
 "errorMessage":"PIN is wrong: "B"",
 "errorType":"java.io.IOException",
 "stackTrace": ["example.Hello.handleRequest(Hello.java:83)" ]
}

我的问题:

1)lambda函数中的异常应该花费那么多时间来处理亚马逊,这是正常的吗?为什么?如果没有,为什么我有这个问题?

2)处理异常的推荐方法是什么?我不应该让一个函数以异常结束吗?


好像我发现了问题所在。亚马逊Lambda似乎做了某种内部"异常初始化",只是第一次在容器中获得异常(要清楚,我的意思是一个异常,它没有被捕获
用户的处理程序,并允许冒泡到内部Amazon Lambda代码)。

因此,假设您有一些很少发出异常的代码,例如,它在1秒内运行,超时为3秒。如果此代码抛出未捕获的异常(由于错误或设计),Lambda将初始化其内部异常处理,对于128MB的最低内存配置大约需要7秒。
由于超时为3秒,因此没有时间完成,初始化将无法完成。下次抛出异常时,初始化将再次启动并再次超时。

如果提高内存,异常初始化将运行得更快,并可能在超时之前完成。另一种可能性是将超时限制提高到超过异常初始化完成所需的时间。一旦Lambda能够完成此异常初始化,就不必再次初始化(在此特定容器中)。随后的例外将非常快。

所有这一切的含义是你绝不允许异常泡到亚马逊(可能是通过在try / catch中包装句柄代码),否则超时应该足以完成异常初始化(比通常需要多7秒) ,在128MB)。


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-gateway-proxy-integration-拉姆达功能的Java