2023-12-16
中间件
0

目录

全文搜索的本质
核心全文搜索查询详解
match 查询的进阶用法
match_phrase 与短语搜索
matchphraseprefix - 实现搜索建议
multi_match 查询的多种策略
best_fields(默认)
most_fields
cross_fields
高亮显示(Highlighting)
基础高亮配置
自定义高亮标签和样式
多种高亮器(Highlighter)选择
高亮进阶配置
实战案例:技术博客搜索系统
Java 代码实现
性能优化与最佳实践
高亮性能优化
搜索质量优化建议
总结

在上一篇中,我们学习了基础查询和复合查询。本篇将深入探讨 Elasticsearch 最核心的能力——全文搜索,以及如何通过高亮显示提升搜索体验。

全文搜索的本质

在深入具体查询之前,我们需要理解全文搜索与精准匹配的根本区别:

  • 精准匹配:关心"是否完全匹配",用于 keyword、date、number 等字段

  • 全文搜索:关心"相关程度",用于 text 字段,需要考虑 分词、同义词、相关性评分

全文搜索的工作流程

当执行一个全文搜索时,Elasticsearch 会经历以下过程:

text
"苹果手机新款发布" → [分词] → ["苹果", "手机", "新款", "发布"] → [查询扩展] → (可能包含: "iphone", "智能手机") → [相关性计算] → 根据 TF/IDF 或 BM25 算法计算得分 → [结果排序] → 按 _score 降序排列

核心全文搜索查询详解

match 查询的进阶用法

基础的 match 查询我们已经了解,现在来看看它的强大配置选项:

json
// 高级 match 查询配置 GET /articles/_search { "query": { "match": { "content": { "query": "深度学习 神经网络 算法", "operator": "and", // 必须包含所有词条 "minimum_should_match": "70%", // 至少匹配70%的词条 "fuzziness": "AUTO", // 允许模糊匹配 "boost": 2.0 // 权重加倍 } } } }

关键参数说明:

  • operator:默认为 or,设为 and 要求必须匹配所有分词结果

  • minimum_should_match:控制最少需要匹配的词条数,支持百分比或具体数字

  • fuzziness:允许编辑距离,AUTO 会根据词条长度自动选择

  • boost:调整查询的权重,影响最终的相关性评分

match_phrase 与短语搜索

短语搜索要求词条必须按顺序出现,且位置接近:

json
// 基础短语搜索 GET /products/_search { "query": { "match_phrase": { "description": "无线蓝牙耳机" } } }

slop 参数 - 灵活控制短语间隔:

json
// 允许短语间有间隔 GET /documents/_search { "query": { "match_phrase": { "text": { "query": "人工智能 技术", "slop": 3 // 允许词条间最多间隔3个位置 } } } }

实际应用场景:

  • slop: 0:严格短语匹配 → "人工智能技术"

  • slop: 2:宽松匹配 → "人工智能的先进技术"

  • slop: 5:更宽松 → "在人工智能这个领域的技术"

match_phrase_prefix - 实现搜索建议

用于实现"搜索即输入"的功能,对最后一个词条进行前缀匹配:

json
// 输入提示功能 GET /products/_search { "query": { "match_phrase_prefix": { "name": { "query": "苹果手", // 用户正在输入"苹果手" "max_expansions": 10 // 限制扩展数量,控制性能 } } } }

这会匹配 "苹果手机"、"苹果手表" 等,但要注意性能问题。

multi_match 查询的多种策略

multi_match 查询支持不同的类型,每种类型有不同的评分策略:

best_fields(默认)

以最佳匹配字段的得分作为文档得分:

json
// 最佳字段策略 - 适合字段间互斥的场景 GET /articles/_search { "query": { "multi_match": { "query": "机器学习", "fields": ["title^3", "content", "tags^2"], "type": "best_fields", "tie_breaker": 0.3 } } }

title^3:title 字段权重是默认的 3 倍

tie_breaker:其他匹配字段的得分会乘以这个系数加到总分中

most_fields

合并所有匹配字段的得分:

json
// 多字段合并策略 - 适合同义词场景 GET /articles/_search { "query": { "multi_match": { "query": "机器学习", "fields": [ "title", "title.english", // 英文分词版本 "content", "content.synonyms" // 同义词扩展版本 ], "type": "most_fields" } } }

