ElasticSearch架构和写入、更新、删除、查询的底层逻辑

ES插入增产改查流程

简单说,ES 架构基于 "分布式集群 + 分片" 实现高可用与横向扩展,而读写删查的底层逻辑则围绕 "倒排索引、分片协作、版本控制" 展开,确保性能与数据一致性。

一、Elasticsearch 核心架构

ES 是分布式搜索引擎,核心架构设计的目标是 "分片存储、集群协作",先明确关键组件及其作用:

1. 集群与节点(Cluster & Node)
  • 集群(Cluster) :由多个节点组成的整体,对外提供统一服务,通过 cluster.name 标识(默认 elasticsearch)。
  • 节点(Node) :单个 ES 进程,按功能分为 3 类核心节点:
    • 主节点(Master Node) :管理集群元数据(如分片分配、节点加入 / 退出),默认每个节点都可参选,通过 node.master: true 配置。
    • 数据节点(Data Node) :存储分片数据,负责读写删查操作,通过 node.data: true 配置(性能敏感,需足够 CPU / 内存 / 磁盘)。
    • 协调节点(Coordinating Node) :接收客户端请求,转发到对应节点并汇总结果,默认所有节点都是协调节点(可通过 node.master: falsenode.data: false 配置纯协调节点)。
2. 索引与分片(Index & Shard)
  • 索引(Index) :类似关系型数据库的 "表",是逻辑上的数据集(如 user_index 存储用户数据)。
  • 分片(Shard) :索引的物理拆分,解决 "单节点存储海量数据" 的问题,分为两类:
    • 主分片(Primary Shard):数据写入的第一站,每个索引默认 1 个(可在创建时指定,创建后不可修改),负责数据的索引构建与存储。
    • 副本分片(Replica Shard):主分片的副本,用于 "高可用" 和 "分担查询压力",数量可动态调整(默认 1 个),仅能从主分片同步数据,不直接接收写入。

例:若 user_index 配置 3 个主分片、2 个副本,则总分片数 = 3(主) + 3×2(副本)= 9 个,数据会均匀分布到 3 个主分片中。

二、写入(Index)底层逻辑

ES 写入数据的核心是 "先写主分片,再同步副本,最后构建倒排索引",确保数据可靠与查询可用,步骤如下:

  1. 请求路由 :客户端发送写入请求到协调节点,协调节点通过公式 shard = hash(routing) % 主分片数 计算数据应写入的主分片(routing 默认是文档 ID,可自定义)。
  2. 主分片写入 :协调节点将请求转发到主分片所在的数据节点,该节点执行以下操作:
    • 验证请求(如字段类型、权限);
    • 将数据写入 内存缓冲区(In-Memory Buffer) ,同时记录 事务日志(Translog)(防止内存数据丢失);
    • 当内存缓冲区满(默认 100MB)或达到刷新间隔(默认 1 秒),触发 刷新(Refresh) :将内存数据生成不可变的 段(Segment) 写入磁盘(但不刷盘,仍在操作系统缓存),此时数据可被查询;
  3. 副本同步:主分片写入成功后,将数据同步到所有副本分片所在节点,副本执行与主分片相同的写入逻辑(内存 + Translog+Segment)。
  4. 最终刷盘:当 Translog 满(默认 512MB)或达到刷盘间隔(默认 30 分钟),触发 ** flush**:将所有 Segment 刷到磁盘,清空 Translog,完成数据持久化。

三、更新(Update)底层逻辑

ES 没有 "原地更新",本质是 "删除旧文档 + 写入新文档",步骤如下:

  1. 查询旧文档:协调节点先根据文档 ID 找到主分片,查询旧文档的版本号(ES 用版本控制避免并发冲突)。
  2. 标记删除 :将旧文档标记为 已删除(Tombstone),不直接物理删除(物理删除会在后续 "段合并" 时执行)。
  3. 写入新文档:按 "写入逻辑" 将更新后的新文档写入主分片,同时版本号 +1。
  4. 同步副本:主分片将 "删除旧文档 + 写入新文档" 的操作同步到副本,确保集群数据一致。

注意:若更新时未指定文档 ID(如 _update_by_query),会先查询符合条件的文档,再逐个执行 "删旧写新",性能较低,建议尽量按 ID 更新。

