Solr Cache使用介绍及分析

2019-03-27 01:17|来源: 网路

本文将介绍Solr查询中涉及到的Cache使用及相关的实现。Solr查询的核心类就是SolrIndexSearcher,每个core通常在同一时刻只由当前的SolrIndexSearcher供上层的handler使用(当切换SolrIndexSearcher时可能会有两个同时提供服务),而Solr的各种Cache是依附于SolrIndexSearcher的,SolrIndexSearcher在则Cache生,SolrIndexSearcher亡则Cache被清空close掉。Solr中的应用Cache有filterCache、queryResultCache、documentCache等,这些Cache都是SolrCache的实现类,并且是SolrIndexSearcher的成员变量,各自有着不同的逻辑和使命,下面分别予以介绍和分析。

 1、SolrCache接口实现类

     Solr提供了两种SolrCache接口实现类:solr.search.LRUCache和solr.search.FastLRUCache。FastLRUCache是1.4版本中引入的,其速度在普遍意义上要比LRUCache更fast些。

下面是对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.1、solr.search.LRUCache

 LRUCache可配置参数如下:

1)size:cache中可保存的最大的项数,默认是1024

2)initialSize:cache初始化时的大小,默认是1024。

3)autowarmCount:当切换SolrIndexSearcher时,可以对新生成的SolrIndexSearcher做autowarm(预热)处理。autowarmCount表示从旧的SolrIndexSearcher中取多少项来在新的SolrIndexSearcher中被重新生成,如何重新生成由CacheRegenerator实现。在当前的1.4版本的Solr中,这个autowarmCount只能取预热的项数,将来的4.0版本可以指定为已有cache项数的百分比,以便能更好的平衡autowarm的开销及效果。如果不指定该参数,则表示不做autowarm处理。

实现上,LRUCache直接使用LinkedHashMap来缓存数据,由initialSize来限定cache的大小,淘汰策略也是使用LinkedHashMap的内置的LRU方式,读写操作都是对map的全局锁,所以并发性效果方面稍差。

1.2、solr.search.FastLRUCache

在配置方面,FastLRUCache除了需要LRUCache的参数,还可有选择性的指定下面的参数:

1)minSize:当cache达到它的最大数,淘汰策略使其降到minSize大小,默认是0.9*size。

2)acceptableSize:当淘汰数据时,期望能降到minSize,但可能会做不到,则可勉为其难的降到acceptableSize,默认是0.95*size。

3)cleanupThread:相比LRUCache是在put操作中同步进行淘汰工作,FastLRUCache可选择由独立的线程来做,也就是配置cleanupThread的时候。当cache大小很大时,每一次的淘汰数据就可能会花费较长时间,这对于提供查询请求的线程来说就不太合适,由独立的后台线程来做就很有必要。

实现上,FastLRUCache内部使用了ConcurrentLRUCache来缓存数据,它是个加了LRU淘汰策略的ConcurrentHashMap,所以其并发性要好很多,这也是多数Java版Cache的极典型实现。

2、filterCache

filterCache存储了无序的lucene documentid集合,该cache有3种用途:

1)filterCache存储了filterqueries(“fq”参数)得到的document id集合结果。Solr中的query参数有两种,即q和fq。如果fq存在,Solr是先查询fq(因为fq可以多个,所以多个fq查询是个取结果交集的过程),之后将fq结果和q结果取并。在这一过程中,filterCache就是key为单个fq(类型为Query),value为document id集合(类型为DocSet)的cache。对于fq为range query来说,filterCache表现出其有价值的一面。

2)filterCache还可用于facet查询(http://wiki.apache.org/solr/SolrFacetingOverview),facet查询中各facet的计数是通过对满足query条件的documentid集合(可涉及到filterCache)的处理得到的。因为统计各facet计数可能会涉及到所有的doc id,所以filterCache的大小需要能容下索引的文档数。

3)如果solfconfig.xml中配置了<useFilterForSortedQuery/>,那么如果查询有filter(此filter是一需要过滤的DocSet,而不是fq,我未见得它有什么用),则使用filterCache。

下面是filterCache的配置示例:

      -->
    <filterCache
      class="solr.LRUCache"
      size="16384"
      initialSize="4096"
 
autowarmCount="4096"/>

       对于是否使用filterCache及如何配置filterCache大小,需要根据应用特点、统计、效果、经验等各方面来评估。对于使用fq、facet的应用,对filterCache的调优是很有必要的。

3、queryResultCache

顾名思义,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是个什么结构呢?就是下面的类(key的hashcode就是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;
  }

因为查询参数是有start和rows的,所以某个QueryResultKey可能命中了cache,但start和rows却不在cache的document 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重合度很低,也不是很有必要开着它。

4、documentCache

又顾名思义,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"/>

5、User/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"/>
    -->

6、The LuceneFieldCache

lucene中有相对低级别的FieldCache,Solr并不对它做管理,所以,lucene的FieldCache还是由lucene的IndexSearcher来搞。

7、autowarm

上面有提到autowarm,autowarm触发的时机有两个,一个是创建第一个Searcher时(firstSearcher),一个是创建个新Searcher(newSearcher)来代替当前的Searcher。在Searcher提供请求服务前,Searcher中的各个Cache可以做warm处理,处理的地方通常是SolrCache的init方法,而不同cache的warm策略也不一样。