cross_fields

将多个字段视为一个大字段进行处理:

json
// 跨字段搜索 - 适合个人信息等场景 GET /users/_search { "query": { "multi_match": { "query": "张三 北京", "fields": ["first_name", "last_name", "city", "address"], "type": "cross_fields", "operator": "and" } } }

这种策略会智能处理"词条频率"和"字段长度归一化"。

高亮显示(Highlighting)

高亮显示是提升搜索体验的关键功能,让用户快速定位匹配内容。

基础高亮配置

json
// 基础高亮查询 GET /news/_search { "query": { "match": { "content": "人工智能" } }, "highlight": { "fields": { "content": {} // 对content字段启用高亮 } } }

返回结果中会包含高亮片段:

json
"hits": [ { "_source": { "title": "AI技术发展", "content": "人工智能正在改变世界..." }, "highlight": { "content": [ "<em>人工</em><em>智能</em>正在改变世界..." ] } } ]

自定义高亮标签和样式

json
// 自定义高亮样式 GET /documents/_search { "query": { "match": { "content": "区块链技术" } }, "highlight": { "pre_tags": ["<mark class=\"highlight\">"], "post_tags": ["</mark>"], "fields": { "content": { "fragment_size": 150, // 每个片段长度 "number_of_fragments": 3, // 返回片段数量 "no_match_size": 150 // 无匹配时返回开头内容 } } } }

多种高亮器(Highlighter)选择

Elasticsearch 提供三种高亮器,适应不同场景:

unified(默认)- 推荐使用

json
// 使用unified高亮器 { "highlight": { "type": "unified", "fields": { "content": { "fragment_size": 150, "number_of_fragments": 3 } } } }

plain - 简单场景

json
// plain高亮器,适用于简单需求 { "highlight": { "type": "plain", "fields": { "content": { "fragment_size": 100 } } } }

fvh(Fast Vector Highlighter)- 复杂需求

需要字段开启 term_vector:

json
// 首先需要设置mapping PUT /documents { "mappings": { "properties": { "content": { "type": "text", "term_vector": "with_positions_offsets" } } } } // 然后使用fvh高亮器 { "highlight": { "type": "fvh", "fields": { "content": { "boundary_scanner": "sentence", // 按句子分片 "phrase_limit": 10 // 限制短语数量 } } } }

高亮进阶配置

json
// 复杂高亮配置示例 GET /legal_docs/_search { "query": { "match_phrase": { "content": "知识产权保护" } }, "highlight": { "pre_tags": ["<strong>"], "post_tags": ["</strong>"], "order": "score", // 按得分排序片段 "fields": { "content": { "type": "unified", "fragment_size": 200, "number_of_fragments": 5, "fragment_offset": 10, // 从匹配位置前10字符开始 "boundary_scanner": "word", // 按单词边界分割 "boundary_max_scan": 20, // 边界扫描距离 "max_analyzed_offset": 1000000 // 限制分析长度 }, "title": { "number_of_fragments": 0 // 返回整个字段 } } } }

实战案例:技术博客搜索系统

让我们构建一个完整的技术博客搜索,包含全文搜索和高亮:

json
// 完整的技术博客搜索示例 GET /tech_blogs/_search { "query": { "bool": { "must": [ { "multi_match": { "query": "微服务架构设计", "fields": [ "title^3", "content^2", "tags^2", "description" ], "type": "best_fields", "operator": "and", "minimum_should_match": "75%" } } ], "filter": [ { "range": { "publish_date": { "gte": "2022-01-01" } } }, { "terms": { "status": ["published", "popular"] } } ], "should": [ { "match_phrase": { "content": { "query": "微服务 架构", "slop": 2, "boost": 1.5 } } } ] } }, "highlight": { "pre_tags": ["<mark class=\"search-highlight\">"], "post_tags": ["</mark>"], "order": "score", "fields": { "title": { "number_of_fragments": 0 }, "content": { "type": "unified", "fragment_size": 200, "number_of_fragments": 3, "no_match_size": 200 }, "tags": { "type": "plain", "fragment_size": 50, "number_of_fragments": 3 } } }, "from": 0, "size": 10, "_source": ["title", "author", "publish_date", "read_count"], "sort": [ { "_score": "desc" }, { "read_count": "desc" } ] }

Java 代码实现

