我的应用程序正在处理大量的HTTP请求/响应事务,其中来自一个服务的响应导致下一步和后续请求,依此类推一系列步骤。
为了使代码更优雅,我使用了一个下载和回调结构,可以简单地表示如下:
private void runFirstStep() {
String firstRequest = buildRequest();
sendHttpRequest(firstRequest, this::handleFirstStepResponse);
}
private void handleFirstStepResponse(InputStream responseBody) {
doStuffWithFirstStepResponse(responseBody);
String secondRequest = buildSecondRequest();
sendHttpRequest(secondRequest, this::handleSecondStepResponse);
}
private void handleSecondStepResponse(InputStream responseBody) {
doStuffWithSecondStepResponse(responseBody);
String thirdRequest = buildThirdRequest();
sendHttpRequest(thirdRequest, this::handleThirdStepResponse);
}
private void handleThirdStepResponse(InputStream responseBody) {
doStuffWithThirdStepResponse(responseBody);
// The flow has finished, so no further HTTP transactions.
}
虽然在我的情况下,序列长度目前达到约26步,但所有链接都以这种方式。
这工作正常,但我碰巧注意到控制台中的日志行很清楚,每个方法都只是等待链中的所有其他方法完成(这在我考虑时很明显)。但它也让我觉得我使用的这种模式可能会导致堆栈溢出。
所以问题是:
sendHttpRequest
方法已经使用JDK 11 HTTP框架来生成CompletableFuture<HttpResponse<InputStream>>
,但是我的sendHttpRequest
方法只是等待它完成并使用结果调用指定的回调方法。是否应该创建一个新线程来处理CompletableFuture
,以便调用方法可以优雅地关闭?如何在不导致JVM关闭的情况下执行此操作(看作缓慢的HTTP响应会让JVM在此期间没有执行任何方法)?正在堆栈溢出(网站,而不是例外)我当然在寻找涉及Java的裸机制的答案,而不是猜测或轶事。
更新:只是为了澄清,我的sendHttpRequest
方法目前有这种形状:
private void sendHttpRequest(String request,
Consumer<InputStream> callback) {
HttpRequest httpRequest = buildHttpRequestFromXml(request);
CompletableFuture<HttpResponse<InputStream>> completableExchange
= httpClient.
sendAsync(httpRequest, BodyHandlers.ofInputStream());
HttpResponse<InputStream> httpResponse = completableExchange.join();
InputStream responseBody = getBodyFromResponse(httpResponse);
callback.accept(responseBody);
}
重要的一点是Java的HttpClient.sendAsync
方法返回CompletablFuture
,然后在该对象上调用join()
以等待接收HTTP响应并作为HttpResponse
对象返回,然后用于将响应主体提供给指定的回调方法。但我的问题并不是专门针对HTTP请求/响应序列,而是处理任何类型的流程时的风险和最佳实践,这些流程有助于等待结果和回调结构。
首先,如果一个方法将回调作为参数,它不应该阻塞调用线程。如果你正在阻塞,则不需要回调。(你可以从sendHttpRequest返回InputStream并用它调用下一个方法。)
你应该使用CompletableFuture完全异步。但是你必须考虑一件事。并行流操作和CompletableFuture在没有在Executor(线程池)上专门执行时使用公共池。由于http下载是阻塞操作,因此不应在common-pool中执行它(以不阻止执行IO操作的公共池线程)。您应该创建IO池并将其传递给CompletableFuture方法,这些方法在下载时将Executor作为参数。
至于将会发生什么如果你继续当前的设计;
调用方法时,将创建堆栈帧并将其推送到调用线程的堆栈。此框架将保存此方法调用的返回地址,此方法采用的参数以及方法的局部变量。如果这些参数和变量是基本类型,它们将存储在堆栈中,如果它们是对象,它们的地址将存储在堆栈中。当这个方法完成执行时,它的框架将被销毁。
26个方法调用链不应该是堆栈溢出的问题。您也可以使用-Xss开关控制堆栈大小。每个平台的默认堆栈大小都不同(32位和64位也会影响默认大小。)如果您要为您的应用程序提供可执行命令,您可能需要定义此值如果您担心这一点。