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。当某个层次中的段数量超过这个阈值时,就会触发合并操作,将这些段合并为一个更大的段。

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

相关推荐
NE_STOP11 小时前
Vide Coding--AI编程工具的选择
java
大树8811 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
码云数智-园园11 小时前
C++20 Modules 模块详解
java·开发语言·spring
程序员黑豆11 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程
大志哥12311 小时前
ES和Logstash日志链路系统上线后遭遇切片爆炸(解决)
大数据·elasticsearch
东方佑11 小时前
FRSM 规模效应与架构对比补充报告
架构
小宇宙Zz12 小时前
Maven依赖冲突
java·服务器·maven
swordbob12 小时前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
咖啡八杯12 小时前
GoF设计模式——享元模式
java·spring·设计模式·享元模式
十五喵源码网12 小时前
基于springboot2+vue2的租房管理系统
java·毕业设计·springboot·论文笔记