整理面试复盘:设计Elasticsearch索引与高效多级分类筛选
前言
在面试中,设计Elasticsearch(ES)索引和实现高效的多级分类筛选是常见问题。本文以RestHighLevelClient为基础,结合实际场景(如商品搜索),简述ES索引结构的设计思路,以及如何实现高效的多级分类筛选。
设计ES索引的考虑因素
在设计ES索引时,尤其是针对商品索引,需要综合考虑以下几个方面:
-
业务需求分析:
- 商品索引需要支持哪些字段?如商品名称、描述、价格、分类、品牌、库存等。
- 搜索场景:是全文搜索、精确匹配,还是多字段组合搜索?
- 筛选需求:是否需要支持多级分类、价格区间、品牌等过滤?
- 排序需求:按价格、销量、评分等排序。
-
性能优化:
- 索引字段的映射(Mapping)设计要合理,避免过多字段导致性能下降。
- 选择合适的字段类型(如
keyword
用于精确匹配,text
用于全文搜索)。 - 合理设置分片(Shards)和副本(Replicas)以平衡查询性能和数据高可用。
-
扩展性和维护性:
- 索引结构要支持未来字段扩展。
- 考虑数据量增长,使用别名(Alias)管理索引,方便滚动更新。
商品索引结构示例
以一个电商平台的商品索引为例,设计如下:
json
PUT /products
{
"mappings": {
"properties": {
"product_id": { "type": "keyword" }, // 商品唯一ID
"name": {
"type": "text", // 商品名称,支持全文搜索
"fields": {
"keyword": { "type": "keyword" } // 用于精确匹配或聚合
}
},
"description": { "type": "text" }, // 商品描述
"price": { "type": "float" }, // 价格
"stock": { "type": "integer" }, // 库存
"brand": { "type": "keyword" }, // 品牌
"categories": { // 多级分类
"type": "nested",
"properties": {
"level1": { "type": "keyword" }, // 一级分类,如"电子产品"
"level2": { "type": "keyword" }, // 二级分类,如"手机"
"level3": { "type": "keyword" } // 三级分类,如"智能手机"
}
},
"tags": { "type": "keyword" }, // 标签,如"新品""促销"
"create_time": { "type": "date" }, // 创建时间
"sales": { "type": "long" }, // 销量
"rating": { "type": "float" } // 评分
}
},
"settings": {
"number_of_shards": 5, // 根据数据量调整
"number_of_replicas": 1
}
}
设计说明
-
字段类型:
name
使用text
支持全文搜索,同时通过fields
添加keyword
子字段,用于排序或聚合。categories
使用nested
类型,支持多级分类的独立查询,避免数组类型导致的误匹配。brand
、tags
等使用keyword
,适合精确匹配和聚合。
-
分片与副本:
- 分片数根据数据量和查询并发量设置(如5个分片)。
- 副本数设为1,保证高可用,同时避免过多副本增加写压力。
-
性能优化:
- 避免过多字段,聚焦核心字段。
- 对
text
字段启用适当的分词器(如ik_max_word
中文分词器)。 - 使用
nested
类型确保多级分类筛选的准确性。
高效多级分类筛选的实现
在基于ES的商品搜索中,多级分类筛选(如按一级分类"电子产品" -> 二级分类"手机" -> 三级分类"智能手机")是常见需求。以下是实现高效筛选的步骤:
1. 使用nested
查询实现多级分类筛选
由于categories
字段使用了nested
类型,可以通过nested
查询实现精准的多级分类筛选。示例查询如下:
less
SearchRequest searchRequest = new SearchRequest("products");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 构建嵌套查询
NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery(
"categories",
QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("categories.level1", "电子产品"))
.filter(QueryBuilders.termQuery("categories.level2", "手机"))
.filter(QueryBuilders.termQuery("categories.level3", "智能手机")),
ScoreMode.None
);
// 添加查询到请求
searchSourceBuilder.query(QueryBuilders.boolQuery().filter(nestedQuery));
// 可选:添加其他筛选条件,如价格区间
searchSourceBuilder.query(QueryBuilders.boolQuery()
.filter(nestedQuery)
.filter(QueryBuilders.rangeQuery("price").gte(1000).lte(5000))
);
// 可选:添加排序
searchSourceBuilder.sort("sales", SortOrder.DESC);
// 设置分页
searchSourceBuilder.from(0).size(10);
// 执行查询
searchRequest.source(searchSourceBuilder);
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
2. 使用聚合(Aggregation)动态获取分类选项
在筛选界面,通常需要动态展示分类选项(如当前可选择的一级、二级分类)。可以使用nested
聚合实现:
ini
SearchRequest searchRequest = new SearchRequest("products");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 构建嵌套聚合
NestedAggregationBuilder nestedAgg = AggregationBuilders.nested("by_categories", "categories");
TermsAggregationBuilder level1Agg = AggregationBuilders.terms("by_level1").field("categories.level1");
TermsAggregationBuilder level2Agg = AggregationBuilders.terms("by_level2").field("categories.level2");
TermsAggregationBuilder level3Agg = AggregationBuilders.terms("by_level3").field("categories.level3");
// 嵌套聚合层级
nestedAgg.subAggregation(level1Agg.subAggregation(level2Agg.subAggregation(level3Agg)));
searchSourceBuilder.aggregation(nestedAgg);
// 执行查询
searchRequest.source(searchSourceBuilder);
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
3. 性能优化技巧
-
缓存:对热门分类的聚合结果进行缓存,减少ES查询压力。
-
查询优化:
- 使用
filter
上下文,避免不必要的评分计算。 - 限制返回字段(
fetch_source
或fields
),减少网络传输开销。
- 使用
-
索引设计:
- 确保
categories
字段的keyword
类型适合聚合。 - 避免过深的
nested
结构,控制分类层级(如最多3级)。
- 确保
-
异步处理 :使用RestHighLevelClient的异步API(如
searchAsync
),提高并发处理能力。
4. 注意事项
- 数据一致性:分类数据需在写入时规范化(如统一大小写),避免查询时出现不一致。
- 分页问题 :深分页可能导致性能下降,建议使用
search_after
或限制分页深度。 - 分词器选择 :对于商品名称等字段,中文场景建议使用
ik
分词器,并根据需求调整ik_max_word
或ik_smart
。
总结
设计ES索引需要从业务需求、性能优化和扩展性出发,合理定义字段类型(如text
、keyword
、nested
)和索引设置(如分片数)。对于商品搜索的多级分类筛选,nested
类型结合nested
查询和聚合是核心解决方案。通过优化查询结构、缓存结果和异步处理,可以显著提升筛选效率。在实际开发中,还需结合RestHighLevelClient的API,灵活应对复杂查询场景。