Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说10年程序员闲谈HttpClient「终于解决」,希望能够帮助你!!!。
如果我说,写技术文章会上瘾,你信么?当遇到不熟悉的技术,想办法去理解,去总结,并记录下来,它就会变成你的东西。
记得去年时的一个项目,由于对方没有接入SOA服务,只能基于httpclient进行http请求,当时使用HttpClient时,由于缺乏经验,只能照搬别人的参数模板,使用之后才发现这里的坑有多深,也许这些坑大家都可能踩过,今天就总结下使用httpclient的正确姿势,目前用的比较多的版本是3.1和4.2.3。
当使用HttpClient的项目上线之后,不出问题还好,一旦出了问题就很难排查,但大部分都是由于对参数不了解,随意设置导致的,下面以4.2.3为例,对参数进行说明:
private native int socketRead0(FileDescriptor fd, byte b[], int off, int len, int timeout) throws IOException;
HttpClient可以基于连接池管理器PoolingClientConnectionManager重复利用connection,这里有几个参数需要说明一下:
PoolingClientConnectionManager pool = new PoolingClientConnectionManager(); pool.setMaxTotal(100); pool.setDefaultMaxPerRoute(50);
如果项目的并发量不是很大,使用3.1的版本没有问题,如果并发量很大,最好升级到4.2.3,如下是4.2.3的示例代码:
public class HttpClientCase { public static void main(String[] args) throws Exception { HttpParams httpParams = new BasicHttpParams(); httpParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000); httpParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 1000); httpParams.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true); httpParams.setBooleanParameter(CoreConnectionPNames.SO_KEEPALIVE, true); httpParams.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT, 5000L); PoolingClientConnectionManager pool = new PoolingClientConnectionManager(); pool.setMaxTotal(100); pool.setDefaultMaxPerRoute(50); ExecutorService executorService = Executors.newFixedThreadPool(100); int i = 1; while(true && i < 1000) { executorService.execute(new Task(pool, httpParams, i++)); } } public static class Task implements Runnable { PoolingClientConnectionManager pool; HttpParams httpParams; int i; public Task(PoolingClientConnectionManager pool, HttpParams httpParams, int i) { this.pool = pool; this.httpParams = httpParams; this.i = i; } @Override public void run() { HttpResponse response = null; try { System.out.println(i); HttpClient client = new DefaultHttpClient(pool, httpParams); HttpGet httpGet = new HttpGet("http://www.baidu.com"); response = client.execute(httpGet); // handle response } catch (Exception e) { e.printStackTrace(); } finally { if (response != null) { try { EntityUtils.consume(response.getEntity()); } catch (IOException e) { //e.printStackTrace(); } } } } } }
上述代码中,利用线程池模拟大量请求,看看不同的参数设置会发生什么?
1、如果SO_TIMEOUT设置的太小,容易发生SocketTimeoutException: Read timed out异常;
2、如果线程池大小 > DefaultMaxPerRoute,且CONN_MANAGER_TIMEOUT设置过小,容易发生如下异常
这些参数都需要根据业务场景进行调优。
需要注意的是,最后必须通过 EntityUtils.consume 释放资源,不然该connection会一直被占用,导致后面的请求无法获取connection,实现如下:
public static void consume(final HttpEntity entity) throws IOException { if (entity == null) { return; } if (entity.isStreaming()) { InputStream instream = entity.getContent(); if (instream != null) { instream.close(); } } }
Keep-Alive
Keep-Alive是tcp保鲜定时器,当客户端和服务端建立tcp连接之后,闲置了tcp_keepalive_time时间后(即在tcp_keepalive_time时间内双方没有数据交流),服务器内核会尝试向客户端发送侦测包,判断TCP连接状况(有可能客户端崩溃、强制关闭了应用、主机不可达等等)。如果没有收到对方的ack包,则会在 tcp_keepalive_intvl时间后再次发送侦测包,一共会尝试 tcp_keepalive_probes次,每次的间隔时间在这里分别是15s、30s、45s、60s、75s。如果尝试tcp_keepalive_probes,依然没有收到对方的ack包,则会丢弃该tcp连接。
tcp连接默认闲置时间是2小时,一般设置为30分钟足够了。
注意事项
1、如果使用Keep-Alive时,response一定要设置Content-Length,否则就不算是长连接,具体原因如下:
2、httpclient 3.1内部采用synchronized + wait + notifyAll实现,当发生大量请求时,会造成线程饥饿;httpclient 4.2.3内部采用ReentrantLock(默认非公平) + Condition(每个线程一个)实现,这种情况几乎不会出现。
上一篇
已是最后文章
下一篇
已是最新文章