在上一篇中,我们学习了基础查询和复合查询。本篇将深入探讨 Elasticsearch 最核心的能力——全文搜索,以及如何通过高亮显示提升搜索体验。
在深入具体查询之前,我们需要理解全文搜索与精准匹配的根本区别:
精准匹配:关心"是否完全匹配",用于 keyword、date、number 等字段
全文搜索:关心"相关程度",用于 text 字段,需要考虑 分词、同义词、相关性评分
全文搜索的工作流程
当执行一个全文搜索时,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 // 权重加倍
}
}
}
}
关键参数说明:
operator:默认为 or,设为 and 要求必须匹配所有分词结果
minimum_should_match:控制最少需要匹配的词条数,支持百分比或具体数字
fuzziness:允许编辑距离,AUTO 会根据词条长度自动选择
boost:调整查询的权重,影响最终的相关性评分
短语搜索要求词条必须按顺序出现,且位置接近:
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:更宽松 → "在人工智能这个领域的技术"
用于实现"搜索即输入"的功能,对最后一个词条进行前缀匹配:
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 类型:
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 许可协议。转载请注明出处!