【问题标题】:How to make batch http call in java如何在java中进行批量http调用
【发布时间】:2017-11-29 21:05:51
【问题描述】:

我正在尝试通过 Http 访问另一个服务以使用 HttpClient 获取数据。 uri 应该类似于端点:80/.../itemId。

我想知道是否有办法进行批量调用以指定一组 itemIds?在创建请求时,我确实发现有人建议 .setHeader(HttpHeaders.CONNECTION, "keep-alive") 。通过这样做,我如何在获取所有数据后释放客户端?

另外,这个方法似乎仍然需要得到一个响应然后发送另一个请求?这是否可以异步执行,以及如何执行?顺便说一句,在这种情况下,由于某种原因,我似乎无法使用 AsyncHttpClient。

由于我对 HttpClient 几乎一无所知,所以这个问题可能看起来很愚蠢。真心希望有人能帮我解决问题。

【问题讨论】:

  • 请在 HTTP 上下文中定义 batching。您的意思是通过单个 TCP 连接吗?那么答案就是keep-alive标头。还是一次有多个ID?那么答案是是的,如果服务器支持这种用法并且与HTTP无关。你能做到异步吗?是的,但这并不等同于以任何方式进行批处理...您的问题太模糊而无法回答。

标签: java http batch-file apache-httpclient-4.x


【解决方案1】:

服务器上的 API 支持

API 支持一次请求多个 ID 的可能性很小(例如,使用 http://endpoint:80/.../itemId1,itemId2,itemId3 形式的 URL)。检查 API 文档,看看这是否可用,因为如果是这样,那将是最好的解决方案。

持久连接

看起来 Apache HttpClient 默认使用持久(“保持活动”)连接(请参阅 @kichik's comment 中链接的 Connection Management tutorial)。 logging facilities 可以帮助验证连接是否被多个请求重用。

要释放客户端,请使用close() 方法。来自2.3.4. Connection manager shutdown

当不再需要 HttpClient 实例并且即将超出范围时,关闭其连接管理器以确保关闭由管理器保持活动状态的所有连接并释放由这些连接分配的系统资源,这一点很重要。

CloseableHttpClient httpClient = <...>
httpClient.close();

持久连接消除了建立新连接的开销,但正如您所注意到的,客户端在发送下一个请求之前仍会等待响应。

多线程和连接池

您可以使您的程序多线程并使用PoolingHttpClientConnectionManager 来控制与服务器建立的连接数。以下是基于2.3.3. Pooling connection manager2.4. Multithreaded request execution 的示例:

import java.io.*;
import org.apache.http.*;
import org.apache.http.client.*;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.*;
import org.apache.http.impl.client.*;
import org.apache.http.impl.conn.*;
import org.apache.http.protocol.*;

// ...
PoolingHttpClientConnectionManager cm =
        new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200); // increase max total connection to 200
cm.setDefaultMaxPerRoute(20); // increase max connection per route to 20
CloseableHttpClient httpClient = HttpClients.custom()
        .setConnectionManager(cm)
        .build();

String[] urisToGet = { ... };
// start a thread for each URI
// (if there are many URIs, a thread pool would be better)
Thread[] threads = new Thread[urisToGet.length];
for (int i = 0; i < threads.length; i++) {
    HttpGet httpget = new HttpGet(urisToGet[i]);
    threads[i] = new Thread(new GetTask(httpClient, httpget));
    threads[i].start();
}
// wait for all the threads to finish
for (int i = 0; i < threads.length; i++) {
    threads[i].join();
}

class GetTask implements Runnable {
    private final CloseableHttpClient httpClient;
    private final HttpContext context;
    private final HttpGet httpget;

    public GetTask(CloseableHttpClient httpClient, HttpGet httpget) {
        this.httpClient = httpClient;
        this.context = HttpClientContext.create();
        this.httpget = httpget;
    }

    @Override
    public void run() {
        try {
            CloseableHttpResponse response = httpClient.execute(
                httpget, context);
            try {
                HttpEntity entity = response.getEntity();
            } finally {
                response.close();
            }
        } catch (ClientProtocolException ex) {
            // handle protocol errors
        } catch (IOException ex) {
            // handle I/O errors
        }
    }
}

多线程有助于使链路饱和(保持尽可能多的数据流动),因为当一个线程发送请求时,其他线程可以接收响应并利用下行链路。

流水​​线

HTTP/1.1 支持pipelining,它在一个连接上发送多个请求,而无需等待响应。 Asynchronous I/O based on NIO tutorial3.10. Pipelined request execution 部分中有一个示例:

HttpProcessor httpproc = <...>
HttpAsyncRequester requester = new HttpAsyncRequester(httpproc);
HttpHost target = new HttpHost("www.apache.org");
List<BasicAsyncRequestProducer> requestProducers = Arrays.asList(
    new BasicAsyncRequestProducer(target, new BasicHttpRequest("GET", "/index.html")),
    new BasicAsyncRequestProducer(target, new BasicHttpRequest("GET", "/foundation/index.html")),
    new BasicAsyncRequestProducer(target, new BasicHttpRequest("GET", "/foundation/how-it-works.html"))
);
List<BasicAsyncResponseConsumer> responseConsumers = Arrays.asList(
    new BasicAsyncResponseConsumer(),
    new BasicAsyncResponseConsumer(),
    new BasicAsyncResponseConsumer()
);
HttpCoreContext context = HttpCoreContext.create();
Future<List<HttpResponse>> future = requester.executePipelined(
    target, requestProducers, responseConsumers, pool, context, null);

HttpCore Examples(“Pipelined HTTP GET requests”)中有此示例的完整版本。

较旧的 Web 服务器可能无法正确处理流水线请求。

【讨论】:

  • 谢谢回答,这次我决定选择多线程和连接池。这是一个同伴的问题。如果我们启动与请求数一样多的线程。如果我们不控制并行线程的数量,这是否可能使守护进程崩溃。如果是这样,我可以使用 ExexutorService 来设置固定池吗?另外,对于每个线程的中间结果,我可以将其存储在并发映射中(线程安全),还是必须使用 Feature 接口?
  • 来自this question,如果您有超过一千个线程,您可能会面临内存不足的风险。一个 ExecutorService 就可以了。并发映射可以工作(只要您在使用结果之前使用 Thread.join()ExecutorService.awaitTermination() 等待所有任务完成)。
  • 如果您需要更多帮助,请提出新问题。我看到你前几天已经问过a question,但没有得到很好的回应;以后一定要使用minimal, complete examples,如果你仍然没有得到很好的回应,你可以通过对我的一个答案写评论来联系我(包括你的问题的链接)。不过不要滥用此功能:)
  • 感谢您的热情回复。目前,为了避免过多的并行线程使守护进程崩溃。我正在使用 executorService 预留一个固定的线程池并使用 Future 接口来收集响应。我确实有后续行动。如何释放连接以允许其他连接?现在我关闭输入流触发释放,不知道有没有更好的方法?
  • 关闭输入流才是释放连接的正确方式,见Ensuring release of low level resources
猜你喜欢
  • 1970-01-01
  • 2021-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多