Elasticsearch全文搜索实战:分词器配置与复合查询DSL

Elasticsearch是基于Lucene的分布式全文搜索引擎,通过倒排索引实现毫秒级文本检索。实际项目中搜索引擎的效果高度依赖分词器配置和查询DSL的编写质量。默认的standard分词器对中文只能按单字切分,无法识别词语边界;复合查询若未能正确组合bool与function_score,则难以实现多因子排序。本文以商品搜索为例,演示自定义分词器配置、索引映射设计、复合查询DSL编写和搜索性能调优。

中文分词器配置与索引映射

安装IK Analysis插件提供中文分词能力。IK支持ik_smart(粗粒度)和ik_max_word(细粒度)两种分词模式,并支持自定义词典扩展:

# 安装IK插件(Elasticsearch 8.x)
./bin/elasticsearch-plugin install \
  https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.14.0/elasticsearch-analysis-ik-8.14.0.zip

# 配置自定义词典
# config/analysis-ik/IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
  <comment>IK Analyzer 扩展配置</comment>
  <entry key="ext_dict">custom_dict.dic;brand_names.dic</entry>
  <entry key="ext_stopwords">stopwords.dic</entry>
  <entry key="remote_ext_dict">http://dict.internal/api/words</entry>
</properties>

创建索引时在settings中配置分词器组合,在mappings中定义字段类型和分析器。商品搜索索引配置:

PUT /products
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "analysis": {
      "analyzer": {
        "ik_smart_analyzer": {
          "type": "custom",
          "tokenizer": "ik_smart",
          "filter": ["lowercase", "asciifold", "length_filter"]
        },
        "ik_max_word_analyzer": {
          "type": "custom",
          "tokenizer": "ik_max_word",
          "filter": ["lowercase", "asciifold"]
        },
        "pinyin_analyzer": {
          "type": "custom",
          "tokenizer": "pinyin_tokenizer",
          "filter": ["lowercase"]
        },
        "search_analyzer": {
          "type": "custom",
          "tokenizer": "ik_smart",
          "filter": ["lowercase", "asciifold", "synonym_filter"]
        }
      },
      "filter": {
        "length_filter": {
          "type": "length",
          "min": 2,
          "max": 50
        },
        "synonym_filter": {
          "type": "synonym",
          "synonyms": [
            "手机,智能手机,mobile phone",
            "笔记本,笔记本电脑,laptop",
            "耳机,蓝牙耳机,earphone"
          ]
        }
      },
      "tokenizer": {
        "pinyin_tokenizer": {
          "type": "pinyin",
          "keep_first_letter": true,
          "keep_full_pinyin": true,
          "keep_original": true,
          "lowercase": true
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "product_name": {
        "type": "text",
        "analyzer": "ik_max_word_analyzer",
        "search_analyzer": "search_analyzer",
        "fields": {
          "keyword": { "type": "keyword" },
          "pinyin": {
            "type": "text",
            "analyzer": "pinyin_analyzer"
          },
          "suggest": {
            "type": "completion",
            "analyzer": "ik_smart_analyzer"
          }
        }
      },
      "description": {
        "type": "text",
        "analyzer": "ik_max_word_analyzer",
        "search_analyzer": "search_analyzer"
      },
      "category": { "type": "keyword" },
      "brand": { "type": "keyword" },
      "price": { "type": "scaled_float", "scaling_factor": 100 },
      "sales_count": { "type": "integer" },
      "rating": { "type": "float" },
      "tags": { "type": "keyword" },
      "status": { "type": "keyword" },
      "created_at": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }
    }
  }
}

index阶段使用ik_max_word进行最细粒度分词以最大化召回率,search阶段使用ik_smart进行粗粒度分词配合同义词过滤器以提升精确度。这种index/search分词器分离策略是中文搜索的标准实践。

复合查询DSL:bool多字段检索与相关性排序

商品搜索需要同时匹配名称、描述和分类,并按相关性、销量和评分综合排序。使用bool查询组合must/should/filter子句,配合function_score实现多因子加权排序:

POST /products/_search
{
  "size": 20,
  "query": {
    "bool": {
      "must": [
        { "term": { "status": "on_sale" } }
      ],
      "should": [
        {
          "multi_match": {
            "query": "无线蓝牙耳机",
            "fields": ["product_name^3", "description^1", "product_name.pinyin^2"],
            "type": "best_fields",
            "tie_breaker": 0.3,
            "minimum_should_match": "75%"
          }
        },
        {
          "match_phrase": {
            "product_name": {
              "query": "蓝牙耳机",
              "boost": 5,
              "slop": 1
            }
          }
        }
      ],
      "filter": [
        { "range": { "price": { "gte": 50, "lte": 2000 } } },
        { "terms": { "brand": ["sony", "bose", "jbl", "apple", "huawei"] } }
      ],
      "minimum_should_match": 1
    }
  },
  "function_score": {
    "functions": [
      {
        "field_value_factor": {
          "field": "sales_count",
          "factor": 0.01,
          "modifier": "log1p",
          "missing": 1
        }
      },
      {
        "field_value_factor": {
          "field": "rating",
          "factor": 2,
          "modifier": "sqrt",
          "missing": 3
        }
      },
      {
        "filter": { "term": { "tags": "hot" } },
        "weight": 1.5
      },
      {
        "gauss": {
          "created_at": {
            "origin": "now",
            "scale": "90d",
            "offset": "30d",
            "decay": 0.5
          }
        }
      }
    ],
    "score_mode": "sum",
    "boost_mode": "multiply",
    "max_boost": 10
  },
  "sort": [
    { "_score": { "order": "desc" } },
    { "sales_count": { "order": "desc" } }
  ],
  "highlight": {
    "pre_tags": ["<em class='highlight'>"],
    "post_tags": ["</em>"],
    "fields": {
      "product_name": { "fragment_size": 150 },
      "description": { "fragment_size": 200, "number_of_fragments": 2 }
    }
  },
  "aggs": {
    "brand_stats": {
      "terms": { "field": "brand", "size": 10 }
    },
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 100 },
          { "from": 100, "to": 300 },
          { "from": 300, "to": 800 },
          { "from": 800 }
        ]
      }
    }
  }
}

multi_match的best_fields策略在多个字段中取最高分作为该匹配的贡献分,tie_breaker=0.3将其余字段得分的30%累加。match_phrase对短语查询给予boost=5的高权重,确保短语完整匹配的文档排在前面。

function_score的score_mode=sum将各函数得分相加,boost_mode=multiply将函数总分与查询相关性分相乘。log1p(sales_count)对销量取对数避免高销量商品垄断排序;gauss函数对创建时间做高斯衰减,90天发布的新品获得权重加成。

搜索性能调优与聚合优化

当数据量超过千万级时,全文搜索的延迟可能从毫秒级退化为百毫秒级。以下为生产环境中的关键调优措施:

分片数量影响并行度和单分片数据量。number_of_shards应约为数据量的GB数除以30,单分片不宜超过50GB。对于3亿条商品数据(约150GB),6个分片较为合理。

# 索引别名实现零停机重建索引
POST /_aliases
{
  "actions": [
    { "add": { "index": "products_v2", "alias": "products_search" } },
    { "remove": { "index": "products_v1", "alias": "products_search" } }
  ]
}

# 使用reindex API迁移数据
POST /_reindex
{
  "source": { "index": "products_v1" },
  "dest": { "index": "products_v2" }
}

对于高频搜索请求,启用缓存和预过滤。filter子句的结果自动被Elasticsearch缓存,避免重复执行query phase:

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "product_name": "蓝牙耳机" } }
      ],
      "filter": [
        { "term": { "category": "audio_device" } },
        { "term": { "status": "on_sale" } },
        { "range": { "price": { "gte": 50, "lte": 2000 } } }
      ]
    }
  },
  "size": 20,
  "_source": ["product_name", "price", "brand", "rating", "images"]
}

过滤条件放在filter中不参与相关性评分但被缓存,must中的match才走打分逻辑。_source限定返回字段减少网络传输。使用search_after替代from+size实现深度分页,避免deep pagination的全局排序开销:

// 第一页
GET /products/_search
{
  "size": 20,
  "sort": [
    { "_score": "desc" },
    { "_id": "asc" }
  ]
}

// 后续页(使用上一页最后一条记录的sort值)
GET /products/_search
{
  "size": 20,
  "search_after": [1.2345, "abc123"],
  "sort": [
    { "_score": "desc" },
    { "_id": "asc" }
  ]
}

search_after方式每次查询基于上一页的排序值定位起点,时间复杂度为O(logN),而from+size方式的from值越大排序成本越高,from=10000时性能急剧下降。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/elasticsearch-quan-wen-sou-suo-shi-zhan-fen-ci-qi-pei-zhi/

(0)
小编小编
上一篇 1小时前
下一篇 1小时前

相关推荐

Elasticsearch全文搜索实战:分词器配置与相关性评分调优方案

传统关系型数据库的LIKE模糊查询在百万级数据量下性能急剧下降,且无法实现相关性排序和中文分词。Elasticsearch作为分布式全文搜索引擎,基于倒排索引提供毫秒级全文检索能力,支持复杂的分词、高亮、聚合分析。电商商品搜索、日志检索、文档管理等场景中,Elasticsearch已成为数据查询优化的标准方案。

中文分词器配置与索引映射设计

Elasticsearch默认的Standard分词器对中文按单字切分,搜索精度极低。生产环境必须配置专用中文分词器。IK分词器是最常用的中文分词插件,支持细粒度切分和智能切分两种模式。

# 安装IK分词器(需匹配ES版本)
./bin/elasticsearch-plugin install \
  https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.12.0/elasticsearch-analysis-ik-8.12.0.zip

# 创建索引并配置分词器
PUT /articles
{
  "settings": {
    "analysis": {
      "analyzer": {
        "ik_smart_analyzer": {
          "type": "custom",
          "tokenizer": "ik_smart",
          "filter": ["lowercase", "asciifolding"]
        },
        "ik_max_word_analyzer": {
          "type": "custom",
          "tokenizer": "ik_max_word",
          "filter": ["lowercase", "asciifolding"]
        },
        "ik_search_analyzer": {
          "type": "custom",
          "tokenizer": "ik_smart",
          "filter": ["lowercase"]
        }
      }
    },
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_max_word_analyzer",
        "search_analyzer": "ik_search_analyzer",
        "fields": {
          "keyword": { "type": "keyword" }
        }
      },
      "content": {
        "type": "text",
        "analyzer": "ik_max_word_analyzer",
        "search_analyzer": "ik_search_analyzer"
      },
      "author": {
        "type": "keyword"
      },
      "publish_date": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      },
      "tags": {
        "type": "keyword"
      },
      "view_count": {
        "type": "integer"
      }
    }
  }
}

索引时使用ik_max_word分词器做最大粒度切分,确保所有可能的词项都被索引。搜索时使用ik_smart分词器做智能切分,避免过度切分导致召回率过高。这种”索引切多、搜索切少”的策略在中文搜索中效果最佳。

自定义词典与热更新配置

IK分词器默认词典无法覆盖行业术语和新词。通过网络流行语、专业术语的持续更新,可通过远程词典实现热加载。

# IK分词器远程词典配置
# config/IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 配置</comment>
    <entry key="remote_ext_dict">http://192.168.1.200/ik/dict.txt</entry>
    <entry key="remote_ext_stopwords">http://192.168.1.200/ik/stop.txt</entry>
</properties>

# dict.txt 示例内容
大模型
混合专家
MoE架构
向量数据库
RAG系统
Prompt工程
微服务架构
服务网格
分布式事务
弹性伸缩

# stop.txt 停用词
的
了
在
是
我
有
就
不
也
都

远程词典HTTP响应头需包含Last-Modified或ETag字段,IK分词器每隔60秒检查一次,检测到变更后自动重新加载词典,无需重启Elasticsearch节点。

相关性评分调优与搜索查询优化

Elasticsearch使用BM25算法计算文档相关性评分。默认评分可能不符合业务需求,如标题匹配权重应高于正文匹配。通过function_score和boost机制可自定义评分策略。

