Elasticsearch作为分布式全文检索引擎,索引设计和查询优化直接决定检索性能和资源消耗。从分词器配置到索引别名管理,从查询DSL优化到聚合分析,每个环节的配置差异可能带来数量级的性能变化。本文覆盖Elasticsearch生产环境的核心优化实践。
索引Mapping设计与分词器配置
Mapping定义了字段类型和分词策略,是索引性能的基础。生产环境禁止使用动态Mapping(dynamic mapping),所有字段显式声明类型。
PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s",
"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"]
},
"pinyin_analyzer": {
"type": "custom",
"tokenizer": "pinyin_tokenizer",
"filter": ["lowercase"]
},
"edge_ngram_analyzer": {
"type": "custom",
"tokenizer": "edge_ngram_tokenizer",
"filter": ["lowercase"]
}
},
"tokenizer": {
"edge_ngram_tokenizer": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 20,
"token_chars": ["letter", "digit"]
},
"pinyin_tokenizer": {
"type": "pinyin",
"keep_first_letter": true,
"keep_full_pinyin": true,
"keep_original": true
}
}
}
},
"mappings": {
"dynamic": "strict",
"properties": {
"product_id": {"type": "keyword"},
"name": {
"type": "text",
"analyzer": "ik_max_word_analyzer",
"search_analyzer": "ik_smart_analyzer",
"fields": {
"keyword": {"type": "keyword", "ignore_above": 256},
"pinyin": {"type": "text", "analyzer": "pinyin_analyzer"},
"suggest": {"type": "text", "analyzer": "edge_ngram_analyzer"}
}
},
"description": {
"type": "text",
"analyzer": "ik_max_word_analyzer",
"search_analyzer": "ik_smart_analyzer",
"index_options": "freqs"
},
"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": "strict_date_optional_time||epoch_millis"},
"location": {"type": "geo_point"}
}
}
}
关键设计要点:
- name字段配置多子字段:ik_max_word用于索引时最大粒度分词,ik_smart用于搜索时减少噪声匹配,pinyin支持拼音搜索,edge_ngram支持前缀补全
- scaled_float替代double存储价格,减少存储空间并提升数值比较性能
- 不需要排序和聚合的text字段设置index_options为freqs,跳过position信息减少索引体积
- refresh_interval设为30s(默认1s),降低segment合并频率,提升写入吞吐
多字段搜索与相关性调优
实际搜索场景中需要跨多个字段匹配查询词,并通过boost调整字段权重。以下是一个电商搜索的查询示例,结合名称、描述、品牌、标签多字段搜索,并加入价格和销量排序因子:
POST /products/_search
{
"query": {
"function_score": {
"query": {
"bool": {
"must": [{
"multi_match": {
"query": "无线蓝牙耳机",
"fields": ["name^3", "name.pinyin^2", "name.suggest^2",
"description^1", "brand^2", "tags^1.5"],
"type": "cross_fields",
"operator": "and",
"minimum_should_match": "75%",
"tie_breaker": 0.3
}
}],
"filter": [
{"term": {"status": "active"}},
{"range": {"price": {"gte": 0, "lte": 10000}}}
]
}
},
"functions": [
{"field_value_factor": {"field": "sales_count", "modifier": "log1p", "factor": 0.5, "missing": 0}},
{"field_value_factor": {"field": "rating", "modifier": "log1p", "factor": 1.0, "missing": 3.0}},
{"gauss": {"created_at": {"origin": "now", "scale": "30d", "decay": 0.5}}}
],
"score_mode": "multiply",
"boost_mode": "sum",
"max_boost": 10
}
},
"highlight": {
"fields": {
"name": {"pre_tags": ["<em>"], "post_tags": ["</em>"], "fragment_size": 50},
"description": {"pre_tags": ["<em>"], "post_tags": ["</em>"], "fragment_size": 100, "number_of_fragments": 2}
}
},
"size": 20,
"from": 0
}
tie_breaker参数控制最佳匹配分数与其他匹配分数的混合权重。设为0.3表示非最佳字段的分数以30%权重计入总评分,避免单一字段高分主导排序结果。
聚合查询与分桶优化
聚合分析是Elasticsearch的核心能力。在商品搜索结果中动态生成分类筛选和价格区间,使用terms聚合和range聚合组合:
POST /products/_search
{
"size": 0,
"query": {
"bool": {"filter": [{"term": {"status": "active"}}]}
},
"aggs": {
"categories": {
"terms": {"field": "category", "size": 20, "order": {"_count": "desc"}},
"aggs": {
"avg_price": {"avg": {"field": "price"}},
"price_ranges": {
"range": {"field": "price", "ranges": [
{"to": 100}, {"from": 100, "to": 500},
{"from": 500, "to": 1000}, {"from": 1000, "to": 5000},
{"from": 5000}
]}
}
}
},
"brands": {"terms": {"field": "brand", "size": 15}},
"price_stats": {"stats": {"field": "price"}},
"price_histogram": {
"histogram": {"field": "price", "interval": 100, "min_doc_count": 1}
}
}
}
聚合性能优化策略:
- 聚合字段必须是keyword类型或启用fielddata的text类型,keyword类型基于doc_values列式存储,聚合效率远高于fielddata
- terms聚合的size参数控制返回桶数量,大size值消耗大量内存。设置shard_size调整分片级聚合精度
- 对高基数字段(如用户ID)使用cardinality聚合时,precision_threshold参数控制内存与精度权衡,默认3000
索引别名与零停机重建
生产环境修改Mapping无法直接操作已有索引,需要通过别名(alias)实现零停机索引重建。标准流程如下:
# 1. 创建别名指向当前索引
POST /_aliases
{"actions": [{"add": {"index": "products_v1", "alias": "products"}}]}
# 2. 创建新索引(带新Mapping)
PUT /products_v2
{"settings": {}, "mappings": {}}
# 3. 使用reindex迁移数据
POST /_reindex
{
"source": {"index": "products_v1"},
"dest": {"index": "products_v2"},
"script": {
"source": "if (ctx._source.price != null) {ctx._source.price = ctx._source.price * 100}",
"lang": "painless"
}
}
# 4. 切换别名
POST /_aliases
{"actions": [
{"remove": {"index": "products_v1", "alias": "products"}},
{"add": {"index": "products_v2", "alias": "products"}}
]}
# 5. 删除旧索引
DELETE /products_v1
reindex操作支持slicing并行处理,大索引迁移时设置slices参数加速:
POST /_reindex?slices=auto&refresh=true
{
"source": {"index": "products_v1", "size": 5000},
"dest": {"index": "products_v2", "op_type": "create"}
}
对于持续写入的场景,reindex过程中新数据可能同时写入旧索引。使用别名写入路由(is_write_index)解决此问题:旧索引设为write index,reindex完成后切换write index到新索引,确保切换期间的写入不丢失。
# 设置products_v1为写入索引
POST /_aliases
{"actions": [
{"add": {"index": "products_v1", "alias": "products", "is_write_index": true}},
{"add": {"index": "products_v2", "alias": "products"}}
]}
# reindex完成后切换写入索引
POST /_aliases
{"actions": [
{"add": {"index": "products_v2", "alias": "products", "is_write_index": true}},
{"remove_index": {"index": "products_v1"}}
]}
切换前需要对增量数据做一次reindex补齐。通过在reindex请求中添加filter,只迁移切换时点之后更新的文档,避免全量重复迁移。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/elasticsearch-suo-yin-she-ji-yu-cha-xun-you-hua-shi-zhan/