一、概述

Solr查询的核心类就是SolrIndexSearcher,每个core通常在同一时刻只由当前的SolrIndexSearcher供上层的handler使用(当切换SolrIndexSearcher时可能会有两个同时提供服务),而Solr的各种Cache是依附于SolrIndexSearcher的,SolrIndexSearcher存在则Cache生,SolrIndexSearcher亡则Cache被清空close掉。Solr在Lucene之上开发了很多Cache功能,从目前提供的Cache类型有:

(1)filterCache

(2)documentCache

(3)fieldvalueCache

(4)queryresultCache

(5)User/GenericCaches

其中1、2、3、4都是SolrCache的实现类,并且是SolrIndexSearcher的成员变量,各自有着不同的逻辑和使命。

二、SolrCache接口实现类

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

下面是对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, CacheRegeneratorregenerator);

/**: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();

2.1、solr.search.LRUCache

LRU又称最近最少使用。假如缓存的容量为10,那么把缓存中的对象按访问(插入)的时间先后排序,当容量不足时,删除时间最早的。Solr中LRUCache是通过LinkedHashMap来实现的。

LRUCache可配置参数如下:

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

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

3)autowarmCount:当切换SolrIndexSearcher时,可以对新生成的SolrIndexSearcher做autowarm(预热)处理。autowarmCount表示从旧的SolrIndexSearcher中取多少项来在新的SolrIndexSearcher中被重新生成,如何重新生成由CacheRegenerator实现。1.4版本的Solr中,这个autowarmCount只能取预热的项数,如果不指定该参数,则表示不做autowarm处理。

实现上,LRUCache直接使用LinkedHashMap来缓存数据,由initialSize来限定cache的大小,淘汰策略也是使用LinkedHashMap内置的LRU方式,读写操作都是对map的全局锁,所以在高并发条件下,其性能会有所影响。因此Solr用另外一种方式实现了LRUCache,即FastLRUCache。

2.2、solr.search.FastLRUCache

FastLRUCache内部采用了ConcurrentLRUCache实现,而ConcurrentLRUCache内部又采用ConcurrentHashMap实现,所以是线程安全的。缓存通过CacheEntry中的访问标记lastAccessed来维护CacheEntry被访问的先后顺序。 即每当Cache有get或者put操作,则当前CacheEntry的lastAccessed都会变成最大的(state.accessCounter)。当FastLRUCache容量已满时,通过markAndSweep方式来剔除缓存中lastAccessed最小的N个项以保证缓存的大小达到一个acceptable的值。

在配置方面,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的极典型实现。

三 、 Cache原理及配置

 

3.1 filterCache

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

filterCache存储了无序的lucenedocument id集合,该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。

配置示例:

<!-- Internal cache used bySolrIndexSearcher 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大小,需要根据应用特点、统计、效果、经验等各方面来评估。对于使用fq、facet的应用,对filterCache的调优是很有必要的。

3.2、documentCache

顾名思义,documentCache是用来保存<doc_id,document>对的。如果使用documentCache,就尽可能开大些,至少要大过<max_results> * <max_concurrent_queries>,否则因为cache的淘汰,一次请求期间还需要重新获取document一次。也要注意document中存储的字段的多少,避免大量的内存消耗。documentCache主要是对document结果的Cache,一般而言如果查询不是特别固定,命中率将不会很高。

配置示例:

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

3.3、queryResultCache

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 fastestthings)

// 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、filterquerysortFiled一致才行。因为查询参数是有start和rows的,所以某个QueryResultKey可能命中了cache,但start和rows却不在cache的document id set范围内。当然,document id set是越大命中的概率越大,但这也会很浪费内存,这就需要个参数:queryResultWindowSize来指定document id set的大小。

相比filterCache来说,queryResultCache内存使用上要更少一些,但它的效果如何就很难说。就索引数据来说,通常我们只是在索引上存储应用主键id,再从数据库等数据源获取其他需要的字段。这使得查询过程变成,首先通过solr得到document id set,再由Solr得到应用id集合,最后从外部数据源得到完成的查询结果。如果对查询结果正确性没有苛刻的要求,可以在Solr之外独立的缓存完整的查询结果(定时作废),这时queryResultCache就不是很有必要,否则可以考虑使用queryResultCache。当然,如果发现在queryResultCache生命周期内,query重合度很低,也不是很有必要开着它。

配置示例:

<!-- 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"/>

3.4 filterValueCache

fieldValueCache在SolrIdexSearcher的定义如下:

SolrCache<String,UnInvertedField> fieldValueCache; 其中key代表FieldName,value是一种数据结构UnInvertedField。 fieldValueCache在solr中只用于multivalued Field。一般用到它的就是facet操作。关于这个缓存需要注意的是,如果没有在solrconfig.xml中配置,那么它是默认存在的(初始大小10,最大10000,不会autowarm)会有内存溢出的隐患。

由于该cache的key为FieldName,而一般一个solrCore中的字段最多也不过几百。在这么多字段中,multivalued 字段会更少,会用到facet操作的则少之又少。所以在solrconfig.xml中的配置不必过大,大了也是浪费。

该缓存存储排序好的docIds,一般是topN。这个缓存占用内存会比filterCache小。因为它存储的是topN。但是如果QueryCommand中带有filter(DocSet类型),那么该缓存不会起作用。主要因为DocSet在执行hashcode和equals方法时比较耗时。

3.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"/>

-->

四、solr基于Cache 的优化

solr应用中为了提高查询速度有可以利用几种cache来优化查询速度,在日常使用中最为立竿见影,在这章节中基于之前做过的一个职位搜索的例子来介绍两个基于cache的优化,职位搜索截取集群中一个collection节点作为示例,数据量78万。

4.1查询结果缓存应用

将solrconfig.xml中所有的缓存配置都去掉,此时搜索条件为:jobsName:软件工程师 and educateBackground:本科 and workPlace:济南

查询结果如下图:

在solrconfig.xml中开启查询结果缓存配置

<queryResultCache class="solr.LRUCache"  size="512"  initialSize="512"autowarmCount="0"/>

<documentCache class="solr.LRUCache" size="512"  initialSize="512"   autowarmCount="0"/>

<queryResultMaxDocsCached>1024</queryResultMaxDocsCached>

从上面结果可以很明显看出查询走缓存的效果还是非常明显的,并且不用担心数据更新然后再次打开SolrIndexSearcher后缓存不一致的情况。当切换SolrIndexSearcher时,可以对新生成的SolrIndexSearcher做autowarm(预热)处理,会从旧的SolrIndexSearcher中取出原来缓存的项在新的SolrIndexSearcher中重新生成。

4.2基于filterCache查询优化

在常规的搜索查询中,查询经常会带一些基础查询条件,比如在职位搜索中经常会基于学历为本科,职位类型是全职等为前提条件的查询,这些前提条件变动小并且每次查询都要带上,这个时候filtercache就能很好的发挥出它的优势了。

为了验证filtercache,首先去除所有cache配置,重启搜索服务,在不使用任何cache的情况下不同搜索条件的耗时如下:

查询条件

查询耗时(ms)

jobDescrip: 文案编辑 and employeeSex:不限 and jobsType:全职and educateBackground:本科  and workPlace:深圳 and salary:面议

114

jobDescrip: 工程师 and employeeSex:不限 and jobsType:全职and educateBackground:本科  and workPlace:深圳 and salary:面议

147

jobDescrip: 高级软件开发工程师 and employeeSex:不限 and jobsType:全职and educateBackground:本科  and workPlace:深圳 and salary:面议

210

在去除其他cache配置条件下只添加filtercache配置:

<filterCacheclass="solr.FastLRUCache" size="512"

initialSize="512"

autowarmCount="0"/>

当查询条件为:jobDescrip: 软件工程师,其中filterquery为: employeeSex:不限 andjobsType:全职  and educateBackground:本科  and workPlace:深圳 and salary:面议。此时查询如下(重启后首次查询的耗时包含了初次搜索时基础数据的初始化):

接着进行二次搜索,搜索效果如下:

通过如下缓存命中监控图可以看出第二次搜索filtercache直接命中,命中率为50%。

此时在filterquery条件不变的情况下,只改变基础搜索词:jobDescrip:文案编辑

通过上图可以看出查询时间大大降低,此时再查看filtercache缓存命中率,总共命中两次,并且第二、三查询速度明显加快

但是在使用filterquery的时候需要注意query跟filterquery中不能有重复的字段,

例如:

Query=jobDescrip: 软件工程师and employeeSex:不限

Filterquery=employeeSex:不限 andjobsType:全职  and educateBackground:本科  and workPlace:深圳 and salary:面议

如上,条件中(employeeSex:不限)在query和filterquery中都会出现,这样的写法非但起不到查询优化的目的,而且还会增加查询的性能开销。

参考资料

Grainger T, Potter T, Seeley Y. Solr inaction[M]. Cherry Hill: Manning, 2014.

Kuć R. Apache Solr 4 Cookbook[M]. PacktPublishing Ltd, 2013.

https://wiki.apache.org/solr/SolrCaching#User.2FGeneric_Caches

http://www.solr.cc/blog/?p=239

solr中Cache综述的更多相关文章

  1. lucene、solr中的日期衰减方法-------function query --尚未测试在solr4.8

    经常有一种情景是这样的:我们索引了N年的文章,而查询时候无论直接用相关度.或者用时间排序,都是比较鲁莽的:我们想要一种既要相关度比较高,又要时间上比较新的文章. 这时候的解决办法就是,自定义日期衰减的 ...

  2. solr入门之多线程操作solr中索引字段的解决

    涉及的问题: 建索引时有一个字段是该词语出现的次数,这个字段是放在solr里的  而我用的是多线程来进行全量导入的,这里就涉及到了多线程问题 多个线程操作同一个变量时怎样处理? 我是这样子做的 : 首 ...

  3. 在Solr中配置和使用ansj分词

    在上一节[编译Ansj之Solr插件]中介绍如何编译ansj分词在solr(lucene)环境中使用的接口,本章将介绍如何在solr中使用ansj,其步骤主要包括:下载或者编译ansj和nlp-lan ...

  4. solr与.net系列课程(八)solr中重跑索引的注意事项

    solr与.net系列课程(八)solr中重跑索引的注意事项 我们如果在项目中使用solr,那肯定就是把数据库中的数据跑进solr服务器中,solr有两种操作一种是新建索引,一种是增量索引,这里我们来 ...

  5. Solr中初学Demo

    import java.util.Collection; import java.util.Date; import org.apache.solr.client.solrj.SolrQuery; i ...

  6. ASP.NET缓存中Cache过期的三种策略

    原文:ASP.NET缓存中Cache过期的三种策略 我们在页面上添加三个按钮并双击按钮创建事件处理方法,三个按钮使用不同的过期策略添加ASP.NET缓存. <asp:Button ID=&quo ...

  7. C#中Cache用法

    C#中Cache用法     Cache 是分配在服务器上的一个公共的内存片,所谓公共指的cache只要一创建是任何一个客户端浏览器都可以通过后台代码访问到它,它面向的是所有用户,相对而言sessio ...

  8. solr中重跑索引

    solr与.net系列课程(八)solr中重跑索引的注意事项   solr与.net系列课程(八)solr中重跑索引的注意事项 我们如果在项目中使用solr,那肯定就是把数据库中的数据跑进solr服务 ...

  9. Solr中Field常用属性

    FieldType 实例:<fieldType name="text_ik" class="solr.TextField"></fieldTy ...

随机推荐

  1. linux 常用命令积累

    工作中常用的linux记录一下,方便查询使用 1.ln 创建连接 ,就是window上的快捷方式 创建软连接  ln -s 源文件 目标文件名   经常访问的文件夹(项目文件夹),在~创建一个软连很方 ...

  2. 阿里云 oss 图片上传解决方案 vue (web直传)

    我们通过aliyun-oss-web这个npm去解决 该文章主要介绍如何获取 imgSignature 和 imgPolicy 这两个参数 首先下载 web直传的案例 : http://files.c ...

  3. mybatis 学习笔记 -详解mybatis 及实例demo

    快速入门1 要点: 首先明白mybatis 是什么 这是一个持久层的框架.之前叫做ibatis.所以,在它的代码中出现ibatis这个词的时候,不要感到惊讶.不是写错了,它确实就是这个样子的. 首先, ...

  4. JPA、Hibernate框架、通用mapper

    JPA是描述对象-关系表的映射关系,将运行期实体对象持久化到数据库中,提出以面向对象方式操作数据库的思想. Hibernate框架核心思想是ORM-实现自动的关系映射.缺点:由于关联操作提出Hql语法 ...

  5. 选择器与I/O多路复用

    Selector选择器是NIO技术中的核心组件,可以将通道注册进选择器中,其主要作用是使用1个线程来对多个通道中的已就绪通道进行选择,然后就可以对选择的通道进行数据处理,属于一对多的关系,也就是使用1 ...

  6. Python之删除空白

    Python能够找出字符串开头.末尾.两端多余的空白. lstrip()方法可以剔除字符串开头的空白: rstrip()方法可以剔除字符串末尾的空白: strip()可以剔除字符串两端的空白: fav ...

  7. Java for-each循环解惑

    Java for-each循环解惑 2014/04/24 | 分类: 技术之外 | 0 条评论 | 标签: JAVA 分享到:21 本文由 ImportNew - liqing 翻译自 javarev ...

  8. c# excel xls保存

    public HSSFWorkbook Excel_Export(DataTable query,string title,int[] rowweight,string[] rowtitle) { H ...

  9. 安装FireEye渗透测试套件commando-vm

    前两天FireEye开源了套他们自己的渗透测试工具,玩了下,这里简单讲一下我安装的过程. 1.首先是虚拟机,在virtualbox或者vmware中安装一个新的Windows系统,win7或者win1 ...

  10. dll被设置为用记事本打开的解决方法

    dll被设置为用记事本打开的解决方法: 打开注册表编辑器 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Fi ...