// 多字段加权搜索 + 函数评分
POST /articles/_search
{
  "query": {
    "function_score": {
      "query": {
        "bool": {
          "should": [
            {
              "match": {
                "title": {
                  "query": "分布式事务 Seata",
                  "boost": 3
                }
              }
            },
            {
              "match": {
                "content": {
                  "query": "分布式事务 Seata",
                  "boost": 1
                }
              }
            },
            {
              "terms": {
                "tags": ["分布式事务", "Seata", "微服务"],
                "boost": 2
              }
            }
          ],
          "minimum_should_match": 1
        }
      },
      "functions": [
        {
          "gauss": {
            "publish_date": {
              "origin": "now",
              "scale": "90d",
              "decay": 0.5
            }
          },
          "weight": 1.5
        },
        {
          "field_value_factor": {
            "field": "view_count",
            "modifier": "log1p",
            "factor": 0.5,
            "missing": 1
          },
          "weight": 0.5
        }
      ],
      "score_mode": "sum",
      "boost_mode": "multiply"
    }
  },
  "highlight": {
    "fields": {
      "title": {
        "pre_tags": ["<em>"],
        "post_tags": ["</em>"],
        "number_of_fragments": 0
      },
      "content": {
        "pre_tags": ["<em>"],
        "post_tags": ["</em>"],
        "fragment_size": 150,
        "number_of_fragments": 3
      }
    }
  },
  "size": 20
}

上述查询实现了三层评分逻辑:文本相关性(BM25)为基础分,标题匹配加权3倍,标签匹配加权2倍;发布时间使用高斯衰减函数,90天前的文档衰减50%;浏览量取对数后加权。最终评分 = 基础分 x (时间衰减 x 时间权重 + 浏览量因子 x 浏览量权重)。

聚合分析与分面搜索

// 按标签聚合统计 + 按时间分桶
POST /articles/_search
{
  "size": 0,
  "query": {
    "match": {
      "content": "微服务架构"
    }
  },
  "aggs": {
    "tag_distribution": {
      "terms": {
        "field": "tags",
        "size": 20,
        "order": { "_count": "desc" }
      }
    },
    "monthly_trend": {
      "date_histogram": {
        "field": "publish_date",
        "calendar_interval": "month",
        "format": "yyyy-MM"
      },
      "aggs": {
        "avg_views": {
          "avg": { "field": "view_count" }
        }
      }
    },
    "author_stats": {
      "terms": { "field": "author", "size": 10 },
      "aggs": {
        "total_views": { "sum": { "field": "view_count" } }
      }
    }
  }
}

聚合结果可直接用于搜索页面的分面导航——用户可以在搜索结果页点击标签、作者、时间范围进行二次筛选,实现电商商品列表的筛选体验。

Elasticsearch性能调优常见问题

深分页查询性能差。from + size方式在深分页时需要排序大量文档。使用search_after基于排序值游标翻页,或scroll API批量导出。from + size的from参数建议不超过10000。

聚合查询内存溢出。对高基数字段(如用户ID)做terms聚合时,每个分片维护大量桶消耗堆内存。可通过设置max_buckets限制和fielddata过滤降低风险,或使用composite聚合分批获取。

索引写入速率波动。批量写入优化配置如下:

PUT /articles/_settings
{
  "index": {
    "refresh_interval": "30s",
    "translog": {
      "sync_interval": "30s",
      "durability": "async"
    },
    "number_of_replicas": 0
  }
}

# 批量写入API
POST /_bulk
{ "index": { "_index": "articles", "_id": "1" } }
{ "title": "分布式事务实战", "content": "...", "tags": ["Seata"] }
{ "index": { "_index": "articles", "_id": "2" } }
{ "title": "全文搜索配置", "content": "...", "tags": ["Elasticsearch"] }

批量写入完成后恢复refresh_interval为1s和replicas为1,确保搜索实时性和数据库高可用架构的容灾能力。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/elasticsearch-quan-wen-sou-suo-shi-zhan-fen-ci-qi-pei-zhi/

(0)
小编小编
上一篇 22小时前
下一篇 22小时前

相关推荐