四、删除(Delete)底层逻辑

ES 同样不支持 "物理删除",而是 "标记删除 + 延迟清理",步骤如下:

  1. 请求路由:协调节点根据文档 ID 找到主分片,发送删除请求。
  2. 标记删除 :主分片将目标文档标记为 Tombstone(墓碑),此时文档不可被查询,但仍占用磁盘空间。
  3. 同步副本:主分片将删除标记同步到所有副本,确保副本也不可查询该文档。
  4. 物理清理 :当 ES 后台执行 段合并(Segment Merge) 时(将多个小 Segment 合并为大 Segment),会过滤掉标记为 Tombstone 的文档,释放磁盘空间。

五、查询(Search)底层逻辑

ES 查询分为 "查询(Query)" 和 "取回(Fetch)" 两阶段,支持 "近实时查询",步骤如下:

1. 查询阶段(Query Phase):找到匹配文档的 ID 和得分
  • 请求分发:协调节点将查询请求广播到索引的所有主分片和副本分片(默认随机选一个,分担压力)。
  • 分片查询 :每个分片在本地的 Segment 中执行查询(基于倒排索引快速匹配),返回 "匹配的文档 ID + 相关性得分(Score)",并按得分排序,取前 N 条(如分页查询的 size 数量)。
  • 结果汇总:协调节点收集所有分片返回的结果,再次按得分排序,筛选出最终的前 N 条文档 ID。
2. 取回阶段(Fetch Phase):获取完整文档数据
  • 请求文档 :协调节点根据最终的文档 ID,向对应的分片(主或副本)发送 "获取文档" 请求,获取完整的文档字段(默认返回所有字段,可通过 _source 筛选)。
  • 返回结果:协调节点汇总所有完整文档,返回给客户端。

优化点:若查询仅需部分字段(如 nameage),可在查询时指定 _source: ["name", "age"],减少数据传输量,提升性能。

六、关键底层技术支撑

  1. 倒排索引(Inverted Index):查询的核心,将 "字段值" 映射到 "文档 ID"(如 "关键词:Elasticsearch" 对应文档 ID 1、3、5),实现快速全文检索。
  2. 段(Segment):ES 存储数据的最小单元,是不可变的 Lucene 索引文件,不可变特性保证了查询性能(无需锁竞争),但也导致更新 / 删除需 "标记 + 重建"。
  3. 版本控制 :通过 _version 字段实现,更新 / 删除时需验证版本号,防止并发操作导致数据覆盖(默认乐观锁,也支持悲观锁)。

七、Elasticsearch 读写删查核心步骤对比表

该表汇总了写入(Index)、更新(Update)、删除(Delete)、查询(Search)四大操作的核心流程、关键技术点及核心目标,方便快速对比和记忆。

操作类型 核心步骤(按执行顺序) 关键技术 / 组件 核心目标
写入(Index) 1. 协调节点按 hash(routing)%主分片数 路由到主分片2. 主分片验证请求,写入内存缓冲区 + Translog3. 触发 Refresh:内存数据生成 Segment(可查询)4. 主分片同步数据到所有副本分片5. 触发 Flush:Segment 刷盘,清空 Translog routing 路由、内存缓冲区、Translog、Segment、Refresh/Flush 数据可靠存储(持久化)、近实时可查
更新(Update) 1. 协调节点路由到主分片,查询旧文档并获取版本号2. 主分片标记旧文档为 Tombstone(逻辑删除)3. 按写入流程将更新后的数据写入主分片(版本号 + 1)4. 同步 "删旧写新" 操作到所有副本5. 旧文档待 Segment Merge 时物理删除 版本控制、Tombstone、Segment Merge 保证数据更新一致性,避免并发冲突
删除(Delete) 1. 协调节点路由到主分片,验证文档存在性2. 主分片标记目标文档为 Tombstone3. 同步删除标记到所有副本分片(副本同步标记后,文档不可查)4. 待 Segment Merge 时,过滤 Tombstone 文档,释放磁盘空间 routing 路由、Tombstone、Segment Merge 快速标记删除(不阻塞操作),延迟清理释放空间
查询(Search) 1. 查询阶段 :- 协调节点广播请求到所有相关分片(主 / 副本)- 分片执行查询(基于倒排索引),返回 "文档 ID + 得分" 并排序- 协调节点汇总结果,再次排序筛选出前 N 个 ID2. 取回阶段:- 协调节点向对应分片请求完整文档数据- 汇总文档,返回给客户端 倒排索引、协调节点汇总、相关性得分(Score)、_source 筛选 快速匹配目标文档,返回准确且排序合理 的结果

