Java教程

JDK Httpclient 使用和性能测试

本文主要是介绍JDK Httpclient 使用和性能测试,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Httpclient 使用和性能测试

上篇,通过简介和架构图,我们对HttpClient有了初步的了解。
本篇我们展示HttpClient的简单使用,同时为了说明httpclient的使用性能,我们将Httpclient的同步和异步模式与apache的Httpclient4作比较。。

1. HttpClient示例代码

以下基本是官方示例,分别展示了如何使用Get和Post请求。

HttpClient client = HttpClient.newBuilder()
    .version(Version.HTTP_1_1)	//可以手动指定客户端的版本,如果不指定,那么默认是Http2
    .followRedirects(Redirect.NORMAL)	//设置重定向策略
    .connectTimeout(Duration.ofSeconds(20))	//连接超时时间
    .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))	//代理地址设置
    .authenticator(Authenticator.getDefault()) 
    //.executor(Executors.newFixedThreadPoolExecutor(8))  //可手动配置线程池
    .build();   

HttpRequest request = HttpRequest.newBuilder()       
    .uri(URI.create("https://foo.com/"))    //设置url地址
    .GET()
    .build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());  	//同步发送
System.out.println(response.statusCode()); 	//打印响应状态码
System.out.println(response.body());  


HttpRequest request = HttpRequest.newBuilder()
       .uri(URI.create("https://foo.com/")) 
       .timeout(Duration.ofMinutes(2))	//设置连接超时时间
       .header("Content-Type", "application/json")	
       .POST(BodyPublishers.ofFile(Paths.get("file.json")))    //设置请求体来源
       .build();   
client.sendAsync(request, BodyHandlers.ofString()) 		//异步发送
    .thenApply(HttpResponse::body) 	//发送结束打印响应体
    .thenAccept(System.out::println);  

可以看到,应用编写的代码相对流畅自然。不过,也有几个注意点

  • Http连接池不支持手动配置,默认是无限复用的
  • 重试次数不支持手动配置
  • 不指定Http客户端或请求的版本,会默认使用Http2模式进行连接,受挫后会进行降级
  • 请求的同步发送模式(send)实际上会后台另开线程

短短的几行代码只是实现了功能,那么,它的性能如何呢?我们把它和业界标杆——Apache 的HttpClient作对比。

2. 服务器测试代码编写

为了简便,使用node.js的http模块运行一个简易的服务器。该服务器驻守在8080端口,每收到一个请求,停留500ms后返回响应。

let http = require("http")
let server = http.createServer()
server.addListener("request", (req, res) => {
    if (req.url.startsWith("/")) {
        //接到任意请求,停留0.5秒后返回
        setTimeout(() => {
            res.end("haha")
        }, 500)
    }
}
)
server.listen(8080, () => console.log("启动成功!"))

使用node运行该js文件,提示已启动成功

3. JDK httpclient 和apache Httpclient 测试代码

首先定义公共的测试接口:

public interface Tester {

    //测试参数
    class TestCommand {

    }

    /**
     * 测试主方法
     * @param testCommand 测试参数
     */
    void test(TestCommand testCommand) throws Exception;

    /**
     * 重复测试多次
     * @param testName 测试名称
     * @param times 测试次数
     * @param testCommand 每次测试的参数
     */
    default void testMultipleTimes(String testName, int times, TestCommand testCommand) throws Exception{
        long startTime = System.currentTimeMillis();
        System.out.printf(" ----- %s开始,共%s次 -----\n", testName, times);
        for (int i = 0; i < times; i++) {
            long currentStartTime = System.currentTimeMillis();
            test(testCommand);
            System.out.printf("第%s次测试用时:%sms\n", i + 1, (System.currentTimeMillis() - currentStartTime));
        }
        long usedTime = System.currentTimeMillis() - startTime;
        System.out.printf("%s次测试共用时:%sms,平均用时:%sms\n", times, usedTime, usedTime / times);
    }
}

定义测试类,包含三个静态嵌套类,分别用作JDK httpclient的异步模式、同步模式和apache Httpclient的同步模式

public class HttpClientTester {

    /** Http请求的真正测试参数*/
    static class HttpTestCommand extends Tester.TestCommand {

        /**目的url*/
        String url;
        /**单次测试请求次数*/
        int requestTimes;
        /**请求线程数*/
        int threadCount;

        public HttpTestCommand(String url, int requestTimes, int threadCount) {
            this.url = url;
            this.requestTimes = requestTimes;
            this.threadCount = threadCount;
        }
    }


    static class BlocklyHttpClientTester implements Tester {

        @Override
        public void test(TestCommand testCommand) throws Exception {
            HttpTestCommand httpTestCommand = (HttpTestCommand) testCommand;
            testBlockly(httpTestCommand.url, httpTestCommand.requestTimes,httpTestCommand.threadCount);
        }

        /**
         * 使用JDK Httpclient的同步模式进行测试
         * @param url 请求的url
         * @param times 请求次数
         * @param threadCount 开启的线程数量
         * @throws ExecutionException
         * @throws InterruptedException
         */
        void testBlockly(String url, int times, int threadCount) throws ExecutionException, InterruptedException {
            threadCount = threadCount <= 0 ? 1 : threadCount;
            ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
            HttpClient client = HttpClient.newBuilder().build();
            Callable<String> callable1 = () -> {
                HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
                HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
                return response.body();
            };
            List<Future<String>> futureList1 = new ArrayList<>();
            for (int i = 0; i < times; i++) {
                Future<String> future1 = executorService.submit(callable1);
                futureList1.add(future1);
            }
            for (Future<String> stringFuture : futureList1) {
                //阻塞直至所有请求返回
                String s = stringFuture.get();
            }
            executorService.shutdown();
        }
    }


