Solr本身的性能不錯,但是在使用過程中,還是會遇到一些使用錯誤,或是沒考慮到的地方;在出現瓶頸時,可以首先考慮哪些點呢?下面就來看一下Solr官方的總結,個人覺得總結的很好。SOLR+LUCENE的官網還是挺給力的
對Schema設計的考慮
索引域的數量增長會很大程度的影響以下的內容:
段的合並時間
優化(optimization)時間
如果設置omitNorms="true" ,則可以減小對這些影響
批注:如果設置Norms,則會影響評分的標准,但會大大的增大索引文件的大小,如果對該字段沒有需求,建議關掉
存儲域
通過查詢結果獲取存儲域的值是一個相當大的開銷。如果文檔的數據特別大,或者一些數據存儲到了分布式的磁盤中(需要更多的IO來查詢域)時,那么花費將會很大。這在存儲大數據時很容易被考慮到,尤其是整個文檔內容的存儲。
考慮將大數據的存儲放到solr之外。如果非要這么做,那么可以考慮使用壓縮域,這將會用CPU的開銷來換取IO的開銷。
如果你並不需要使用所有的存儲域,允許延遲加載(enableLazyFieldLoading)將會是很好的方式,由於是對那些壓縮的字段。
批注:延遲加載在查詢期間很有用,尤其是需要對某些字段作額外的處理時,它既能減少內存使用,又加速了程序的處理。另外,盡量減小索引的大小絕對不是壞事。
SOLR配置考慮
mergeFactor
mergeFactor大致決定了段的數量。mergeFactor的值告訴lucene有多少個段需要進行合並。它可以被認為是一個基本的數量系統。
舉個例子,如果你設置mergeFactor為10,每1000個文檔時會創建一個新的段到硬盤中。當第10個段被添加時,所有的10個段將被合並為1個段 (包含10000個文檔);當這樣的10個文檔被創建時,它們又會被合並為個包含100,000個文檔的段,依次類推(當然也有上限)。這樣,在任何時候,都不會有多余9個的段(相同索引大小情況下)存在。
該值在solrconfig.xml中的mainIndex設置(它會忽略indexDefaults)。
批注:關於合並的策略,請看我之前的博客:lucene內部的合並策略
mergeFactor Tradeoffs
高值的merge factor(比如25):
Con:低合並延遲,在查詢時需要搜索更多的文件,所以會使查詢變慢
低值的merge factor(比如2):
Con:更多的文件合並,將使索引變慢
批注:一般來說不需要這么極端,設10即可。保證讀速度的同時,也保證合並的速度。
HashDocSet最大值的考慮
SOLR1.4之后不支持了,不再描述。
cache中autoWarm數量的考慮
當一個新的searcher被打開時,它的cache可以從舊的searcher中重新加載或者自動預熱(autowarmd)緩存的對象。autowarmCount是將被拷貝到新searcher中的對象的數量,你需要根據autowarm的時間來設置autowarmCount。如何使用autowarmCount,需要你根據時間和數量來設定。
批注:autoWarm即新的searcher會有多少數據被緩存,如果沒有緩存,一些熱點數據無疑會變得很慢。所以,合理的這是這個值,能大大加快查詢的效率。
緩存命中率
在Solr的admin中監控緩存的統計。增加緩存的大小通常是提高性能的最好方法,尤其是你對一個指定的緩存類型作逐出操作時。請關注filterCache,它也被用來作solr的facetting。
批注:一個典型的場景是范圍查詢,類似fl=price:[100 TO 200]這樣的情況,將數據該范圍存儲起來時,對其他的一些查詢都可以復用這個緩存的數據,很高效。
對排序的域作明確的預熱
如果你的工作大多基於排序的方式,那么你最好在“newSearcher”和“firstSearcher”時間監聽器中添加明確的預熱查詢規則,這樣FiledCache可以在用戶的查詢被執行前就將數據加載。
優化的考慮
你可能想在任何時候都可以優化你的索引。比如你創建索引后,就沒有修改過它。
如果你的索引收到了一串需要更新的流,那么請考慮以下的因素:
1. 如果過多的段被添加到索引中,那么查詢的性能將會下降;lucene的段自動合並能將段的數量控制在一定范圍
2. auto-warming的時間也會延長,它通常依賴於所做的查詢
3. 優化后的第一次分布耗時比之后的分布耗時要長。具體請看 Collection Distribution
4. 在優化期間索引的問題大小會加倍,優化后會回到原始大小或更小
5. 如果可以,請確保沒有並發的commit請求,否則會有很大的性能損失
在優化時所有的索引會放到唯一的段中;優化索引會避免“文件打開數過多”的問題。
這里有一篇關於該問題的文章:ONJava Article
更新和提交的頻率
如果slaves收到的數據過頻,那么性能必然受損。為了避免這個問題,你必須了解slaver的更新機制,這樣你才能更好的調整相關的參數(commit的數量/頻率、snappullers、autowarming/autocount)以使新數據的寫入不會那么頻繁。
2. slaver上的Snappuller會運行corn去檢查master上是否有新的快照,如果它找到新的版本,就會把它拿過來並install這些新的數據。
3. 當一個新的searcher被打開時,autowarming會先於Solr的查詢請求之前完成。有了預熱的緩存,查詢的延遲將會小很多。
這里有三個相關的參數:
snappluller:基於cron,他可以精確到秒級別。它們運行時,會獲取最近它們沒有的集合
緩存預熱:在solrconfig.xml中配置
查詢響應的壓縮
在Solr返回xml應答給客戶端之前對其進行壓縮有時是值得做的。如果應答結果非常大,或者網絡IO有限制,或者沒有千兆網卡,請考慮使用壓縮機制。
壓縮會增加CPU的使用,並且Solr本身也是CPU密集型的應用,所以壓縮會降低查詢的性能。壓縮會使文件減小到1/6的大小,使網絡包減小到1/3的大小;相對的,查詢的性能會降低15%左右。
請查看你的應用服務器的相關文檔(tomcat、resion、jetty...)來獲取關於壓縮的信息。
索引的性能
一般情況下,一次更新多個文檔比一個一個更新要快。
對於這種塊級的更新方式,考慮使用StreamingUpdateSolrServer.java,它提供多線程多連接的方式來更新流數據。
批注:StreamingUpdateSolrServer類相對CommonsHttpSolrServer要快很多,主要在於它將原本單個的文檔寫入變為了批量寫入,加上多線程多連接的方式,性能上快了超多。我們的測試數據表明,至少要快4-6倍以上。
內存使用的考慮
OutOfMemoryErrors
如果你的solr實例沒有足夠的內存,那么JVM有時會拋出OutOfMemoryErrors。這並不會對數據有影響,並且solr也會試圖優美的恢復它。任何 添加/刪除/提交 的命令在異常拋出時都可能不成功;其他不利的影響也可能會產生。對應用而言,如果SimpleFSLock 的鎖機制在使用的話,OutOfMemoryError 會導致solr丟失這個鎖。如果這發生了,更新索引的結果將會是這樣的異常:
- SEVERE: Exception during commit/optimize:java.io.IOException: Lock obtain timed out: SimpleFSLock@/tmp/lucene-5d12dd782520964674beb001c4877b36-write.lock
SEVERE: Exception during commit/optimize:java.io.IOException: Lock obtain timed out: SimpleFSLock@/tmp/lucene-5d12dd782520964674beb001c4877b36-write.lock
如果你想在OOM時看堆的情況,請設置"-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/the/dump"
JVM內存的分配
針對這個錯誤的最簡單方法,在JVM並沒有完全使用你的物理內存時,考慮加大JVM的內存容量:
- java -Xms512M -Xmx1024M -jar start.jar
java -Xms512M -Xmx1024M -jar start.jar
影響內存使用的因素
你可能想去減小solr的內存使用。
一個方式就是減小文檔的大小。
當運行add命令時,標准的xml更新請求會有兩個限制:
2. 每個<field>...</field>標簽都必須放入到內存中,而不管maxFieldLength
注意一些不同的add請求會在不同的線程中並發運行。越多的線程,就會導致越多的內存使用。
我的一些其他使用經驗:
1.schema中的類型定義很重要,它直接影響了索引的性能
2.盡量少用filter,雖然它很好用,但是其hashSet的數量如果過多,很容易oom
3. cache的類,都用FastLRUCache吧,LRUCache還有鎖,太慢了
4. 通過docId取doc的過程看似平常,但是量大了就是一個災難,在這點需要根據實際場景考慮
5. 能用緩存的用緩存,不能用緩存的,嘗試使用MMapDirectoryFactory,最好是SSD硬盤
6.其他,待想到了再補充