首页 > 其他分享 >Solr之缓存篇

Solr之缓存篇

时间:2023-04-25 11:06:53浏览次数:30  
标签:cache cmd true Cache 缓存 SolrIndexSearcher null Solr


Solr在Lucene之上开发了很多Cache功能,从目前提供的Cache类型有:

(1)filterCache

(2)documentCache

(3)fieldvalueCache

(4)queryresultCache

而每种Cache针对具体的查询请求进行对应的Cache。本文将从几个方面来阐述上述几种Cache在Solr的运用,具体如下:

(1)Cache的生命周期

(2)Cache的使用场景

(3)Cache的配置介绍

(4)Cache的命中监控

1 Cache生命周期

所有的Cache的生命周期由SolrIndexSearcher来管理,如果Cache对应的SolrIndexSearcher被重新构建都代表正在运行的Cache对象失效,而SolrIndexSearcher是否重新打开主要有几个方面影响。

(1)增量数据更新后提交DirectUpdateHandler2.commit(CommitUpdateCommand cmd),该方法代码如下:



if (cmd.optimize) {
      optimizeCommands.incrementAndGet();
    } else {
      commitCommands.incrementAndGet();
      if (cmd.expungeDeletes) expungeDeleteCommands.incrementAndGet();
    }

    Future[] waitSearcher = null;
    if (cmd.waitSearcher) {//是否等待打开SolrIndexSearcher,一般新的Searcher会做一些预备工作,比如预热Cache
      waitSearcher = new Future[1];
    }

    boolean error=true;
    iwCommit.lock();
    try {
      log.info("start "+cmd);

      if (cmd.optimize) {//是否优化索引,一般增量数据不优化
        openWriter();
        writer.optimize(cmd.maxOptimizeSegments);
      } else if (cmd.expungeDeletes) {
        openWriter();
        writer.expungeDeletes();//一般对于标记删除的文档进行物理删除,当然优化也能将标记删除的doc删除,
//但是该方法会比优化快很多
 }

      closeWriter();//关闭增量打开的Writer对象

      callPostCommitCallbacks();
      if (cmd.optimize) {//如果有listener的话会执行这部分代码
        callPostOptimizeCallbacks();
      }
      // open a new searcher in the sync block to avoid opening it
      // after a deleteByQuery changed the index, or in between deletes
      // and adds of another commit being done.
      core.getSearcher(true,false,waitSearcher);//该方法是重新打开Searcher的关键方法,
   //其中有重要参数来限定是否new open 或者reopen IndexReader.

      // reset commit tracking
      tracker.didCommit();//提供Mbean的一些状态监控

      log.info("end_commit_flush");

      error=false;
    }
    finally {//commlit后将一些监控置0
      iwCommit.unlock();
      addCommands.set(0);
      deleteByIdCommands.set(0);
      deleteByQueryCommands.set(0);
      numErrors.set(error ? 1 : 0);
    }

    // if we are supposed to wait for the searcher to be registered, then we should do it
    // outside of the synchronized block so that other update operations can proceed.
    if (waitSearcher!=null && waitSearcher[0] != null) {
       try {
        waitSearcher[0].get();//等待Searcher经过一系列操作,例如Cache的预热。
      } catch (InterruptedException e) {
        SolrException.log(log,e);
      } catch (ExecutionException e) {
        SolrException.log(log,e);
      }
    }
  }


其中最重要的方法



core.getSearcher(true,false,waitSearcher);



再展开来看参数含义,

参数1 boolean forceNew,是否打开新的searcher对象

参数2 boolean returnSearcher,是否返回最新的searcher对象

参数3 final Future[] waitSearcher 是否等待searcher的预加工动作,也就是调用该方法的线程将会等待这个searcher对象的预加工动作,如果该searcher对象管理很多的 Cache并设置较大的预热数目,该线程将会等待较长时间才能返回。(预热,也许会很多人不了解预热的含义,我在这里稍微解释下,例如一个Cache已经 缓存了比较多的值,如果因为新的IndexSearcher被重新构建,那么新的Cache又会需要重新累积数据,那么会发现搜索突然会在一段时间性能急 剧下降,要等到Cache重新累计了一定数据,命中率才会慢慢恢复。所以这样的情形其实是不可接受的,那么我们可以做的事情就是将老Cache对应的 key,在重新构建SolrIndexSearcher返回之前将这些已经在老Cache中Key预先从磁盘重新load Value到Cache中,这样暴露出去的SolrIndexSearcher对应的Cache就不是一个内容为空的Cache。而是已经“背地”准备好 内容的Cache)

