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 使用和性能测试的更多相关文章

  1. JDK HttpClient 单次请求的生命周期

    HttpClient 单次请求的生命周期 目录 HttpClient 单次请求的生命周期 1. 简述 2. uml图 3. Http连接的建立.复用和降级 3.1 调用流程及连接的建立和复用 3.2 ...

  2. JDK HttpClient 多重请求-响应的处理

    HttpClient 多重请求-响应的处理 目录 HttpClient 多重请求-响应的处理 1. 简述 2. 请求响应流程图 3. 用户请求的复制 4. 多重请求处理概览 5. 请求.响应过滤的执行 ...

  3. JDK httpClient 详解(源码级分析)——概览及架构篇

    1. 前言 2018年9月,伴随着java 11的发布,内置的httpclient正式登上了历史的舞台.此前,JDK内置的http工具URLConnection性能羸弱,操作繁琐,饱受诟病,也因此令如 ...

  4. Jmh测试JDK,CGLIB,JAVASSIST动态代理方式的性能

    前言 JDK,CGLIB,JAVASSIST是常用的动态代理方式. JDK动态代理仅能对具有接口的类进行代理. CGLIB动态代理方式的目标类可以没有接口. Javassist是一个开源的分析.编辑和 ...

  5. 在java中使用JMH(Java Microbenchmark Harness)做性能测试

    文章目录 使用JMH做性能测试 BenchmarkMode Fork和Warmup State和Scope 在java中使用JMH(Java Microbenchmark Harness)做性能测试 ...

  6. 小师妹学JVM之:JIT中的LogCompilation

    目录 简介 LogCompilation简介 LogCompilation的使用 解析LogCompilation文件 总结 简介 我们知道在JVM中为了加快编译速度,引入了JIT即时编译的功能.那么 ...

  7. HashMap 的 7 种遍历方式与性能分析

    前言 随着 JDK 1.8 Streams API 的发布,使得 HashMap 拥有了更多的遍历的方式,但应该选择那种遍历方式?反而成了一个问题. 本文先从 HashMap 的遍历方法讲起,然后再从 ...

  8. HttpURLConnection与 HttpClient 区别/性能测试对比

    HttpClient是个开源框架,封装了访问http的请求头,参数,内容体,响应等等, HttpURLConnection是java的标准类,什么都没封装,用起来太原始,不方便 HttpClient实 ...

  9. centos7 lvm合并分区脚本初探-linux性能测试 -centos7修改网卡名字-jdk环境安装脚本-关键字查询文件-批量添加用户

    1.#!/bin/bash lvmdiskscan | grep centos > /root/a.txt a=`sed -n '1p' /root/a.txt` b=`sed -n '2p' ...

随机推荐

  1. 【编程思想】【设计模式】【结构模式Structural】front_controller

    Python版 https://github.com/faif/python-patterns/blob/master/structural/front_controller.py #!/usr/bi ...

  2. 【Linux】【Services】【SaaS】Docker+kubernetes(5. 安装和配置ETCD集群)

    1. 简介: 1.1. ETCD是kubernetes和openstack都用到的组件,需要首先装好 1.2. 官方网站:https://coreos.com/etcd/ 1.3. ETCD的作用: ...

  3. 【Java 多线程】Java线程池类ThreadPoolExecutor、ScheduledThreadPoolExecutor及Executors工厂类

    Java中的线程池类有两个,分别是:ThreadPoolExecutor和ScheduledThreadPoolExecutor,这两个类都继承自ExecutorService.利用这两个类,可以创建 ...

  4. 对于React各种状态管理器的解读

    首先我们要先知道什么是状态管理器,这玩意是干啥的? 当我们在多个页面中使用到了相同的属性时就可以用到状态管理器,将这些状态存到外部的一个单独的文件中,不管在什么时候想使用都可以很方便的获取. reac ...

  5. Log4j2 Jndi 漏洞原理解析、复盘

    " 2021-12-10一个值得所有研发纪念的日子." 一波操作猛如虎,下班到了凌晨2点25. 基础组件的重要性,在此次的Log4j2漏洞上反应的淋漓尽致,各种"核弹级漏 ...

  6. Spring Cloud Eureka源码分析之服务注册的流程与数据存储设计!

    Spring Cloud是一个生态,它提供了一套标准,这套标准可以通过不同的组件来实现,其中就包含服务注册/发现.熔断.负载均衡等,在spring-cloud-common这个包中,org.sprin ...

  7. Containing ViewControllers

    Containing ViewControllers 转自:https://www.cocoanetics.com/2012/04/containing-viewcontrollers/ For a ...

  8. ciscn_2019_s_4***(栈迁移)

    这是十分经典的栈迁移题目 拿到题目例行检查 32位程序开启了nx保护 进入ida,发现了很明显的system 我们进入main函数查看vul 可以看到溢出的部分不够我们rop所以这道题通过栈迁移去做 ...

  9. 替换错误Table.ReplaceErrorValues(Power Query 之 M 语言)

    数据源: 任意数据源,数据中有错误值 目标: 将错误值替换为0 操作过程: [转换]>[替换值]>[替换错误] M公式: = Table.ReplaceErrorValues( 表, {{ ...

  10. LuoguP6904 [ICPC2015 WF]Amalgamated Artichokes 题解

    Content 已知常数 \(p,a,b,c,d\),我们知道,第 \(k\) 天的股价公式为 \(price_k=p\times(\sin(a\times k+b)+\cos(c\times k+d ...