假设你是一个电商平台的开发者,目标是让用户在搜索"手机壳"时,既能快速找到想要的东西,又能感受到结果的"贴心"。今天我们就围绕SearchHits
聊聊,从一个最简单的搜索请求开始,一步步走进复杂但高效的方案,顺便看看相关性算法(比如Scores)是怎么玩转的。
最朴素的开始:一个简单的SearchRequest
我们先从最基础的玩儿法入手。想象你刚接手这个任务,领导说:"给个搜索框,用户输入'手机壳',把相关的商品吐出来就行。"你心想,这还不简单?于是写了个最直白的SearchRequest
:
java
SearchRequest request = new SearchRequest("products");
QueryBuilder query = QueryBuilders.matchQuery("name", "手机壳");
request.source().query(query);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
运行一下,SearchHits
里还真出来了几个结果,比如: - "iPhone 13手机壳透明款"
-
"手机壳华为P40防摔"
-
"手机壳可爱卡通图案"
你一看,嗯,能用!hits.getHits()
里每个SearchHit
都有商品信息,标题、价格啥的都能拿出来展示。但很快问题就来了:用户说,"为啥第一个结果是个透明款?我想要卡通的啊!"你再一看,顺序好像有点随意,相关性完全没体现出来。
朴素策略的问题:相关性哪去了?
这时候你得琢磨了,刚刚那个matchQuery
虽然简单,但它默认的排序是个啥逻辑呢?翻了翻文档,原来默认情况下,Elasticsearch会给每个结果算一个_score
,但因为我们没指定啥特别的字段权重或者排序规则,这个_score
基本就是"谁命中得多谁靠前",而且还不一定贴合电商场景。比如: - "手机壳"这个词在标题里出现了三次的结果,可能排在前面。 - 但用户其实更想要"卡通"这种风格的,透明款反而没啥吸引力。
再看SearchHits
里的数据,假设返回了3个结果: - Hit 1: "iPhone 13手机壳透明款",_score = 1.5
- Hit 2: "手机壳华为P40防摔",_score = 1.3
- Hit 3: "手机壳可爱卡通图案",_score = 1.2
你盯着这堆数字发呆:为啥卡通的得分最低?仔细一想,matchQuery
只管词频,不管用户意图,也不管商品的销量、点击率这些电商里常见的"隐藏加分项"。这时候,朴素策略的短板就暴露了:它太"机械"了,完全不理解用户的真实需求。
进化第一步:引入相关性调优
既然发现了问题,咱们就得动手优化。电商搜索里,用户输入"手机壳",通常不只是想要标题里带这个词的商品,而是希望结果既相关又"实用"。怎么做呢?先从相关性算法入手,加点权重。
比如,我们知道标题比描述更重要,品牌可能比款式更关键。于是改进了SearchRequest
:
java
SearchRequest request = new SearchRequest("products");
QueryBuilder query = QueryBuilders.boolQuery()
.should(QueryBuilders.matchQuery("name", "手机壳").boost(2.0f)) // 标题权重高
.should(QueryBuilders.matchQuery("description", "手机壳").boost(1.0f)); // 描述权重低
request.source().query(query);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
这回SearchHits
里的_score
变了: - Hit 1: "iPhone 13手机壳透明款",_score = 3.0
(标题命中,权重翻倍) - Hit 2: "手机壳可爱卡通图案",_score = 2.8
(标题+描述都命中) - Hit 3: "手机壳华为P40防摔",_score = 2.5
你一看,卡通的排名上来了,感觉有点靠谱了!通过boost
调整字段权重,相关性算法开始"听话"了。但问题还没完,用户又反馈:"我想要销量高的卡通手机壳,为啥还是透明款排第一?"
再进一步:电商特色的排序
这时候你意识到,光靠文本匹配还不够,电商搜索得考虑更多维度,比如销量、评分这些"业务指标"。Elasticsearch支持function_score
查询,咱们试试把销量加进去:
java
SearchRequest request = new SearchRequest("products");
QueryBuilder baseQuery = QueryBuilders.matchQuery("name", "手机壳");
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(baseQuery)
.add(ScoreFunctionBuilders.fieldValueFactorFunction("sales").factor(0.001f).modifier(Modifier.LOG1P));
request.source().query(functionScoreQuery);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
假设商品数据是这样的: - "iPhone 13手机壳透明款",销量1000,_score = 3.0 + log(1 + 1000 * 0.001) ≈ 3.69
- "手机壳可爱卡通图案",销量5000,_score = 2.8 + log(1 + 5000 * 0.001) ≈ 3.90
- "手机壳华为P40防摔",销量200,_score = 2.5 + log(1 + 200 * 0.001) ≈ 2.77
这下SearchHits
的排序变成: 1. "手机壳可爱卡通图案",_score = 3.90
2. "iPhone 13手机壳透明款",_score = 3.69
3. "手机壳华为P40防摔",_score = 2.77
你拍拍手,销量高的卡通款终于排第一了!用户的需求被更好地满足,_score
也开始体现"商业价值"。
解析SearchHits:给用户一个直观的展示
拿到SearchHits
后,怎么呈现给用户呢?我们可以遍历结果,把关键信息掏出来:
java
for (SearchHit hit : hits) {
System.out.println("商品: " + hit.getSourceAsMap().get("name") +
", 得分: " + hit.getScore() +
", 销量: " + hit.getSourceAsMap().get("sales"));
}
输出可能是:
makefile
商品: 手机壳可爱卡通图案, 得分: 3.90, 销量: 5000
商品: iPhone 13手机壳透明款, 得分: 3.69, 销量: 1000
商品: 手机壳华为P40防摔, 得分: 2.77, 销量: 200
用户一看就明白,这结果不仅相关,还考虑了热销程度,体验好多了。
还能怎么优化?
走到这一步,已经比最开始的朴素方案强了不少,但还有提升空间。看看现在的短板: 1. 个性化缺失 :不同用户可能偏好不同,比如有人爱便宜的,有人只买大牌。 2. 动态性不足 :销量是静态的,没考虑实时点击率或库存状态。 3. 语义理解不够:如果用户搜"防摔手机壳",单纯的词频匹配可能漏掉"抗摔"这种同义词。
优化方向呢?其实跟现在主流电商的复杂方案不谋而合:
- 个性化推荐 :加个用户行为因子,比如用
function_score
引入用户历史点击的商品类别权重。 - 实时反馈 :结合短期的点击率或转化率,动态调整_score
,可以用外部数据流实时更新。 - 语义搜索:引入向量搜索(vector search),把"防摔"和"抗摔"映射到相近的语义空间,靠 embedding 提升召回。
总结:从简单到贴心
从最开始的matchQuery
,到加权字段,再到销量因子,我们一步步让搜索结果更贴近电商的真实需求。SearchHits
里的_score
不再是冷冰冰的数字,而是用户意图和业务价值的综合体现。未来要是再加上个性化、实时性和语义理解,妥妥能媲美大厂的搜索体验。你说,这趟旅程是不是挺有意思?