getSearcher()关于Cache有2个最重要的代码段,其一,重新构造新的SolrIndexSearcher:



newestSearcher = getNewestSearcher(false);
      String newIndexDir = getNewIndexDir();
      File indexDirFile = new File(getIndexDir()).getCanonicalFile();
      File newIndexDirFile = new File(newIndexDir).getCanonicalFile();
      // reopenReaders在solrconfig.xml配置,如果为false,每次都是重新打开新的IndexReader
      if (newestSearcher != null && solrConfig.reopenReaders
          && indexDirFile.equals(newIndexDirFile)) {
        IndexReader currentReader = newestSearcher.get().getReader();
        IndexReader newReader = currentReader.reopen();//如果索引目录没变则是reopen indexReader

        if (newReader == currentReader) {
          currentReader.incRef();
        }

        tmp = new SolrIndexSearcher(this, schema, "main", newReader, true, true);//构建新的SolrIndexSearcher
      } else {//根据配置的IndexReaderFactory来返回对应的IndexReader
        IndexReader reader = getIndexReaderFactory().newReader(getDirectoryFactory().open(newIndexDir), true);
        tmp = new SolrIndexSearcher(this, schema, "main", reader, true, true);//返回构建新的SolrIndexSearcher
      }



在看看创建SolrIndexSearcher构造函数关于Cache的关键代码:


if (cachingEnabled) {//如果最后的参数为true代表可以进行Cache
      ArrayList<SolrCache> clist = new ArrayList<SolrCache>();
      fieldValueCache = solrConfig.fieldValueCacheConfig==null ? null : solrConfig.fieldValueCacheConfig.newInstance();
      if (fieldValueCache!=null) clist.add(fieldValueCache);//如果solrconfig配置 <fieldValueCache....,构建新的Cache
      filterCache= solrConfig.filterCacheConfig==null ? null : solrConfig.filterCacheConfig.newInstance();
      if (filterCache!=null) clist.add(filterCache);//如果solrconfig配置  <filterCache ...,构建新的Cache
      queryResultCache = solrConfig.queryResultCacheConfig==null ? null : solrConfig.queryResultCacheConfig.newInstance();
      if (queryResultCache!=null) clist.add(queryResultCache);//如果solrconfig配置  <queryResultCache...,构建新的Cache
      documentCache = solrConfig.documentCacheConfig==null ? null : solrConfig.documentCacheConfig.newInstance();
      if (documentCache!=null) clist.add(documentCache);//如果solrconfig配置  <documentCache...,构建新的Cache

      if (solrConfig.userCacheConfigs == null) {
        cacheMap = noGenericCaches;
      } else {//自定义的Cache
        cacheMap = new HashMap<String,SolrCache>(solrConfig.userCacheConfigs.length);
        for (CacheConfig userCacheConfig : solrConfig.userCacheConfigs) {
          SolrCache cache = null;
          if (userCacheConfig != null) cache = userCacheConfig.newInstance();
          if (cache != null) {
            cacheMap.put(cache.name(), cache);
            clist.add(cache);
          }
        }
      }

      cacheList = clist.toArray(new SolrCache[clist.size()]);
    }

其二,将老searcher对应的Cache进行预热:


future = searcherExecutor.submit(
                new Callable() {
                  public Object call() throws Exception {
                    try {
                      newSearcher.warm(currSearcher);
                    } catch (Throwable e) {
                      SolrException.logOnce(log,null,e);
                    }
                    return null;
                  }
                }
        );



展开看warm(SolrIndexSearcher old)方法(具体如何预热Cache将在其他文章进行详述):


