我正在尝试使用 Apache HTTPClient 4.3.6 连接池管理器来提高 HTTP 调用的吞吐量。我的假设是,HTTPClient 实现通常使用持久连接。然而,我的测试代码(包含在末尾)的结果表明,使用 JDK URLConnection
的多个并发 HTTP 连接性能更好。
- 如何使
HTTPClient
更快? HTTPClient
是否对http://localhost:9000/user/123
和http://localhost:9000/user/456
使用相同的 HTTP 连接?
谢谢
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
public class FooTest {
public static void main(String[] args) throws Exception {
runWithConnectionPool();
}
private static String extract(BufferedReader reader) throws Exception {
StringBuilder buffer = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
return buffer.toString();
}
private static void runWithConnectionPool() throws Exception {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(1);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.setMaxConnTotal(100)
.setMaxConnPerRoute(100)
.build();
long start = System.currentTimeMillis();
HttpGet getReq = new HttpGet("http://www.google.com");
/*
Option A: Using HTTP connection pool
Option B: Individual JDK 8 URL connection
*/
// Thread[] workers = generateAndStart(10, httpClient, getReq, 0); // (A)
Thread[] workers = generateAndStart(10, getReq.getURI().toURL(), 0); // (B)
for (int i = 0; i < workers.length; i++) {
workers[i].join();
}
System.out.println("Elasped: " + (System.currentTimeMillis() - start));
}
private static Thread[] generateAndStart(int num, URL url, long delay) {
Thread[] workers = new Thread[num];
for (int i = 0; i < num; i++) {
System.out.println("Starting worker: " + i);
int j = i;
workers[i] = new Thread(() -> connect(url, delay, j));
workers[i].start();
}
return workers;
}
private static void connect(URL url, long delay, int ndx) {
try {
System.out.println(url.toURI().toString() + " started.");
} catch (Exception e) {
e.printStackTrace();
}
try {
URLConnection connection = url.openConnection();
connection.addRequestProperty("Accept", "application/json");
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
ObjectMapper mapper = new ObjectMapper();
System.out.println(line);
}
if (delay > 0) {
System.out.println("Delayed.");
sleep(delay);
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static Thread[] generateAndStart(int num, CloseableHttpClient httpClient, HttpGet getReq, long delay) {
Thread[] workers = new Thread[num];
for (int i = 0; i < num; i++) {
System.out.println("Starting worker: " + i);
final int j = i;
workers[i] = new Thread(() -> connect(httpClient, getReq, delay, j));
workers[i].start();
}
return workers;
}
private static void connect(CloseableHttpClient httpClient, HttpGet request, long delay, int ndx) {
System.out.println(request.getURI().toString() + " started.");
try(
CloseableHttpResponse response = httpClient.execute(request, HttpClientContext.create());
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))) {
String line;
while ((line = reader.readLine()) != null) {
ObjectMapper mapper = new ObjectMapper();
System.out.println(line);
}
if (delay > 0) {
System.out.println("Delayed.");
sleep(delay);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (Exception e) {
e.printStackTrace();
}
}
}
更新 1(2017 年 3 月 28 日)
我做了一些观察和结论,
- JDK
java.net.URLConnection
在调用URLConnection.getInputStream()
之前不会建立连接。
如果发生错误连接, java.net.URLConnection
将关闭当前套接字。 HTTP 错误,并创建一个新的套接字。- 在多线程环境中使用从同一
java.net.URL
实例创建的java.net.URLConnection
实例将创建多个到服务器的套接字。相反,为了简单起见,请在synchronized
block 中调用URL.openConnection()
。同样,这并不意味着每次调用我错了。创建的套接字数量取决于调用URL.openConnection()
都会创建一个新的套接字。我相信URL
对此进行了规范。URL.openConnection()
的线程数量。 - 在很多地方都提到过,我再次提到,关闭/断开 URLConnection 不会关闭套接字。
- 连接到同一服务器的不同路径不会创建另一个连接套接字。换句话说,持久连接可用于不同的路径。
- Apache HTTPClient 通常更易于使用且更直观。它支持多线程环境中的持久连接(使用相同的套接字进行连接),无需用户干预。
- 我无法获取符合
http.maxConnections
和http.keepAlive
的URL
。例如,我在运行时包含-Dhttp.keepAlive=false
不会阻止Connection: keep-alive
包含在 HTTP header 中。
我的观察来自于我粘贴的 here 示例。它们是比上面粘贴的代码更好的示例。
最佳答案
在尝试 JDK URLConnection
和 Apache HTTPClient 后,我找到了答案。
URLConnection
速度很快,因为它为每个线程与服务器建立的每个连接打开新的套接字,而 Apache HTTPClient 根据多线程环境中的设置控制打开的套接字数量。当套接字限制为少数时,两个 HTTP 库所花费的总连接时间大致相同。- Apache HTTPClient 对不同的 URL 使用与同一服务器的持久连接。
mitmproxy是一个很好且易于使用的 HTTP 连接验证工具。
关于java - Apache HTTPClient 4.x 与多个 JDK URL 连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42995772/