记一次ES搜索引擎对响应结果排序优化

3 篇文章 0 订阅

记一次ES搜索引擎对响应结果排序优化


author: codezhao

1.需求:根据关键词分页搜索小说名称,排序优先响应和关键字前缀相同的小说名称

如果我们按照ES默认的相关度分数排序,写法如下:

//条件
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.must(QueryBuilders.termsQuery("status", "1"));
queryBuilder.must(QueryBuilders.wildcardQuery("bookName", "*" + keyword.toLowerCase() + "*"));
//查询
SearchResponse response = client.prepareSearch()
                    .setIndices(BookEsEntity.INDEX_NAME)
                    .setTypes(BookEsEntity.INDEX_TYPE)
                    .setQuery(queryBuilder )
                    .setSize(searchBookDTO.getLimit())
                    .setFrom(searchBookDTO.getPage() - 1)
                    .addSort(SortBuilders.scoreSort().order(SortOrder.DESC))
                    .execute().actionGet();

假设我们搜索hello,那么响应的顺序 为 简爱 hello hello , hello神雕侠侣 , hello论语
很明显ES默认的评分机制(在es5.0版本之前使用了TF/IDF算法实现,而在5.0之后默认使用BM25方法实现。)会综合考虑多个因素,如上述的结果 hello出现的次数多排在了第一等不符合产品的需求。

2.改进第一步,使用FunctionScoreQueryBuilder 对特定的查询条件进行提权,设置weight权重值
写法如下:

//条件
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.must(QueryBuilders.termsQuery("status", "1"));
queryBuilder.must(QueryBuilders.wildcardQuery("bookName", "*" + keyword.toLowerCase() + "*"));

FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[1];
            ScoreFunctionBuilder<WeightBuilder> scoreFunctionBuilder = new WeightBuilder();
            //设置权重9999 够大了
            scoreFunctionBuilder.setWeight(9999);
            //设置权重对应的查询条件,prefixQuery 前缀匹配查询
            QueryBuilder termQuery = QueryBuilders.prefixQuery("bookName", searchBookDTO.getKeyword());
            FunctionScoreQueryBuilder.FilterFunctionBuilder category = new FunctionScoreQueryBuilder.FilterFunctionBuilder(termQuery, scoreFunctionBuilder);
            filterFunctionBuilders[0] = category;
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(queryBuilder, filterFunctionBuilders)
                    .scoreMode(FiltersFunctionScoreQuery.ScoreMode.SUM).boostMode(CombineFunction.SUM);

//查询
SearchResponse response = client.prepareSearch()
                    .setIndices(BookEsEntity.INDEX_NAME)
                    .setTypes(BookEsEntity.INDEX_TYPE)
                    .setQuery(queryBuilder )
                    .setSize(searchBookDTO.getLimit())
                    .setFrom(searchBookDTO.getPage() - 1)
                    .addSort(SortBuilders.scoreSort().order(SortOrder.DESC))
                    .execute().actionGet();
                   

我们还是搜索hello关键字,响应的顺序 为 hello神雕侠侣 , hello论语 ,简爱 hello hello
看起来是解决了我们的需求,上述的结果 hello按照符合产品的需求。

但QA测试发现有本小说名称有大写的HELLO 没有查询到,BUG来了

3.改进第二步,使用 lowercase analyzer 大小写忽略分词器

{
	"template": "*",
	"settings": {
		"number_of_shards": 5,
		"number_of_replicas": 1,
		"analysis": {
			"analyzer": {
				"lowercase_whitespace": {
					"type": "custom",
					"tokenizer": "whitespace",
					"filter": ["lowercase"]
				}
			}
		}
	}
}

我们搜索hello关键字,响应的顺序 为 简爱 hello hello , hello神雕侠侣 , hello论语 ,HELLO
omg,大小写的结果虽然都查出来了,但前缀优先匹配排序又失效了,一波虽平一波又起。

原来 prefix前缀匹配 若字段是Text 分词的场景下,会对分词后的每个单词进行前缀匹配,而对于英文名称的小说来说,英语天生带有空格,所以 简爱 hello hello 被分成了 简爱,hello,hello 三个单词,然后再进行前缀匹配,明显也是满足条件的

那么 我们只能删除索引,重新将索引改回keyword 类型,且删掉lowercase 分词器,也就是前缀匹配和大小写匹配是个冲突,鱼与熊掌不可兼得

4.改进第三步,根据业务场景出发
因需求是前缀优先匹配,固不能使用小写分词器,根据小说名称的特性,要么小写 要写大写,要么首字母大写,不存在其他场景
所以这样解决:

BoolQueryBuilder shouldQueryBuilder = QueryBuilders.boolQuery();
shouldQueryBuilder.should(QueryBuilders.wildcardQuery("bookName", "*" + key.toLowerCase() + "*"));
shouldQueryBuilder.should(QueryBuilders.wildcardQuery("bookName", "*" + key.toUpperCase() + "*"));
shouldQueryBuilder.should(QueryBuilders.wildcardQuery("bookName", "*" + ToolUtils.toUpperFirstChar(key) + "*"));
queryBuilder.must(shouldQueryBuilder);

使用should或条件,查询大写,小写,和首字母大写的条件查询,既满足前缀匹配,又能个查询大小写的小说

5.改进第四步,冗余字段
也可以在ES中多冗余存储一个大写或小写的小说名称,查询的时候统一将关键字转大写或小写查询,这样就能不费吹灰之力解决,但后续可能不仅仅根据书名来搜索,产品你懂得,后续扩展到根据10个字段搜索,冗余就不好扩展了,当然一个码农界的名人曾说过,一段能解决当前需求的代码已经是一段很好的代码了,先这样,后续优化再续写,
2021-03-18 codezhao

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值