Translog 刷盘机制Segment Merge 触发条件

拆解 Translog 刷盘机制Segment Merge 触发条件 的核心细节,帮你搞懂 ES 数据持久化与空间优化的底层逻辑。

一、Translog 刷盘机制:确保写入不丢数据

Translog 是 ES 用于 "防止内存数据丢失" 的关键组件,本质是一个追加写入的日志文件,其刷盘机制直接决定数据的持久化可靠性,核心逻辑可从 "触发时机" 和 "刷盘流程" 两方面看。

1. 核心作用
  • 写入数据时,先写内存缓冲区(In-Memory Buffer),同时同步写 Translog(顺序写,性能高)。
  • 若节点突然宕机,内存缓冲区数据会丢失,但 Translog 已落盘,重启后可通过 Translog 恢复数据,避免丢失。
2. 刷盘触发时机(3 种核心场景)

Translog 并非实时刷盘,而是通过 "定时 + 定量" 机制平衡性能与可靠性,具体触发条件如下:

触发类型 具体条件 配置参数(默认值) 核心目的
定量触发 Translog 文件大小达到阈值 index.translog.flush_threshold_size: 512MB 避免 Translog 文件过大,导致恢复时耗时过长
定时触发 距离上次刷盘时间达到间隔 index.translog.flush_threshold_period: 30m 即使数据量小,也能定期持久化,降低丢失风险
手动触发 调用 API 强制刷盘 执行 POST /索引名/_flush(或 _flushall 全局刷盘) 运维场景(如节点重启前),确保所有数据落盘
3. 刷盘核心流程
  1. 触发刷盘后,ES 会先关闭当前 Translog 文件,新建一个新的 Translog 文件接收后续写入(避免刷盘阻塞新请求)。
  2. 将关闭的 Translog 文件强制刷到磁盘 (调用操作系统 fsync 命令,确保数据真正写入磁盘,而非停留在操作系统缓存)。
  3. 刷盘成功后,清空内存缓冲区中已写入 Segment 的数据(未触发 Refresh 的数据仍在内存),完成一次刷盘周期。

二、Segment Merge 触发条件:优化查询性能 + 释放空间

Segment 是 ES 存储数据的最小单元(不可变),写入时会生成大量小 Segment,过多小 Segment 会导致 "查询时需遍历所有小 Segment,性能下降",且删除 / 更新标记的文档无法释放空间。Segment Merge 就是将 "多个小 Segment 合并为大 Segment" 的后台任务,触发条件分为 "自动触发" 和 "手动触发"。

1. 核心作用
  • 提升查询性能:合并后 Segment 数量减少,查询时只需遍历少量大 Segment,减少 IO 开销。
  • 释放磁盘空间:合并过程中,会过滤掉标记为 Tombstone(删除 / 更新旧文档)的记录,真正释放磁盘空间。
  • 减少元数据开销:每个 Segment 都有独立的元数据,合并后元数据总量减少,降低内存占用。
2. 自动触发条件(4 种核心场景)

ES 会通过后台线程(MergeScheduler)自动检测并触发 Merge,核心判断逻辑基于 "Segment 数量 + 大小":

