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有效,则返回文本:
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