• httpClient同步、异步性能对比


    0、测试目的

    同步阻塞模式下,如果服务端接口响应较慢,那会直接影响客户端接口请求的吞吐量,虽然可以通过在应用代码中通过异步线程的方式优化,但是会增加客户端的线程开销。所以考虑用异步模式来解决这个问题

    因此测试时,主要是针对线程数设置比较小的情况下,客户端发起请求的吞吐量来进行对比

    1、准备工作

    用spring boot写一个最简单的接口:sleep 1s,然后返回ok
    在这里插入图片描述

    客户端程序引入httpClient依赖:

    
        org.apache.httpcomponents.client5
         httpclient5
         5.1.3
     
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、同步模式

    代码:

    import lombok.SneakyThrows;
    import org.apache.hc.client5.http.ClientProtocolException;
    import org.apache.hc.client5.http.classic.methods.HttpGet;
    import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    import org.apache.hc.client5.http.impl.classic.HttpClients;
    import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
    import org.apache.hc.core5.http.ClassicHttpResponse;
    import org.apache.hc.core5.http.HttpEntity;
    import org.apache.hc.core5.http.HttpStatus;
    import org.apache.hc.core5.http.ParseException;
    import org.apache.hc.core5.http.io.HttpClientResponseHandler;
    import org.apache.hc.core5.http.io.entity.EntityUtils;
    
    import java.io.IOException;
    import java.util.concurrent.atomic.AtomicBoolean;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class SyncClientHttpTest {
    
        static final CloseableHttpClient httpclient;
    
        static {
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
            connectionManager.setMaxTotal(1000);
            connectionManager.setDefaultMaxPerRoute(100);
    
            httpclient = HttpClients.custom().setConnectionManager(connectionManager).build();
        }
    
        public static void main(final String[] args) throws Exception {
            AtomicInteger atomicInteger = new AtomicInteger(0);
    
            AtomicBoolean stop = new AtomicBoolean(false);
    
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    while (!stop.get()) {
                        httpGet();
                        atomicInteger.incrementAndGet();
                    }
                }).start();
            }
    
            Thread.sleep(30000);
    
            stop.set(true);
            Thread.sleep(1000);
    
            System.out.println(atomicInteger.get());
    
    
            System.exit(0);
    
        }
    
        @SneakyThrows
        private static void httpGet() {
            final HttpGet httpget = new HttpGet("http://localhost:8080/test");
    
            // Create a custom response handler
            final HttpClientResponseHandler<String> responseHandler = new HttpClientResponseHandler<String>() {
    
                @Override
                public String handleResponse(
                        final ClassicHttpResponse response) throws IOException {
                    final int status = response.getCode();
                    if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_REDIRECTION) {
                        final HttpEntity entity = response.getEntity();
                        try {
                            return entity != null ? EntityUtils.toString(entity) : null;
                        } catch (final ParseException ex) {
                            throw new ClientProtocolException(ex);
                        }
                    } else {
                        throw new ClientProtocolException("Unexpected response status: " + status);
                    }
                }
    
            };
            final String responseBody = httpclient.execute(httpget, responseHandler);
    //            System.out.println(responseBody);
            if(!responseBody.equals("ok")) {
                throw new RuntimeException("error");
            }
        }
    }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    开启5个线程,循环发起请求30s

    打印结果:150
    差不多每秒5个请求,符合预期

    改为10个线程
    打印结果:300

    改为100个线程
    打印结果:3000

    请求吞吐和线程数呈线性增长关系(线程数应小于maxPerRoute)

    3、异步模式

    代码:

    import lombok.SneakyThrows;
    import org.apache.hc.client5.http.async.methods.*;
    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.PoolingAsyncClientConnectionManager;
    import org.apache.hc.core5.concurrent.FutureCallback;
    import org.apache.hc.core5.reactor.IOReactorConfig;
    import org.apache.hc.core5.util.Timeout;
    
    import java.util.concurrent.Future;
    import java.util.concurrent.atomic.AtomicBoolean;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * Example of asynchronous HTTP/1.1 request execution.
     */
    public class AsyncClientHttpTest {
    
        static final CloseableHttpAsyncClient client;
    
        static final AtomicInteger atomicInteger = new AtomicInteger(0);
        static final AtomicBoolean stop = new AtomicBoolean(false);
    
        static {
            PoolingAsyncClientConnectionManager connectionManager = new PoolingAsyncClientConnectionManager();
            connectionManager.setMaxTotal(1000);
            connectionManager.setDefaultMaxPerRoute(100);
    
            IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
                .setSoTimeout(Timeout.ofSeconds(5))
                    .setIoThreadCount(5) //IO线程数
                    .build();
    
            client = HttpAsyncClients.custom()
                    .setIOReactorConfig(ioReactorConfig)
                    .setConnectionManager(connectionManager)
                    .build();
    
            client.start();
        }
    
        public static void main(final String[] args) throws Exception {
    
             new Thread(()->{
                 while (!stop.get()) {
                     httpGet();
                 }
             }).start();
    
            Thread.sleep(5000);
            stop.set(true);
    
            Thread.sleep(25000);
    
            System.out.println(atomicInteger.get());
    
    //        client.close(CloseMode.GRACEFUL);
    
            System.exit(0);
        }
    
        @SneakyThrows
        private static void httpGet() {
            final SimpleHttpRequest request = SimpleRequestBuilder.get()
                    .setUri("http://localhost:8080//test")
                    .build();
    
            final Future<SimpleHttpResponse> future = client.execute(
                    SimpleRequestProducer.create(request),
                    SimpleResponseConsumer.create(),
                    new FutureCallback<SimpleHttpResponse>() {
    
                        @Override
                        public void completed(final SimpleHttpResponse response) {
    //                        System.out.println(request + "->" + new StatusLine(response));
    //                        System.out.println(response.getBody().getBodyText());
                            if(!response.getBody().getBodyText().equals("ok")) {
                                throw new RuntimeException("error");
                            }
                            atomicInteger.incrementAndGet();
                        }
    
                        @Override
                        public void failed(final Exception ex) {
                            System.out.println(request + "->" + ex);
                        }
    
                        @Override
                        public void cancelled() {
                            System.out.println(request + " cancelled");
                        }
    
                    });
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    ps: 这里代码其实不够严谨,不过测试结果对比已经很悬殊了,不影响最终结论

    开启5个IO线程(不设置默认为cpu核数)
    客户端1个线程循环发起请求5s,之后再sleep 25s打印结果

    打印结果:2700

    修改代码:connectionManager.setDefaultMaxPerRoute(100);
    ->connectionManager.setDefaultMaxPerRoute(200);
    调大maxPerRoute为200

    打印结果:5400

    可以看到异步模式下,每秒的吞吐受maxPerRoute的影响较大(基本持平)
    注意如果不手动设置,这个默认值为5,所以如果不进行ConnectionManager设置,异步的测试结果会很差

    3、结论

    异步模式下因为使用了多路复用,一个IO线程管理多个连接,所以只需少量线程即可进行大量的远程接口调用

  • 相关阅读:
    Python每日一练(牛客新题库)——第17天:综合练习
    Android 12系统源码_SystemUI(一)SystemUI的启动流程
    深入探究音视频开源库WebRTC中NetEQ音频抗网络延时与抗丢包的实现机制
    快速申请注册微信小程序的方法
    【开源】基于SpringBoot的农村物流配送系统的设计和实现
    微信小程序2022年发展方向曝光
    线性表
    人工神经网络的基本模型,人工神经网络数学模型
    Spark Driver CPU 占用异常问题排查
    数据结构-二叉排序树(建立、查找、修改)
  • 原文地址:https://blog.csdn.net/li281037846/article/details/127683503