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 进行查询即可:
其中 lookups 为当前cache 查询数, hitratio 为当前cache命中率,inserts为当前cache插入数,evictions从cache中踢出来的数据个数,size 为当前cache缓存数, warmuptime为当前cache预热所消耗时间,而已cumulative都为该类型Cache累计的查询,命中,命中率,插入、踢出的数目。
相关推荐
Solr的缓存机制包括filterCache、queryResultCache和documentCache,它们分别用于优化过滤器、查询结果和文档的缓存操作。文档还提供了一个关于SolrJ的介绍,SolrJ是Solr的Java客户端,它支持与Solr服务器的交互。...
在本篇文章中,我们将深入探讨如何使用Java API来与Solr 7.1.0进行交互,并了解Solr最新支持的SQL查询功能。 首先,让我们来讨论如何通过Java API与Solr 7.1.0进行通信。Solr提供了一个名为SolrJ的客户端库,它允许...
本篇文章将详细讲解如何在Linux和Windows环境中部署Solr。 **一、Solr在Linux环境中的部署** 1. **系统准备**:确保你的Linux系统(如Ubuntu或CentOS)已经安装了Java运行环境(JRE)和Java开发工具集(JDK)。你...
### Apache Solr 架构分析内部设计篇 #### 一、引言 随着大数据时代的到来,搜索引擎技术在各个领域中的应用越来越广泛。Apache Solr作为一款高性能、可伸缩的企业级搜索平台,凭借其强大的功能及灵活性,在众多...
本篇我们将深入探讨如何利用Solr5来索引网络上的远程文件,让数据检索的触角延伸到互联网的每一个角落。 首先,理解Solr的基本架构至关重要。Solr是以Lucene为基础的搜索服务器,它提供了分布式、可扩展、高可用的...
本篇将围绕“跟益达学Solr5之使用IK分词器”这一主题,详细讲解如何在Solr5中集成并运用IK分词器,以及它的工作原理和优化技巧。 首先,让我们了解下什么是分词器。在中文搜索引擎中,由于中文句子没有明显的分隔符...
本篇文章将详细阐述如何利用Solr实现京东搜索的功能。 一、关键词搜索 在商品信息搜索中,关键词搜索是最基础也最重要的功能。用户输入关键词后,Solr会通过分析器对关键词进行分词处理,然后在索引库中匹配与之...
在本篇中,我们将深入探讨Solr 7.7.3的配置细节,并了解如何将其与Spring Boot 2.x进行整合,构建一个高效、可扩展的搜索引擎应用。 **一、Solr 7.7.3核心配置** 1. **安装与启动** - 首先,你需要下载Solr 7.7.3...
### Apache Solr搜索架构分析——外部设计篇 #### 一、引言 随着互联网技术的飞速发展,数据量呈爆炸性增长,高效且准确地检索海量数据成为了现代信息技术领域的一项重要挑战。Apache Solr作为一款开源的企业级...
在本篇博文中,我们将探讨基于Apache Solr构建的网站索引架构,这是搜索引擎技术中的一个关键组件。Solr是一个开源、高性能的全文检索服务,它允许开发者为大量数据建立索引,从而实现快速的搜索功能。在"基于solr的...
同时,Solr的缓存机制(如文档缓存、查询结果缓存等)可以显著提升查询速度。 Solr 6.6还引入了一些新特性,如Lucene 6.6的最新改进,这可能包括更高效的倒排索引结构、新的搜索算法等。此外,Solr 6.6的更新可能还...
Solr 是一个基于 Lucene 的开源搜索引擎,专为全文检索、高效分析和处理大量数据而设计。本压缩包“solr7部署...这篇博文是一个很好的参考资料,可以帮助用户避免部署过程中可能遇到的问题,确保 Solr 能够顺利运行。
本篇文章将重点探讨在Solr5中如何实现拼音分词,以提升中文搜索的准确性和用户体验。 首先,我们了解下Solr的基本概念。Solr是基于Lucene的搜索服务器,它提供了更高级别的API和服务,如分布式搜索、缓存、复制和...
本篇将详细阐述Lucene和Solr的基本概念、工作原理以及如何在实际应用中使用它们。 **1. Lucene简介** Lucene是一个由Apache软件基金会开发的全文检索库,它提供了高效的、可扩展的文本搜索功能。Lucene的核心功能...
本篇Solr调研总结涵盖了48页的内容,可能涉及以下几个关键知识点: 1. **Solr架构**:Solr采用分布式架构,支持多节点集群,可以实现数据的分布式存储和处理,提高系统的可用性和性能。通过Sharding(分片)和...
通过这篇教程,我们可以获得关于如何安装配置Solr,如何通过Solr进行索引构建和搜索,以及如何优化Solr性能的详细指导。同时,也能够了解到SolrJ在Java应用中如何与Solr服务器交互,实现数据的索引和搜索功能。此外...
本篇文章将详细探讨基于Solr实现的千亿级检索设计,以及其背后的Lucene结构和倒排索引技术。 首先,我们要理解Solr的核心功能:提供高效的全文搜索、 faceted search(分面搜索)、命中高亮、动态集群、实时添加...
这篇博客将带你初次接触 Solr,了解其基本概念、安装过程和简单的索引构建。 一、Solr简介 1.1 Solr 架构 Solr 构建在分布式架构之上,支持集群部署,可以实现数据的横向扩展和高可用性。它由多个独立的 Solr 实例...
这篇学习笔记将深入探讨如何搭建 Solr 服务器,并利用其特性进行高效的索引和查询操作。 ### 一、Solr 的核心概念 1. **Core(核心)**:Solr 的工作单元,类似于数据库中的数据库,每个 Core 都有自己的配置和...