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/