基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Elasticsearch 写入文档(Write)的流程

知识点图片

Elasticsearch 的写入流程(Indexing)是其核心机制之一,理解这个流程对于优化写入性能、保证数据一致性以及排查问题至关重要。

ES 的写入流程可以分为两个层面来理解:

  1. 宏观层面(集群与路由):数据如何在节点之间传输。
  2. 微观层面(分片与存储):数据如何在底层 Lucene 和磁盘中处理。

一、 宏观层面:集群与路由流程

当客户端发起一个 Index(写入)请求时,流程如下:

  1. 客户端请求 (Request)
    客户端向集群中的任意一个节点发送写入请求。

    • 接收请求的节点被称为 协调节点 (Coordinating Node)
  2. 路由计算 (Routing)
    协调节点负责确定该文档应该存储在哪个分片(Shard)上。路由公式如下:
    shard=hash(routing)%number_of_primary_shardsshard = hash(routing) \% number\_of\_primary\_shards

    • 默认情况下,routing 值是文档的 _id
    • 计算结果指向一个特定的 主分片 (Primary Shard)
  3. 转发至主分片 (Forwarding)
    协调节点将请求转发给持有该主分片的节点。

  4. 主分片写入 (Primary Operation)
    主分片节点接收到请求后:

    • 验证请求(字段类型等)。
    • 在本地执行写入操作(具体的微观流程见下文)。
    • 如果写入成功,主分片会将该操作并行转发给所有的 副本分片 (Replica Shards)
  5. 副本分片同步 (Replica Operation)
    副本分片接收到主分片的同步请求后,在本地执行写入操作,并向主分片报告成功。

  6. 响应客户端 (Response)
    一旦所有的副本分片(或者满足 wait_for_active_shards 设置的数量)都报告成功,主分片向协调节点报告成功,协调节点再向客户端返回 "Created" 或 "Updated" 响应。


二、 微观层面:分片内部存储流程 (Lucene & Translog)

这是数据真正落地的过程,也是理解 Near Real-Time (近实时)数据持久化 的关键。

1. 写入内存缓冲区与 Translog

当分片(主或副本)接收到写请求时:

  • Memory Buffer (内存缓冲区): 文档首先被写入 Lucene 的内存缓冲区。此时数据不可被搜索
  • Translog (事务日志): 为了防止数据丢失(Crash Safety),文档同时被追加写入到 Translog 中。

2. Refresh (刷新) —— 实现“近实时”搜索

默认情况下,ES 每隔 1秒 执行一次 refresh 操作(或者当 Buffer 满了时):

  • 生成 Segment: 内存缓冲区中的文档被写入到一个新的 Segment (段) 中。
  • 写入 OS Cache: 这个 Segment 被写入到文件系统的缓存(Page Cache)中,而不是直接刷入物理磁盘(因为 fsync 很昂贵)。
  • 打开查询: 新的 Segment 被打开,此时文档可以被搜索到了
  • 清空 Buffer: 内存缓冲区被清空,但 Translog 不会清空。

注意:这就是为什么 ES 被称为“近实时”搜索引擎,因为写入后通常需要 1 秒才能被搜到。

3. Flush (冲刷) —— 实现数据持久化

随着时间推移,Translog 会越来越大。当 Translog 达到一定大小(默认 512MB)或时间间隔(默认 30分钟)时,会触发 flush 操作:

  • Lucene Commit: 所有在 OS Cache 中的 Segment 被强制 fsync 到物理磁盘。
  • 清空 Translog: 旧的 Translog 被删除,创建一个新的 Translog。
  • Commit Point: 记录一个 commit point 文件,标记哪些 Segment 已经持久化。

三、 流程图解总结

plaintext
Client
  |
  v
[Coordinating Node]  <-- 1. 计算路由 hash(_id) % primary_shards
  |
  +-----> [Node A (Primary Shard)]
            |
            +-- 2. Write to Memory Buffer (不可搜索)
            +-- 3. Append to Translog (保证持久性)
            |
            +-- [Refresh Interval (1s)]
            |     |
            |     +-> Write to Segment (OS Cache) -> Open (可搜索)
            |     +-> Clear Memory Buffer
            |
            +-- [Flush (30min / Size limit)]
            |     |
            |     +-> fsync Segments to Disk (持久化)
            |     +-> Clear Translog
            |
            +-----> [Node B (Replica Shard)] (并行同步)
            +-----> [Node C (Replica Shard)] (并行同步)
            |
  <---------+ (Ack)
  |
  v
Client (Success)

四、 核心概念解析

  1. Translog (事务日志)

    • 作用: 解决 Lucene 提交(Commit)成本过高的问题。Lucene 为了性能,不会每条数据都刷盘。Translog 保证了即使节点断电,内存中未刷盘的数据也能通过日志恢复。
    • 配置: 默认 index.translog.durabilityrequest,即每次请求都落盘(安全但慢);设置为 async 可以提高写入性能但有丢失几秒数据的风险。
  2. Segment (段)

    • ES 中的数据是存储在一个个 Segment 中的。Segment 是不可变的(Immutable)。
    • Merge (段合并): 由于 Refresh 频率高,会产生大量小 Segment。后台会有线程负责将小 Segment 合并成大 Segment,并物理删除被标记为 Deleted 的文档。
  3. Consistency (一致性)

    • 通过 wait_for_active_shards 参数控制。例如设置为 all 需要所有副本写入成功才返回,设置为 1 只要主分片成功即可。

五、 写入性能优化建议

基于上述流程,如果需要优化大量数据的写入速度(Bulk Insert):

  1. 加大 Refresh Interval: 将 refresh_interval 从默认的 1s 改为 30s-1(导入期间关闭),减少生成 Segment 的频率。
  2. 使用 Bulk API: 批量写入减少网络开销和协调节点的处理压力。
  3. 调整 Translog: 将 index.translog.durability 设置为 async,减少磁盘 IO 等待。
  4. 减少副本: 在大量导入数据期间,将 number_of_replicas 设置为 0,导入完成后再开启副本(利用恢复机制同步),避免写入时的网络复制开销。
  5. 使用自动生成的 ID: 如果手动指定 ID,ES 需要检查 ID 是否冲突(读盘);自动生成 ID 可以跳过此步骤。
00:00
00:00