solr的缓存查询
本文将介绍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中可保存的最大的项数,默认是10242)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、filterCachefilterCache存储了无序的lucene document id集合,该cache有3种用途:1)filterCache存储了filter queries(“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条件的document id集合(可涉及到filterCache)的处理得到的。因为统计各facet计数可能会涉及到所有的doc id,所以filterCache的大小需要能容下索引的文档数。3)如果solfconfig.xml中配置了,那么如果查询有filter(此filter是一需要过滤的DocSet,而不是fq,我未见得它有什么用),则使用filterCache。下面是filterCache的配置示例:复制代码 对于是否使用filterCache及如何配置filterCache大小,需要根据应用特点、统计、效果、经验等各方面来评估。对于使用fq、facet的应用,对filterCache的调优是很有必要的。3、queryResultCache顾名思义,queryResultCache是对查询结果的缓存(SolrIndexSearcher中的cache缓存的都是document id set),这个结果就是针对查询条件的完全有序的结果。下面是它的配置示例:复制代码 复制代码缓存的key是个什么结构呢?就是下面的类(key的hashcode就是QueryResultKey的成员变量hc):复制代码public QueryResultKey(Query query, List 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上的解释很深简单明了:复制代码 50复制代码相比filterCache来说,queryResultCache内存使用上要更少一些,但它的效果如何就很难说。就索引数据来说,通常我们只是在索引上存储应用主键id,再从数据库等数据源获取其他需要的字段。这使得查询过程变成,首先通过solr得到document id set,再由Solr得到应用id集合,最后从外部数据源得到完成的查询结果。如果对查询结果正确性没有苛刻的要求,可以在Solr之外独立的缓存完整的查询结果(定时作废),这时queryResultCache就不是很有必要,否则可以考虑使用queryResultCache。当然,如果发现在queryResultCache生命周期内,query重合度很低,也不是很有必要开着它。4、documentCache又顾名思义,documentCache用来保存对的。如果使用documentCache,就尽可能开大些,至少要大过 * ,否则因为cache的淘汰,一次请求期间还需要重新获取document一次。也要注意document中存储的字段的多少,避免大量的内存消耗。下面是documentCache的配置示例: 5、User/Generic CachesSolr支持自定义Cache,只需要实现自定义的regenerator即可,下面是配置示例:复制代码 anything name desc price desc populartiy desc anything name desc, price desc, populartiy desc anything category inStock:true price:[0 TO 100] 复制代码3)documentCache:因为新索引的document id和索引文档的对应关系发生变化,所以documentCache没有warm的过程,落得白茫茫一片真干净。尽管autowarm很好,也要注意autowarm带来的开销,这需要在实际中检验其warm的开销,也要注意Searcher的切换频率,避免因为warm和切换影响Searcher提供正常的查询服务。