java
// Java 客户端实现全文搜索与高亮 SearchRequest searchRequest = new SearchRequest("tech_blogs"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 构建多字段查询 MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery( "微服务架构设计", "title^3", "content^2", "tags^2", "description" ) .type(MultiMatchQueryBuilder.Type.BEST_FIELDS) .operator(Operator.AND) .minimumShouldMatch("75%"); // 构建布尔查询 BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() .must(multiMatchQuery) .filter(QueryBuilders.rangeQuery("publish_date").gte("2022-01-01")) .filter(QueryBuilders.termsQuery("status", "published", "popular")) .should(QueryBuilders.matchPhraseQuery("content", "微服务 架构").slop(2).boost(1.5f)); sourceBuilder.query(boolQuery); // 配置高亮 HighlightBuilder highlightBuilder = new HighlightBuilder(); highlightBuilder.preTags("<mark class=\"search-highlight\">"); highlightBuilder.postTags("</mark>"); highlightBuilder.order(HighlightBuilder.Order.SCORE); // 标题高亮 - 返回整个字段 HighlightBuilder.Field titleField = new HighlightBuilder.Field("title"); titleField.numOfFragments(0); highlightBuilder.field(titleField); // 内容高亮 - 返回多个片段 HighlightBuilder.Field contentField = new HighlightBuilder.Field("content"); contentField.fragmentSize(200); contentField.numOfFragments(3); highlightBuilder.field(contentField); // 标签高亮 HighlightBuilder.Field tagsField = new HighlightBuilder.Field("tags"); tagsField.fragmentSize(50); tagsField.numOfFragments(3); highlightBuilder.field(tagsField); sourceBuilder.highlighter(highlightBuilder); // 其他配置 sourceBuilder.from(0); sourceBuilder.size(10); sourceBuilder.fetchSource(new String[]{"title", "author", "publish_date", "read_count"}, null); sourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.DESC)); sourceBuilder.sort(SortBuilders.fieldSort("read_count").order(SortOrder.DESC)); searchRequest.source(sourceBuilder); // 执行查询并处理结果 SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); for (SearchHit hit : response.getHits().getHits()) { // 获取高亮内容 Map<String, HighlightField> highlightFields = hit.getHighlightFields(); String highlightedTitle = null; if (highlightFields.containsKey("title")) { highlightedTitle = highlightFields.get("title").fragments()[0].string(); } List<String> contentFragments = new ArrayList<>(); if (highlightFields.containsKey("content")) { for (Text fragment : highlightFields.get("content").fragments()) { contentFragments.add(fragment.string()); } } // 使用高亮内容或原始内容 String displayTitle = highlightedTitle != null ? highlightedTitle : hit.getSourceAsMap().get("title").toString(); // 在前端展示... }

性能优化与最佳实践

高亮性能优化

json
// 性能优化配置 { "highlight": { "fields": { "content": { "number_of_fragments": 2, // 限制片段数量 "fragment_size": 150, // 控制片段长度 "max_analyzed_offset": 1000000, // 限制分析范围 "boundary_max_scan": 10 // 减少边界扫描 } } } }

搜索质量优化建议

选择合适的 multi_match 类型:

  • best_fields:字段内容互斥时

  • most_fields:多语言或同义词场景

  • cross_fields:个人信息等跨字段搜索

合理使用高亮器:

  • 大多数场景使用 unified

  • 超大文档考虑 fvh(需要额外存储)

  • 简单需求使用 plain

控制高亮成本:

  • 避免对太长字段高亮

  • 限制片段数量和大小

  • 考虑在索引时存储预计算的高亮

总结

通过本文,我们深入掌握了:

  • match 查询的高级配置:operator、minimum_should_match、fuzziness

  • 短语搜索:match_phrase 和 slop 参数的灵活运用

  • 多字段搜索策略:best_fields、most_fields、cross_fields 的区别

  • 完整的高亮显示配置:标签自定义、多种高亮器、性能优化

关键收获:

  • 全文搜索的核心是理解相关性计算

  • 高亮显示显著提升用户体验

  • 不同的业务场景需要选择不同的搜索策略

  • 性能和搜索质量需要平衡考虑

实战建议:在你的项目中尝试实现一个带有高亮显示的搜索功能,体验不同的配置对搜索结果的影响。

本文作者:柳始恭

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!