    static class NonBlocklyHttpClientTester implements Tester {


        @Override
        public void test(TestCommand testCommand) throws Exception {
            HttpTestCommand httpTestCommand = (HttpTestCommand) testCommand;
            testNonBlockly(httpTestCommand.url, httpTestCommand.requestTimes);
        }

        /**
         * 使用JDK Httpclient的异步模式进行测试
         * @param url 请求的url
         * @param times 请求次数
         * @throws InterruptedException
         */
        void testNonBlockly(String url, int times) throws InterruptedException {
            //给定16个线程,业务常用 2 * Runtime.getRuntime().availableProcessors()
            ExecutorService executor = Executors.newFixedThreadPool(16);
            HttpClient client = HttpClient.newBuilder()
                    .executor(executor)
                    .build();
            //使用倒计时锁来保证所有请求完成
            CountDownLatch countDownLatch = new CountDownLatch(times);
            HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
            while (times-- >= 0) {
                client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                        .whenComplete((stringHttpResponse, throwable) -> {
                            if (throwable != null) {
                                throwable.printStackTrace();
                            }
                            if (stringHttpResponse != null) {
                                stringHttpResponse.body();
                            }
                            countDownLatch.countDown();
                        });
            }

            //阻塞直至所有请求完成
            countDownLatch.await();
            executor.shutdown();
        }
    }

    static class ApacheHttpClientTester implements Tester {

        @Override
        public void test(TestCommand testCommand) throws Exception {
            HttpTestCommand httpTestCommand = (HttpTestCommand) testCommand;
            testBlocklyWithApacheClient(httpTestCommand.url, httpTestCommand.requestTimes,httpTestCommand.threadCount);
        }
        /**
         * 使用Apache HttpClient进行测试
         * @param url 请求的url
         * @param times 使用时长
         * @param threadCount 开启的线程数量
         * @throws ExecutionException
         * @throws InterruptedException
         */
        void testBlocklyWithApacheClient(String url, int times, int threadCount) throws ExecutionException, InterruptedException {

            threadCount = threadCount <= 0 ? 1 : threadCount;
            ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
            //设置apache Httpclient连接复用无限制,体现其最大性能
            connectionManager.setDefaultMaxPerRoute(Integer.MAX_VALUE);
            connectionManager.setMaxTotal(Integer.MAX_VALUE);
            CloseableHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connectionManager).build();
            Callable<String> callable1 = () -> {
                HttpGet httpGet = new HttpGet(url);
                CloseableHttpResponse response = httpClient.execute(httpGet);
                return EntityUtils.toString(response.getEntity());
            };
            List<Future<String>> futureList1 = new ArrayList<>();
            for (int i = 0; i < times; i++) {
                Future<String> future1 = executorService.submit(callable1);
                futureList1.add(future1);
            }
            for (Future<String> stringFuture : futureList1) {
                //阻塞直至所有请求返回
                String s = stringFuture.get();
            }
            executorService.shutdown();
        }
    }

测试的main方法:

    public static void main(String[] args) {
        try {
            //
            HttpTestCommand testCommand = new HttpTestCommand("http://localhost:8080", 200, 16);
            //每个测试重复3轮,减少误差
            final int testTimes = 3;
            new BlocklyHttpClientTester().testMultipleTimes("JDK HttpClient同步模式测试", testTimes, testCommand);
            new NonBlocklyHttpClientTester().testMultipleTimes("JDK HttpClient异步模式测试", testTimes, testCommand);
            new ApacheHttpClientTester().testMultipleTimes("Apache Httpclient同步模式测试", testTimes, testCommand);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

4. 测试结果

----- JDK HttpClient同步模式测试开始,共3次 -----
第1次测试用时:4414ms
第2次测试用时:3580ms
第3次测试用时:3620ms
3次测试共用时:11620ms,平均用时:3873ms
----- JDK HttpClient异步模式测试开始,共3次 -----
第1次测试用时:568ms
第2次测试用时:595ms
第3次测试用时:579ms
3次测试共用时:1742ms,平均用时:580ms
----- Apache Httpclient同步模式测试开始,共3次 -----
第1次测试用时:3719ms
第2次测试用时:3557ms
第3次测试用时:3574ms
3次测试共用时:10851ms,平均用时:3617ms

可见,Httpclient同步模式与apacheHttpclient同步模式性能接近;异步模式由于充分利用了nio非阻塞的特性,在线程数相同的情况下,效率大幅优于同步模式。

需要注意的是,此处的“同步”“异步”并非I/O模型中的同步,而是指编程方式上的同步/异步。

5. 总结

通过以上示例代码,可以看出HttpClient具有编写流畅、性能优良的特点,也有可定制性不足的遗憾。

下一节,我们将深入客户端的构建和启动过程,接触选择器管理者这一角色,探寻它和Socket通道的交互的交互过程。

这篇关于JDK Httpclient 使用和性能测试的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!