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

来源:转载


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

下面给出2种在solr中使用日期衰减的方法
比如我们的索引中的时间字段是time,正常查询是title:哈哈 keyword:哈哈,
1、使用已有的各种functionQuery的组合
solr中日期衰减的查询方式则是:{!boost b=recip(ms(NOW/HOUR,time),3.16e-11,1,1)}title:哈哈 keyword:哈哈
前面这个式子的含义可以去查询solr wiki:http://wiki.apache.org/solr/FunctionQuery#What_is_a_Function.3F

这个方式,时间的衰减比较平缓,比如昨天的权重是0.999,前天是0.998,一年前的今天是0.5.。。。。。
如果我们需要一个时间衰减比较剧烈的方式,则需要自定义了。
2、自定义ValueSource:实现FieldCacheSource
这里我们以lucene4.1为例(各个版本的代码有所偏差,需要根据情况实现),大致原理是:给每个时间设置一个时间衰减因子,然后把文档的相关度乘上时间因子就是最后得分。
2.1和2.3中的实现方式,在得到相关度以后,每次搜索,都会获取所有文档的时间字段,并计算时间权重值。这在效率上是比较慢的,数据在千万级别的时候还可接受,更多的数据则会比较慢。
所以第3部分提供了这个思路的另一个实现方式,它只会计算搜索结果中的文档的时间权重,大大降低了时间。
2.1 先实现是 一个ValueSource。