public void warm(SolrIndexSearcher old) throws IOException {
    // Make sure this is first!  filters can help queryResults execute!
    boolean logme = log.isInfoEnabled();
    long warmingStartTime = System.currentTimeMillis();
    // warm the caches in order...
    for (int i=0; i<cacheList.length; i++) {//遍历所有配置的Cache,将进行old-->new 的Cache预热。
      if (logme) log.info("autowarming " + this + " from " + old + "\n\t" + old.cacheList[i]);
      this.cacheList[i].warm(this, old.cacheList[i]);
      if (logme) log.info("autowarming result for " + this + "\n\t" + this.cacheList[i]);
    }
    warmupTime = System.currentTimeMillis() - warmingStartTime;//整个预热所耗时间
  }


到这里为止,SolrIndexSearcher进行Cache创建就介绍完毕,而Cache的销毁也是通过SolrIndexSearcher的关闭一并进行,见solrIndexSearcher.close()方法:



public void close() throws IOException {
    if (cachingEnabled) {
      StringBuilder sb = new StringBuilder();
      sb.append("Closing ").append(name);
      for (SolrCache cache : cacheList) {
        sb.append("\n\t");
        sb.append(cache);
      }
      log.info(sb.toString());//打印Cache状态信息,例如当前Cache命中率。累积命中率,大小等。
    } else {
      log.debug("Closing " + name);
    }
    core.getInfoRegistry().remove(name);

    // super.close();
    // can't use super.close() since it just calls reader.close() and that may only be called once
    // per reader (even if incRef() was previously called).
    if (closeReader) reader.decRef();//Reader对象计数减1

    for (SolrCache cache : cacheList) {
      cache.close();//关闭Cache
    }

    // do this at the end so it only gets done if there are no exceptions
    numCloses.incrementAndGet();
  }


OK,到这里,Cache经由SolrIndexSearcher管理的逻辑就完整介绍完毕。

2 Cache的使用场景

(1)filterCache

该Cache主要是针对用户Query中使用fq的情况,会将fq对应的查询结果放入Cache,如果业务上有很多比较固定的查询Query,例如固定状 态值,比如固定查询某个区间的Query都可以使用fq将结果缓存到Cache中。查询query中可以设置多个fq进行Cache,但是值得注意的是多 个fq都是以交集的结果返回。

另外一个最为重要的例外场景,在Solr中如果设置,useFilterForSortedQuery=true,filterCache不为空,且带有sort的排序查询,将会进入如下代码块:


