Solr Cache使用介紹及分析


本文將介紹Solr查詢中涉及到的Cache使用及相關的實現。Solr查詢的核心類就是SolrIndexSearcher,每個core通常在同一時刻只由當前的SolrIndexSearcher供上層的handler使用(當切換SolrIndexSearcher時可能會有兩個同時提供服務),而Solr的各種Cache是依附於SolrIndexSearcher的,SolrIndexSearcher在則Cache生,SolrIndexSearcher亡則Cache被清空close掉。Solr中的應用CachefilterCachequeryResultCachedocumentCache等,這些Cache都是SolrCache的實現類,並且是SolrIndexSearcher的成員變量,各自有着不同的邏輯和使命,下面分別予以介紹和分析。

 1SolrCache接口實現類

     Solr提供了兩種SolrCache接口實現類:solr.search.LRUCachesolr.search.FastLRUCacheFastLRUCache1.4版本中引入的,其速度在普遍意義上要比LRUCachefast些。

下面是對SolrCache接口主要方法的注釋:

public  interface SolrCache {
   /**
   * Solr在解析配置文件構造SolrConfig實例時會初始化配置中的各種CacheConfig,
   * 在構造SolrIndexSearcher時通過SolrConfig實例來newInstance SolrCache,
   * 這會調用init方法。參數args就是和具體實現(LRUCache和FastLRUCache)相關的
   * 參數Map,參數persistence是個全局的東西,LRUCache和FastLRUCache用其來統計
   * cache訪問情況(因為cache是和SolrIndexSearcher綁定的,所以這種統計就需要個
   * 全局的注入參數),參數regenerator是autowarm時如何重新加載cache,
   * CacheRegenerator接口只有一個被SolrCache warm方法回調的方法:
   * boolean regenerateItem(SolrIndexSearcher newSearcher,
   * SolrCache newCache, SolrCache oldCache, Object oldKey, Object oldVal)
   
*/
   public Object init(Map args, Object persistence, CacheRegenerator regenerator);
   /**  :TODO: copy from Map  */
   public  int size();
   /**  :TODO: copy from Map  */
   public Object put(Object key, Object value);
   /**  :TODO: copy from Map  */
   public Object get(Object key);
   /**  :TODO: copy from Map  */
   public  void clear();
   /**
   * 新創建的SolrIndexSearcher autowarm方法,該方法的實現就是遍歷已有cache中合適的
   * 范圍(因為通常不會把舊cache中的所有項都重新加載一遍),對每一項調用regenerator的
   * regenerateItem方法來對searcher加載新cache項。
   
*/
   void warm(SolrIndexSearcher searcher, SolrCache old)  throws IOException;
   /**  Frees any non-memory resources  */
   public  void close();

}

1.1solr.search.LRUCache

 LRUCache可配置參數如下:

1sizecache中可保存的最大的項數,默認是1024

2initialSizecache初始化時的大小,默認是1024

3autowarmCount:當切換SolrIndexSearcher時,可以對新生成的SolrIndexSearcherautowarm(預熱)處理。autowarmCount表示從舊的SolrIndexSearcher中取多少項來在新的SolrIndexSearcher中被重新生成,如何重新生成由CacheRegenerator實現。在當前的1.4版本的Solr中,這個autowarmCount只能取預熱的項數,將來的4.0版本可以指定為已有cache項數的百分比,以便能更好的平衡autowarm的開銷及效果。如果不指定該參數,則表示不做autowarm處理。

實現上,LRUCache直接使用LinkedHashMap來緩存數據,由initialSize來限定cache的大小,淘汰策略也是使用LinkedHashMap的內置的LRU方式,讀寫操作都是對map的全局鎖,所以並發性效果方面稍差。

1.2solr.search.FastLRUCache

在配置方面,FastLRUCache除了需要LRUCache的參數,還可有選擇性的指定下面的參數:

1minSize:當cache達到它的最大數,淘汰策略使其降到minSize大小,默認是0.9*size

2acceptableSize:當淘汰數據時,期望能降到minSize,但可能會做不到,則可勉為其難的降到acceptableSize,默認是0.95*size

3cleanupThread:相比LRUCache是在put操作中同步進行淘汰工作,FastLRUCache可選擇由獨立的線程來做,也就是配置cleanupThread的時候。當cache大小很大時,每一次的淘汰數據就可能會花費較長時間,這對於提供查詢請求的線程來說就不太合適,由獨立的后台線程來做就很有必要。

