Lucene 8.x 使用 FunctionScoreQuery 实现自定义的文档评分
在使用不管是 Lucene 或者 ElasticSearch 进行全文搜索中,检索到文档很简单,但是把搜索者最想要的结果排在最前面,这是最具挑战性的。
我们举个最简单的例子:假设我们需要做一个博客搜索,当我们用 Lucene 默认的匹配度进行排序,那你可能会发现搜索出来的都是一堆垃圾文章。所以一般我们会把博客文章的其他因素作为排序方法来替代匹配度。例如会考虑 “阅读数、文章长度、是否编辑推荐” 等诸多扩展因素综合进行排序。
这种时候我们一般会构造一个包含多个 SortField 的 Sort 对象,例如前面提到的阅读数、长度、是否推荐等。
但是!这个排序对象在搜索前就已经固定下来的,这样可能会出现一种情况,一些相关度非常低,但它的阅读数很高,同时又是编辑推荐的博客文章会排在前面。
有没有这样一种可能,我想根据不同的内容相关度,再结合文章的一些扩展指标来进行排序。比如,当我在搜博客的时候,只有当相关度大于某个值的时候,我会通过这些文章扩展指标来加分,让这些被推荐的文章排在前面。
这里就要用到 lucene 的 FunctionScoreQuery 来实现了,在老的版本里,是一个叫 CustomScoreQuery 的类,在新版已经被废弃了。 Lucene 的 API 就是这么变态,新版本发布经常都会有 API 的大变更。
大概的使用过程是这样的:
0. 需要引用的类
import org.apache.lucene.expressions.Expression; import org.apache.lucene.expressions.SimpleBindings; import org.apache.lucene.expressions.js.JavascriptCompiler; import org.apache.lucene.queries.function.FunctionScoreQuery; import org.apache.lucene.queryparser.classic.ParseException;
1. 首先定义一个变量表:
SimpleBindings bindings = new SimpleBindings(); bindings.add("$score", DoubleValuesSource.SCORES); bindings.add("$recomm", DoubleValuesSource.fromIntField("recomm")); bindings.add("$viewcount", DoubleValuesSource.fromIntField("viewcount")); bindings.add("$wordcount", DoubleValuesSource.fromIntField("wordcount"));
2. 定义一个自定义排序的表达式:
String sort_method= "mysort($score,$recomm,$viewcount,$wordcount)"; HashMap<String, Method> scoreMethods = new HashMap(); scoreMethods.put("mysort", getClass().getDeclaredMethod("mysort", double.class, double.class, double.class, double.class)); Expression expr = JavascriptCompiler.compile(sort_method, scoreMethods, getClassLoader());
注意这里的 scoreMethods 用来存放所有自定义函数对应的 Java 方法,如下所示:
3. 定义一个 mysort 方法,接受 double 参数,返回 double 参数
public double mysort(double score, double recomm, double viewcount, double wordcount) { if(score >= 40) { if(recomm > 0) score += 100; if(viewcount >= 1000) score += 100; if(wordcount >= 200) score += 20; } return score; }
4. 接下来你就可以使用 FunctionScoreQuery
FunctionScoreQuery query = new FunctionScoreQuery(oldQuery, expr.getDoubleValuesSource(bindings));
这里的 oldQuery 就是你原先用来做全文搜索的 Query 实例。
你可以试试打印一下 query 看看,我这边一个真实打印的结果如下:
FunctionScoreQuery(+type:[2 TO 2] +fork:[0 TO 0] +(((+name:j2cache)^20.0 (+description:j2cache)^10.0 (+detail:j2cache)^1.0 (+tags:j2cache)^10.0 (+catalogs:j2cache)^10.0 (+owner.name:j2cache)^2.0)~1), scored by expr(repo_sort($score,$recomm,$stars,$gindex)))
最后
当使用 FunctionScoreQuery 后,排序的时候只需要直接用 Sort.RELEVANCE 即可。
尝试一下吧,不过当结果集很大的时候 FunctionScoreQuery 的效率会比较差,具体需要在实际的环境中进行验证。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
高级前端:详解手写原生Ajax的实现
点击上方“ 前端印象 ”,选择“ 设为星标 ” 第一时间关注技术干货! 对于Ajax,肯定很多小伙伴都听过甚至用过了,那么没听过的也不用着急,本文会对Ajax进行讲解,其次,一定还有一些人只用过JQuery封装好了的Ajax却对原生的Ajax并不了解,那么也不用着急,本文从最基本的Ajax开始讲起,然后最后会尽可能得模仿JQuery对其进行封装,让我刚才提到的两类人能对Ajax有进一步的了解。 一、什么是Ajax Ajax(Asynchronous JavaScript And XML)是2005年新出现的技术,它的出现是为了解决这样一个场景:整个页面中,只有一小部分的数据需要进行更新,按照传统的前后端交互,我们需要向服务器请求该网页的所有数据,然后再在客户端重新渲染,这无疑是非常低效的操作。因此,Ajax就可以做到只向服务器请求我们想要的那一小部分数据,而不用请求全部数据,进而在刷新整个页面的前提下更新那部分的数据。 举个例子,我们去饭店吃饭,然后点了一桌子菜,后来发现其中有一道菜太咸了,因此我们只需要让服务员端回去给厨师重新做这一道菜再拿回来就行了。 在这个例子中的人、物对比Aja...
- 下一篇
跟坚哥学QUIC系列:连接迁移(Connection Migration)
当代人的日常生活中,手机网络连接会经常在 Wi-Fi 和 蜂窝网络(Cellular)中进行切换。比如:早上从家里出门,连接从 Wi-Fi 变为 cellular;到喜欢的餐厅吃早餐,手机自动连接餐厅的 Wi-Fi;从餐厅到公司,连接又经历了 Wi-Fi -> cellular -> Wi-Fi 的过程。如果我们正在看短视频或者直播,网络在切换的过程出现中断,这无疑是非常影响体验的。 TCP 的连接标识是通过 “源IP + 源Port + 目标IP + 目标Port + 协议号“ 组成的唯一五元组,一旦其中一个参数发生变化,则需要重新创建新的 TCP 连接。 而 QUIC 的连接标识是一个 64位的连接 ID,用户在 Wi-Fi 和 蜂窝网络(Celluar)切换时,无论是 IP 或者端口(Port)发生变化,QUIC 连接中的连接 ID 保持不变,因此不需要重新创建连接。这种用户无感知的网络切换特性,叫连接迁移(Connection Migration)。 在短视频爆发和全民主播时代,QUIC 的连接迁移(Connection Migration)支持在 Wi-Fi 和 ...
相关文章
文章评论
共有0条评论来说两句吧...