轻松把玩HttpClient之封装HttpClient工具类(八),优化启用Http连接池策略

       写了HttpClient工具类后,有人一直在问我怎么启用http连接池,其实我没考虑过这个问题。不过闲暇的时候,突然间想起了这个问题,就想把这个问题搞一搞。

       之前没用过,但是理解起来应该不算难。作为一个Coder,就算没用过http连接池,但是肯定用过数据库连接池。二者的功能是类似的,就是把建立链接和断开链接的时间节省下来。众所周知,http建立链接是需要3次握手,了解的深入一些的,还知道断开链接需要4次握手。这些操作都是自动完成的,如果我们把这些建立和断开链接的时间节省掉,对于大批量的http请求(如爬虫)是很有用的。

       关于HttpClient如何启用连接池,可以参考这篇文章:http://www.cnblogs.com/likaitai/p/5431246.html。介绍了如何通过连接池获取链接,以及在不用连接时,如果处理不会导致链接直接关闭。

       说了这么多,下面切入正题:HttpClient工具类如何启用http连接池?其实只需要修改创建链接方法即可:

  之前在工具类的核心方法execute方法里获取httpclient对象,调用的是create(String url)方法。返回的是默认的一个HttpClient对象。现在要启用连接池,必须修改此方法。配置连接池的类是HCB,而execute方法接受的参数是HttpConfig参数,所以,首先要在HttpConfig里添加一个HCB对象。然后修改create方法。具体如下:

	/**
	 * HCB对象,用于创建HttpClient对象
	 */
	private HCB hcb;

	public HCB hcb() {
		return hcb;
	}

	/**
	 * HCB对象,用于自动从连接池中获得HttpClient对象<br>
	 * <font color="red"><b>请调用hcb.pool方法设置连接池</b></font>
	 * @throws HttpProcessException
	 */
	public HttpConfig hcb(HCB hcb) throws HttpProcessException {
		this.hcb = hcb;
		return this;
	}
	/**
	 * 判定是否开启连接池、及url是http还是https <br>
	 * 		如果已开启连接池,则自动调用build方法,从连接池中获取client对象<br>
	 * 		否则,直接返回相应的默认client对象<br>
	 *
	 * @throws HttpProcessException
	 */
	private static void create(HttpConfig config) throws HttpProcessException  {
		if(config.hcb()!=null && config.hcb().isSetPool){ //如果设置了hcb对象,且配置了连接池,则直接从连接池取
			if(config.url().toLowerCase().startsWith("https://")){
				config.client(config.hcb().ssl().build());
			}else{
				config.client(config.hcb().build());
			}
		}else{
			if(config.client()==null){//如果为空,设为默认client对象
				if(config.url().toLowerCase().startsWith("https://")){
					config.client(client4HTTPS);
				}else{
					config.client(client4HTTP);
				}
			}
		}
	}

       至于关闭方面,fmt2String以及fmt2Stream方法中,在EntityUtils.toString和EntityUtils.consume方法中已经close了instream,释放了资源。最后调用close(HttpClient)即执行client.close()方法。这样就不会直接关闭链接了,会被连接池自动回收再次使用。

       最后分享一个测试类,分组测试Get请求、Down操作,在开启和关闭Http线程池完成请求的耗时情况。代码如下:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.http.Header;

import com.tgb.ccl.http.common.HttpConfig;
import com.tgb.ccl.http.common.HttpHeader;
import com.tgb.ccl.http.exception.HttpProcessException;
import com.tgb.ccl.http.httpclient.HttpClientUtil;
import com.tgb.ccl.http.httpclient.builder.HCB;

/**
 * 测试启用http连接池
 *
 * @author arron
 * @date 2016年11月7日 下午1:08:07
 * @version 1.0
 */
public class TestHttpPool {

	// 设置header信息
	private static final Header[] headers = HttpHeader.custom().userAgent("Mozilla/5.0").from("http://blog.csdn.net/newest.html").build();

	// URL列表数组,GET请求
	private static final String[] urls = {
			"http://blog.csdn.net/xiaoxian8023/article/details/49883113",
			"http://blog.csdn.net/xiaoxian8023/article/details/49909359",
			"http://blog.csdn.net/xiaoxian8023/article/details/49910127",
			"http://blog.csdn.net/xiaoxian8023/article/details/49910885",
			"http://blog.csdn.net/xiaoxian8023/article/details/51606865",
	};

	// 图片URL列表数组,Down操作
	private static final String[] imgurls ={
			"http://ss.bdimg.com/static/superman/img/logo/logo_white_fe6da1ec.png",
			"https://scontent-hkg3-1.xx.fbcdn.net/hphotos-xaf1/t39.2365-6/11057093_824152007634067_1766252919_n.png"
	};

