在使用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);
早期主要是寬帶慢的原因,現在每款瀏覽器都有自己的默認併發連接數,而且瀏覽器默認對同一域下的資源,只保持一定的連接數,會阻塞過多的連接,這都會影響到瀏覽器對網頁的加載速度。