solr Highlighter (高亮)显示分析
2012-10-29 18:28
本文原创,转载请说明出处:http://ronxin999.blog.163.com/blog/static/42217920201292951457295/
solr 高亮显示是根据我们搜索的内容中,根据搜索的关键字,在内容中取一段摘要,类似百度,google搜索结果中出现关键字的一段描述。
首先要solr的高亮功能work,必须在搜索请求的URL里加上参数:hl=on,该参数只是告诉solr我要高亮查询。接下来就是其他的参数,下面主要说明重要的参数:
solr 高亮显示是根据我们搜索的内容中,根据搜索的关键字,在内容中取一段摘要,类似百度,google搜索结果中出现关键字的一段描述。
首先要solr的高亮功能work,必须在搜索请求的URL里加上参数:hl=on,该参数只是告诉solr我要高亮查询。接下来就是其他的参数,下面主要说明重要的参数:
hl.fl
hl.fl是说明你要关键字的摘要在那个field中取,我们一般是content字段。
hl.useFastVectorHighlighter
该参数很重要,如果你不看代码,是很难发现他的好处,默认是false,即文本段的划分是按每50个字符来划分,然后在这个50个字符中取关键字相关的摘要,摘要长度为100,参考后面的参数(hf.fragsize),如果我们在参数中指定值为true,那么SOLR会根据关键词的在文本中的偏移量来计算摘要信息,前提是你的field要加上 termPositions="true" termOffsets="true"这两项。
hl.snippets
hl.snippets参数是返回高亮摘要的段数,因为我们的文本一般都比较长,含有搜索关键字的地方有多处,如果hl.snippets的值大于1的话,会返回多个摘要信息,即文本中含有关键字的几段话,默认值为1,返回含关键字最多的一段描述。solr会对多个段进行排序。hl.fragsize
hl.fragsize参数是摘要信息的长度。默认值是100,这个长度是出现关键字的位置向前移6个字符,再往后100个字符,取这一段文本。hl.boundaryScanner hl.bs.maxScan hl.bs.chars
boundaryScanner是边界扫描,就是怎么取我们高亮摘要信息的起始位置和结束位置,这个是我们摘要信息的关键,因为我们模式高亮摘要的开始和结束可能是某句话中截段的。上面这三个参数需要放在一起来说明,因为后两个是在我们没有给hl.boundaryScanner设定值,即默认值时才会有效,对应的class为:SimpleBoundaryScanner,上面讲了hl.fragsize参数,
SimpleBoundaryScanner会根据hl.fragsize参数决定的关键字的起始偏移量和结束偏移量,重新计算摘要的起始偏移量,首先说开始偏移量,源码如下:
结束便宜量和计算起始偏移量是一样的,只不过是从关键词的位置往后100个字符的位置往后找分隔符,maxScan的默认值为10,hl.sc.chars的默认值为.,!? 	 public int findStartOffset(StringBuilder buffer, int start) { // avoid illegal start offset if( start > buffer.length() || start < 1 ) return start;
//maxScan是hl.bs.maxScan的值,它是说明从关键字出现的位置往前6个字符开始向前,在maxScan个字符内找是否出 现一个
//一个由参数hl.bs.chars指定的分界符。即从这里作为摘要的起始偏移。 如果往前maxScan个字符内没有发现指定的字符,
//则按起始便宜为start,即关键词往前的6个字符。
int offset, count = maxScan; for( offset = start; offset > 0 && count > 0; count-- ){ // found? if( boundaryChars.contains( buffer.charAt( offset - 1 ) ) ) return offset; offset--; } // if we scanned up to the start of the text, return it, its a "boundary" if (offset == 0) { return 0; } // not found return start; }
hl.boundaryScanner参数我们不指定值时,按上面的算法计算高亮摘要信息,但solr还提供了一种算法,即breakIterator,我们在请求参数中添加&hl.boundaryScanner=breakIterator时生效,这是solr通过java jdk的BreakIterator来计算分界符的。他相关的参数为,hl.bs.type,这个是主要的参数,决定BreakIterator怎么划分界定符,值有:CHARACTER, WORD, SENTENCE and LINE,SENTENCE 是按句子来划分,即你高亮摘要信息是一个完整的句子,而不会被截断。
solr multivalued 说明
solr的schema.xml配置文件在配置field的时候有个属性:
multiValued: true if this field may contain multiple values per document,这个说明有些模糊。
下面具体说明下:
1 我们怎么给同一个field加多个值呢。可以这样:
假如我的 keywords 域是multivalued 的,那我可以这样添加多个值。
<doc>
<field name=keywords>solr<field/>
<field name=keywords>lucene<field/>
<field name=keywords>seach<field/>
</doc>
但你搜索的时候,响应给客户端的域keywords是一个列表list。如下:
<arr name="keywords">
<str>solr</str>
<str>lucene</str>
<str>seach</str>
</arr>
如果不是multivalued 类型的,那么就是<str></str>格式的。
Solr facet 分析
2011-11-06 20:59:57| 分类: Luence/Solr | 标签:solr facetcomponent facet |字号 订阅
此文原创,转载请说明出处:http://ronxin999.blog.163.com/blog/static/42217920201110682657684/
Solr FacetComponent是实现对词Term的层面统计。FacetComponent给客户端返回四种类型的结果,分别是:
facet_queries,facet_fields,facet_dates,facet_ranges,我们用的最多是facet_fields,即对field的词出现的个数的一个统计。下面开始讲solr实现的原理。
因为我们在用到FacetComponent时,查询请求的url里定要加上facet=true.FacetComponet是发挥作用前,QueryComponent会先处理q参数里的查询,查询的结果的DocID保存在DocSet里,这里是一个无序的document ID 的集合。QueryComponent处理完后,把docSet封装到SimpleFacets里,在FacetComponent会用到。
FacetComponent 在根据某个field的词时,会用到fieldValueCache,key是facet.field的值,value是UnInvertedField,
UnInvertedField这个类主要负责完成把field域每个词Term,以及词Term在所有文档field域的频率,即出现的次数。保存在一个数组中,创建的UnInvertedField保存在fieldValueCache缓存中,
得到UnInvertedField后,调用UnInvertedField的getCounts方法,跟查询到的document ID 做交集,如果不是查询结果的document ID,,则该Field的词的个数为0,除此之外,还对field出现的词做一个排序,solr的FacetComponet有两种排序选择,分别是count和index,count是按每个词出现的次数,index是按词的字典顺序。如果查询参数不指定facet.sort,solr默认是按count排序。
Solr Luence 排序
2011-11-05 16:10:07| 分类: Luence/Solr | 标签:solr 排序 fieldcache |字号 订阅
此文原创,转载请说明出处:http://ronxin999.blog.163.com/blog/static/42217920201110532554485/
luence 和solr排序都有排序功能,solr的排序就是基于luence的排序来实现的。solr通过url里加solr=true来排序,把后面带的参数封装成SortField,然后根据luence的底层来排序。下面开始讲luence排序的实现。
luence排序是基于luence有一个最小堆PriorityQueue,PriorityQueue最小堆的比较规则,由子类实现,即lessThan方法。
Luence的FieldValueHitQueue继承了PriorityQueue,我们排序的时候,有可能是根据一个field或者是多个field来排序。
那luence对应的FieldValueHitQueue有两个子类,分别是OneComparatorFieldValueHitQueue和MultiComparatorsFieldValueHitQueue,就是如果按一个Field排序和多个Field的比较方法不一样。分别如下:
//当个field排序的比较方法。
@Override
protected boolean lessThan(final Entry hitA, final Entry hitB) {
assert hitA != hitB;
assert hitA.slot != hitB.slot;
final int c = oneReverseMul * comparator.compare(hitA.slot, hitB.slot);
if (c != 0) {
return c > 0;
}
// avoid random sort order that could lead to duplicates (bug #31241):
return hitA.doc > hitB.doc;
}
//多个field排序的比较方法。
@Override
protected boolean lessThan(final Entry hitA, final Entry hitB) {
assert hitA != hitB;
assert hitA.slot != hitB.slot;
int numComparators = comparators.length;
for (int i = 0; i < numComparators; ++i) {
final int c = reverseMul[i] * comparators[i].compare(hitA.slot, hitB.slot);
if (c != 0) {
// Short circuit
return c > 0;
}
}
// avoid random sort order that could lead to duplicates (bug #31241):
return hitA.doc > hitB.doc;
}
先讲下comparators和reverseMul:
comparators: 是一个数组,如果是当个Field,就是给comparators[0] = field.getComparator(size, 0);即根据不同Field不同的数据类型创建不同的比较器。如果是多个Field,则为每个Field创建一个比较器。
reverseMul:决定按升序还是降序。
从上面两个方法可以看出,如果是多个Field排序,如果第一个Field比较的结果不相等,则按第一Field决定,不会再比较后面的Field,如果第一个Field的值相等,则按后面的Field比较。如果都相等,则按docID的大小来比较。
比较器比较的肯定是要排序Field的值,那Field的值是在什么时候取到的呢,这就是比较器FieldComparator有一个setNextReader的方法。这个方法在Iuence的IndexSearch的search方法里回调用。代码如下:
//这里说明下,collector,如果有排序,collector为TopFieldCollector。
for (int i = 0; i < subReaders.length; i++) { // search each subreader
collector.setNextReader(subReaders[i], docStarts[i]);
Scorer scorer = weight.scorer(subReaders[i], !collector.acceptsDocsOutOfOrder(), true);
if (scorer != null) {
scorer.score(collector);
}
}
而TopFieldCollector的内部子类多个Field的排序的代码为:
@Override
public void setNextReader(IndexReader reader, int docBase) throws IOException {
this.docBase = docBase;
for (int i = 0; i < comparators.length; i++) {
comparators[i].setNextReader(reader, docBase);
}
}
从上面可以看出,其实是比较器FieldComparator的setNextReader方法。FieldComparator的方法就是通过FieldCache的实现类FieldCacheImpl去取对应Field的值,如果没有,则通过Reader去索引库取,然后放到FieldCache缓存。
Solr 函数查询(FunctionQuery)原理分析
2011-11-01 16:05:01| 分类: Luence/Solr | 标签:solr functionquery _val_ |字号 订阅
此文原创,转载请说明出处:http://ronxin999.blog.163.com/blog/static/4221792020111013131992/
solr 函数查询有4种方式来实现,这个在solr的wiki里描述的比较清楚,但是这个东西到底是个什么意思,原理是什么,solr wiki并没有说的很清楚,很多朋友也是对此一头雾水,现在我经过一定时间的调试,总结了点经验,写下来,和喜欢solr的朋友共勉。
这里说其中的一种,也是最诡异的一种实现方法: _val_
举个查询例子为q=title:hadoop _val_:count 这里顺便说下hadoop和_val_中间的空格,由于solr默认是OR,所以这里是一个or组合的booleanQuery,如果是q=title:hadoop AND _val_:count 则是一个有AND组合的BooleanQuery查询。
title和count都是solr配置文件schema.xml里面的一个field,title:hadoop很明细,搜索文档的field值包含hadoop的文档。假如
索引库里总共有5个document 1,2,3,4,5,其中doucument 2,5的field包含hadoop值,那title:hadoop搜索到2,5两个document,
_val_:count
由于_val_代表的是functionQuery,由于我们索引库里的每个document都包含count域。所以solr会把所有库里的5个document
ID取到,其实solr是直接读reader.maxDoc(),这里值为5,
由于是 OR,solr底层luence会对两者坐并集,然后对并集的doc ID 打分。关键就在这个打分,也是_val_:count的体现。
由于文档2,5在两个集合里都出现,所以,document 2和5的得分也是两个得分之和。title:hadoop返回的文档2和5的得分就是luence的评分公式计算出来的,而_val_:count是有FunctionQuery的内部类FunctionWeight来计算的,不过FunctionWeight计算权重时,idf的值为1,由FunctionWeight计算的值再乘以该document域count的值,所以整个的查询结果是5个document,但是2,5这个两个文档的得分会很高。但跟这个两个文档的count域的值有关。
同样如果是q=title:hadoop AND _val_:count 逻辑于查询,那结果只有两个文档,因为luence会对其期交集,同样的是也会把相同的文档的得分相加。
顺便再说下,如果_val_:3这也,后面直接跟数据,solr就不要到索引库里去取指定域如上面count域的值了,而是直接为3,如果是跟field,solr会用到fieldCache,把该域的值取出来放到fieldCache中,下次取就不要再读索引库了。
这里再讲下_val_:rod(count)也就是_val_后面跟函数的情况。
这里用我自己自定义的函数代码如下:
public class MyValueSourceParser extends ValueSourceParser {
public MyValueSourceParser(){
super();
System.out.println("========初始化MyValueSourceParser实例==========");
}
@Override
public ValueSource parse(FunctionQParser fp) throws ParseException {
System.out.println("========开始执行MyValueSourceParser的parse方法==========");
String field = fp.parseId();
//我们这里是要根据域field的值从而可以关联一个得分。我这个Field是字符串形式的。
//StrKeywordsFieldSource 是继承StrFieldSource,solr有什么类型的Filed有相应的ValueSource。
StrKeywordsFieldSource stringFieldSource = new StrKeywordsFieldSource(field);
return stringFieldSource;
}
}
//StrKeywordsFieldSource类的源码如下:
public class StrKeywordsFieldSource extends StrFieldSource {
private static final long serialVersionUID = 1L;
//必须的
public StrKeywordsFieldSource(String field) {
super(field);
// TODO Auto-generated constructor stub
}
/**
* 重写getValues方法,StrFieldSource就是取field对应的值,我们要 根据值再做具体的运算。
* 由于,在函数查询里最终是调用getValues方法来取得结果的,所以我们都要重写getValues方法。
* getValues方法返回的是DocValues ,计算得分是DocValues 的floatVal方法。我们的都要重写。
*/
@Override
public DocValues getValues(Map context, IndexReader reader) throws IOException {
return new StringIndexDocValues(this, reader, field) {
@Override
protected String toTerm(String readableValue) {
return readableValue;
}
@Override
public float floatVal(int doc) {
String isbn = strVal(doc);
System.out.println("doc["+doc+"] isbn value is ["+isbn+"]");
if("hadoop".equals(isbn)){
return 100f;
}else if("action".equals(isbn)){
return 10f;
}else{
return 1f;
}
}
@Override
public int intVal(int doc) {
int ord=order[doc];
return ord;
}
@Override
public long longVal(int doc) {
return (long)intVal(doc);
}
@Override
public double doubleVal(int doc) {
return (double)intVal(doc);
}
@Override
public String strVal(int doc) {
int ord=order[doc];
return lookup[ord];
}
@Override
public String toString(int doc) {
return description() + '=' + strVal(doc);
}
};
}
}
自己写的函数查询要添加到solrconfig.xml配置文件才能生效。配置文件修改如下:
//函数名为:testfunc
<valueSourceParser name="testfunc"
class="com.solr.parse.plugin.MyValueSourceParser" />
现在我们可以通过_val_:"testfunc(keywords)"来函数查询了。记住函数一定要用""引起来。
比如我查询标题有hadoop的文档为 q=title:hadoop AND _val_:"testfunc(keywords)"这样就查到包含hadoop的文档的keywords域的值可以改变打分。如果keywords域就一个值还,如果keywords域有多个term,那就有多个值。那用那个呢,还有如果文档比较少,但是keywords域包含的词有很多这个又是怎么弄的。经过测试。可以回答上面两个问题。
1 solr会把所有文档的keywords域的Term放在一个枚举里面,这所有的Term是按字典排好序的。也就是取的时候是按字典顺序 取的。假如我查询title域包含hadoop的文件就3个,而我这3个文档title域包含6个Term,那Solr只取按字典排好序的前3个Term.
讲了这么多,再总结下,其实solr函数查询关键取出所有的文档,在luence的打分基础上乘以函数计算的值作为得分。
Solr Field 延迟加载 enableLazyFieldLoading 分析
2011-10-18 13:38:38| 分类: Luence/Solr | 标签:solr enablelazyfieldloadi |字号 订阅
此文原创,转载请说明出处:http://ronxin999.blog.163.com/blog/static/42217920201191812745332/
Solr 的solrconfig.xml配置文件的enableLazyFieldLoading的配置为:
<enableLazyFieldLoading>true</enableLazyFieldLoading>
默认的值为true。
这样solr在根据读取Document信息时,如果enableLazyFieldLoading为True,把要返回的Field集合封装为一个SetNonLazyFieldSelector,这里的Field的值都是立即加载的,即到索引库里把该Field的值取出来保存到Doc中的。doc的其他的Field的值则是通过延迟加载的。也是就在document调用具体的get(String name)方式时,由LazyField去取值的。可见设置延迟加载为enableLazyFieldLoading 为True,而且我们要返回的Field也很少时,那我们去读索引库所花的时间就少了。
Solr 利用缓存(Cache)的时刻
2011-07-11 22:04:36| 分类: Luence/Solr | 标签:solr cache solrcache |字号 订阅
此文原创,转载请说明出处:http://ronxin999.blog.163.com/blog/static/42217920201161194837450/
我们都知道Solr配置文件有三种缓存分别是 filterCache,queryResultCache,documentCache 但Solr是在什么时候,什么情况下会用到这些缓存呢,通过看Solr的源码,下面对Solr三种缓存做说明
filterCache 缓存filterCache:当搜索请求参数中带有参数"ids"时,Solr会去filterCache里查,filterCache里Key是query,值是DocSet,,也就是无序的Document id,如果有多个ids里包含多个id,则用分隔符“,”分开。如果filterCache中没有对应的值,则通过reader都查找
对应的DocSet,并添加到filterCache缓存中。
queryResultCache 缓存
如果搜索请求参数没有ids参数时,则会不去filterCache缓存里找,而且没有Filter时,才是去queryResultCache里查找,queryResultCache 里保存的是有序的DocList。在查到docList后,回去取docSet,即会在filterCache中查,没有的话会加到filterCache中, 如果queryResultCache缓存中没有值,也先去取docSet,即通过filterCache,没有对应的值的话,则重新构建,添加到缓存中, 则通过一般的查找方式找到。然后添加到queryResultCache缓存中。 documentCache 缓存 documentCache 是在通过doc(int i) 方法取document时,用到的。 documentCache 不存在的话,这通过reader去取,取到document后,添加到documentCache 缓存。 fieldValueCache 缓存 fieldValueCache 缓存是在solr组件FacetComponent组件里发货作用的。条件是如果要统计的Field是multiValued,也就是有 多个值的情况,solr 会根据field创建一个field反正类UnInvertedField,通过注解大概了解是节约内存和加速facet统计。 httpCache 缓存 可以看我的博客:http://ronxin999.blog.163.com/blog/static/42217920201191293032232/?suggestedreading 终于写完了solr的所有缓存的应用场景。
Solr httpCache 缓存分析
2011-10-12 21:36:36| 分类: Luence/Solr | 标签:solr httpcache |字号 订阅
If-Modified-Since和If-None-Match这两个header信息可以参考我的另一篇博客
有关Last-Modified 与 If-Modified-Since
要想Solr的httpCache缓存生效,需要修改solr的配置文件solrconfig.xml,因为solr的过滤器会做如下判断:HttpCacheHeaderUtil.setCacheControlHeader(config, resp, reqMethod);
if (config.getHttpCachingConfig().isNever304() ||
!HttpCacheHeaderUtil.doCacheHeaderValidation(solrReq, req, reqMethod, resp)){
这里是没有httpcache缓存要做的所有工作。
}
要缓存,首先让solr生成header信息,这个代码就是HttpCacheHeaderUtil.setCacheControlHeader里完成的,
代码如下:
if (Method.POST==method || Method.OTHER==method) {
return;
}
final String cc = conf.getHttpCachingConfig().getCacheControlHeader();
if (null != cc) {
resp.setHeader("Cache-Control", cc);
}
Long maxAge = conf.getHttpCachingConfig().getMaxAge();
if (null != maxAge) {
resp.setDateHeader("Expires", System.currentTimeMillis()
+ (maxAge * 1000L));
}
但是solr默认是没有启用的,需要改solrconfig配置文件,改动如下:
<httpCaching never304="true" >
<cacheControl>max-age=30, public</cacheControl>
</httpCaching>
<!--
<httpCaching lastModifiedFrom="openTime"
etagSeed="Solr">
<cacheControl>max-age=30, public</cacheControl>
</httpCaching>
-->
两个可以任选一个,如果两个都选,则第一个有效。
把这个httpcache的注释去掉就可以,solr在初始化时取cacheControl这个值的。上面代码CC就是cacheControl的值,
从上面代码可以看出,max-age的值写到header的Expires表示该资源的有效期,单位没秒。
public 表示可以所有的资源。如果cc的值为空的话,SOlr就不会生成header信息,导致在客户端下次请求时相关的header信息就位空。
config.getHttpCachingConfig().isNever304() 的值就是配置文件solrconfig.xml中
<httpCaching never304="true" /> 的值,默认是true,从上面的if判断可以看出,为true的话,就是不启用httpCache缓存。
所以要启用httpcache缓存,先把这个值改为false,这里改好了,solr就根据head头来判断是否要直接用httpcache了。这个就是在HttpCacheHeaderUtil.doCacheHeaderValidation里判断实现的.代码如下:
if (Method.POST==reqMethod || Method.OTHER==reqMethod) {
return false;
}
final long lastMod = HttpCacheHeaderUtil.calcLastModified(solrReq);
final String etag = HttpCacheHeaderUtil.calcEtag(solrReq);
resp.setDateHeader("Last-Modified", lastMod);
resp.setHeader("ETag", etag);
if (checkETagValidators(req, resp, reqMethod, etag)) {
return true;
}
if (checkLastModValidators(req, resp, lastMod)) {
return true;
}
从上面可以看出,如果是post请求,不会启用httpCache缓存,
lastMod 的值是索引最近修改时间,这里是根据取的是<httpCaching lastModifiedFrom="openTime" etagSeed="Solr">
里lastModifiedFrom的值来计算,当为opentime时,lastModifiedFrom为solr index的打开时间。如果没有,默认也是。
etagSeed的值是用来计算etag 的,根据etag的值生成一个唯一的值。并写会给客户端。
Solr SpellCheckComponent(拼写建议组件)实践与分析
2011-08-30 17:42:47| 分类: Luence/Solr |字号 订阅
此文为原创,转载请说明出处,下面为原文连接
原文链接:http://ronxin999.blog.163.com/blog/static/4221792020117304579589/
首先需要说明的一点是Solr 的组件类(Component)和handler 类的关系,组件是绑定在handler上的,即handler在调用他的两个方法prepare和process时,分别调用该handler上的组件,那么Component是怎么加到handler上去的呢,下面会讲到。
spellcheckComponent
Solr 查询中fq参数的解析原理
2011-07-16 21:59:00| 分类: Luence/Solr | 标签:solr fq solr过滤器 |字号 订阅
首先看Lucene进行索引查询的一个核心方法:IndexSearcher.java
public void search(Weight weight, Filter filter, Collector collector)
其中Weight是用来计算查询的权重并生成Scorer(这是一个集合迭代器),它一般由顶层的Query对象使用一个Seacher对象来创建(Query.createWeight(Searcher)),
Filter的作用是得到一个文档集,只有在这个集合内的文档才会返回,
Collector是原始查询结果的收集器。
Solr的查询就是基于Lucene的查询方式的,因此进行一次查询时就需要的对象与上面列出的相同。
核心的查询对象由Solr扩展为SolrIndexSearcher,但最终查询依然是调用IndexSearcher的search方法。
1、fq参数解析
QueryComponent.java的prepare方法中对参数进行解析
String[] fqs = req.getParams().getParams(CommonParams.FQ);
if (fqs!=null && fqs.length!=0) {
List filters = rb.getFilters();
if (filters==null) {
filters = new ArrayList();
rb.setFilters( filters );
}
for (String fq : fqs) {
if (fq != null && fq.trim().length()!=0) {
QParser fqp = QParser.getParser(fq, null, req);
filters.add(fqp.getQuery());
}
}
}
2、获取解析对象
由上面的代码可以看到filters这个集合中存放着所有fq参数解析得到的Query对象,哪一种QParser由fq的具体内容决定
QParserPlugin.java中可以看到所有的
public static final Object[] standardPlugins = {
LuceneQParserPlugin.NAME, LuceneQParserPlugin.class,
OldLuceneQParserPlugin.NAME, OldLuceneQParserPlugin.class,
FunctionQParserPlugin.NAME, FunctionQParserPlugin.class,
PrefixQParserPlugin.NAME, PrefixQParserPlugin.class,
BoostQParserPlugin.NAME, BoostQParserPlugin.class,
DisMaxQParserPlugin.NAME, DisMaxQParserPlugin.class,
ExtendedDismaxQParserPlugin.NAME, ExtendedDismaxQParserPlugin.class,
FieldQParserPlugin.NAME, FieldQParserPlugin.class,
RawQParserPlugin.NAME, RawQParserPlugin.class,
NestedQParserPlugin.NAME, NestedQParserPlugin.class,
FunctionRangeQParserPlugin.NAME, FunctionRangeQParserPlugin.class,
};
这里fq中使用frange本地参数的情况由FunctionRangeQParserPlugin来进行解析,在这个类中可以看到:
fq 的参数格式是这样的:{!frange l=1000 u=50000}
public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
return new QParser(qstr, localParams, params, req) {
ValueSource vs;
String funcStr;
public Query parse() throws ParseException {
funcStr = localParams.get(QueryParsing.V, null);
Query funcQ = subQuery(funcStr, FunctionQParserPlugin.NAME).parse();
if (funcQ instanceof FunctionQuery) {
vs = ((FunctionQuery)funcQ).getValueSource();
} else {
vs = new QueryValueSource(funcQ, 0.0f);
}
String l = localParams.get("l"); // l 表示小值的一端,API是这样说明的:the lower bound, optional
String u = localParams.get("u"); // u表示大的一端 API: the upper bound, optional)
boolean includeLower = localParams.getBool("incl",true); //如果incl为TRUE,则包含值I
boolean includeUpper = localParams.getBool("incu",true); //如果为TRUE,则包含值u
// TODO: add a score=val option to allow score to be the value
ValueSourceRangeFilter rf = new ValueSourceRangeFilter(vs, l, u, includeLower, includeUpper);
SolrConstantScoreQuery csq = new SolrConstantScoreQuery(rf);
return csq;
}
};
由此可以知道使用fq进行范围查询时所得到具体Query对象是SolrConstantScoreQuery的对象。
SolrConstantScoreQuery类相关问题,创建Scorer对象:
public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer) throws IOException {
return new ConstantScorer(similarity, reader, this);
}
其中ConstantScorer是内部类
ConstantScorer的迭代基础:
在其构造函数中:
DocIdSet docIdSet = filter instanceof SolrFilter ? ((SolrFilter)filter).getDocIdSet(w.context, reader) : filter.getDocIdSet(reader);
if (docIdSet == null) {
docIdSetIterator = DocIdSet.EMPTY_DOCIDSET.iterator();
} else {
DocIdSetIterator iter = docIdSet.iterator();
if (iter == null) {
docIdSetIterator = DocIdSet.EMPTY_DOCIDSET.iterator();
} else {
docIdSetIterator = iter;
}
}
由此可以ConstantScorer的迭代器起始就是这里的docIdSet的迭代器
docIdSet的迭代器有SolrFilter进行获取,之前已经看到这个SolrFilter起始就是ValueSourceRangeFilter
它的方法:
public DocIdSet getDocIdSet(final Map context, final IndexReader reader) throws IOException {
return new DocIdSet() {
public DocIdSetIterator iterator() throws IOException {
return valueSource.getValues(context, reader).getRangeScorer(reader, lowerVal, upperVal, includeLower, includeUpper);
}
};
}
实际的Scorer由DocValues来创建:
public ValueSourceScorer getRangeScorer(IndexReader reader, String lowerVal, String upperVal, boolean includeLower, boolean includeUpper)
它实际返回的是重写了matchesValue方法的ValueSourceScorer的一子类:
return new ValueSourceScorer(reader, this) {
@Override
public boolean matchesValue(int doc) {
float docVal = floatVal(doc);
System.out.println("Document id '" + doc + "' score = " + docVal);
return docVal >= l && docVal <= u;
}
};
回到ValueSourceScorer,我们可以发现这个迭代器是如何工作的:
private int doc = -1;
protected final int maxDoc;
public int nextDoc() throws IOException {
for (; {
doc++;
if (doc >= maxDoc) return doc = NO_MORE_DOCS;
if (matches(doc)) return doc;
}
}
也就是这个迭代器默认是匹配所有文档的,只是由重写它的部分方法来实现文档过滤。
3、使用解析到的Query对象
具体的查询时在SolrIndexSearcher中进行的,由以下方法开始:
public QueryResult search(QueryResult qr, QueryCommand cmd)
其中QueryResult和QueryCommand都是SolrIndexSearcher的内部类,分别包装了查询结果和查询条件相关内容。
fq解析得到的Query对象的List在QueryCommand中作为filterList成员变量来保存:
private List filterList;
具体到实际查询时(如果结果缓存中没有),Solr会先根据filter或filterList(filter和filterList不能同时都存在,否则报错)来先查询到一个文档集合作为过滤器:
DocSet filter = cmd.getFilter()!=null ? cmd.getFilter() : getDocSet(cmd.getFilterList());
其中getDocSet()方法负责根据fq的查询条件来查询到一个文档集,查询方式与普通的查询类似
该过滤器如果存在,那么就能到一个Lucene可用的Filter对象:
final Filter luceneFilter = filter==null ? null : filter.getTopFilter();
最后使用这个对象来进行查询:
super.search(query, luceneFilter, collector);
这个里面的query是查询参数中q以及其他相关参数(不包括fq)解析得到的Query对象
处理collector收集到的文档:
TopDocs topDocs = topCollector.topDocs(0, len);
maxScore = totalHits>0 ? topDocs.getMaxScore() : 0.0f;
nDocsReturned = topDocs.scoreDocs.length;
ids = new int[nDocsReturned];
scores = (cmd.getFlags()&GET_SCORES)!=0 ? new float[nDocsReturned] : null;
for (int i=0; i
ScoreDoc scoreDoc = topDocs.scoreDocs[i];
ids[i] = scoreDoc.doc;
if (scores != null) scores[i] = scoreDoc.score;
}
Solr 启动分析 从web.xml文件开始
2011-07-07 21:53:12| 分类: Luence/Solr | 标签:solr solr启动 web.xml |字号 订阅
web.xml配置文件
SolrDispatchFilter一个Filter,过滤所有的PATH
<filter-mapping>
<filter-name>SolrRequestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>SolrRequestFilter</filter-name>
<filter-class>org.apache.solr.servlet.SolrDispatchFilter</filter-class>
</filter>
SolrDispatchFilter.init – 加载CoreContainer,开始启动solr应用
谈CoreContainer类 – CoreContainer
a.实例化CoreContainer时会去寻找solr的home目 录,寻找的方法有两个:1.到WEB的环境变量里面去找KEY=java:comp/env/solr/home,2.通过 System.getProperty去找KEY=solr.solr.home,3.如果都找不到则使用默认的路径solr/
b.到 Filter的初始化参数里面找KEY=solrconfig-filename,如果没有则使用solr.xml,作为solr里包含core的配置文 件,
如果找到这个文件则加载CoreContainer,首先实例化一个SolrResourceLoader,可以参考接口 ResourceLoader的定义
InputStream openResource(String resource) – 获取文件的输入流
List<String> getLines(String resource) – 获取文件里KEY-VLAUE数据
Object newInstance(String cname, String... subpackages) – 获取指定类名和包路径的类的实例化
读取core配置文件solr.xml获取如下属性
solr/ @persistent
solr/ @sharedLib – 如果此项不是NULL则将这个所有core使用的公共JAR包目录,
将这个目录放入SolrResourceLoader的类加载目录的路径中
solr/property – 获取solr所有property里包含的KEY-VALUE,PUT到CoreContainer.containerProperties里去
solr/cores/core – 获取solr下的core,迭代solr/cores/core链表加载core
------
solr/cores/core/ @name – core的name
solr/cores/core/ @instanceDir – core的配置文件目录
solr/cores/core/ @config – core的配置文件名称,默认是solrconfig.xml
solr/cores/core/ @schema – core的schema文件名,默认是schema.xml
solr/cores/core/ @properties – core的property文件名,默认是conf/solrcore.properties
solr/cores/core/ @dataDir – core的存放索引文件的目录,默认是data/
solr/cores/core/property – core的KEY-VALUE数据,会把上面的信息都放到这个Properties中
根据以上信息对这个core实例化一个CoreDescriptor,再根据这个CoreDescriptor创建一个SolrCore
1.实例化一个SolrResourceLoader作为这个core的资源加载器,初始类加载地址为CoreContainer的类加载地址,
同时读取core的property文件将
读到KEY-VALUE和core.properties合并起来给这个资源加载器以后用
2.用这个ResourceLoader&core.configName实例化一个SolrConfig,在实例化中使用ResourceLoader
获取core.configName的文件流,再将这个文件流DOM化,再根据solrconfig.xml加载SolrConfig
------
config/lib – JAR包路径都加到SolrResourceLoader中
config/indexDefaults config/mainIndex – 实例化两个SolrIndexConfig对象,将这两个Node下的属性SolrIndexConfig bean化,里面包含的属性主要是lucene建索引需要用到的,比如 useCompoundFile,maxBufferedDocs,mergePolicyInfo。indexDefaults作为mainIndex 的默认选项
config/mainIndex/reopenReaders – reopenReaders : updateHandler每次commit后会跟新solrCore的当前的reader,如果这个值是true则获取当前的 reader.reopen,如果是false则Index.open获取索引的reader
config/query/maxBooleanClauses –booleanQueryMaxClauseCount : solrCore用来设置BooleanQuery子条件最多个数
config/query/boolTofilterOptimizer/ @enabled - filtOptEnabled
config/query/boolTofilterOptimizer/ @cacheSize - filtOptCacheSize
config/query/boolTofilterOptimizer/ @threshold –filtOptThreshold
config/query/useFilterForSortedQuery - useFilterForSortedQuery
config/query/queryResultWindowSize - queryResultWindowSize
config/query/queryResultMaxDocsCached - queryResultMaxDocsCached
config/query/enableLazyFieldLoading –enableLazyFieldLoading
------
core的四个cache配置加载,针对cache配置节点需要加载如下信息
1.node的名称,比如filterCache
2. @class – 得到这个cache的类名,solr提供了两种FastLRUCache&LRUCache
3. @regenerator – 得到在预热新的searcher时旧的searcher的cache添加到新的searcher的行为
4.再让ResourceLoader根据 @class去找到cache的class,根据 @regenerator加载其实例
config/query/filterCache - filterCacheConfig
config/query/queryResultCache - queryResultCacheConfig
config/query/documentCache - documentCacheConfig
config/query/fieldValueCache – fieldValueCacheConfig
在加载完这四个cacheConfig后,如果有哪个没有配置regenerator则会为其设置一个默认的,具体逻辑见SolrIndexSearcher
------
config/mainIndex/unlockOnStartup – unlockOnStartup : solrCore用来首次启动是否清空write.lock锁
config/query/useColdSearcher – useColdSearcher : solrCore当前的searcher引用为null的话且有
其他线程在加载的话来自搜索组件的获取searcher的线程会阻塞等其加载好,
如果这个值设置为TRUE则其加载好searcher后这些线程就可以立即返回,
否则要等到这个新建的searcher经过热身后才会返回
config/dataDir – dataDir : 索引存放目录
config/query/cache – userCacheConfigs
config/HashDocSet/ @loadFactor – hashSetInverseLoadFactor
config/HashDocSet/ @maxSize – hashDocSetMaxSize
config/query/maxWarmingSearchers – maxWarmingSearchers : solrCore用来限制其正在进行warm操作的
IndexSearcher的个数,warm操作即将其前一个IndexSearcher的cache复制给自己
加载插件 – 根据PATH读取DOM里面所有的NODE,对于每个NODE实例化一个PluginInfo,主要是提取如下信息
a.type -> TagName,例如requestHandler
b.name -> @name 插件名称
c.className -> @class 类名
d.initArgs -> 为NamedListInitializedPlugin准备的,当实例化插件时,如果是NamedListInitializedPlugin会将这个
作为init函数的参数传入做初始化操作
e.attributes –> 属性MAP化
config/requestHandler : solrCore用来配置RequestHandler,转发给请求指明的handler去处理
config/queryParser : solrCore用来配置QueryParser
config/queryResponseWriter : solrCore用来配置QueryResponseWriter,如果查询有指定输出格式则到配置的QueryResponseWriter里查,没有则用默认的
config/searchComponent : solrCore用来配置SearchComponent给RequestHandler用的
config/listener : solrCore用来配置相关事件LISTENER
config/directoryFactory : solrCore用来配置DirectoryFactory
config/mainIndex/deletionPolicy : solrCore用来配置IndexDeletionPolicy
config/indexReaderFactory : solrCore用来配置IndexReaderFactory
config/updateRequestProcessorChain : solrCore用来配置UpdateRequest处理责任链
加载UpdateHandlerInfo,使用updateHandler/ @class,updateHandler/autoCommit/maxDocs
updateHandler/autoCommit/maxTime,updateHandler/commitIntervalLowerBound实例化一个UpdateHandlerInfo :
solrCore用来配置这个core的UpdateHandler
------
3.实例化此core的IndexSchema,使用core schema配置文件schema.xml初始化IndexSchema,
IndexSchema是一个组织 Document.Field类型的数据结构,里面主要分为两个部分一是对类型的定义,
二是Document里面包括哪些Field,需指定这些Field的名称,类型;同时还可以指定默认的Field,设置唯一键,
设置BooleanQuery的默认关系AND|OR a.schema/types/fieldtype | schema/types/fieldType -
一个Node实例化一个FieldType根据 @type到ResourceLoader里加载FieldType,注入 @name,获取./analyzer[ @type='query']作为此FiledType的query分词器,获取./analyzer[not( @type)] | ./analyzer[ @type='index']作为此FieldType的index分词器,如果有任何一方没有配置则index&query使用同一个分词器。将Node的属性除了 @name,
@type的其他属性作用到FieldType上b.schema/fields/field | schema/fields/dynamicField - 一个Node实例化一个SchemaField
根据 @type指定的类型到a.加载对应的FieldType. @name获取对应的FieldType,处理Index,Store,TermVector相关的属性这些SchemaField作为这个core下DOC的Field的定义
c.schema/similarity - 加载算分器
d.schema/defaultSearchField/text() – 默认查询字段
e.schema/solrQueryParser/ @defaultOperator – BooleanQuery的默认关系
f.schema/uniqueKey/text() – 唯一键
4.根据core的SolrConfig, CoreDescriptor, IndexSchema创建SolrCore
--------
a.获取solrConfig.maxWarmingSearchers –> maxWarmingSearchers
获取solrConfig.booleanQueryMaxClauseCount –> BooleanQuery.setMaxClauseCount
--------
b.获取solrConfig里配置的SolrEventListener插件,属性 @event=firstSearcher的放入链表firstSearcherListeners
属性 @event=newSearcher的放入链表newSearcherListeners
--------
c.获取solrConfig里配置的IndexDeletionPolicy插件,使用ResourceLoader加载类,如果是NamedListInitializedPlugin
使用其initArgs初始化,如果没配置使用SolrDeletionPolicy作为此core的索引文件删除决策者
--------
d.获取solrConfig里配置的DirectoryFactory插件,实例化->init(initArgs),如果没有使用StandardDirectoryFactory
获取solrConfig里配置的IndexReaderFactory插件,实例化->init(initArgs),如果没有使用StandardIndexReaderFactory
如果这个目录是JVM级别第一次打开&目录存在&solrConfig.unlockStartup则去看目录下的write.lock
有没有因为进程的突然死亡而没释放掉,如果没有释放掉则释放这个锁
如果目录不存在则实例化一个SolrIndexWriter,再关闭它,这样做的目的是创建这个目录
--------
e. 获取solrConfig里配置的QueryResponseWriter插件,这类插件是定义查询输出格式的,
solr目前支持 xml,json,python,php,javabin,raw,ruby等输出格式,加载完solrConfig定义的ResponseWriter
后将solr支持的但没出现在solrconfig.xml里面的ResponseWriter添加到responseWriters中,
如果 solrconfig.xml没设置默认的ResponseWriter则用xml做默认格式
--------
f.获取solrConfig里配置的QueryParser插件,这类插件是定义query语句解析用的,solr提供的几种解析器接下来会提到
将solrconfig.xml定义的查询语法解析器和solr提供的解析器放入qParserPlugins中
--------
g.获取solrConfig里配置的SearchComponent插件,放入searchComponents中,solr提供了几个SearchComponent:
QueryComponent FacetComponent MoreLikeThisComponent HighlightComponent StatsComponent DebugComponent
如果solrConfig没有配置这些默认的则主动注入
--------
h.获取solrConfig里配置的UpdateRequestProcessorChain插件,这个插件作为此core处理UpdateRequest的责任链,如果
没有配置是默认的责任链是LogUpdateProcessorFactory -> RunUpdateProcessorFactory
--------
i.实例化RequestHandlers-此core的solr请求处理器SolrRequestHandler管理者,用solrcofig配置文件初始化
SolrRequestHandler的启动分为两种,一种是在系统启动阶段就初始化好;另一种是lazy启动模式,只有在第一次调用handleRequest时才去实例化&初始化,有属性 @startup=lazy
遍历SolrRequestHandler节点,对于lazy型的初始化LazyRequestHandlerWrapper(core, className, initArgs)
对于非lazy型的使用ResourceLoader加载实例再调用初始化函数init(initArgs)|init(PluginInfo)-PluginInfoInitialized
将创建的name -> RequestHandler映射注册到RequestHandlers.handlers上
SolrRequestHandler初始化主要做的事是初始化其几个属性: NameList-initArgs
SolrParams-defaults,appends,invariants
比较典型的两个RequestHandlers是处理查询请求的SearchHandler和处理更新请求的Handler
--------
j.打开一个SolrIndexSearcher,同时根据solrconfig.updateHandlerInfo实例化UpdateHandler,默认为
DirectUpdateHandler2
--------
k. 在将全部需要加载的类都加载完后,solrCore还需要通知SolrCoreAware类对象core需要的类都实例化完 了,SolrCoreAware可能需要做的初始化操作可以做了,solrCore会管理一个SolrCoreAware的链表,在使用 ResourceLoader加载对象时判断类是否是SolrCoreAware,如果是加入到链表中等着回调。重点关注SearchHandler-用 来处理查询请求的Handler,其在回调函数inform里
主要的做的事是:
SearchHandler是SolrRequestHandler的一种,用来处理搜索请求的,前面有提到过SearchComponent,之前core
已经把用户注入的SearchComponent和它支持的SearchComponent都加载好了,这些SearchComponent是
SearchHandler在处理搜索请求时干活的组件,如果这个Handler自己配置了组件则使用它配置的组件,如果没有配置则使用solr默认的搜索组件
------
加载完core后将core @name -> core的映射PUT到CoreContainer.cores上,CoreContainer类加载完成
Solr 获取searcher实例分析
2011-07-07 12:51:07| 分类: Luence/Solr | 标签:solr search registeredsearcher solr查询 |字号 订阅
每一个搜索请求都会持有一个searcher的引用,而不是创建一个新的searcher,处理完后会释放掉这个引用。
Solr在初始化化时,通过SolrCore核心类要做很多的初始化工作,包过读取solrconfig.xml配置文件里的内容,代码如下:
booleanQueryMaxClauseCount(); //设置布尔查询最多个数。
initListeners(); //读取配置文件的search实例的监听器。
initDeletionPolicy();
initIndex();
initWriters();
initQParsers();
initValueSourceParsers();
this.searchComponents = loadSearchComponents();
// Processors initialized before the handlers
updateProcessorChains = loadUpdateProcessorChains();
reqHandlers = new RequestHandlers(this);
reqHandlers.initHandlersFromConfig( solrConfig );
highlighter = initHighLighter();
// Handle things that should eventually go away
initDeprecatedSupport();
loadSearchComponents方法就是初始化indexSearch实例。详细说明如下:
getSearcher – (forceNew, returnSearcher, waitSearcher-Futures)
关注solr全局三个点调用getSearcher函数 : solrCore初始化时(false, false, null),QueryComponent处理查询
请求时(false, true, null),UpdateHandler在处理commit请求时(true, false, new Future[1])
---------
1.solrCore初始化时
根据solrconfig配置的IndexReaderFactory&DirectoryFactory获取索引的IndexReader,再使用这个reader
封装一个SolrIndexReader,再使用这个SolrIndexReader封装一个RefCounted(searcher的引用计数器,当搜索
组件获取一个组件后引用++,用完后调用close引用--,当引用数为0时将这个引用从core管理的一个当前被使用的
searcher的链表移除,同时调用searcher.close回收资源),将这个引用添加到core管理的一个当前被使用的searcher
的链表里如果firstSearcherListeners不为空则回调这些监听器,这个回调是交给core的一个newSingleThreadExecutor去
做的,再往这个线程池里添加一个任务:将这个RefCounted设置为core当前最新的searcher的引用计数器
最后返回null,因为returnSearcher=false
在solrCore初始化时这样做的主要目的是在初始化时就加载好IndexSearcher,搜索请求来了之后能立即返回,
而不必等待加载IndexSearcher
---------
2.QueryComponent处理查询请求时
由于core当前最新的searcher的引用计数器不为null且这个获取IndexSearcher的请求不是强制要求获取最新的,且
returnSearcher=true故直接返回core当前最新的searcher的引用计数器,且这个引用计数器做++
这里面还有段当前searcher的引用计数器为null的逻辑,但是没有发现有什么情况会导致这种情况发生故不累述了
---------
3.UpdateHandler在处理commit请求时
首先到core管理的一个当前被使用的searcher的链表里获取目前最新的searcher;同时会加载索引目录下的
index.properties文件(如果存在的话),拿到KEY=’index’的值,其指明目前索引的存放地方;如果获取的目录和当前
最新的searcher使用的目录一致且solrConfig.reopenReaders为true则获取通过searher.reader.reopen获取
最新的reader -> 封装成searcher,否则直接IndexReader.open获取reader。
获取到searcher后的一段逻辑[RefCount封装,添加到searchers链表]和core初始化时是一样的,接下来的逻辑是
如果solrConfig.useColdSearcher为TRUE其当前searcher的引用为null-导致来自QueryComponent的请求阻塞
[现在还没发现什么情况会导致searcher的引用为null]
立即将这个新的searcher的引用设置为core当前最新的searcher的引用计数器,这样来自QueryComponent的请求
拿到这个引用后返回,当时这时这个新建的searcher是没有经过其前一个searcher的cache热身的,同时这样会导致这个
新建的searcher不会进行热身活动
如果solrConfig.useColdSearcher为FALSE则会往线程池里添加一个热身的任务
如果newSearcherListeners不为空则回调这些监听器,也是给线程池的任务
最后如果先前没有做将新的searcher的引用设置为core当前最新的searcher的引用计数器的行为的话,则往线程池添加
一个任务 – 将新的searcher的引用设置为core当前最新的searcher的引用计数器
最后返回null,因为returnSearcher=false
Solr Cache使用介绍及分析
2011-07-06 16:25:58| 分类: Luence/Solr | 标签:solr solr缓存 solrcache |字号 订阅
原文:http://www.cnblogs.com/wycg1984/archive/2010/08/02.html
本文将介绍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接口主要方法的注释:
* 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) */publicObjectinit(Mapargs,Objectpersistence, CacheRegenerator regenerator); /** :TODO: copy from Map */publicintsize(); /** :TODO: copy from Map */publicObjectput(Objectkey,Objectvalue);/** :TODO: copy from Map */ publicObjectget(Objectkey);/** :TODO: copy from Map */publicvoidclear();/** * 新创建的SolrIndexSearcher autowarm方法,该方法的实现就是遍历已有cache中合适的 * 范围(因为通常不会把旧cache中的所有项都重新加载一遍),对每一项调用regenerator的 * regenerateItem方法来对searcher加载新cache项。 */voidwarm(SolrIndexSearcher searcher, SolrCache old)throwsIOException; /** Frees any non-memory resources */publicvoidclose();}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 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为documentid集合(类型为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中配置了<useFilterForSortedQuery/>,
那么如果查询有filter(此filter是一需要过滤的DocSet,而不是fq,我未见得它有什么用),
则使用filterCache。下面是filterCache的配置示例:
<!-- Internal cache used by SolrIndexSearcher for filters (DocSets),unordered sets of *all* documentsthat match a query.When a new searcher is opened, its caches may be prepopulatedor "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.--> <filterCacheclass="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 ofdocument ids (DocList) based on a query, a sort, and the rangeof documents requested.--><queryResultCacheclass="solr.LRUCache"size="16384"initialSize="4096"autowarmCount="1024"/>缓存的key是个什么结构呢?就是下面的类(key的hashcode就是QueryResultKey的成员变量hc):
publicQueryResultKey(Query query, List<Query>filters, Sort sort,intnc_flags) { this.query=query; this.sort=sort; this.filters=filters; this.nc_flags=nc_flags; inth=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,ah^=(h<<8)|(h>>>25); // reversible hashif(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 searchis requested, a superset of the requested numberof document idsare collected. For example, of a search for a particular queryrequests matching documents 10 through 19, and queryWindowSize is 50,then documents 0 through 50 will be collected and cached. Any furtherrequests 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).--><documentCacheclass="solr.LRUCache"size="16384"initialSize="16384"/>5、User/Generic CachesSolr支持自定义Cache,只需要实现自定义的regenerator即可,下面是配置示例:
<!-- Example of a generic cache. These caches may be accessed by namethrough 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 implementationof 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 Lucene FieldCachelucene中有相对低级别的FieldCache,Solr并不对它做管理,所以,lucene的FieldCache还是由lucene的IndexSearcher来搞。
7、autowarm
上面有提到autowarm,autowarm触发的时机有两个,一个是创建第一个Searcher时(firstSearcher),一个是创建个新Searcher(newSearcher)来代替当前的Searcher。在Searcher提供请求服务前,Searcher中的各个Cache可以
solrConfig.filterCacheConfig.setRegenerator(newCacheRegenerator(){publicbooleanregenerateItem
做warm处理,处理的地方通常是SolrCache的init方法,而不同cache的warm策略也不一样。
1)filterCache:filterCache注册了下面的CacheRegenerator,就是由旧的key查询索引得到新值put到新cache中。(SolrIndexSearcher newSearcher, SolrCache newCache, SolrCache oldCache,ObjectoldKey,ObjectoldVal) throwsIOException{newSearcher.cacheDocSet((Query)oldKey,null,false);returntrue;}}); 2)queryResultCache:queryResultCache的autowarm不在SolrCache的init(也就是说,不是去遍历已 有的queryResultCache中的query key执行查询),而是通过SolrEventListener接口的void newSearcher(SolrIndexSearcher newSearcher, SolrIndexSearcher currentSearcher)方法,来执行配置中特定的query查询,达到显示的预热lucene FieldCache的效果。 queryResultCache的配置示例如下: <listenerevent="newSearcher"class="solr.QuerySenderListener"><arrname="queries"><!-- seed common sort fields --><lst><strname="q">anything</str><strname="sort">name desc price desc populartiy desc</str></lst></arr> </listener><listenerevent="firstSearcher"class="solr.QuerySenderListener"><arrname="queries"> <!-- seed common sort fields --><lst><strname="q">anything</str><strname="sort"> name desc, price desc, populartiy desc</str></lst><!-- seed common facets and filter queries --> <lst><strname="q">anything</str><strname="facet.field">category</str> <strname="fq">inStock:true</str><strname="fq">price:[0 TO 100]</str></lst></arr></listener> 3)documentCache:因为新索引的document id和索引文档的对应关系发生变化,所以documentCache没有warm的过程, 落得白茫茫一片真干净。尽管autowarm很好,也要注意autowarm带来的开销,这需要在实际中检验其warm的开销, 也要注意Searcher的切换频率,避免因为warm和切换影响Searcher提供正常的查询服务。 8、参考文章http://wiki.apache.org/solr/SolrCaching
在solr建立索引的时候,如果你提交的doc中没有 id 这个Field,结果Solr在建立索引时候出现如下错误:
org.apache.solr.common.SolrException: Document [null] missing required field: id
主要是因为Solr 的solrconfig配置文件中定义了<uniqueKey>id</uniqueKey>,默认了ID 是唯一的。如果你的索引字段不需要ID,就可以把这个改掉,比如你是博客应用,就可以改为 <uniqueKey>url</uniqueKey> 当然前题是先要定义好url Field字段。如下:<field name="url" type="string" indexed="true" stored="true"/>
solr 查询参数说明
2011-07-04 15:30:51| 分类: Luence/Solr | 标签:solr 参数 |字号 订阅
- q - 查询字符串,必须的。
- fl - 指定返回那些字段内容,用逗号或空格分隔多个。
- start - 返回第一条记录在完整找到结果中的偏移位置,0开始,一般分页用。
- rows - 指定返回结果最多有多少条记录,配合start来实现分页。
- sort - 排序,格式:sort=<field name>+<desc|asc>[,<field name>+<desc|asc>]… 。示例:(inStock desc, price asc)表示先 “inStock” 降序, 再 “price” 升序,默认是相关性降序。
- wt - (writer type)指定输出格式,可以有 xml, json, php, phps, 后面 solr 1.3增加的,要用通知我们,因为默认没有打开。
- fq - (filter query)过虑查询,作用:在q查询符合结果中同时是fq查询符合的,例如:q=mm&fq=date_time:[20081001 TO 20091031],找关键字mm,并且date_time是20081001到20091031之间的。官方文档:http://wiki.apache.org/solr/CommonQueryParameters#head-6522ef80f22d0e50d2f12ec487758577506d6002
不常用
- q.op - 覆盖schema.xml的defaultOperator(有空格时用"AND"还是用"OR"操作逻辑),一般默认指定
- df - 默认的查询字段,一般默认指定
- qt - (query type)指定那个类型来处理查询请求,一般不用指定,默认是standard。
其它
- indent - 返回的结果是否缩进,默认关闭,用 indent=true|on 开启,一般调试json,php,phps,ruby输出才有必要用这个参数。
- version - 查询语法的版本,建议不使用它,由服务器指定默认值。
http://localehost:8089/solr/select?indent=on&version=2.2&q=solr&fq=&start=0&rows=10&fl=*%2Cscore&qt=dismax&wt=standard&debugQuery=on&explainOther=&hl=on&hl.fl=title
本方案中,Solr作为处理搜索结果的源和入口,有效的减轻对Nutch的搜索负担,让Nutch负责她最擅长的工作:抓取(crawling)和提取(extracting)内容。使用Solr作为搜索后端,换句话说,就是允许使用所有Solr Server的高级特性,诸如:查询拼写检查(spell-check),搜索提醒(suggestion),数据复制(data-replication),查询缓存等等。
Nutch和Solr的安装
首先下载我们需要的软件,Apache Solr 和 Nutch。
1、从官方下载Solr Version 1.3.0
2、解压Solr安装包。
3、下载Nutch Version 1.0
4、解压Nutch安装包。
5、配置Solr
简单起见,我们以Solr Example中的配置为基础
a、从apache-nutch-1.0/conf拷贝Nutch Schema到apache-solr-1.3.0/example/solr/conf目录下,覆盖掉已经存在的。
我们希望允许Solr为搜索结果创建摘要,因此我们需要存储内容以便能够索引它。
b、调整schema.xml,以便"content"字段的"stored"属性等于true。
<field name="content" type="text" stored="true" indexed="true"/>
另外,我们希望能够容易的调整查询的关联度,因此这里创建一个新的请求处理器(request handler)叫dismax。
c、打开apache-solr-1.3.0/example/solr/conf/solrconfig.xml文件,把下面这段粘贴进去
<requestHandler name="/nutch" class="solr.SearchHandler" >
<lst name="defaults">
<str name="defType">dismax</str>
<str name="echoParams">explicit</str>
<float name="tie">0.01</float>
<str name="qf">
content^0.5 anchor^1.0 title^1.2
</str>
<str name="pf">
content^0.5 anchor^1.5 title^1.2 site^1.5
</str>
<str name="fl">
url
</str>
<str name="mm">
2<-1 5<-2 6<90%
</str>
<int name="ps">100</int>
<bool hl="true"/>
<str name="q.alt">*:*</str>
<str name="hl.fl">title url content</str>
<str name="f.title.hl.fragsize">0</str>
<str name="f.title.hl.alternateField">title</str>
<str name="f.url.hl.fragsize">0</str>
<str name="f.url.hl.alternateField">url</str>
<str name="f.content.hl.fragmenter">regex</str>
</lst>
</requestHandler>
6、启动Solr
cd apache-solr-1.3.0/example
java -jar start.jar
7、配置Nutch
a、打开apache-nutch-1.0/conf下的nutch-site.xml,用下面的内容(我们制定了蜘蛛的名称,激活插件,限制单机一次运行抓取的最大URL数为100)替换:
<?xml version="1.0"?>
<configuration>
<property>
<name>http.agent.name</name>
<value>nutch-solr-integration</value>
</property>
<property>
<name>generate.max.per.host</name>
<value>100</value>
</property>
<property>
<name>plugin.includes</name>
<value>protocol-http|urlfilter-regex|parse-html|index-(basic|anchor)|query-(basic|site|url)|response-(json|xml)|summary-basic|scoring-opic|urlnormalizer-(pass|regex|basic)</value>
</property>
</configuration>
b、打开apache-nutch-1.0/conf下的regex-urlfilter.txt,用下面的内容替换:
-^(https|telnet|file|ftp|mailto):
# skip some suffixes
-\.(swf|SWF|doc|DOC|mp3|MP3|WMV|wmv|txt|TXT|rtf|RTF|avi|AVI|m3u|M3U|flv|FLV|WAV|wav|mp4|MP4|avi|AVI|rss|RSS|xml|XML|pdf|PDF|js|JS|gif|GIF|jpg|JPG|png|PNG|ico|ICO|css|sit|eps|wmf|zip|ppt|mpg|xls|gz|rpm|tgz|mov|MOV|exe|jpeg|JPEG|bmp|BMP)$
# skip URLs containing certain characters as probable queries, etc.
-[?*!@=]
# allow urls in foofactory.fi domain
+^http://([a-z0-9\-A-Z]*\.)*lucidimagination.com/
# deny anything else
-.
8、创建一个种子列表(初始化的URL列表)
mkdir urls
echo "http://www.haoguoliang.com/" > urls/seed.txt
9、将种子URL列表导入Nutch的crawldb(注意在nutch文件夹下执行)
bin/nutch inject crawl/crawldb urls
10、生成获取(fetch)列表,以便获取和分析内容
bin/nutch generate crawl/crawldb crawl/segments
以上命令在crawl/segments目录下生成了一个新的segment目录,里边存储了抓到的URLs,下边的指令里,我们需要最新的segment目录作为参数,存储到环境变量SEGMENT里:
export SEGMENT=crawl/segments/`ls -tr crawl/segments|tail -1`
现在,启动抓取程序真正开始抓取内容
bin/nutch fetch $SEGMENT -noParsing
接下来我们分析、解析抓取下来的内容
bin/nutch parse $SEGMENT
更 新Nutch crawldb,updatedb命令会存储以上两步抓取(fetch)和分析(parse)最新的segment而得到的新的URLs到Nutch crawldb,以便后续的继续抓取,除了URLs之外,Nutch也存储了相应的页面内容,防止相同的URLs被反反复复的抓取。
bin/nutch updatedb crawl/crawldb $SEGMENT -filter -normalize
到此,一个完整的抓取周期结束了,你可以重复步骤10多次以便可以抓取更多的内容。
11、创建超链库
bin/nutch invertlinks crawl/linkdb -dir crawl/segments
12、索引所有segments中的内容到Solr中
bin/nutch solrindex http://127.0.0.1:8983/solr/ crawl/crawldb crawl/linkdb crawl/segments/*
现在,所有Nutch抓取的内容已经被Solr索引了,你可以通过Solr Admin执行查询操作了
http://127.0.0.1:8983/solr/admin