利用Solr RankQuery对查询结果进行打散处理
背景
公司新上了一个展示优惠打折商品列表的页面,由于参与活动的打折商品非常多,而打折频道页面商品展位有限。产品经理提出了这样一个需求,需要根据用户偏好(品牌,价格)作为前提的条件下,将首页内容显示具有一定的随机性。
这是一个非常好的需求,因为首页列表随机打散的需求具有普遍性,在很多列表展示场景下会使用到。
实现方法
问题确定
实现方案中有几个问题需要确定:
需要确定一个随机因子
可以让首页显示的列表随机展示,随机因子可以是时间、Math.random()、或者其他会时间不断变化的参数都可以。
需要确定如何让随机因子起作用?
让随机因子起作用最直接的途径是,让随机因子来影响文档排序的打分Score。另外,随机因子影响Score得分的权重可以设置,因为影响文档前后的排序主要因素应该是Lucene默认的文档相关性算法,随机因子在只能起到微调的作用。
具体实现
代码扩展
需要让随机因子来影响文档排序Socre得分,这里会使用到Solr的RankQuery,简单介绍一下RankQuery:
RankQuery提供了提供了三个扩展方法:
public class RankQuery extends ExtendedQueryBase {
public TopDocsCollector getTopDocsCollector(
int var1, QueryCommand var2, IndexSearcher var3) throws IOException;
public MergeStrategy getMergeStrategy();
public RankQuery wrap(Query query);
}
其中最重要的是getTopDocsCollector
方法,它可以让用户扩展查询过程中覆写Lucene的Collector收集器(查询过程中所有命中的记录都会执行Collector的回调,记录是否命中和在结果集中的排序先后会由Collector来决定,下面我们来看看如何扩展getTopDocsCollector()方法:
getTopDocsCollector()方法返回的是TopDocsCollectorPriorityQueue
在对内重新调整元素次序
private class ShufferCollector extends TopDocsCollector<ScoreDoc> {
ScoreDoc pqTop;
public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
final int docBase = context.docBase;
return new ScorerLeafCollector() {
@Override
public void collect(int doc) throws IOException {
// 这样可以让score产生随机性,理论上来说 可以打乱结果
float score = scorer.score() + (doc % mod) * multi;
// This collector cannot handle these scores:
assert score != Float.NEGATIVE_INFINITY;
assert !Float.isNaN(score);
totalHits++;
if (score <= pqTop.score) {
return;
}
pqTop.doc = doc + docBase;
pqTop.score = score;
pqTop = pq.updateTop();
}
};
}
}
以上在collect()方法中,覆写了score的得分,此处随机因子是mod,每次更具docid取模,模的大小可以由客户端发送到服务端来,然后再乘上一个权重因子multi
,覆写完成,测试运行首页记录的次序就会呈现随机性。
详细代码请查看ShuffleQParserPlugin.java
SolrConfig配置
客户端查询示例
总结
RankQuery在Solr中是属于比较底层的,Solr以它作为桥梁扩展底层Lucene的Collector接口,在很多业务场景下会用到,此文说到的使用场景只是众多场景之一,起到了抛砖引玉的作用,更多的使用场景期待大家去发现。