Appearance
Elasticsearch 索引创建与搜索原理
一、索引与分片结构
- 一个 Elasticsearch 索引由一个或多个主分片(Primary Shards)组成。
- 每个分片会进一步由多个segment(段)构成,每个 segment 是一个独立的倒排索引。
- segment 一旦写入后即不可变,只能通过段合并(segment merge)来减少段的数量并提高查询效率。
二、写入流程与原理
写入请求路由
- 客户端向任意节点发送写入请求,该节点作为协调节点(Coordinating Node)。
- 协调节点通过
_id
及路由算法shard = hash(routing) % number_of_primary_shards
确定文档应该存储到的主分片。注意:分片的数量在索引创建时确定,后期只能通过 reindex 重建索引。
- 协调节点将文档发送至对应主分片所在的节点。主分片节点将文档写入本地并同步到相应的副本分片。
- 所有分片写入成功后,向协调节点返回成功响应,最终返回给客户端。
写入缓冲与可见性
- 文档写入时会先记录到 translog(事务日志,存储于磁盘)中,并暂存于 in-memory buffer(内存缓冲区)。
- 在完成写入后,刚写入的文档仍不可检索,只有执行了 refresh 操作后,文档才会对搜索可见。
Refresh 操作
refresh_interval
(默认 1s)会由 Elasticsearch 定期自动触发或可手动调用_refresh
接口,创建一个新的搜索器视图。- 执行过程:
- 将 in-memory buffer 中的文档写入新的 segment(存储于磁盘)。
- 清空 in-memory buffer(但不会清空 translog)。
- 创建新的搜索器,使新写入的文档可被查询到。
- 由于 segment 不可变,频繁的 refresh 会导致小 segment 过多,需要后台的 segment merge 来优化。
Flush 操作
- Flush 主要用于生成安全的提交点(commit),确保数据持久化并清空 translog。
- 执行过程:
- 将还未刷新到 segment 的文档写入新的 segment 并执行 Lucene commit,确保数据安全落盘。
- 清空 translog(因为有最新的 commit 点,重启时可从 segment 重建索引状态)。
- 注意:Flush 并不是段合并操作,段合并由 Elasticsearch 的后台进程定期执行,用于减少 segment 数量并提高检索性能。
三、查询与检索过程(Query Then Fetch)
Elasticsearch 默认的查询模式是 QUERY_THEN_FETCH,可以分为两个阶段:
查询阶段(Query)
- 客户端发送查询请求至协调节点。
- 协调节点将查询分发至集群内每个需要查询的分片(主分片或副本分片)。
- 每个分片在本地执行查询,对文档打分并根据
from+size
只返回最高分文档 ID 及相关排序信息给协调节点。 - 协调节点对来自各分片的结果做合并与全局排序,确定最终要返回的文档集合。
取回阶段(Fetch)
- 协调节点根据全局排序结果,向对应的分片节点发送 fetch 请求,获取实际文档内容(如
_source
)。 - 分片节点将文档内容返回给协调节点;协调节点汇总后,把最终结果返回给客户端。
- 协调节点根据全局排序结果,向对应的分片节点发送 fetch 请求,获取实际文档内容(如
集群规划及节点角色规划最佳实践
在设计和规划 Elasticsearch 集群时,需要确定节点角色与数量,保证稳定、高效。
一、节点角色
候选主节点(Master-eligible Node)
- 负责创建/删除索引、管理分片分配。
- 重要性:一个稳定的主节点对保持集群健康至关重要。
- 通过投票选举产生实际主节点。
协调节点(Coordinating Node)
- 类似“智能负载均衡器”,负责查询的分发(scatter)与结果汇总(gather)。
- 可以减轻数据节点在查询时的合并压力。
数据节点(Data Node)
- 执行大部分 CRUD、搜索和聚合操作。
- I/O、内存、CPU 等资源使用都相对密集。
Ingest 节点
- 负责写入前的数据预处理(如管道处理、数据清洗)。
下表总结了常见节点角色及所需资源需求(仅作参考):
角色 | 描述 | 存储 | 内存 | 计算 | 网络 |
---|---|---|---|---|---|
数据节点 | 存储和检索数据 | 极高 | 高 | 高 | 中 |
主节点 | 管理集群状态 | 低 | 低 | 低 | 低 |
Ingest 节点 | 转换/预处理输入数据 | 低 | 中 | 高 | 中 |
机器学习节点 | 内置的机器学习分析 | 低 | 极高 | 极高 | 中 |
协调节点 | 请求转发与结果合并 | 低 | 中 | 中 | 中 |
二、实战建议
不混合部署
- 一台物理机或虚拟机只部署一个 Elasticsearch 节点,避免端口冲突与资源争用。
- 不要在同一台机器上混合部署其他应用,如 Redis、Kafka 等。
大型集群要独立节点角色
- 不建议将主节点当作协调节点,尽量有独立的协调节点。
- 不建议将主节点和数据节点混合部署,尽可能保证角色单一。
- 客户端配置连接的节点应尽量指定为协调节点。
超大规模集群建议
- 典型规划:3 个主节点 + 3 个协调节点 + 其他数据节点。
- 主节点硬件配置建议:8C16G 以上。
- 当写入量极大时,数据节点建议配置:32C64GB(可支撑约 5 万/s 写入)。
集群硬件与规模规划
在正式部署前,需要对集群所处环境做硬件与资源层面的规划。
一、硬件层面
CPU
- 直接决定计算能力;影响索引、查询、聚合的并发度。
内存
- 堆内存:一般建议占系统内存的 50%,且不超过 32GB;过大可能导致 G1、CMS 等 GC 阶段效率下降。
- 堆外内存(操作系统缓存):用于缓存 Lucene 段文件,加快全文检索、聚合与排序。
磁盘
- SSD:适用于高并发、低延迟要求的“热”数据存储。
- 机械硬盘:可用于存放“暖”、“冷”数据。结合生命周期管理,减少硬件成本。
网络
- 在大规模集群中,ingest、搜索和副本复制都会产生大量数据传输,可能导致带宽瓶颈。
- 要确保节点之间有足够的网络带宽与低延迟。
二、节点规划
- 参考前文角色规划,根据业务场景与预算确定节点数量与类型。
- 小规模集群可混合角色,但仍需注意 Master 节点的稳定性。
三、分片和副本规划
1. 用途
分片(Shards)
- 提升系统水平扩展能力,使大型索引能分割到多台机器并行处理读写。
- 分割超大索引,便于并行读写。
副本分片(Replicas)
- 增强高可用:若主分片故障,可用副本快速切换。
- 提升读取吞吐:查询可以在副本分片上并行执行。
- 副本分片数可动态修改,但主分片数不可。
2. 主分片数量设置不当
分片设置过少
- 一旦出现故障,可能导致数据恢复难度增大。
- 无法充分利用多节点资源,影响写入/查询并行度和效率。
分片设置过多
- 会产生大量的 segment,文件句柄数升高,可能导致集群崩溃。
- 主节点需要管理大量元数据,可能超载甚至导致集群无响应。
- 写入/查询请求被拆分得过碎,跨分片的搜索会耗尽搜索线程池。
- 分片过多会增加内存和 CPU 的开销,导致查询和写入性能下降。
3. 分片设置参考
- 单个 Lucene segment 最大支持:约 2^32 ≈ 20 亿文档。
- 单个分片大小官方建议 30GB-50GB 左右。
- 1GB 堆内存大约支持 20-30 个分片。
- 单节点支持分片数不建议超过 1000(
cluster.max_shards_per_node
默认为 1000)。
4. 数据量参考
数据量较小(<100GB)的索引
- 一般可设置 3-5 个主分片(视节点数而定),副本数设置为 1。
数据量较大(>100GB)的索引
- 单分片控制在 30GB-50GB,让索引压力分摊到多个节点。
- 可通过
index.routing.allocation.total_shards_per_node
参数进行分片均匀分配。
大规模集群
- 若 shard 数量(不含副本)超过 50,常会引发查询拒绝率上升。
- 可考虑将一个 index 拆分为多个 index,或采用 rollover/ILM 等方案。
- 搭配自定义 routing,降低每个查询需要访问的 shard 数量。
四、容量规划
1. 需要考虑的关键点
- 每天索引多少原始数据?保留多少天?
- 副本设置多少?
- 单个数据节点内存/磁盘配置多少?
- 业务是否有足够的预留空间以应对突发增长或后期扩容?
2. 基本计算方法
总数据量(GB)
[ \text{每天原始数据量(GB)} \times \text{保留天数} \times \text{净膨胀系数} \times (\text{副本数}+1) ]磁盘存储(GB)
[ \text{总数据量(GB)} \times (1 + 0.15 + 0.05) ]这里的 0.15、0.05 分别表示给磁盘保留的 15% “警戒水位”空间和 5%“活动余量”。
数据节点数量
[ \left\lceil \frac{\text{磁盘存储(GB)}}{\text{每个数据节点的磁盘空间} \times 0.85} \right\rceil ]乘以 0.85 是预留一些富余空间。
可结合测试工具 esrally 等,对性能和容量做更准确测算。
集群性能调优及原理
一、性能调优整体思路
1. 硬件资源优化
CPU(线程池与队列)
- 主要关注 Search Thread Pool(
search
/suggest
/count
等)和写入(bulk
)线程池等。 - 建议的线程池大小可参考:
int((# of allocated processors * 3)/2) + 1
,也需要结合实际负载进行调优。
- 主要关注 Search Thread Pool(
内存
- JVM 堆:一般设置为物理内存的 50%,上限为 32GB。
- 堆外内存:留给操作系统做文件系统缓存。
磁盘
- 优先使用本地 SSD,远程文件系统(如 NFS)会导致双重开销。
- 根据冷热数据分层,匹配不同磁盘类型。
2. 参数与集群配置调优
- 写入独占节点:在大集群中,把大批量写入任务独立到专用节点,减少对检索的干扰。
- 路由设置:针对同一业务数据采用固定的路由,既能减少查询时的分片访问范围,又能提高写入的局部性。
- Index Sorting:对满足排序需求的字段提前进行物理排序,可减少查询阶段的数据扫描量。
3. 调优经验
使用缓存
- 合理利用操作系统文件系统缓存。
- 针对不参与打分的过滤场景,可使用
filter
查询,大幅提高缓存命中率。
“不不不不” 策略
- 不滥用前缀模糊匹配(
wildcard
等)。 - 不执行深度分页(
from+size
过大影响性能)。 - 不返回不必要的字段(限制
_source
或stored_fields
)。 - 不执行复杂度极高的嵌套聚合或关联(
join
、nested
)。
- 不滥用前缀模糊匹配(
查询语句优化
- 合理使用
match_phrase
、term
、range
等不同查询子句。 - 合并小查询,减少多次 I/O 开销。
- 合理使用
数据模型优化
- 避免复杂关联,多表 join 或 nested 结构会造成查询负担。
- 进行冷热分层,存储与检索重点不同的数据于不同硬件。
- 重要数据可在系统初始化或更新后进行“预热”,提升缓存命中。
二、检索性能调优
1. 常见检索类型及优化要点
Wildcard 查询
- 高危操作,若可行应采用
ngram
分词或match_phrase + slop
替代。 - 避免通配符
*
出现在开头(*foo
)。 - 正则查询也需谨慎,限制用户随意使用复杂正则。
- 高危操作,若可行应采用
match_phrase
替代match
match_phrase
精度更高,更少无关文档的干扰。
query_string
- 若需复杂的布尔逻辑检索,可使用该查询,自带解析器。
合并检索
- 一次多条件综合检索通常比多次单一检索效率更高。
range
查询替换- 若字段只有几个固定枚举值,可用
term
替换range
。
- 若字段只有几个固定枚举值,可用
充分利用缓存
- 无需打分的查询部分可采用
filter
,极大提升缓存命中。
- 无需打分的查询部分可采用
精简 DSL
- 仅检索与返回必要字段,减少
_source
或stored_fields
的开销。
- 仅检索与返回必要字段,减少
聚合优化
- 默认聚合结果是非精确的 Top-N,如需全量精确统计,开销更大。
- 业务可接受近似时,可限制聚合深度或使用分段统计策略。
深度翻页
from-size
大会影响查询性能,上限默认 10000。- 可用
scroll
处理离线批量查询;可用search_after
实现实时深度分页,但也要谨慎。 - 业务侧应避免一次性全量聚合或全量分页的需求。
避免返回大数据集
- 大批量数据处理可用
Scroll API
,并控制单批大小。 - 高亮或大字段会增加 I/O 与缓存负载;仅索引/存储关键字段。
- 仅统计总数时使用
_count
。
- 大批量数据处理可用
冷热数据隔离
- 近期热数据放在 SSD 等高性能存储节点,历史数据可迁移到低成本节点。
- 搭配 ILM 或 curator 进行自动 rollover 和别名管理。
三、写入性能调优
副本分片设置
- 大批量写入前可将副本数设为 0,待写入结束再恢复以减少同步负载。
ID 生成
- 优先使用 ES 自动生成的
_id
,避免自定义 ID 带来的额外 hash 开销。
- 优先使用 ES 自动生成的
刷新频率
- 调大
refresh_interval
(如 30s)可减少频繁 segment merge 对写入性能的影响。
- 调大
索引缓冲区大小
- 合理增大
index_buffer
,减少频繁写入和合并,但要注意 JVM 堆占用。
- 合理增大
保证堆外内存空间
- 预留足够物理内存给操作系统缓存,可有效提升索引写入与检索效率。
Bulk 批量写入
- 批量写入性能远高于单条写入,也可减少网络 overhead 与段合并开销。
多线程并发
- 在客户端或中间层多线程并行写入,配合 Bulk 降低单节点写入压力。
线程池与队列大小
- 调整
write
/bulk
线程池大小和队列长度,避免阻塞或拒绝请求。
- 调整
合理设置 Mapping
- 生产环境避免默认
dynamic mapping
,对各字段类型(text/keyword/number 等)进行显式定义。 - 分词器(如 IK)需根据业务场景设置。
- 生产环境避免默认
分词器选择
- 分词越细,索引体量越大,但检索召回率更好;需要在性能和需求间平衡。
磁盘选择
- 高写入量场景下,SSD 依旧是“终极手段”之一。
集群节点角色拆分
- 大规模集群中,主节点、数据节点、协调节点分开,保证写入和检索的各自效率。
官方客户端
- 官方客户端更好地支持连接池、连接管理与版本兼容性。
四、典型实战问题分析
1. 用户画像宽表性能问题
问题场景
- 某些团队将用户画像存入单一宽表索引,字段上千甚至破千后性能显著下降。
解决思路
- 字段裁剪:保留真正业务所需字段,减少无关字段的索引和存储。
- Mapping 优化:针对不同字段类型(
text
、keyword
、nested
等)做精细设计。 - 索引分拆:按业务功能或字段分组拆分为多个索引或使用索引模式(index pattern)。
- 冷热分层:将部分历史字段或数据做分层,减少单索引宽度。
2. 高写入量导致查询性能下降
问题场景
indexing rate
达到 5 万条/s,查询性能显著下降。
解决思路
- 专用写入节点:把写操作和检索操作物理隔离。
- Bulk 写入:减少单条写入带来的管理开销。
- 刷新频率:调大
refresh_interval
。 - 分片数量:平衡分片数量,不宜过多也不可过少。
- 线程池:
search
与write
线程池独立配置,防止相互抢占。 - 冷热数据:历史数据迁移至冷节点,减轻热节点压力。
3. 一次性召回 4000 篇文档,目标 30ms 内完成
思路
- 路由或索引拆分:若可确定召回范围,使用路由减少分片查询量。
- 预热与缓存:对热门查询或常用 segment 做预热。
- 硬件与角色分离:使用 SSD、大内存,拆分协调节点和数据节点,提高查询并行度。
- DSL 精简:只返回必要字段,减少网络开销。
- Index Sorting:对常用排序字段预排序。
4. 检索 30 天数据,共 1.9 亿条记录,耗时 50s
场景
- 三台服务器(8 核 32GB),ES 堆内存仅 6GB,其余资源被其他服务占用;每天一个索引,6 个分片,保留 30 天共 180 个分片。
- Kibana 默认查询过去 30 天且无过滤条件,耗时 50s。
瓶颈与优化
- 查询无过滤:每个索引 20GB,全部分片都要扫描,耗时巨大。
- 内存不足:堆外缓存也有限,难以缓冲大规模 segment。
- 过多分片:180 个分片会增加并发开销及主节点调度负荷。
- 聚合/排序:Kibana 可能带默认聚合,进一步增大开销。
解决思路
- 先做业务过滤:限定时间或类型范围,减少扫描量。
- 减少分片数量:对旧索引进行合并或减少分片。
- 增大内存:适度提升堆内存 (不超过 32GB) 并给操作系统更多缓存空间。
- 冷热分层:热数据放 SSD,历史数据放冷节点。
- 精简聚合:尽量避免无必要的全量聚合。
数据建模最佳实践
数据建模是确保高效写入和检索的关键,以下总结了常见的设计要点。
一、Mapping 设计
- Elasticsearch 不支持对已有字段做删除或类型修改,如需变更只能通过 reindex。
- 避免使用默认
dynamic mapping
,尽量在设计阶段对核心字段进行明确定义(text/keyword/number 等)。 - 对需要全文检索的字段使用
text
;只做精确匹配、聚合、排序的字段用keyword
;数值字段选择最合适的类型(long、integer 等)。 - 可以结合
multi_fields
(例如对同一字符串字段既做 IK 分词,又做 keyword)满足多种检索需求。 - 对一些不需要检索或排序聚合的字段,可通过
index:false
、enabled:false
或关闭doc_values
等方式减少开销。
常用字段类型及建议
- text:用于分词检索,常搭配
analyzer
进行中文分词。 - keyword:适用于精确匹配(如状态码、标签等),也支持排序、聚合。
- numeric:long、integer、double 等,多用于数值查询、聚合。
- date:日期字段,内部以 long 存储,支持 range 查询及聚合。
- boolean:只存 true/false。
- nested / join:一对多或父子关系,更新与查询都更复杂,需谨慎使用。
二、单索引建模
1. Settings
- index.number_of_shards(主分片数,创建后不可改)
- index.number_of_replicas(副本数,可动态调整)
- index.refresh_interval(默认 1s,业务对实时性要求不高可调大)
- index.max_result_window(默认 10000,深度分页需求大时可调高,但会影响性能)
2. Mapping
- 强烈建议提前定义 Mapping,严格控制
dynamic
为strict
或false
。 - 优化字段类型、分词器和索引选项(
index_options
、norms
、doc_values
等)。
3. Multi-fields 示例
json
PUT mix_index
{
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"standard": {
"type": "text",
"analyzer": "standard"
},
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
content
字段既用ik_max_word
做细粒度中文分词,又可以通过content.keyword
精确匹配或聚合。
三、多索引建模
- 建议**使用别名(alias)**访问索引,可在做索引切换或数据迁移时实现“热切换”而不影响上层应用。
- 可使用索引模板对相同模式的多个索引统一管理(Settings、Mappings、Aliases 等)。
- 在海量数据(TB 级甚至 PB 级)场景下,可使用 ILM(Index Lifecycle Management)或 rollover 分段管理索引。
1. 索引模板(template)
- index_patterns:匹配特定前缀或通配的索引名称。
- order:多个模板生效时的优先级,数字越大优先级越高。
- 设置默认别名、默认 pipeline、映射等。
2. 别名(alias)
- 可以为索引创建一个或多个别名,读写都可通过别名进行操作。
- 可以原子化操作一个别名从旧索引切换到新索引,实现在用户无感知下完成数据更新/迁移。
3. Pipeline
- Ingest Pipeline 可在文档写入时进行预处理(例如字段转换、日志解析等)。
- 可通过
index.default_pipeline
指定默认的 pipeline。
4. ILM / Rollover
- ILM(Index Lifecycle Management):自动管理索引从 hot → warm → cold → delete 各阶段。
- Rollover:当索引达到一定大小或文档数后,自动滚动创建新索引。
- 结合别名可将“写”切换到新索引,“读”既可在新旧索引上进行,也能进一步用 curator 或 ILM 做清理。
冷热集群架构
将数据分层为“热数据”(hot)与“冷数据”(cold),并分配到硬件条件不同的节点上,以节约成本并提高查询效率。
实现步骤
设置节点属性
在elasticsearch.yml
中配置:yamlnode.attr.box_type: hot
可通过
_cat/nodeattrs
查看节点属性。分片分配策略
httpPUT logs_01 { "settings": { "index.routing.allocation.include.box_type": "hot", "number_of_replicas": 0 } }
表示此索引仅能分配到
box_type=hot
的节点上。数据迁移
当数据老化,需要迁移到冷节点时:httpPUT logs_01/_settings { "index.routing.allocation.include.box_type": "cold" }
Elasticsearch 会将该索引分片迁移至
box_type=cold
节点。
索引生命周期管理(ILM)实战
- 配置 ILM Policy
- 定义各个阶段(hot、warm、cold、delete)和执行的操作(rollover、shrink、freeze、delete 等)。
- 创建模板并绑定该 Policy,指定别名。
- 创建初始索引后,后续自动按照 Policy 进行滚动、迁移或删除。
跨集群检索实战
- Cross Cluster Search:可以在一个集群上查询另一个远程集群的索引。
- 需要在本地集群做远程集群配置,并通过
remote_cluster_name:index_name
来检索。
分片分配策略
分片分配是主节点的核心职能之一,会在以下情况触发:
- 集群启动或分片初始恢复时。
- 新副本分配或重新平衡时。
- 有节点加入或退出时。
一、集群级分片分配策略
基于磁盘的分片分配
cluster.routing.allocation.disk.watermark.low
(默认 85%):超过此阈值会阻止新的副本分配到该节点上。cluster.routing.allocation.disk.watermark.high
(默认 90%):超过此阈值会触发分片重新分配。cluster.routing.allocation.disk.watermark.flood_stage
(默认 95%):超出后对索引进行只读保护。
跨机房 / 分区感知
- 可以设置节点属性
node.attr.rack_id
并通过cluster.routing.allocation.awareness.attributes
强制分片在不同机架/机房之间进行分配,提升容灾能力。
- 可以设置节点属性
二、索引级分片分配策略
- index.routing.allocation.total_shards_per_node:控制单个索引每个节点上能分配的最大分片数,防止分片过度集中。