Apache HttpClient IO模型 以及 开启HTTP/2

一、Apache HttpClient 4 采用的是 BIO

默认情况下,Apache HttpClient(即同步客户端)采用的是传统的阻塞 I/O(BIO)模型。这意味着在发送 HTTP 请求时,每个请求会占用一个线程进行处理,直到请求完成返回结果为止。

二、Apache HttpAsyncClient(HttpClient 5支持) 采用的是 NIO

  • CloseableHttpAsyncClientHttpAsyncClients.createDefault()
  • 采用 Apache HttpAsyncClient(基于 java.nio.channels.SocketChannelorg.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 怎么理解

setDefaultMaxPerRouteApache 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) 方法会在以下三种情况下返回:

  1. 有一个或多个通道准备好进行 I/O 操作:如果在调用 select(long timeout) 后的 100 毫秒内有通道变为就绪状态,select 方法会立即返回,而不会继续等待到 1 秒超时。

  2. 调用线程被中断:如果在等待期间,调用 select 方法的线程被中断,select 方法也会提前返回。

  3. 超时时间到达:如果在指定的超时时间内(例如 1 秒)没有通道变为就绪状态,select 方法将在超时到达时返回。

因此,Selector.select(1000) 方法在等待期间一旦检测到有通道就绪,便会立即返回,而不会继续等待到 1 秒超时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值