触发场景 具体条件 配置参数(默认值) 说明
Segment 数量超标 同一分片下,大小小于 merge.policy.floor_segment 的小 Segment 数量超过阈值 merge.policy.max_merge_at_once: 10(一次最多合并 10 个小 Segment)merge.policy.floor_segment: 2MB(小于 2MB 的视为 "小 Segment") 最常见的触发场景,避免小 Segment 堆积
Segment 大小比例超标 当一个 Segment 的大小,是其相邻更大 Segment 大小的一定比例以上 merge.policy.max_merged_segment: 5GB(合并后的大 Segment 最大不超过 5GB)merge.policy.segments_per_tier: 10(每层 Segment 数量不超过 10 个,超过则合并) 防止出现 "大小差异过大的 Segment",平衡查询效率
写入触发 每次 Refresh 生成新 Segment 后,会检查当前 Segment 数量,若超标则触发 Merge 无单独参数,依赖上述 "数量 / 大小" 阈值 写入频繁时,小 Segment 生成快,Merge 也会更频繁
定时触发 后台 Merge 线程会定期(默认 1 秒)检查是否需要合并 indices.memory.index_buffer_size: 10%(间接影响,内存缓冲满触发 Refresh 后,进而可能触发 Merge) 兜底机制,确保即使写入少,也能清理过期数据
3. 手动触发条件(运维场景)

当自动 Merge 不及时(如大量删除后需快速释放空间),可通过 API 手动触发 Merge:

  • 合并指定索引:POST /索引名/_forcemerge
  • 合并所有索引:POST /_forcemerge
  • 关键参数:max_num_segments=1(可选,强制将分片的 Segment 合并为 1 个大 Segment,适合只读索引场景,但会占用大量 IO,需避开业务高峰)
4. 注意事项
  • Merge 是 IO 密集型任务 :合并大 Segment 时会占用大量磁盘 IO 和 CPU,可能影响查询性能。ES 默认会限制 Merge 的 IO 速率(通过 indices.store.throttle.max_bytes_per_sec 配置,默认 20MB/s),避免过度抢占资源。
  • 只读索引建议手动 Merge:对于日志等只读索引(写入后不再更新),可在写入完成后手动触发 _forcemerge max_num_segments=1,最大化查询性能。

Segment为什么分层

在 Elasticsearch 中,"每层 segment" 是指基于TieredMergePolicy(分层合并策略)下,按照段的大小划分的不同 "层级"。

Elasticsearch 使用 Lucene 作为底层索引引擎,Lucene 采用段化存储来管理索引,段是不可变的磁盘索引文件集合,包含倒排索引、正向索引等数据结构。随着数据不断写入,会不断生成新的段,而过多的小段会影响查询性能,因为每个搜索请求都需要访问多个段。为了优化查询性能和磁盘占用,Elasticsearch 会在后台定期执行合并操作,将多个小段合并成较大的段。

TieredMergePolicy会将索引中的段分成若干层,每个层次内的段符合某些大小范围。例如,较小的段可能处于较低的层次,随着不断合并,段的大小逐渐增大,会晋升到更高的层次。每个层次都有一个最大段数限制,由index.merge.policy.segments_per_tier参数控制,默认值为 10。当某个层次中的段数量超过这个阈值时,就会触发合并操作,将这些段合并为一个更大的段。

之所以引入 "层" 的概念,主要是为了更好地管理段的合并过程。通过分层,可以避免过度合并小段,同时确保合并后的段大小合理,能够有效地提升查询性能。如果没有分层的概念,可能会导致频繁地对所有段进行合并,消耗大量的系统资源,或者无法及时将小段合并成大段,影响查询性能。

相关推荐
小鹿学程序3 小时前
虚拟机之间配置免密登录(Centos)
大数据·linux·运维·centos
zandy10113 小时前
架构解析:衡石科技如何基于AI+Data Agent重构智能数据分析平台
人工智能·科技·架构
学Java的bb3 小时前
ElasticSearch-基础
大数据·elasticsearch·搜索引擎
北极糊的狐3 小时前
IntelliJ IDEA插件:CodeGeeX 智能助手插件
java·ide·intellij-idea
悟能不能悟3 小时前
jdk25结构化并发和虚拟线程如何配合使用?有什么最佳实践?
java·开发语言
图扑软件3 小时前
热力图可视化为何被广泛应用?| 图扑数字孪生
大数据·人工智能·信息可视化·数字孪生·可视化·热力图·电力能源
熙客3 小时前
Java8:Lambda表达式
java·开发语言
小咕聊编程3 小时前
【含文档+PPT+源码】基于java web的篮球馆管理系统系统的设计与实现
java·开发语言
wok1574 小时前
如何构建构高性能、高可用、可扩展的集群?
架构·集群