江湖开发者 | Java粉
网站地图
首页> Java> HttpClient连接池
2017
10-23

HttpClient连接池

HttpClient连接池

package httppool;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.HttpConnectionFactory;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultHttpResponseParserFactory;
import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.impl.io.DefaultHttpRequestWriterFactory;
import org.apache.http.util.EntityUtils;

/**
 * httpclient version 4.5.3
 */
public class HttpClientPool {
	
	
	static PoolingHttpClientConnectionManager manager=null;
	
	static CloseableHttpClient httpClient = null;
	
	public static synchronized CloseableHttpClient getHttpClient(){
		
		if(httpClient ==null){
			//注册访问相关协议相关的Socket工厂
			Registry<ConnectionSocketFactory> socketFactoryRegistry= 
					RegistryBuilder.<ConnectionSocketFactory>create()
					.register("http", PlainConnectionSocketFactory.INSTANCE)
					.register("https", SSLConnectionSocketFactory.getSystemSocketFactory())
					.build();
			
			//HttpConnection工厂:配置写请求/解析响应处理器
			HttpConnectionFactory<HttpRoute,ManagedHttpClientConnection> connFactory = 
					new ManagedHttpClientConnectionFactory(
							DefaultHttpRequestWriterFactory.INSTANCE,
							DefaultHttpResponseParserFactory.INSTANCE);
			
			//DNS解析器
			DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE;
			
			//创建池化连接管理器
			manager = new PoolingHttpClientConnectionManager(socketFactoryRegistry,connFactory,dnsResolver);
			
			//默认为Socket配置
			SocketConfig defaultSocketConfig = SocketConfig.custom()
					.setTcpNoDelay(true).build();
			
			manager.setDefaultSocketConfig(defaultSocketConfig);
			
			manager.setMaxTotal(300);//设置整个连接池的最大连接数
			
			//每个路由的默认最大连接,每个路由实际最大连接数默认为
			//DefaultMaxPerRoute控制,而MaxTotal是控制整个池子最大数
			//设置过小无法支持大并发(ConnectionPoolTimeoutExcepiton:
			//Timeout waiting for connection from pool),路由是对maxTotal的细分
			
			manager.setDefaultMaxPerRoute(200); //每个路由最大连接数
			
			//在从连接池获取连接时,连接不活跃多长时间后需要进行一次验证,默认为2s
			manager.setValidateAfterInactivity(5*1000);
			
			//默认请求配置
			RequestConfig defaultRequestConfig = RequestConfig.custom()
					.setConnectTimeout(2*1000) //设置连接超时时间2s
					.setSocketTimeout(5*1000)  //设置等待数据超时时间,5s
					.setConnectionRequestTimeout(2000) //设置从连接池获取连接的等待超时时间
					.build();
			
			//创建HttpClient
			httpClient = HttpClients.custom()
					.setConnectionManager(manager)
					.setConnectionManagerShared(false) //连接池不是共享模式
					.evictIdleConnections(60, TimeUnit.SECONDS) //定期回收空闲连接
					.evictExpiredConnections() //定期回收过期连接
					.setConnectionTimeToLive(60, TimeUnit.SECONDS) //连接存活时间,如果不配置,则根据长连接信息决定 
					.setDefaultRequestConfig(defaultRequestConfig) //设置默认请求配置
					
					//连接重用策略,即是否能keepAlive
					.setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE)
					
					//长连接配置,即获取长连接生产多长时间
					.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
					
					//设置重试次数,默认是3次;当前是禁用掉(根据需要开启)
					.setRetryHandler( new DefaultHttpRequestRetryHandler(0,false))
					.build();
			
			//JVM停止或重启时,关闭连接池释放掉连接(跟数据库连接池相似)
			Runtime.getRuntime().addShutdownHook(new Thread(){
				@Override
				public void run() {
					try {
						httpClient.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
					super.run();
				}
			});
					
		}
		return httpClient;
	}
	
	/**
	 * 1.以可以通过如下方法为某个路由单独设置其连接数大小
	 * manager.setMaxPerRoute(new HttpRoute(new Host("jd.com",80)),100);
	 * 
	 * 2.setConnectionManagerShared方法用于配置此连接池是否在多个HttpClient之间共享(默认为false),如果是共享的话,那么如
	 * IdelConnectionEvictor就不能每个HttpClient一个,而只需要定义一个即可。
	 * 
	 * 通过evictIdleConnections和evictExpiredConnections方法配置一个后台线程定期释放过期和空闲连接。HttpClientBuilder
	 * 将创建IdleConnectionEvictor并定期进行过期,如果连接池是共享的,多个HttpClient共用一个连接池,则这两个配置无效。
	 * 
	 * 在进行释放过期连接和空闲连接时,IdleConnectionEvictor会周期性调用closeCxpiredConnections和closeIdleConnections这
	 * 两个方法,但它们的实现是通过一把大的锁锁贪玩了整个连接池,然后进行遍历。另外,建议只启用closeExpiredConnections,这需要HTTP服务
	 * 生产者在返回响应中包含超时时间"Keep-Alive:timeout=time",这样就不需要使用closeIdleConnections进行过期空闲连接了。
	 * 
	 */
	
	
	
	public static void main(String[] args) {
		HttpResponse response = null;
		HttpGet get = new HttpGet("https://www.baidu.com");
		try {
			response= getHttpClient().execute(get);
			if(response.getStatusLine().getStatusCode()!=HttpStatus.SC_OK){
				EntityUtils.consume(response.getEntity());
				//error
			}else{
				String result =EntityUtils.toString(response.getEntity());
				//ok
				System.out.println(result);
			}
		}catch (Exception e) {
			if(response !=null){
				try {
					EntityUtils.consume(response.getEntity());
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
			e.printStackTrace();
		}
	}
	
	/**
	 * 要使用EntityUtils.consume(response.getEntity())或者EntityUtils.toString(response.getEneity())消费
	 * 响应体,不推荐HttpEntity#getContent#close 方法来释放连接,处理不好异常将导致连接不释放,也不推荐使用CloseableHttpResponse#close
	 * 关闭连接,它将直接关闭Socket,导致长连接不能复用。
	 */

}

/**
 * 须注意的是:
 * 
 * 1.在开启长连接时才是真正的连接池,如果是短连接,则只是作为一个信号量来限制总请求数,连接并没有实现复用。
 * 2.JVM在停止或重启时,记得关闭连接池释放连接。
 * 3.HttpClient是线程安全的,不要每次使用都创建一个
 * 4.如果连接池配置得比较大,则可以考虑创建多个HttpClient实例,而不是使用一个HttpClient实例。
 * 5.使用连接池时,要尽快消费响应体并释放连接到连接池,不要保持太久。
 * 
 */


Java江湖     
全部评论:

表情验证码,看不清楚,换一张

随机文章

云标签

公众号

微信

分享:分享我们的知识;专注:专注个人技术的提升;