一、Apache HttpClient 4 采用的是 BIO
默认情况下,Apache HttpClient(即同步客户端)采用的是传统的阻塞 I/O(BIO)模型。这意味着在发送 HTTP 请求时,每个请求会占用一个线程进行处理,直到请求完成返回结果为止。
二、Apache HttpAsyncClient(HttpClient 5支持) 采用的是 NIO
CloseableHttpAsyncClient(HttpAsyncClients.createDefault())- 采用 Apache HttpAsyncClient(基于
java.nio.channels.SocketChannel和org.apache.hc.core5.reactor.IOSession) - 适用于高并发、长连接、异步请求的场景
选择:
- 如果你的场景是 同步调用、少量请求,用
CloseableHttpClient(BIO)。 - 如果需要 高并发、异步非阻塞,用
CloseableHttpAsyncClient(NIO)。
三、HttpClient 5 HttpAsyncClient 需要用到连接池吗
HttpClient 5 的 HttpAsyncClient 使用连接池来管理和复用连接,以提高性能和资源利用率。
配置连接池示例:
// 创建连接池管理器
PoolingAsyncClientConnectionManager connManager = PoolingAsyncClientConnectionManagerBuilder.create()
.setMaxTotal(100) // 设置最大连接数
.setDefaultMaxPerRoute(10) // 设置每个路由的默认最大连接数
.build();
// 创建 HttpAsyncClient
CloseableHttpAsyncClient httpAsyncClient = HttpAsyncClients.custom()
.setConnectionManager(connManager)
.build();
// 启动客户端
httpAsyncClient.start();
四、setDefaultMaxPerRoute 怎么理解
setDefaultMaxPerRoute 是 Apache HttpClient 中用于配置连接池的一个方法,用来设置每个路由(即每个目标主机)的默认最大连接数。
参数解释:
- MaxTotal:连接池的最大连接数,即客户端可以与所有服务器建立的总连接数上限。
- DefaultMaxPerRoute:每个路由的默认最大连接数,即客户端与每个服务器(路由)可以并发的最大连接数。
示例:
假设将 MaxTotal 设置为 200,DefaultMaxPerRoute 设置为 20,这意味着:
- 客户端最多可以与所有服务器建立 200 个并发连接。
- 默认情况下,客户端与每个服务器(路由)最多可以建立 20 个并发连接。
如果需要为特定的服务器(路由)设置不同的最大连接数,可以使用 setMaxPerRoute 方法。例如:
HttpHost specificHost = new HttpHost("www.example.com", 80);
connManager.setMaxPerRoute(new HttpRoute(specificHost), 50);
这段代码为 www.example.com 设置了最大 50 个并发连接,而其他服务器仍使用默认的 20 个并发连接限制。
路由在此上下文中指的是特定的目标主机和端口组合,即客户端与特定服务器之间的连接路径。
五、HttpAsyncClient 一个连接可以同时发出两个 http请求吗
-
在 HTTP/1.1 中,默认情况下,一个连接在同一时间只能处理一个 HTTP 请求。虽然存在管道化技术,但由于其局限性,实际应用中并不常用。
-
在 HTTP/2 中,一个连接可以同时处理多个 HTTP 请求和响应,得益于多路复用特性。
HttpAsyncClient 默认情况下使用 HTTP/1.1
六、HttpAsyncClient 开启 HTTP/2
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http2.HttpVersionPolicy;
public class Http2AsyncExample {
public static void main(String[] args) throws Exception {
// 强制使用 HTTP/2
try (CloseableHttpAsyncClient client = HttpAsyncClients.custom()
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2)
.build()) {
client.start();
SimpleHttpRequest request = SimpleHttpRequests.get("https://http2.akamai.com/demo");
client.execute(request, new FutureCallback<SimpleHttpResponse>() {
@Override
public void completed(SimpleHttpResponse response) {
System.out.println("HTTP/2 响应状态码:" + response.getCode());
System.out.println("响应内容:" + response.getBodyText());
}
@Override
public void failed(Exception ex) {
System.err.println("请求失败:" + ex.getMessage());
}
@Override
public void cancelled() {
System.out.println("请求被取消");
}
});
// 为确保异步操作有时间完成,主线程休眠几秒
Thread.sleep(5000);
}
}
}
需要注意的是:
- 开启 HTTP/2 依赖于 ALPN 机制(应用层协议协商),因此运行环境必须支持(通常要求 Java 9 或者手动引入 ALPN 库)。
- 如果服务器不支持 HTTP/2,建议设置版本策略为 NEGOTIATE,让客户端自动协商使用 HTTP/1.1 或 HTTP/2。
在 HttpClient 5 中,当你设置版本策略为 NEGOTIATE 时,意思是客户端会通过 ALPN(应用层协议协商)自动与服务器协商所使用的 HTTP 协议版本。也就是说,如果服务器支持 HTTP/2,则使用 HTTP/2;如果不支持,则回退到 HTTP/1.1,从而实现协议版本的“自动协商”。
七、HttpAsyncClient 普通 post请求 使用案例
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;
import org.apache.hc.client5.http.entity.StringEntity;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
public class AsyncPostExample {
public static void main(String[] args) throws Exception {
try (CloseableHttpAsyncClient client = HttpAsyncClients.createDefault()) {
client.start();
// 构造 POST 请求
SimpleHttpRequest request = SimpleHttpRequests.post("https://httpbin.org/post");
// 设置 JSON 请求体
String jsonPayload = "{\"name\":\"Apache HttpClient 5\"}";
request.setBody(new StringEntity(jsonPayload, ContentType.APPLICATION_JSON));
// 异步执行请求
client.execute(request, new FutureCallback<SimpleHttpResponse>() {
@Override
public void completed(SimpleHttpResponse response) {
System.out.println("POST请求返回状态码:" + response.getCode());
System.out.println("POST请求返回内容:" + response.getBodyText());
}
@Override
public void failed(Exception ex) {
System.err.println("POST请求失败:" + ex.getMessage());
}
@Override
public void cancelled() {
System.out.println("POST请求被取消");
}
});
// 主线程等待,确保回调能被执行
Thread.sleep(5000);
}
}
}
八、HttpAsyncClients 自定义配置
import org.apache.hc.client5.http.async.HttpAsyncClientBuilder;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.impl.nio.PoolingNHttpClientConnectionManager;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.nio.ssl.SSLIOSessionStrategy;
import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.util.Timeout;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.client5.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.hc.client5.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.ssl.SSLContexts;
import javax.net.ssl.SSLContext;
public class CustomAsyncClientConfig {
public static void main(String[] args) throws Exception {
// 1. 配置 IOReactor 参数
IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
.setSelectInterval(500) // 每500毫秒唤醒一次进行事件检测
.setIoThreadCount(Runtime.getRuntime().availableProcessors())
.setSoTimeout(Timeout.ofSeconds(5))
.setConnectTimeout(Timeout.ofSeconds(5))
.build();
// 2. 创建连接池管理器
PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactorConfig);
connManager.setMaxTotal(200);
connManager.setDefaultMaxPerRoute(20);
// 3. SSL配置(例如信任自签名证书)
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(new TrustSelfSignedStrategy())
.build();
SSLIOSessionStrategy sslStrategy = new SSLIOSessionStrategy(
sslContext,
new String[]{"TLSv1.2", "TLSv1.3"},
null,
NoopHostnameVerifier.INSTANCE
);
// 4. 代理设置
HttpHost proxy = new HttpHost("proxy.example.com", 8080);
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
// 5. 请求配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(5))
.setResponseTimeout(Timeout.ofSeconds(5))
.build();
// 6. 通过 builder 整合所有配置
HttpAsyncClientBuilder builder = HttpAsyncClients.custom()
.setConnectionManager(connManager)
.setIOReactorConfig(ioReactorConfig)
.setSSLStrategy(sslStrategy)
.setRoutePlanner(routePlanner)
.setDefaultRequestConfig(requestConfig);
// 7. 构建异步 HttpClient
try (CloseableHttpAsyncClient client = builder.build()) {
client.start();
// ... 发起请求
}
}
}
九、IOReactorConfig setSelectInterval
在 IOReactorConfig 中,selectInterval 参数用于控制 I/O 反应器在没有 I/O 活动时轮询 Selector 的时间间隔(以毫秒为单位)。简单来说,它决定了在空闲状态下,I/O 反应器多久检查一次是否有新的 I/O 事件到达。
selectInterval 就是用来作为调用底层 Selector.select(timeout) 时的超时时间
问题:为什么要设置这个 timeout, Selector.select() 可以一直阻塞 直到 有 i/o事件
尽管底层的 Selector.select() 能够无限期阻塞直到有 I/O 事件发生,但在实际的 NIO 事件循环中,我们不仅仅依赖 I/O 事件,还需要周期性地执行其他任务。例如:
- 定时任务和维护:事件循环中可能包含连接管理、心跳检测、超时检查或其他调度任务。如果一直阻塞在 select(),这些非 I/O 任务就无法及时执行。
- 任务队列检查:有时其它线程会向事件循环提交任务(例如注册新的通道),需要事件循环周期性地唤醒去检查任务队列。
- 响应系统变化:在一些情况下,需要及时响应系统关闭或配置变更等信号,如果阻塞过久,可能会延迟这些操作。
因此,通过设置一个 selectInterval 超时值,底层实际上是调用 Selector.select(timeout),这样即使没有 I/O 事件,也能定期唤醒事件循环,处理上述任务,从而提高系统整体的响应性和健壮性。
十、Java Selector
问题:Selector.select(1000) 设置 1s 超时,但是在 100ms 的时候有 I/O事件了,这时候是返回还是等到 1s
在 Java NIO 中,Selector.select(long timeout) 方法会在以下三种情况下返回:
-
有一个或多个通道准备好进行 I/O 操作:如果在调用
select(long timeout)后的 100 毫秒内有通道变为就绪状态,select方法会立即返回,而不会继续等待到 1 秒超时。 -
调用线程被中断:如果在等待期间,调用
select方法的线程被中断,select方法也会提前返回。 -
超时时间到达:如果在指定的超时时间内(例如 1 秒)没有通道变为就绪状态,
select方法将在超时到达时返回。
因此,Selector.select(1000) 方法在等待期间一旦检测到有通道就绪,便会立即返回,而不会继续等待到 1 秒超时。



731

被折叠的 条评论
为什么被折叠?



