异步请求CloseableHttpAsyncClient的使用

异步请求官网链接:http://hc.apache.org/httpcomponents-asyncclient-4.1.x/quickstart.html

应用场景:需要把一些没用影响业务逻辑的http请求改成异步请求,提升请求效率。


1、前言

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

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

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

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

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

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


2、网络调用类型

(1)传统BIO(Blocking IO)

同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

(2)NIO(Not-Blocking IO)

NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

(3) AIO(NIO.2)

异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。


3、CloseableHttpAsyncClient

CloseableHttpAsyncClient是apache在4.0后提供AIO操作的api,基本使用如下

1)pom.xml引用如下

<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>

 异步请求示例:

package com.lei.apitest.c05_project.async;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * @Author:
 * @Date: 2020-09-15 15:51
 * @Version: 1.0
 * @Modified By:
 * @Description:
 */
public class C02_AsynHttpClientV2 {

    private static AtomicInteger finishedCnt = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        // 异步请求
        long startT = System.currentTimeMillis();
        Vector<Thread> vector = new Vector<>();
        for (int index = 0; index < 3; index++) {
            MyRunThread myRunThread = new MyRunThread("threadName:" + index, 30);
            vector.add(myRunThread);
            myRunThread.start();
        }

        for (Thread thread : vector) {
            thread.join();
        }
        long endT = System.currentTimeMillis();
        long spendT = endT - startT;
        System.out.println("way 2...........spendT: " + spendT);
    }

    static class Back implements FutureCallback<HttpResponse>{

        private long start = System.currentTimeMillis();
        private CountDownLatch countDownLatch;

        Back(CountDownLatch countDownLatch){
            this.countDownLatch = countDownLatch;
        }

        public void completed(HttpResponse httpResponse) {
            try {
                if (httpResponse.getStatusLine().getStatusCode() == 200) {
                    System.out.println(" finishedCnt:" + finishedCnt.incrementAndGet());
//                    HttpEntity entity = httpResponse.getEntity();
//                    String res = EntityUtils.toString(entity);
//                    System.out.println("cost is:"+(System.currentTimeMillis()-start)+":"+ res + " finishedCnt:" + finishedCnt.incrementAndGet());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }

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

        public void cancelled() {
            countDownLatch.countDown();
        }
    }
}


class MyRunThread extends Thread {
    private String threadName;
    private int runTimes;
    private CountDownLatch countDownLatch;

    public MyRunThread() { }

    public MyRunThread(String threadName, int runTimes) {
        this.threadName = threadName;
        this.runTimes = runTimes;
        this.countDownLatch = new CountDownLatch(runTimes);
    }

    @Override
    public void run() {

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(500000)
                .setSocketTimeout(500000)
                .setConnectionRequestTimeout(10000)
                .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 = "https://www.cnblogs.com/";
        HttpPost httpPost = new HttpPost(url);

        // start
        client.start();

        // 异步请求
        long start = System.currentTimeMillis();
        for (int i = 0; i < this.runTimes; i++) {
            client.execute(httpPost, new C02_AsynHttpClientV2.Back(countDownLatch));
        }

        try {
            System.err.println(this.threadName + " 全部指令发送完毕");
            countDownLatch.await(); // 等待 latch1 变成0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        long spend = end - start;
        System.out.println(Thread.currentThread().getName() + " spend: " + spend);

        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

几个重要的参数

  • ConnectTimeout : 连接超时,连接建立时间,三次握手完成时间。
  • SocketTimeout : 请求超时,数据传输过程中数据包之间间隔的最大时间。
  • ConnectionRequestTimeout : 使用连接池来管理连接,从连接池获取连接的超时时间。
  • ConnTotal:连接池中最大连接数;
  • ConnPerRoute(1000):分配给同一个route(路由)最大的并发连接数,route为运行环境机器到目标机器的一条线路

同步请求示例:

package com.lei.apitest.c05_project.async;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;


/**
 * @Author:
 * @Date: 2020-09-15 15:51
 * @Version: 1.0
 * @Modified By:
 * @Description:
 */
public class C02_SerialHttpClientV1 {

    public static void main(String[] args) throws Exception {
        // 构造请求
        // String url = "http://www.baidu.com/";
        String url = "https://www.cnblogs.com/";
        // String url = "https://study.163.com/";
        HttpPost httpPost = new HttpPost(url);
        // httpPost.addHeader("Connection", "keep-alive");

        httpPost.setEntity(null);

        // 异步请求
        long start = System.currentTimeMillis();
        CloseableHttpClient httpClient = HttpClients.createDefault();

        for (int i = 0; i < 90; i++) {
            CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
            try {
                if (httpResponse.getStatusLine().getStatusCode() == 200) {
                    System.out.println("ok: " + i);
                    /*HttpEntity revEntity = httpResponse.getEntity();
                    String res = EntityUtils.toString(revEntity);
                    System.out.println("cost is:"+(System.currentTimeMillis()-start)+":"+ res + " finishedCnt:" + i);*/
                    // System.out.println(httpResponse.getEntity().getContent().toString());
                }
            } finally {
                httpResponse.close();
            }
        }

        long end = System.currentTimeMillis();
        long spend = end - start;
        System.out.println("spend:" + spend);
        httpClient.close();
    }
}

小结:通过两者的请求对比,异步请求整体性能有一定幅度提升。 


文章最后,给大家推荐一些受欢迎的技术博客链接

  1. JAVA相关的深度技术博客链接
  2. Flink 相关技术博客链接
  3. Spark 核心技术链接
  4. 设计模式 —— 深度技术博客链接
  5. 机器学习 —— 深度技术博客链接
  6. Hadoop相关技术博客链接
  7. 超全干货--Flink思维导图,花了3周左右编写、校对
  8. 深入JAVA 的JVM核心原理解决线上各种故障【附案例】
  9. 请谈谈你对volatile的理解?--最近小李子与面试官的一场“硬核较量”
  10. 聊聊RPC通信,经常被问到的一道面试题。源码+笔记,包懂
  11. 深入聊聊Java 垃圾回收机制【附原理图及调优方法】

欢迎扫描下方的二维码或 搜索 公众号“大数据高级架构师”,我们会有更多、且及时的资料推送给您,欢迎多多交流!

                                           

       

猜你喜欢

转载自blog.csdn.net/weixin_32265569/article/details/108606783
今日推荐