在使用solr4.2.1时,使用CloudSolrServer进行并发查询,当并发量比较大时,存在线程等待的问题。
"TP-Processor5079" daemon prio=10 tid=0x00007fa7459ca800 nid=0x44ab waiting on condition [0x0000 7fa729588000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000007fe8852d0> (a java.util.concurrent.locks.AbstractQueued Synchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQ ueuedSynchronizer.java:2043) at org.apache.http.pool.PoolEntryFuture.await(PoolEntryFuture.java:131) at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:281) at org.apache.http.pool.AbstractConnPool.access$000(AbstractConnPool.java:62) at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:176) at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:172) at org.apache.http.pool.PoolEntryFuture.get(PoolEntryFuture.java:100) at org.apache.http.impl.conn.PoolingClientConnectionManager.leaseConnection(PoolingClien tConnectionManager.java:212) at org.apache.http.impl.conn.PoolingClientConnectionManager$1.getConnection(PoolingClien tConnectionManager.java:199) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.jav a:456) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:784) at org.apache.solr.client.solrj.impl.HttpSolrServer.request(HttpSolrServer.java:353) at org.apache.solr.client.solrj.impl.HttpSolrServer.request(HttpSolrServer.java:181) at org.apache.solr.client.solrj.impl.LBHttpSolrServer.request(LBHttpSolrServer.java:264) at org.apache.solr.client.solrj.impl.CloudSolrServer.request(CloudSolrServer.java:306) at org.apache.solr.client.solrj.request.QueryRequest.process(QueryRequest.java:90) at org.apache.solr.client.solrj.SolrServer.query(SolrServer.java:301)
使用的代码为:
public static SolrServer getCloudSolrServer(final String zkHost,String defaultCollection) { CloudSolrServer localCloudSolrServer = null; if (zkHost != null) try { localCloudSolrServer = new CloudSolrServer(zkHost); localCloudSolrServer.setDefaultCollection(defaultCollection); localCloudSolrServer.setZkClientTimeout(10000); localCloudSolrServer.setZkConnectTimeout(10000); } catch (MalformedURLException localMalformedURLException) { logger.error("The URL of zkHost is not correct!! Its form must as below:\n zkHost:port"); localMalformedURLException.printStackTrace(); } catch (Exception localException) { localException.printStackTrace(); } return localCloudSolrServer; }
从httpclient-4.2.3.jar的org.apache.http.impl.client.SystemDefaultHttpClient.java类中可以看出默认的设置:protected ClientConnectionManager createClientConnectionManager() { PoolingClientConnectionManager connmgr = new PoolingClientConnectionManager(SchemeRegistryFactory.createSystemDefault()); String s = System.getProperty("http.keepAlive", "true"); if ("true".equalsIgnoreCase(s)) { s = System.getProperty("http.maxConnections", "5"); int max = Integer.parseInt(s); connmgr.setDefaultMaxPerRoute(max); connmgr.setMaxTotal(2 * max); } return connmgr; }
PoolingClientConnectionManager类是线程安全类,也即每创建一个CloudSolrServer实例,就会创建一个PoolingClientConnectionManager实例。什么是一个route?
这里route的概念可以理解为 运行环境机器 到 目标机器的一条线路。举例来说,我们使用HttpClient的实现来分别请求 www.baidu.com 的资源和 www.bing.com 的资源那么他就会产生两个route。
这里为什么要特别提到route最大连接数这个参数呢,因为这个参数的默认值为5,如果 不设置这个参数值默认情况下对于同一个目标机器的最大并发连接只有5个!这意味着如果你正在执行一个针对某一台目标机器的抓取任务的时候,哪怕你设置连接 池的最大连接数为200,但是实际上还是只有5个连接在并发工作,其他剩余的195个连接都在等待,都是为别的目标机器服务的。如果是访问solr服务器的话,那就是客户端访问搜索服务器,一个CloudSolrServer实例同时并发只有5个连接,其它并发请求都在等待。
怎么样蛋疼吧,我是已经有过血的教训了,在切换到HttpClient4.1的起初没有注意到这个配置,最后使得服务承受的压力反而不如从前了,所以在这里特别提醒大家注意。
maxConnectionsPerHost是指针对一个目标服务器,httpclient客户端可以和它建立的并发连接数。之前的版本是2,现在是5。
maxTotalConnections是指针对服务器集群,client可以保持的所有连接数,即同一客户端同时可连接多个目标主机。现在是
maxConnectionsPerHost的2倍。
比如现在有两台Solr搜索服务器提供服务,maxConnectionsPerHost=2表示每个CloudSolrServer实例都可以和其中一台搜索服务器并发两个连接,maxTotalConnections>=maxConnectionsPerHost*提供搜索服务的服务器数量,2台搜索服务的话就可以并发4个连接。
这两个默认值应该是针对浏览器而设置的,但是对于solr这样的请求就不适用了,那要怎么改大这两个值呢?可以通过LBHttpSolrServer来设置:
ModifiableSolrParams params = new ModifiableSolrParams(); params.set(HttpClientUtil.PROP_MAX_CONNECTIONS, 1000);//10 params.set(HttpClientUtil.PROP_MAX_CONNECTIONS_PER_HOST, 500);//5 HttpClient client = HttpClientUtil.createClient(params); LBHttpSolrServer lbServer = new LBHttpSolrServer(client); cloudSolrServer = new CloudSolrServer(zkHost, lbServer); cloudSolrServer.setDefaultCollection(defaultCollection); cloudSolrServer.setZkClientTimeout(SearchConfig.getZookeeperClientTimeout()); cloudSolrServer.setZkConnectTimeout(SearchConfig.getZookeeperConnectTimeout());
HttpSolrServer中的设置是:ModifiableSolrParams params = new ModifiableSolrParams(); params.set(HttpClientUtil.PROP_MAX_CONNECTIONS, 128); params.set(HttpClientUtil.PROP_MAX_CONNECTIONS_PER_HOST, 32); params.set(HttpClientUtil.PROP_FOLLOW_REDIRECTS, followRedirects); httpClient = HttpClientUtil.createClient(params);
早期主要是宽带慢的原因,现在每款浏览器都有自己的默认并发连接数,而且浏览器默认对同一域下的资源,只保持一定的连接数,会阻塞过多的连接,这都会影响到浏览器对网页的加载速度。