当前位置: 首页 > news >正文

httpClient同步、异步性能对比

0、测试目的

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

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

1、准备工作

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

客户端程序引入httpClient依赖:

<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
     <artifactId>httpclient5</artifactId>
     <version>5.1.3</version>
 </dependency>

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");
        }
    }
}

}

开启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");
                    }

                });
    }

}

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

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

打印结果:2700

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

打印结果:5400

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

3、结论

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

相关文章:

  • 吴峰光杀进 Linux 内核
  • 朋友离职了,一周面试了20多场,我直呼内行
  • 创建 MQTT 连接时如何设置参数?
  • C#基础知识
  • 云原生之快速使用Nacos Spring Cloud
  • 什么是GPIO的推挽输出和开漏输出
  • Java 模拟实现 定时器 和 线程池
  • 笔记,正则表达式里的元字符
  • kubernetes 生成认证文件
  • LINUX系统搭建FTP服务器--操作步骤
  • 面试 高频面试题 基础 HTML CSS JS
  • 【Python基础篇020】网络编程初识
  • 11、时序约束
  • 高级IO多路转接之select、poll、epoll
  • Java --- springMVC实现RESTFul案例
  • 大数据相关积累
  • 力扣--有效的括号
  • [Java安全]—Controller内存马
  • Java这些最基础的知识,你还记得多少?
  • 智能优化算法:白鲸优化算法-附代码