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



