import java.io.IOException;import java.util.Map;import org.apache.lucene.index.AtomicReaderContext;import org.apache.lucene.queries.function.FunctionValues;import org.apache.lucene.queries.function.valuesource.FieldCacheSource;public class DateFunction extends FieldCacheSource { private static final long serialVersionUID = 6752223682280098130L; private static long now; public DateFunction(String field) { super(field); now = System.currentTimeMillis(); } @Override public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException { long[] times = cache.getLongs(readerContext.reader(), field, false);//获取各个记录中的时间字段毫秒数 final float[] weights = new float[times.length]; for (int i = 0; i < times.length; i++) { weights[i] = ScoreUtils.getNewsScoreFactor(now, times[i]);//获取每个记录的时间衰减因子 } return new FunctionValues() {//返回 @Override public float floatVal(int doc) { return weights[doc]; } @Override public int intVal(int doc) { return (int) weights[doc]; } @Override public String toString(int doc) { return description() + '=' + intVal(doc); } }; }}

其中用到的scoreutils定义如下:

public class ScoreUtils { private static float[] daysDampingFactor = new float[32]; private static float demoteboost = 0.5f; static { daysDampingFactor[0] = 1; for (int i = 1; i < 7; i++) { daysDampingFactor[i] = daysDampingFactor[i - 1] * demoteboost; } for (int i = 7; i < 31; i++) { daysDampingFactor[i] = daysDampingFactor[i / 7 * 7 - 1] * demoteboost; } for (int i = 31; i < daysDampingFactor.length; i++) { daysDampingFactor[i] = daysDampingFactor[i / 31 * 31 - 1] * demoteboost; } } private static float dayDamping(int delta) { return delta < daysDampingFactor.length ? daysDampingFactor[delta] : daysDampingFactor[daysDampingFactor.length - 1]; } public static float getNewsScoreFactor(long now, long time) { float factor = 1; int day = (int) (time / MiscConstants.DAY_MILLIS); int nowDay = (int) (now / MiscConstants.DAY_MILLIS); if (day < nowDay) { factor = dayDamping(nowDay - day); } else if (day > nowDay) { factor = Float.MIN_VALUE; } else if (now - time <= MiscConstants.HALF_HOUR_MILLIS && now >= time) { factor = 2; } return factor; } public static float getNewsScoreFactor(long time) { long now = System.currentTimeMillis(); return getNewsScoreFactor(now, time); }}class MiscConstants { /** 24x60x60x1000 */ public static final long DAY_MILLIS = 86400000; /** 24x60x60x1000 */ public static final long DAY_SECONDS = 86400; /** 60x1000 */ public static final int MINUTE_MILLIS = 60000; /** 60x1000 */ public static final int HALF_HOUR_MILLIS = 1800000; /** 60x1000 */ public static final int MINUTE_SECONDS = 60;}

2.2 如果是在lucene中使用,则在正常的normalQuery基础上,包装一下即可,如下:
ValueSourceQuery dateBooster = new ValueSourceQuery(new DateFieldSource("ptime")); 
CustomScoreQuery dateScoreQuery = new CustomScoreQuery(normalQuery, dateBooster);
2.3 如果是在solr中使用个,还需要实现valuesourcepaser

import org.apache.lucene.queries.function.ValueSource;import org.apache.solr.common.util.NamedList;import org.apache.solr.search.FunctionQParser;import org.apache.solr.search.SyntaxError;import org.apache.solr.search.ValueSourceParser;public class DateSourceParser extends ValueSourceParser { @Override public void init(NamedList namedList) { } @Override public ValueSource parse(FunctionQParser fp) throws SyntaxError { return new DateFunction("ptime");// 被自定义排序的字段 }}

并且要在solrconfig.xml的config标签中定义这个parser
<valueSourceParser name="dateDeboost" class="org.netease.solr.custom.DateSourceParser" />
这样在搜索的时候就可使用了{!boost b=dateDeboost()}title:哈哈 keyword:哈哈
ps:这里还支持参数;不用参数的时候dateDeboost(),这样调用就可以了。使用参数的时候dateDeboost(param),fqp.parseArg()可以获取参数。这样就可更自由的控制一下逻辑。

3、自定义ValueSource:重用ValueSource
阅读solr的代码后,发现solr中的function query的实现更优雅。
这里记录了solr自定义的各种函数的定义org.apache.solr.search.ValueSourceParser。
其实思路就是不再逐个记录的遍历,主要区别是getValues方法中的实现。具体实现如下:
3.1 实现一个valuesource

import java.io.IOException;import java.util.Map;import org.apache.lucene.index.AtomicReaderContext;import org.apache.lucene.queries.function.FunctionValues;import org.apache.lucene.queries.function.ValueSource;import org.apache.lucene.queries.function.docvalues.FloatDocValues;import org.apache.lucene.search.IndexSearcher;public class DateFunction extends ValueSource { protected final ValueSource source; public DateFunction(ValueSource source) { this.source = source; } @Override public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException { final FunctionValues vals = source.getValues(context, readerContext); return new FloatDocValues(this) { @Override public float floatVal(int doc) { long ptime = vals.longVal(doc); return ScoreUtils.getNewsScoreFactor(ptime); } }; } @Override public void createWeight(Map context, IndexSearcher searcher) throws IOException { source.createWeight(context, searcher); } @Override public String description() { return "This is org.sling.DateFunction."; } @Override public int hashCode() { return source.hashCode(); } @Override public boolean equals(Object o) { if (!(o instanceof DateFunction)) return false; DateFunction other = (DateFunction) o; return source.equals(other.source); }}

其中scoreutils的定义还是和上面一样。
3.2 在solr中使用
import org.apache.lucene.queries.function.ValueSource;import org.apache.solr.common.util.NamedList;import org.apache.solr.search.FunctionQParser;import org.apache.solr.search.SyntaxError;import org.apache.solr.search.ValueSourceParser;public class DateSourceParser extends ValueSourceParser {    @Override    public void init(NamedList namedList) {    }    @Override    public ValueSource parse(FunctionQParser fp) throws SyntaxError {        //ValueSource不能获取两次。所以fp.parseValueSourceList()和fp.parseValueSource()只能用一个        ValueSource source = fp.parseValueSource();//获取这个ValueSource,并在一个sercher中重用它        return new DateFunction(source);    }}
3.3在lucene中使用
读一下fp.parseValueSource()这部分代码,可以发现,其实这也是用了lucene中的一些类。下面直接给出实现吧

ValueSource valueSource = new LongFieldSource(timeField);FunctionQuery scoreField = new FunctionQuery(new DateFunction(valueSource));CustomScoreQuery dateScoreQuery = new CustomScoreQuery(query, scoreField);// TopDocs top = indexSearcher.search(query, 5);//普通查询TopDocs top = indexSearcher.search(dateScoreQuery, 5);//日期衰减查询ScoreDoc[] scoreDocs = top.scoreDocs;

可以发现,在lucene中普通查询和日期衰减查询的区别就是:构造的查询条件不一样而已。。。



分享给朋友:
您可能感兴趣的文章:
随机阅读: