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线程管理多个连接,所以只需少量线程即可进行大量的远程接口调用

一个工作时写的工具包。实现了Java版的Promise 和 HttpClientHttpClient 支持同步异步两种方式,也支持多种不同实现。目前有Netty 和 Apache Compoenet两种实现。本次上传移除了Netty实现。主要解决生产环境中同步httpclient造成的IO阻塞问题。同步http请求将导致 tomcat 的业务线程被阻塞。一旦某接口网络出现问题,可能会阻塞tomcat业务线程,从而无法处理正常业务。很多公司使用另开线程池的方式进行异步调用来解决tomcat线程阻塞问题。但由于本系统中接口网络太不稳定,使用线程池也将导致线程池中的线程不断加大,不管使用怎样的线程池策略,最终要么线程池线程全部挂起,要么部分任务被延迟执行,要么丢失部分任务。这在我们的系统中仍然不能接受。因此才有了这个组件的开发。该组件是单线程非阻塞式的,类似于JS中的ajax请求。都使用单线程异步回调的方式。目前该组件已经初步测试通过。如果大家也需要这样的组件,可以下载尝试一下。所有关键注释都已经写了,如有不明白可以发送邮件 ath.qu.trues@gmail.com 代码分为3个maven模块。 commons-ext : 实现Promise commons-tools: 实现 异步httpclient commons-parent:父模块 测试代码在 commons-tools/src/test/java/HttpTest.java 中. 要求至少Java 8 版本。 注释已经写好。这里贴出异步的http部分测试代码。 /** * 异步方法的Fluent写法 */ public void testAsyncHttpFluent() { SimpleRequest.Get("http://www.baidu.com") .header("h1", "hv1") .header("h2", "hv2") .parameter("p1", "pv1") .parameter("p2", "pv2") .chartUTF8() .build() .asyncExecute() .then(SimpleAsyncHttpClient::asString) .then(html -> { System.out.println(html); }) .catching(Throwable::printStackTrace);//如果有异常,则打印异常 }
HttpClient 是 Apache 提供的一个开源的 HTTP 客户端库,用于发送同步的 HTTP 请求。HttpAsyncClient 则是基于 HttpClient 实现的异步的 HTTP 客户端库,用于发送异步的 HTTP 请求。以下是它们的主要区别: 1. 同步异步HttpClient 发送的是同步的 HTTP 请求,即发送请求后需要等待响应才能继续执行。而 HttpAsyncClient 发送的是异步的 HTTP 请求,即发送请求后可以立即返回,等待响应的过程在后台进行,可以提高程序的并发性能。 2. API 设计:HttpClient 和 HttpAsyncClient 的 API 设计有所不同。HttpClient 的 API 设计比较复杂,需要手动设置很多参数,如连接超时、读取超时、认证、代理等。而 HttpAsyncClient 的 API 设计相对简单,参数设置比较少,主要是通过回调函数来处理响应结果。 3. 性能:HttpAsyncClient 在性能方面比 HttpClient 更好。在高并发的情况下,使用 HttpAsyncClient 可以减少线程的切换和上下文切换,提高程序的并发性能。 4. 应用场景:HttpClient 适用于发送同步的 HTTP 请求,主要用于简单的 HTTP 客户端开发。而 HttpAsyncClient 适用于发送异步的 HTTP 请求,主要用于需要处理大量请求的场景,如高性能的 Web 服务、爬虫等。 综上所述,HttpClient 和 HttpAsyncClient 都有各自的优劣势,需要根据实际需求进行选择。如果需要发送同步的 HTTP 请求,并且需要更多的参数设置和功能支持,可以选择 HttpClient;如果需要发送异步的 HTTP 请求,并且需要更好的性能和并发性能,可以选择 HttpAsyncClient。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值