异步httpclient(httpasyncclient)的使用与总结

1. 前言

HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了, JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说。HttpClient 是Apache HttpComponents 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,特点:

  • 实现了所有 HTTP 的方法(GET,POST,PUT,DELETE 等);
  • 支持自动转向
  • 支持 HTTPS 协议
  • 支持代理服务器等
  • 具有连接池、异步功能,提升效率

我们知道可以用HttpClient来发送同步请求,在并发量大的情况下使用HttpClient的连接池来提高性能。此方法虽然很有效果,但是当访问量极大或网络不好的情况下也会出现某些网络请求慢导致其它请求阻塞的情况。所以我们可以将网络请求变成一个异步的请求,不影响其它的请求。

应用层的网络模型有同步与异步。同步意味当前线程是阻塞的,只有本次请求完成后才能进行下一次请求;异步意味着所有的请求可以同时塞入缓冲区,不阻塞当前的线程。

httpclient在4.x之后开始提供基于nio的异步版本httpasyncclient,httpasyncclient借助了Java并发库和nio进行封装(虽说NIO是同步非阻塞IO,但是HttpAsyncClient提供了回调的机制,与netty类似,所以可以模拟类似于AIO的效果),其调用方式非常便捷,但是其中也有许多需要注意的地方。

注:HttpClient 3 版本和 HttpClient 4 版本差别很大,代码不兼容。

2、pom依赖

<dependency>  
	<groupId>org.apache.httpcomponents</groupId>  
	<artifactId>httpclient</artifactId>  
	<version>4.5.2</version>  
</dependency>  

<dependency>  
	<groupId>org.apache.httpcomponents</groupId>  
	<artifactId>httpcore</artifactId>  
	<version>4.4.5</version>  
</dependency>  

<dependency>  
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpcore-nio</artifactId>
	<version>4.4.5</version>
</dependency>  

<dependency>  
	<groupId>org.apache.httpcomponents</groupId>  
	<artifactId>httpasyncclient</artifactId>  
	<version>4.1.2</version>  
</dependency>

简单示例:

public class TestHttpClient {
    public static void main(String[] args){

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(50000)
                .setSocketTimeout(50000)
                .setConnectionRequestTimeout(1000)
                .build();

        //配置io线程
        IOReactorConfig ioReactorConfig = IOReactorConfig.custom().
                setIoThreadCount(Runtime.getRuntime().availableProcessors())
                .setSoKeepAlive(true)
                .build();
        //设置连接池大小
        ConnectingIOReactor ioReactor=null;
        try {
            ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
        } catch (IOReactorException e) {
            e.printStackTrace();
        }
        PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
        connManager.setMaxTotal(100);
        connManager.setDefaultMaxPerRoute(100);


        final CloseableHttpAsyncClient client = HttpAsyncClients.custom().
                setConnectionManager(connManager)
                .setDefaultRequestConfig(requestConfig)
                .build();


        //构造请求
        String url = "http://127.0.0.1:9200/_bulk";
        HttpPost httpPost = new HttpPost(url);
        StringEntity entity = null;
        try {
            String a = "{ \"index\": { \"_index\": \"test\", \"_type\": \"test\"} }\n" +
                    "{\"name\": \"上海\",\"age\":33}\n";
            entity = new StringEntity(a);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        httpPost.setEntity(entity);

        //start
        client.start();

        //异步请求
        client.execute(httpPost, new Back());

        while(true){
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class Back implements FutureCallback<HttpResponse>{

        private long start = System.currentTimeMillis();
        Back(){
        }

        public void completed(HttpResponse httpResponse) {
            try {
                System.out.println("cost is:"+(System.currentTimeMillis()-start)+":"+EntityUtils.toString(httpResponse.getEntity()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void failed(Exception e) {
            System.err.println(" cost is:"+(System.currentTimeMillis()-start)+":"+e);
        }

        public void cancelled() {

        }
    }
}

3、参数配置

1)三个timeout:

RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(50000)
                .setSocketTimeout(50000)
                .setConnectionRequestTimeout(1000)
                .build();
  • ConnectTimeout:连接超时,连接建立时间、三次握手完成时间。
  • SocketTimeout:请求超时,数据传输过程中数据包之间间隔的最大时间。
  • ConnectionRequestTimeout:使用连接池来管理连接,从连接池获取连接的超时时间

下面针对ConnectionRequestTimeout的情况进行分析。实验条件:

  1. 设置连接池最大连接数为1,每一个异步请求从开始到回调的执行时间在100ms以上;
  2. 实验过程:连续发送2次请求
client.start();

for(int i=0;i<2;i++){
	client.execute(list.get(i), new Back());
}

实验结果:
第一次请求执行时间在200ms左右
第二请求回调直接抛出TimeOutException

java.util.concurrent.TimeoutException
    at org.apache.http.nio.pool.AbstractNIOConnPool.processPendingRequest(AbstractNIOConnPool.java:364)
    at org.apache.http.nio.pool.AbstractNIOConnPool.processNextPendingRequest(AbstractNIOConnPool.java:344)
    at org.apache.http.nio.pool.AbstractNIOConnPool.release(AbstractNIOConnPool.java:318)
    at org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager.releaseConnection(PoolingNHttpClientConnectionManager.java:303)
    at org.apache.http.impl.nio.client.AbstractClientExchangeHandler.releaseConnection(AbstractClientExchangeHandler.java:239)
    at org.apache.http.impl.nio.client.MainClientExec.responseCompleted(MainClientExec.java:387)
    at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.responseCompleted(DefaultClientExchangeHandlerImpl.java:168)
    at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.processResponse(HttpAsyncRequestExecutor.java:436)
    at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.inputReady(HttpAsyncRequestExecutor.java:326)
    at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:265)
    at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:81)
    at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:39)
    at org.apache.http.impl.nio.reactor.AbstractIODispatch.inputReady(AbstractIODispatch.java:114)
    at org.apache.http.impl.nio.reactor.BaseIOReactor.readable(BaseIOReactor.java:162)
    at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvent(AbstractIOReactor.java:337)
    at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvents(AbstractIOReactor.java:315)
    at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:276)
    at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
    at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:588)
    at java.lang.Thread.run(Thread.java:745)

结果分析:由于连接池大小是1,第一次请求执行后连接被占用(时间在100ms),第二次请求在规定的时间内无法获取连接,于是直接连接获取的TimeOutException。

修改ConnectionRequestTimeout=1000,上述两次请求正常执行。

注:如果要设置永不ConnectionRequestTimeout,只需要将ConnectionRequestTimeout设置为小于0即可。

2)连接池配置:

  • ConnTotal:连接池中最大连接数;
  • ConnPerRoute(1000):分配给同一个route(路由)最大的并发连接数,route为运行环境机器到目标机器的一条线路,举例来说,我们使用HttpClient的实现来分别请求 www.baidu.com 的资源和 www.bing.com 的资源那么他就会产生两个route;