實現上,FastLRUCache內部使用了ConcurrentLRUCache來緩存數據,它是個加了LRU淘汰策略的ConcurrentHashMap,所以其並發性要好很多,這也是多數JavaCache的極典型實現。

2filterCache

filterCache存儲了無序的lucene document id集合,該cache3種用途:

1filterCache存儲了filter queries(fq”參數)得到的document id集合結果。Solr中的query參數有兩種,即qfq。如果fq存在,Solr是先查詢fq(因為fq可以多個,所以多個fq查詢是個取結果交集的過程),之后將fq結果和q結果取並。在這一過程中,filterCache就是key為單個fq(類型為Query),valuedocument id集合(類型為DocSet)的cache。對於fqrange query來說,filterCache表現出其有價值的一面。

2filterCache還可用於facet查詢(http://wiki.apache.org/solr/SolrFacetingOverview),facet查詢中各facet的計數是通過對滿足query條件的document id集合(可涉及到filterCache)的處理得到的。因為統計各facet計數可能會涉及到所有的doc id,所以filterCache的大小需要能容下索引的文檔數。

3)如果solfconfig.xml中配置了<useFilterForSortedQuery/>,那么如果查詢有filter(此filter是一需要過濾的DocSet,而不是fq,我未見得它有什么用),則使用filterCache

下面是filterCache的配置示例:

     <!--  Internal cache used by SolrIndexSearcher for filters (DocSets),
         unordered sets of *all* documents that match a query.
         When a new searcher is opened, its caches may be prepopulated
         or "autowarmed" using data from caches in the old searcher.
         autowarmCount is the number of items to prepopulate.  For LRUCache,
         the prepopulated items will be the most recently accessed items.
      
-->
     < filterCache
      
class ="solr.LRUCache"
      size
="16384"
      initialSize
="4096"
autowarmCount="4096"/>

       對於是否使用filterCache及如何配置filterCache大小,需要根據應用特點、統計、效果、經驗等各方面來評估。對於使用fqfacet的應用,對filterCache的調優是很有必要的。

3queryResultCache

顧名思義,queryResultCache是對查詢結果的緩存(SolrIndexSearcher中的cache緩存的都是document id set),這個結果就是針對查詢條件的完全有序的結果。下面是它的配置示例:

  <!-- queryResultCache caches results of searches - ordered lists of

        
 document ids (DocList) based on a query, a sort, and the range
         of documents requested.
      
-->
     < queryResultCache
      
class ="solr.LRUCache"
      size
="16384"
      initialSize
="4096"
      autowarmCount
="1024" />

緩存的key是個什么結構呢?就是下面的類(keyhashcode就是QueryResultKey的成員變量hc):

public QueryResultKey(Query query, List<Query> filters, Sort sort,  int nc_flags) {
     this.query = query;
     this.sort = sort;
     this.filters = filters;
     this.nc_flags = nc_flags;
     int h = query.hashCode();
     if (filters !=  null) h ^= filters.hashCode();
    sfields = ( this.sort != null) ?  this.sort.getSort() : defaultSort;
     for (SortField sf : sfields) {
       //  mix the bits so that sortFields are position dependent
      
//  so that a,b won't hash to the same value as b,a
      h ^= (h << 8) | (h >>> 25);    //  reversible hash
       if (sf.getField() !=  null) h += sf.getField().hashCode();
      h += sf.getType();
       if (sf.getReverse()) h=~h;
       if (sf.getLocale()!= null) h+=sf.getLocale().hashCode();
       if (sf.getFactory()!= null) h+=sf.getFactory().hashCode();
    }
    hc = h;
  }

因為查詢參數是有startrows的,所以某個QueryResultKey可能命中了cache,但startrows卻不在cachedocument id set范圍內。當然,document id set是越大命中的概率越大,但這也會很浪費內存,這就需要個參數:queryResultWindowSize來指定document id set的大小。Solr中默認取值為50,可配置,WIKI上的解釋很深簡單明了:

   <!--  An optimization for use with the queryResultCache.  When a search
         is requested, a superset of the requested number of document ids
         are collected.  For example, of a search for a particular query
         requests matching documents 10 through 19, and queryWindowSize is 50,
         then documents 0 through 50 will be collected and cached.  Any further
         requests in that range can be satisfied via the cache.
    
-->
     < queryResultWindowSize >50 </ queryResultWindowSize >

