在上一篇中,我们学习了基础查询和复合查询。本篇将深入探讨 Elasticsearch 最核心的能力——全文搜索,以及如何通过高亮显示提升搜索体验。
在深入具体查询之前,我们需要理解全文搜索与精准匹配的根本区别:
全文搜索的工作流程
当执行一个全文搜索时,Elasticsearch 会经历以下过程:
text "苹果手机新款发布" → [分词] → ["苹果", "手机", "新款", "发布"] → [查询扩展] → (可能包含: "iphone", "智能手机") → [相关性计算] → 根据 TF/IDF 或 BM25 算法计算得分 → [结果排序] → 按 _score 降序排列
基础的 match 查询我们已经了解,现在来看看它的强大配置选项:
json // 高级 match 查询配置 GET /articles/_search { "query": { "match": { "content": { "query": "深度学习 神经网络 算法", "operator": "and", // 必须包含所有词条 "minimum_should_match": "70%", // 至少匹配70%的词条 "fuzziness": "AUTO", // 允许模糊匹配 "boost": 2.0 // 权重加倍 } } } }
关键参数说明:
短语搜索要求词条必须按顺序出现,且位置接近:
json // 基础短语搜索 GET /products/_search { "query": { "match_phrase": { "description": "无线蓝牙耳机" } } }
slop 参数 - 灵活控制短语间隔:
json // 允许短语间有间隔 GET /documents/_search { "query": { "match_phrase": { "text": { "query": "人工智能 技术", "slop": 3 // 允许词条间最多间隔3个位置 } } } }
实际应用场景:
用于实现"搜索即输入"的功能,对最后一个词条进行前缀匹配:
json // 输入提示功能 GET /products/_search { "query": { "match_phrase_prefix": { "name": { "query": "苹果手", // 用户正在输入"苹果手" "max_expansions": 10 // 限制扩展数量,控制性能 } } } }
这会匹配 "苹果手机"、"苹果手表" 等,但要注意性能问题。
multi_match 查询支持不同的类型,每种类型有不同的评分策略:
以最佳匹配字段的得分作为文档得分:
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:其他匹配字段的得分会乘以这个系数加到总分中
合并所有匹配字段的得分:
json // 多字段合并策略 - 适合同义词场景 GET /articles/_search { "query": { "multi_match": { "query": "机器学习", "fields": [ "title", "title.english", // 英文分词版本 "content", "content.synonyms" // 同义词扩展版本 ], "type": "most_fields" } } }
将多个字段视为一个大字段进行处理:
json // 跨字段搜索 - 适合个人信息等场景 GET /users/_search { "query": { "multi_match": { "query": "张三 北京", "fields": ["first_name", "last_name", "city", "address"], "type": "cross_fields", "operator": "and" } } }
这种策略会智能处理"词条频率"和"字段长度归一化"。
高亮显示是提升搜索体验的关键功能,让用户快速定位匹配内容。
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 // 无匹配时返回开头内容 } } } }
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 客户端实现全文搜索与高亮 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 类型:
合理使用高亮器:
控制高亮成本:
通过本文,我们深入掌握了:
关键收获:
实战建议:在你的项目中尝试实现一个带有高亮显示的搜索功能,体验不同的配置对搜索结果的影响。
本文作者:张豪
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!