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. 使用 Addressables 来管理资源

    使用 Addressables 来管理资源 一.安装 打开Package Manager,在Unity Technologies的目录下找到Addressables,更新或下载. 二.配置 依次打开W ...

  2. 【leetcode】378. Kth Smallest Element in a Sorted Matrix(TOP k 问题)

    Given an n x n matrix where each of the rows and columns is sorted in ascending order, return the kt ...

  3. Oracle中的instr函数

    最近修改某个条件,由原来输入一个数据修改为可以输入多个,如图所示: 在实现时用到了regexp_substr函数进行分割连接起来的数据,查询时还用到了instr函数进行判断,但出现了问题,当子库存输入 ...

  4. Android 实现微信QQ分享以及第三方登录

    集成准备 在微信开放平台创建移动应用,输入应用的信息,包括移动应用名称,移动应用简介,移动应用图片信息,点击下一步,选择Android 应用,填写信息提交审核. 获取Appkey 集成[友盟+]SDK ...

  5. Spring(3):AOP面向切面编程

    一,AOP介绍 AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开 ...

  6. redis入门到精通系列(九):redis哨兵模式详解

    (一)哨兵概述 前面我们讲了redis的主从复制,为了实现高可用,会选择一台服务器作为master,多台服务器作为slave.现在有这样一种情况,master宕机了,这时系统会选择一台slave作为m ...

  7. 【Linux】【Services】【SaaS】Docker+kubernetes(12. 部署prometheus/grafana/Influxdb实现监控)

    1.简介 1.1. 官方网站: promethos:https://prometheus.io/ grafana:https://grafana.com/ 1.2. 架构图 2. 环境 2.1. 机器 ...

  8. 2.7 Rust Structs

    A struct, or structure, is a custom data type that lets you name and package together multiple relat ...

  9. RocketMQ应用及原理剖析

    主流消息队列选型对比分析 基础项对比 可用性.可靠性对比 功能性对比 对比分析 Kafka:系统间的流数据通道 RocketMQ:高性能的可靠消息传输 RabbitMQ:可靠消息传输 RocketMQ ...

  10. Leetcode 78题-子集

    LeetCode 78 网上已经又很多解这题的博客了,在这只是我自己的解题思路和自己的代码: 先贴上原题: 我的思路: 我做题的喜欢在本子或别处做写几个示例,以此来总结规律:下图就是我从空数组到数组长 ...