对于上述的实验,在一定程度上也可以通过增大最大连接数来解决ConnectionRequestTimeout的问题!

参考:https://juejin.im/post/5deb49be6fb9a01613801262

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我解释一下您的问题。您想在filter中实现异步servlet代理功能,并且请求使用异步HttpClient。我假设您想要在servlet容器中实现这个功能,比如Tomcat或Jetty。 首先,您需要编写一个Filter来拦截请求并将请求转发给HttpClient。在这个Filter中,您需要使用Servlet 3.0规范中的异步特性来实现异步处理请求和响应。这可以通过以下步骤完成: 1. 在Filter中调用ServletRequest的startAsync方法,以启动异步处理。 2. 在新的异步上下文中,创建一个异步HttpClient,并使用它来发出异步请求。 3. 一旦HttpClient完成请求并收到响应,您可以将响应写回给原始的ServletResponse,并调用AsyncContext的complete方法以结束异步处理。 下面是一个简单的示例代码,展示了如何在Filter中实现异步servlet代理功能: ``` public class AsyncProxyFilter implements Filter { private final CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault(); @Override public void init(FilterConfig filterConfig) throws ServletException { httpClient.start(); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { final HttpServletRequest httpRequest = (HttpServletRequest) request; final HttpServletResponse httpResponse = (HttpServletResponse) response; // Start asynchronous processing final AsyncContext asyncContext = httpRequest.startAsync(); asyncContext.setTimeout(0); // Disable the timeout // Create an asynchronous HttpClient request final HttpHost target = new HttpHost(httpRequest.getServerName(), httpRequest.getServerPort()); final HttpRequest proxyRequest = new BasicHttpRequest(httpRequest.getMethod(), httpRequest.getRequestURI()); final Header[] headers = ServletUtils.getHeaders(httpRequest); for (Header header : headers) { proxyRequest.addHeader(header); } final HttpAsyncRequestProducer requestProducer = HttpAsyncMethods.create(target, proxyRequest); final AsyncHttpClientHandler responseHandler = new AsyncHttpClientHandler(asyncContext, httpResponse); // Execute the asynchronous request httpClient.execute(requestProducer, responseHandler); } @Override public void destroy() { try { httpClient.close(); } catch (IOException e) { // Ignore } } private static class AsyncHttpClientHandler implements AsyncHandler<Void> { private final AsyncContext asyncContext; private final HttpServletResponse httpResponse; private HttpResponse proxyResponse; public AsyncHttpClientHandler(AsyncContext asyncContext, HttpServletResponse httpResponse) { this.asyncContext = asyncContext; this.httpResponse = httpResponse; } @Override public void completed(Void result) { // Write the proxy response back to the original response ServletUtils.copyHeaders(proxyResponse, httpResponse); ServletUtils.copyBody(proxyResponse, httpResponse); // Complete the asynchronous processing asyncContext.complete(); } @Override public void failed(Exception ex) { // Handle the error asyncContext.complete(); } @Override public void cancelled() { // Handle the cancellation asyncContext.complete(); } @Override public void onStatusReceived(HttpResponse response) throws IOException { this.proxyResponse = response; httpResponse.setStatus(response.getStatusLine().getStatusCode()); } @Override public void onHeadersReceived(HttpResponse response) throws IOException { this.proxyResponse = response; ServletUtils.copyHeaders(response, httpResponse); } @Override public void onBodyReceived(InputStream content) throws IOException { ServletUtils.copyBody(content, httpResponse.getOutputStream()); } } } ``` 在上面的示例代码中,我们创建了一个AsyncProxyFilter类,它实现了Filter接口。在init方法中,我们创建了一个CloseableHttpAsyncClient并启动它。在doFilter方法中,我们首先启动异步处理,然后使用异步HttpClient发送异步请求。我们在AsyncHttpClientHandler中处理异步HttpClient的响应,并将响应写回原始的ServletResponse。最后,我们在completed、failed和cancelled方法中处理异步处理的结束。 这就是在Filter中实现异步servlet代理功能的基本步骤。当然,这只是一个简单的示例代码,您可能需要根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值