if ((flags & (GET_SCORES|NO_CHECK_FILTERCACHE))==0 && useFilterForSortedQuery && cmd.getSort() != null && filterCache != null) {
      useFilterCache=true;
      SortField[] sfields = cmd.getSort().getSort();
      for (SortField sf : sfields) {
        if (sf.getType() == SortField.SCORE) {
          useFilterCache=false;
          break;
        }
      }
    }

    // disable useFilterCache optimization temporarily
    if (useFilterCache) {
      // now actually use the filter cache.
      // for large filters that match few documents, this may be
      // slower than simply re-executing the query.
      if (out.docSet == null) {//在DocSet方法中将会把Query的结果也Cache到filterCache中。
        out.docSet = getDocSet(cmd.getQuery(),cmd.getFilter());
        DocSet bigFilt = getDocSet(cmd.getFilterList());//fq不为空将Cache结果到filterCache中。
        if (bigFilt != null) out.docSet = out.docSet.intersection(bigFilt);//返回2个结果集合的交集
      }
      // todo: there could be a sortDocSet that could take a list of
      // the filters instead of anding them first...
      // perhaps there should be a multi-docset-iterator
      superset = sortDocSet(out.docSet,cmd.getSort(),supersetMaxDoc);//排序
      out.docList = superset.subset(cmd.getOffset(),cmd.getLen());//返回len 大小的结果集合


(2)documentCache主要是对document结果的Cache,一般而言如果查询不是特别固定,命中率将不会很高。

(3)fieldvalueCache 缓存在facet组件使用情况下对multiValued=true的域相关计数进行Cache,一般那些多值域采用facet查询一定要开启该Cache,主要缓存(参考UnInvertedField 的实现):

maxTermCounts 最大Term数目

numTermsInField 该Field有多少个Term

bigTerms 存储那些Term docFreq 大于threshold的term

tnums 一个记录 term和何其Nums的二维数组

每次FacetComponent执行process方法–>SimpleFacets.getFacetCounts()–>getFacetFieldCounts()–>getTermCounts(facetValue)–>

UnInvertedField.getUnInvertedField(field, searcher);展开看该方法


public static UnInvertedField getUnInvertedField(String field, SolrIndexSearcher searcher) throws IOException {
    SolrCache cache = searcher.getFieldValueCache();
    if (cache == null) {
      return new UnInvertedField(field, searcher);//直接返回
    }

    UnInvertedField uif = (UnInvertedField)cache.get(field);
    if (uif == null) {//第一次初始化该域对应的UnInvertedField
      synchronized (cache) {
        uif = (UnInvertedField)cache.get(field);
        if (uif == null) {
          uif = new UnInvertedField(field, searcher);
          cache.put(field, uif);
        }
      }
    }

    return uif;
  }



(4)queryresultCache 对Query的结果进行缓存,主要在SolrIndexSearcher类的getDocListC()方法中被使用,主要缓存具有 QueryResultKey的结果集。也就是说具有相同QueryResultKey的查询都可以命中cache,所以我们看看 QueryResultKey的equals方法如何判断怎么才算相同QueryResultKey:


public boolean equals(Object o) {
    if (o==this) return true;
    if (!(o instanceof QueryResultKey)) return false;
    QueryResultKey other = (QueryResultKey)o;

    // fast check of the whole hash code... most hash tables will only use
    // some of the bits, so if this is a hash collision, it's still likely
    // that the full cached hash code will be different.
    if (this.hc != other.hc) return false;

    // check for the thing most likely to be different (and the fastest things)
    // first.
    if (this.sfields.length != other.sfields.length) return false;//比较排序域长度
    if (!this.query.equals(other.query)) return false;//比较query
    if (!isEqual(this.filters, other.filters)) return false;//比较fq

    for (int i=0; i<sfields.length; i++) {
      SortField sf1 = this.sfields[i];
      SortField sf2 = other.sfields[i];
      if (!sf1.equals(sf2)) return false;//比较排序域
    }

    return true;
  }



从上面的代码看出,如果要命中一个queryResultCache,需要满足query、filterquery sortFiled一致才行。

3 Cache的配置介绍

要使用Solr的四种Cache,只需要在SolrConfig中配置如下内容即可:



<query>
        <filterCache               size="300"      initialSize="10"      autowarmCount="300"/>
        <queryResultCache      size="300"      initialSize="10"      autowarmCount="300"/>
        <fieldValueCache       size="300"      initialSize="10"       autowarmCount="300" />
        <documentCache             size="5000"      initialSize="512"      autowarmCount="300"/>
        <useFilterForSortedQuery>true</useFilterForSortedQuery>//是否能使用到filtercache关键配置
        <queryResultWindowSize>50</queryResultWindowSize>//queryresult的结果集控制
        <enableLazyFieldLoading>false</enableLazyFieldLoading>//是否启用懒加载field
 </query>


其中size为缓存设置大小,initalSize初始化大小,autowarmCount 是最为关键的参数代表每次构建新的SolrIndexSearcher的时候需要后台线程预热加载到新Cache中多少个结果集。

那是不是这个预热数目越大就越好呢,其实还是要根据实际情况而定。如果你的应用为实时应用,很多实时应用的实现都会在很短的时间内去得到重新打开的 内存索引indexReader,而Solr默认实现就会重新打开一个新的SolrIndexSearcher,那么如果Cache需要预热的数目越多, 那么打开新的SolrIndexSearcher就会越慢,这样对实时性就会大打折扣。

但是如果设置很小。每次都打开新的SolrIndexSearcher都是空Cache,基本上那些fq和facet的查询就基本不会命中缓存。所以对实时应用需要特别注意。

4 Cache的命中监控

页面查询:

http://localhost:8080/XXXX/XXXX/admin/stats.jsp 进行查询即可:

Solr之缓存篇_缓存

 

其中 lookups 为当前cache 查询数, hitratio 为当前cache命中率,inserts为当前cache插入数,evictions从cache中踢出来的数据个数,size 为当前cache缓存数, warmuptime为当前cache预热所消耗时间,而已cumulative都为该类型Cache累计的查询,命中,命中率,插入、踢出的数目。

 


标签:cache,cmd,true,Cache,缓存,SolrIndexSearcher,null,Solr
From: https://blog.51cto.com/u_16087105/6223574

相关文章

  • Java中缓存区的基本使用
    前言缓存区是一种内存空间,在计算机程序中被广泛使用来优化I/O操作的效率。在文件I/O操作中,缓存区用于缓存将要写入磁盘或读取到内存中的数据。这样可减少对磁盘的访问次数,提高I/O操作的效率。本文将介绍缓存区的基本使用以及一些注意点,并提供一个实例来演示如何将一个jpg图片复制......
  • 第138篇:了解HTTP协议(TCP/IP协议,DNS域名解析,浏览器缓存)
    好家伙,发现自己的网络知识十分匮乏,赶紧补一下 这里先举个我生活中的例子欸,作业不会写了,上网搜一下用edge浏览器上bing必应搜一下(百度广告太多了,真不想用百度举例子) 假设这是我们第一次访问bing的首页当我向浏览器中输入https://cn.bing.com/并按下回车浏览器做了什么?(我......
  • .NET CORE开源 DDD微服务 支持 多租户 单点登录 多级缓存、自动任务、分布式、日志、
    源代码地址https://github.com/junkai-li/NetCoreKevin基于NET6搭建跨平台DDD思想WebApi架构、IDS4单点登录、多缓存、自动任务、分布式、多租户、日志、授权和鉴权、CAP、SignalR、docker部署 如需简约项目可直接去除项目引用解耦设计都可以单独引用架构默认全部引用并启动......
  • 缓存穿透、并发和失效解决方案
    我们在用缓存的时候,不管是Redis或者Memcached,基本上会通用遇到以下三个问题:缓存穿透缓存并发缓存失效缓存穿透我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓......
  • 数据库和缓存数据一致性
    不好的方案1.先写MySQL,再写Redis图解说明:这是一副时序图,描述请求的先后调用顺序;橘黄色的线是请求A,黑色的线是请求B;橘黄色的文字,是MySQL和Redis最终不一致的数据;数据是从10更新为11;后面所有的图,都是这个含义,不再赘述。请求A、B都是先写MySQL,然后再写......
  • 1 python操作哨兵 、2 python操作集群、3 缓存优化、4 mysql 主从 、5 django使用多数
    目录1python操作哨兵2python操作集群3缓存优化3.1redis缓存更新策略3.2缓存击穿,雪崩,穿透4mysql主从5django使用多数据库做读写分离1python操作哨兵#高可用架构后---》不能直接连某一个主库了---》主库可能会挂掉,后来它就不是主库了#之前学的连接redis的操作,就用不......
  • redis高级-day6——python操作哨兵、python操作集群、缓存优化
    目录一、python操作哨兵二、python操作集群三、缓存优化3.1redis缓存更新策略3.2缓存击穿,雪崩,穿透一、python操作哨兵#高可用架构后---》不能直接连某一个主库了---》主库可能会挂掉,后来它就不是主库了#之前学的连接redis的操作,就用不了了importredisconn=redis.Red......
  • 【Spring】三级缓存解决循环依赖问题
    1、什么是循环依赖  顾名思义“循环依赖”,举一个简单的例子A中依赖B,B中依赖A,在实例化对象过程中,填充属性阶段A需要B对象,就去创建B对象,创建B对象的时候,发现B依赖了A,于是又去创建A对象,但此时A对象又没有创建结束,一级缓存中找不到,于是线程又去创建一个新的A对象,如此一来,就出现了......
  • Mybatis缓存
    1.Mybatis缓存1.1. 理解MyBatis缓存正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持1.     一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Sessionflush 或 close 之后,该Session中的所有 Cache 就将清空。2......
  • Spring缓存注解的使用与源码分析
    SpringCache提供了一个对缓存使用的抽象,以及大量的实现方便开发者使用。SpringCache主要提供了如下注解:注解说明@Cacheable根据方法的请求参数对其结果进行缓存@CachePut根据方法的请求参数对其结果进行缓存,和@Cacheable不同的是,它每次都会触发真实方法的调用@CacheEvict根据一定......