1)filterCache:filterCache注册了下面的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, null, false);
                  return true;
                }
              }
      );

2)queryResultCache:queryResultCache的autowarm不在SolrCache的init(也就是说,不是去遍历已有的queryResultCache中的query key执行查询),而是通过SolrEventListener接口的void newSearcher(SolrIndexSearcher newSearcher, SolrIndexSearchercurrentSearcher)方法,来执行配置中特定的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>

3)documentCache:因为新索引的documentid和索引文档的对应关系发生变化,所以documentCache没有warm的过程,落得白茫茫一片真干净。

尽管autowarm很好,也要注意autowarm带来的开销,这需要在实际中检验其warm的开销,也要注意Searcher的切换频率,避免因为warm和切换影响Searcher提供正常的查询服务。

8、参考文章

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









转自:http://blog.csdn.net/phinecos/article/details/7876385

相关问答

更多
  • 唯一可以肯定的就是尝试一下。 但是,我期望在索引中节省很少,因为索引每次只包含一次实际字符串,其余的是该文本中字符串位置的数据。 它们不是指数的很大一部分。 过滤器缓存只缓存过滤器查询。 它可能对您的确切用例没有用处,但许多确实有用。 例如,根据国家,语言,产品类型等缩小结果。如果经常使用它们,Solr可以避免重新计算这类事情的查询结果。 实际上,你只需要尝试一下并用探查器进行测量。 如果没有深入了解所使用的数据结构,其他任何内容都是纯粹的SWAG。 你的计算和没有分析的人一样好。 文档缓存仅在计算查询之后 ...
  • 除了其他原因,为什么否是一个答案,也是变化的粒度。 Lucene(底层库)以只读形式存储数据。 Solr在其上添加了可更新的文档,但是使它们可见仍然是一个繁重的操作。 Solr的最新版本通过软提交使其变得更容易和更快,但是使可见变化的代价仍然是非常重要的。 因此,它实际上没有针对更新/缓存单个值进行优化。 数据结构针对多文档更新进行了优化,然后通过缓存超过该临时只读状态进行快速搜索。 In additions to other reasons why No would be an answer, is al ...
  • 有条目的十大公司(以及每个公告的通知数量) :面对公司,做一个: -搜索。 如果每个通知都有一个文档,您将在分面请求中获得所需的结果。 每年公布的通知数量 :在日期时间范围内以年份为间隔。 发布通知的最多和最不受欢迎的日/月 :为日期和月份添加两个显式字段,并在这些字段上添加facet。 也许你也可以在工作日编制索引吗? 发布通知当天最受欢迎的小时 :制作一个仅包含小时的字段,其中包含方面。 最长的通知(按字符数) :函数查询是这里的候选者,但是没有strLength函数。 此外,它不适用于您使用文本字段的 ...
  • 而问题是? 只需将'fl'参数定义为要返回的字段列表,在这种情况下为'id'。 如果你从未真正返回其他字段,请不要费心存储它们,只将它们定义为索引。 如果您确实需要存储但很少需要它们,请使用solrconfig.xml中的 enableLazyFieldLoading设置进行测试 And the problem is? Just define the 'fl' parameter to be the list of fields you want to return, in which case 'id'. ...
  • 尝试使用以下fieldType ....
    这不能回答我原来的问题(关于如何仅通过配置执行此操作),但这是我最终在其他人想要这样做的情况下使用的解决方案。 首先,一个自定义分析器将用于预处理来自同义词过滤器的同义词(最重要的是,用Snowball阻止它们): public class SnowballAnalyzer extends Analyzer { /** * Creates a * {@link org.apache.lucene.analysis.Analyzer.TokenStreamComponents} w ...
  • 说应用通配符查询时不会发生分析是一个很好的规则(我自己已经说了这么多次),但有些错误。 确切的解释是, 任何不是MultiTermAware的标记器或过滤器都将被排除 ,因此Solr试图在没有它们的情况下“做正确的事情”。 您可以在key type="multiterm" (仅使用MultiTermAware组件)下定义自己的分析链,以定义多个查询(例如通配符)的自定义链。 从6.3开始,唯一的多重软件标记器是LowerCaseTokenizer 。 此外,KeywordTokenizer将起作用,因为它会 ...
  • 如果您创建自定义版本的EdgeNGramFilterFactory(在java中,然后将其插入schema.xml),则可以在单个分析器链中执行此操作,从后面创建其他ngrams。 否则,您将需要将copyField放入具有单独链的附加字段中。 老实说,第一种选择太麻烦了,但可以肯定。 you could do it in a single analyzer chain if you create your customized version of EdgeNGramFilterFactory (in j ...
  • 只是一个疯狂的猜测 - 你的内容领域的大小(单词数量)是什么? 因为,现在你已经将NGramFilterFactory放入你的过滤器链并且minGramSize为3,那么很多令牌都将被生成,并且全部位于新的位置。 solrconfig.xml中的maxFieldLength设置限制了要编入索引的令牌数量。 默认值为10000(仍然很高),但可以在过滤器链中使用大内容和ngramfilter时被超过。 10000 尝试将此值增加到较高的数字, ...
  • 是的,如果未定义类型,则索引和查询都使用相同的分析器。 Yes, if the type is not defined, the same analyzer is used for both index and query.