	private static StringBuffer buf1=new StringBuffer();
	private static StringBuffer buf2=new StringBuffer();

	//多线程get请求
	public static void testMultiGet(HttpConfig cfg, int count) throws HttpProcessException{
	        try {
				int pagecount = urls.length;
				ExecutorService executors = Executors.newFixedThreadPool(pagecount);
				CountDownLatch countDownLatch = new CountDownLatch(count);
				//启动线程抓取
				for(int i = 0; i< count;i++){
				    executors.execute(new GetRunnable(countDownLatch,cfg.headers(headers).url(urls[i%pagecount])));
				}
				countDownLatch.await();
				executors.shutdown();
			} catch (InterruptedException e) {
				e.printStackTrace();
	        }
	}

	//多线程下载
	public static void testMultiDown(HttpConfig cfg, int count) throws HttpProcessException{
		try {
			int pagecount = imgurls.length;
			ExecutorService executors = Executors.newFixedThreadPool(pagecount);
			CountDownLatch countDownLatch = new CountDownLatch(count);
			//启动线程抓取
			for(int i = 0; i< count;i++){
			    executors.execute(new GetRunnable(countDownLatch, cfg.url(imgurls[i%2]), new FileOutputStream(new File("d://aaa//"+(i+1)+".png"))));
			}
			countDownLatch.await();
			executors.shutdown();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	 static class GetRunnable implements Runnable {
	        private CountDownLatch countDownLatch;
	        private HttpConfig config = null;
	        private FileOutputStream out = null;

	        public GetRunnable(CountDownLatch countDownLatch,HttpConfig config){
	           this(countDownLatch, config, null);
	        }
	        public GetRunnable(CountDownLatch countDownLatch,HttpConfig config,FileOutputStream out){
	        	this.countDownLatch = countDownLatch;
	        	this.config = config;
	        	this.out = out;
	        }

	        @Override
	        public void run() {
	            try {
	            	config.out(out);
	            	if(config.out()==null){
	            		String response = null;
	            		response =  HttpClientUtil.get(config);
	            		System.out.println(Thread.currentThread().getName()+"--获取内容长度:"+response.length());
	            		response = null;

	            	}else{
	            		HttpClientUtil.down(config);
	            		try {
							config.out().flush();
							config.out().close();
						} catch (IOException e) {
							e.printStackTrace();
						}
	            		System.out.println(Thread.currentThread().getName()+"--下载完毕");
	            	}
	            } catch (HttpProcessException e) {
					e.printStackTrace();
				} finally {
	                countDownLatch.countDown();
	            }
	        }
	    }  

	/**
	 * 测试不启用http连接池,get100次,down20次的执行时间
	 * @throws HttpProcessException
	 */
	private static void testNoPool(int getCount, int downCount) throws HttpProcessException {
		long start = System.currentTimeMillis();

		if(getCount>0){
			HttpConfig cfg1 = HttpConfig.custom().client(HCB.custom().build()).headers(headers);
			testMultiGet(cfg1, getCount);
		}
		if(downCount>0){
			HttpConfig cfg2 = HttpConfig.custom().client(HCB.custom().build());
			testMultiDown(cfg2, downCount);
		}

		System.out.println("-----所有线程已完成!------");
        long end = System.currentTimeMillis();
        System.out.println("总耗时(毫秒): -> " + (end - start));
        buf1.append("\t").append((end-start));
	}

	/**
	 * 测试启用http连接池,get100次,down20次的执行时间
	 * @throws HttpProcessException
	 */
	private static void testByPool(int getCount, int downCount) throws HttpProcessException {
		long start = System.currentTimeMillis();

		HCB hcb= HCB.custom().timeout(10000).pool(10, 10).ssl();

		if(getCount>0){
			HttpConfig cfg3 = HttpConfig.custom().hcb(hcb);
			testMultiGet(cfg3, getCount);
		}
		if(downCount>0){
			HttpConfig cfg4 = HttpConfig.custom().hcb(hcb);
			testMultiDown(cfg4, downCount);
		}

		System.out.println("-----所有线程已完成!------");
        long end = System.currentTimeMillis();
        System.out.println("总耗时(毫秒): -> " + (end - start));
        buf2.append("\t").append((end-start));
	}

	public static void main(String[] args) throws Exception {
		File file = new File("d://aaa");
		if(!file.exists() && file.isDirectory()){
			file.mkdir();
		}

		//-------------------------------------------
		//  以下2个方法
		//  分别测试 (get次数,down次数)
		//  {100,0},{200,0},{500,0},{1000,0}
		//  {0,10},{0,20},{0,50},{0,100}
		//  {100,10},{200,20},{500,50},{1000,100}
		//-------------------------------------------

		int[][] times1 = {{100,0} ,{ 200,0 },{ 500,0 },{ 1000,0}};
		int[][] times2 = {{0,10},{0,20},{0,50},{0,100}};
		int[][] times3 = {{100,10},{200,20},{500,50},{1000,100}};
		List<int[][]> list = Arrays.asList(times1,times2,times3);
		int n=5;

		int t=0;
		//测试未启用http连接池,
		for (int[][] time : list) {
			buf1.append("\n");
			for (int i = 0; i < time.length; i++) {
				for (int j = 0; j < n; j++) {
					testNoPool(time[i][0],time[i][1]);
					Thread.sleep(100);
					System.gc();
					Thread.sleep(100);
				}
				buf1.append("\n");
			}
		}

		t=0;
		//测试启用http连接池
		for (int[][] time : list) {
			buf2.append("\n");
			for (int i = 0; i < time.length; i++) {
				for (int j = 0; j < n; j++) {
					testByPool(time[i][0],time[i][1]);
					Thread.sleep(100);
					System.gc();
					Thread.sleep(100);
				}
				buf2.append("\n");
			}
			t++;
		}

		//把结果打印到Console中
		String[] results1 = buf1.toString().split("\n");
		String[] results2 = buf2.toString().split("\n");

		for (int i = 0; i < results1.length; i++) {
			System.out.println(results1[i]);
			System.out.println(results2[i]);
		}

	}
}

       测试结果如下:

操作 请求次数 是否启用Pool 第1次 第2次 第3次 第4次 第5次 平均时间 启用后的效率
GET 100 4801 4807 4853 4810 4522 4758.6 52.89%
2146 1989 2302 2355 2416 2241.6
200 9222 9519 9085 9196 8908 9186 43.15%
4992 4711 4863 7001 4545 5222.4
500 23727 23082 23762 23427 23117 23423 45.88%
12146 12557 12581 13121 12979 12676.8
1000 47518 72445 45028 52860 55764 54723 48.62%
25073 25067 39550 26014 24888 28118.4
Down 10 10605 8273 9440 7774 8740 8966.4 4.37%
10415 7249 7331 8554 9325 8574.8
20 17306 18455 18811 19294 15430 17859.2 2.67%
17234 16028 18152 17530 17971 17383
50 46873 41528 51085 49900 40666 46010.4 -2.93%
44941 50376 46759 43774 50951 47360.2
100 89909 93065 98297 88440 92010 92344.2 -0.93%
91420 96388 94635 88424 95152 93203.8
GET,Down 100,10 15913 13465 14167 15607 11566 14143.6 27.42%
11805 10800 8322 10735 9668 10266
200,20 26579 28744 27791 29712 32360 29037.2 25.76%
20891 24664 19319 19511 23394 21555.8
500,50 71462 72694 74285 76207 72574 73444.4 13.46%
57137 75860 63288 62309 59192 63557.2
1,000,100 147264 149527 143251 143865 139723 144726 16.14%
118305 124517 122511 116823 124673 121365.8
测试使用时间(不含暂停和GC时间)、平均效率 79.1789833 23.04%

       通过测试结果可以看出来,Get请求效果明显,启用后性能要提升50%左右。而Down操作,则反而有所下降。这是为什么呢?其实很简单。连接池是节省了握手的次数,但是握手所消耗的时间,跟一个Down操作相比,肯定要小很多。所以Down操作消耗的时间已经超过了节省握手的时间了,也就起不到优化的作用了,所以要根据实际情况使用连接池。

       最新的完整代码请到GitHub上进行下载:https://github.com/Arronlong/httpclientUtil 。

       httpclientUtil (QQ交流群:548452686 

时间: 2024-10-31 21:50:52

轻松把玩HttpClient之封装HttpClient工具类(八),优化启用Http连接池策略的相关文章

分页封装实用工具类及其使用方法

分页封装实用工具类及其使用方法 作者: javaboy2012 Email:yanek@163.com qq:    1046011462     package com.yanek.util; import java.util.List; public class PageBean { /** * @param args */ public static void main(String[] args) { } private int currentpage; // 当前页数 private

MySQL数据库学习笔记(十一)----DAO设计模式实现数据库的增删改查(进一步封装JDBC工具类)

[正文] 一.DAO模式简介 DAO即Data Access Object,数据访问接口.数据访问:故名思义就是与数据库打交道.夹在业务逻辑与数据库资源中间. DAO模式实际上是两个模式的组合,即Data Accessor (数据访问者)模式和 Active Domain Object(领域对象)模式.Data Accessor 模式实现了数据访问和业务逻辑的分离:Active Domain Object 模式实现了业务数据的对象化封装. 需要注意的是,DAO设计模式是Java EE中的设计模式

MySQL数据库学习笔记(十)----JDBC事务处理、封装JDBC工具类

[正文] 首先需要回顾一下上一篇文章中的内容:MySQL数据库学习笔记(九)----JDBC的PreparedStatement接口重构增删改查 一.JDBC事务处理: 我们已经知道,事务的概念即:所有的操作要么同时成功,要么同时失败.在MySQL中提供了Commit.Rollback命令进行事务的提交与回滚.实际上在JDBC中也存在事务处理,如果要想进行事务处理的话,则必须按照以下的步骤完成. JDBC中事务处理的步骤: 1.要取消掉JDBC的自动提交:void setAutoCommit(b

轻松把玩HttpClient之封装HttpClient工具类(一)(现有网上分享中的最强大的工具类)

       搜了一下网络上别人封装的HttpClient,大部分特别简单,有一些看起来比较高级,但是用起来都不怎么好用.调用关系不清楚,结构有点混乱.所以也就萌生了自己封装HttpClient工具类的想法.要做就做最好的,本工具类支持插件式配置Header.插件式配置httpclient对象,这样就可以方便地自定义header信息.配置ssl.配置proxy等.        是不是觉得说的有点悬乎了,那就先看看调用吧: public static void testSimple() thro

轻松把玩HttpClient之封装HttpClient工具类(六),封装输入参数,简化工具类

       在写这个工具类的时候发现传入的参数太多,以至于方法泛滥,只一个send方法就有30多个,所以对工具类进行了优化,把输入参数封装在一个对象里,这样以后再扩展输入参数,直接修改这个类就ok了.        不多说了,先上代码: /** * 请求配置类 * * @author arron * @date 2016年2月2日 下午3:14:32 * @version 1.0 */ public class HttpConfig { private HttpConfig(){}; /**

轻松把玩HttpClient之封装HttpClient工具类(七),新增验证码识别功能

       这个HttpClientUtil工具类分享在GitHub上已经半年多的时间了,并且得到了不小的关注,有25颗star,被fork了38次.有了大家的鼓励,工具类一直也在完善中.最近比较忙,两个多月前的修改在今天刚修改测试完成,今天再次分享给大家.        验证码识别这项技术并不是本工具类的功能,而是通过一个开源的api来识别验证码的.这里做了一个简单的封装,主要是用来解决登陆时的验证码的问题.在线验证码识别官网:http://lab.ocrking.com/,github地址

轻松把玩HttpClient之封装HttpClient工具类(九),添加多文件上传功能

       在Git上有人给我提Issue,说怎么上传文件,其实我一开始就想上这个功能,不过这半年比较忙,所以一直耽搁了.这次正好没什么任务了,赶紧完成这个功能.毕竟作为一款工具类,有基本的请求和下载功能,就差上传了,有点说不过去.好了,天不早了,咱干点正事吧.        如果你只想了解怎么用HttpClient来上传文件,可以参考这篇文章:http://blog.csdn.net/fengyuzhengfan/article/details/39941851,里面写的很清楚了.这里我主要

轻松把玩HttpClient之封装HttpClient工具类(二),插件式配置HttpClient对象

       上一篇文章中,简单分享一下封装HttpClient工具类的思路及部分代码,本文将分享如何实现插件式配置HttpClient对象.        如果你看过我前面的几篇关于HttpClient的文章或者官网示例,应该都知道HttpClient对象在创建时,都可以设置各种参数,但是却没有简单的进行封装,比如对我来说比较重要的3个:代理.ssl(包含绕过证书验证和自定义证书验证).超时.还需要自己写.所以这里我就简单封装了一下,顺便还封装了一个连接池的配置.        其实说是插件式

轻松把玩HttpClient之封装HttpClient工具类(三),插件式配置Header

       上篇文章介绍了插件式配置HttpClient,本文将介绍插件式配置Header.        为什么要配置header在前面已经提到了,还里再简单说一下,要使用HttpClient模拟请求,去访问各种接口或者网站资源,都有可能有各种限制,比如说java客户端模拟访问csdn博客,就必须设置User-Agent,否则就报错了.还有各种其他情况,必须的设置一些特定的Header,才能请求成功,或者才能不出问题.        好了就说这么多,本次还是采用构造者模式的级联调用方式,来完