相比filterCache來說,queryResultCache內存使用上要更少一些,但它的效果如何就很難說。就索引數據來說,通常我們只是在索引上存儲應用主鍵id,再從數據庫等數據源獲取其他需要的字段。這使得查詢過程變成,首先通過solr得到document id set,再由Solr得到應用id集合,最后從外部數據源得到完成的查詢結果。如果對查詢結果正確性沒有苛刻的要求,可以在Solr之外獨立的緩存完整的查詢結果(定時作廢),這時queryResultCache就不是很有必要,否則可以考慮使用queryResultCache。當然,如果發現在queryResultCache生命周期內,query重合度很低,也不是很有必要開着它。

4documentCache

又顧名思義,documentCache用來保存<doc_id,document>對的。如果使用documentCache,就盡可能開大些,至少要大過<max_results> * <max_concurrent_queries>,否則因為cache的淘汰,一次請求期間還需要重新獲取document一次。也要注意document中存儲的字段的多少,避免大量的內存消耗。

下面是documentCache的配置示例:

     <!--  documentCache caches Lucene Document objects (the stored fields for each document).
      
-->
     < documentCache
      
class ="solr.LRUCache"
      size
="16384"
      initialSize
="16384" />

5User/Generic Caches

Solr支持自定義Cache,只需要實現自定義的regenerator即可,下面是配置示例:

   <!--  Example of a generic cache.  These caches may be accessed by name
         through SolrIndexSearcher.getCache(),cacheLookup(), and cacheInsert().
         The purpose is to enable easy caching of user/application level data.
         The regenerator argument should be specified as an implementation
         of solr.search.CacheRegenerator if autowarming is desired.
    
-->
     <!--
    <cache name="yourCacheNameHere"
      class="solr.LRUCache"
      size="4096"
      initialSize="2048"
      autowarmCount="4096"
      regenerator="org.foo.bar.YourRegenerator"/>
    
-->

6The Lucene FieldCache

lucene中有相對低級別的FieldCacheSolr並不對它做管理,所以,luceneFieldCache還是由luceneIndexSearcher來搞。

7autowarm

上面有提到autowarmautowarm觸發的時機有兩個,一個是創建第一個Searcher時(firstSearcher),一個是創建個新SearchernewSearcher)來代替當前的Searcher。在Searcher提供請求服務前,Searcher中的各個Cache可以做warm處理,處理的地方通常是SolrCacheinit方法,而不同cachewarm策略也不一樣。

1filterCachefilterCache注冊了下面的CacheRegenerator,就是由舊的key查詢索引得到新值put到新cache中。

   solrConfig.filterCacheConfig.setRegenerator(
               new CacheRegenerator() {
                 public  boolean regenerateItem(SolrIndexSearcher newSearcher, SolrCache newCache, SolrCache oldCache, Object oldKey, Object oldVal)  throws IOException {
                  newSearcher.cacheDocSet((Query)oldKey,  nullfalse);
                   return  true;
                }
              }
      );

2queryResultCachequeryResultCacheautowarm不在SolrCacheinit(也就是說,不是去遍歷已有的queryResultCache中的query key執行查詢),而是通過SolrEventListener接口的void newSearcher(SolrIndexSearcher newSearcher, SolrIndexSearcher currentSearcher)方法,來執行配置中特定的query查詢,達到顯示的預熱lucene FieldCache的效果。

queryResultCache的配置示例如下:

        < listener  event ="newSearcher"  class ="solr.QuerySenderListener" >
       < arr  name ="queries" >
         <!--  seed common sort fields  -->
         < lst >  < str  name ="q" >anything </ str >  < str  name ="sort" >name desc price desc populartiy desc </ str >  </ lst >
       </ arr >
     </ listener >
     < listener  event ="firstSearcher"  class ="solr.QuerySenderListener" >
       < arr  name ="queries" >
         <!--  seed common sort fields  -->
         < lst >  < str  name ="q" >anything </ str >  < str  name ="sort" >name desc, price desc, populartiy desc </ str >  </ lst >
         <!--  seed common facets and filter queries  -->
         < lst >  < str  name ="q" >anything </ str > 
               < str  name ="facet.field" >category </ str > 
               < str  name ="fq" >inStock:true </ str >
               < str  name ="fq" >price:[0 TO 100] </ str >
         </ lst >
       </ arr >
     </ listener >

3documentCache:因為新索引的document id和索引文檔的對應關系發生變化,所以documentCache沒有warm的過程,落得白茫茫一片真干凈。

盡管autowarm很好,也要注意autowarm帶來的開銷,這需要在實際中檢驗其warm的開銷,也要注意Searcher的切換頻率,避免因為warm和切換影響Searcher提供正常的查詢服務。

8、參考文章

http://wiki.apache.org/solr/SolrCaching

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM