Elasticsearch 写入文档(Write)的流程
Elasticsearch 的写入流程(Indexing)是其核心机制之一,理解这个流程对于优化写入性能、保证数据一致性以及排查问题至关重要。
ES 的写入流程可以分为两个层面来理解:
- 宏观层面(集群与路由):数据如何在节点之间传输。
- 微观层面(分片与存储):数据如何在底层 Lucene 和磁盘中处理。
一、 宏观层面:集群与路由流程
当客户端发起一个 Index(写入)请求时,流程如下:
客户端请求 (Request)
客户端向集群中的任意一个节点发送写入请求。- 接收请求的节点被称为 协调节点 (Coordinating Node)。
路由计算 (Routing)
协调节点负责确定该文档应该存储在哪个分片(Shard)上。路由公式如下:- 默认情况下,
routing值是文档的_id。 - 计算结果指向一个特定的 主分片 (Primary Shard)。
- 默认情况下,
转发至主分片 (Forwarding)
协调节点将请求转发给持有该主分片的节点。主分片写入 (Primary Operation)
主分片节点接收到请求后:- 验证请求(字段类型等)。
- 在本地执行写入操作(具体的微观流程见下文)。
- 如果写入成功,主分片会将该操作并行转发给所有的 副本分片 (Replica Shards)。
副本分片同步 (Replica Operation)
副本分片接收到主分片的同步请求后,在本地执行写入操作,并向主分片报告成功。响应客户端 (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 已经持久化。
三、 流程图解总结
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)
四、 核心概念解析
Translog (事务日志)
- 作用: 解决 Lucene 提交(Commit)成本过高的问题。Lucene 为了性能,不会每条数据都刷盘。Translog 保证了即使节点断电,内存中未刷盘的数据也能通过日志恢复。
- 配置: 默认
index.translog.durability为request,即每次请求都落盘(安全但慢);设置为async可以提高写入性能但有丢失几秒数据的风险。
Segment (段)
- ES 中的数据是存储在一个个 Segment 中的。Segment 是不可变的(Immutable)。
- Merge (段合并): 由于 Refresh 频率高,会产生大量小 Segment。后台会有线程负责将小 Segment 合并成大 Segment,并物理删除被标记为 Deleted 的文档。
Consistency (一致性)
- 通过
wait_for_active_shards参数控制。例如设置为all需要所有副本写入成功才返回,设置为1只要主分片成功即可。
- 通过
五、 写入性能优化建议
基于上述流程,如果需要优化大量数据的写入速度(Bulk Insert):
- 加大 Refresh Interval: 将
refresh_interval从默认的1s改为30s或-1(导入期间关闭),减少生成 Segment 的频率。 - 使用 Bulk API: 批量写入减少网络开销和协调节点的处理压力。
- 调整 Translog: 将
index.translog.durability设置为async,减少磁盘 IO 等待。 - 减少副本: 在大量导入数据期间,将
number_of_replicas设置为 0,导入完成后再开启副本(利用恢复机制同步),避免写入时的网络复制开销。 - 使用自动生成的 ID: 如果手动指定 ID,ES 需要检查 ID 是否冲突(读盘);自动生成 ID 可以跳过此步骤。