|
经常有一种情景是这样的:我们索引了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 = ScoreUtils.getNewsScoreFactor(now, times);//获取每个记录的时间衰减因子
}
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 = daysDampingFactor[i - 1] * demoteboost;
}
for (int i = 7; i < 31; i++) {
daysDampingFactor = daysDampingFactor[i / 7 * 7 - 1]
* demoteboost;
}
for (int i = 31; i < daysDampingFactor.length; i++) {
daysDampingFactor = 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 = 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
这样在搜索的时候就可使用了{!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中普通查询和日期衰减查询的区别就是:构造的查询条件不一样而已。。。 |
|
|