1. Elasticsearch索引设计核心原则
Elasticsearch的索引设计直接影响查询性能、存储效率和系统可维护性。我在实际项目中总结出几个关键设计原则:
1.1 按业务场景划分索引
不要把所有数据塞进单个索引,应该根据业务边界划分。例如电商系统可以拆分为:
- products_index(商品数据)
- orders_index(订单数据)
- users_index(用户数据)
这样设计的好处是:
- 不同业务的数据生命周期管理策略可以独立配置
- 避免单个索引过大导致性能下降
- 故障隔离,某个索引问题不会影响全站
1.2 冷热数据分离
对于时序数据(如日志、交易记录),建议采用时间滚动索引模式:
- 按天/周创建索引(如logs-2023-08-01)
- 热索引(最近数据)部署在SSD节点
- 冷索引(历史数据)迁移到HDD节点
配合ILM(Index Lifecycle Management)可以自动完成:
json复制PUT _ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50GB",
"max_age": "1d"
}
}
},
"delete": {
"min_age": "30d",
"actions": {
"delete": {}
}
}
}
}
}
1.3 控制分片数量
分片数一旦创建就不能修改,需要提前规划:
- 每个分片建议10-50GB数据
- 分片总数 = 节点数 × 每节点最大分片数(默认1000)× 0.8(安全系数)
- 对于日志类数据,可以使用1个主分片+0副本
注意:分片过少会导致无法水平扩展,过多则增加集群负担。我曾经在一个项目中因为设置100个分片导致集群元数据占用10GB内存。
2. 映射(Mapping)设计实战技巧
2.1 字段类型选择
Elasticsearch的动态映射经常会产生不符合预期的类型,应该明确定义:
| 业务场景 | 推荐类型 | 配置示例 |
|---|---|---|
| 商品标题 | text + keyword | "type": "text", "fields": {"raw": {"type": "keyword"}} |
| 价格数值 | scaled_float | "type": "scaled_float", "scaling_factor": 100 |
| 地理坐标 | geo_point | "type": "geo_point" |
| 时间戳 | date | "type": "date", "format": "epoch_millis" |
2.2 特殊字段优化
- 嵌套对象:使用
nested类型而非默认的object
json复制"comments": {
"type": "nested",
"properties": {
"user": {"type": "keyword"},
"content": {"type": "text"}
}
}
- 高基数字段:启用
eager_global_ordinals
json复制"product_id": {
"type": "keyword",
"eager_global_ordinals": true
}
2.3 禁用不必要的特性
json复制PUT my_index
{
"mappings": {
"_source": {
"enabled": false // 如果不需要文档重建
},
"_all": {
"enabled": false // ES6+已废弃
}
}
}
3. 性能优化关键参数
3.1 索引刷新间隔
默认1秒刷新会导致大量小段(segment),调整为:
json复制PUT my_index/_settings
{
"index.refresh_interval": "30s" // 写入密集型场景可设更大
}
3.2 合并策略调优
json复制PUT my_index/_settings
{
"index.merge.policy": {
"segments_per_tier": 10,
"max_merged_segment": "5gb"
}
}
3.3 查询性能优化
- 对过滤条件字段设置
doc_values: true - 对不参与排序/聚合的字段设置
index: false
json复制"product_name": {
"type": "text",
"index_options": "docs" // 只索引文档不索引词频
}
4. 常见问题解决方案
4.1 映射爆炸问题
症状:字段数超过默认1000限制
解决方法:
json复制PUT _cluster/settings
{
"persistent": {
"index.mapping.total_fields.limit": 2000
}
}
更好的方案是重构数据模型,使用flattened类型处理动态字段。
4.2 索引性能下降
排查步骤:
- 检查
_nodes/hot_threads - 查看
_cat/thread_pool?v - 分析
_stats/merge
4.3 分片不均
使用_cluster/reroute手动调整:
json复制POST _cluster/reroute
{
"commands": [
{
"move": {
"index": "my_index",
"shard": 0,
"from_node": "node1",
"to_node": "node2"
}
}
]
}
5. 实战案例:电商搜索设计
5.1 商品索引示例
json复制PUT products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s"
},
"mappings": {
"properties": {
"id": {"type": "keyword"},
"name": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"raw": {"type": "keyword"}
}
},
"price": {"type": "scaled_float", "scaling_factor": 100},
"categories": {
"type": "nested",
"properties": {
"id": {"type": "keyword"},
"name": {"type": "keyword"}
}
},
"specs": {"type": "flattened"},
"created_at": {"type": "date", "format": "strict_date_optional_time||epoch_millis"}
}
}
}
5.2 查询优化技巧
- 多条件查询使用bool组合:
json复制GET products/_search
{
"query": {
"bool": {
"must": [
{"match": {"name": "手机"}}
],
"filter": [
{"range": {"price": {"gte": 1000, "lte": 5000}}},
{"term": {"categories.name": "电子产品"}}
]
}
}
}
踩坑记录:曾经因为未使用nested类型导致聚合统计错误,商品分类的统计结果比实际多出30%。改用nested后查询性能下降但结果准确,最终通过增加
include_in_parent: true平衡性能。
