Elasticsearch 数据写入流程完全解析:从客户端到持久化的 7 步旅程
作者:IT之一小佬
发布日期:2025年10月22日
阅读时间:22分钟
适合人群:Elasticsearch 开发者、运维、SRE
🌟 引言:一次 PUT 请求背后的复杂世界
当你执行:
curl -X PUT "localhost:9200/logs/_doc/1" -H 'Content-Type: application/json' -d'
{ "message": "Hello World" }'
Elasticsearch 并非简单地将数据“保存”到磁盘。这背后是一套精密的分布式写入协议,涉及:
- ✅ 路由计算(Routing)
- ✅ 内存缓冲(In-Memory Buffer)
- ✅ 事务日志(Translog)
- ✅ 倒排索引构建(Lucene)
- ✅ 分片复制(Replication)
本教程将带你深入 Elasticsearch 数据写入流程的 7 个核心步骤,揭开其高性能、高可用的秘密。
一、写入流程全景图
[客户端]
↓ (1. 接收请求)
[协调节点 Coordinator]
↓ (2. 路由计算)
[主分片 Primary Shard]
↓ (3. 写入内存 + Translog)
[副本分片 Replica Shards]
↓ (4. 同步复制)
[主分片确认]
↓ (5. 客户端响应)
[后台刷新 Refresh]
↓ (6. 可搜索)
[后台合并 Flush]
↓ (7. 持久化)
二、Step 1:请求接收(Coordinator Node)
角色
- 默认情况下,任何数据节点都可作为协调节点
- 负责:
- 接收客户端请求
- 转发到正确分片
- 汇总响应
流程
- 客户端发送
PUT /logs/_doc/1请求 - 集群中任意节点接收(称为 协调节点)
- 协调节点不直接处理,而是转发给负责该文档的主分片
💡 优化建议:使用专用协调节点或让客户端直连数据节点以减少跳数。
三、Step 2:路由计算(Routing)
目标
确定文档应写入哪个主分片。
公式
shard_num = hash(routing) % number_of_primary_shards
参数详解
| 参数 | 默认值 | 说明 |
|---|---|---|
routing | _id 的值 | 可通过 URL 参数指定 ?routing=user_888 |
number_of_primary_shards | 索引设置 | 创建后不可更改 |
示例
// 索引 logs 有 3 个主分片
PUT /logs/_doc/1
// routing = "1"
// hash("1") = 123456789
// shard_num = 123456789 % 3 = 0 → 写入 P0
四、Step 3:主分片写入(Primary Shard)
这是写入流程的核心阶段,包含两个关键操作:
1. 写入 In-Memory Buffer
- 文档被添加到内存缓冲区
- 目的:快速接收写入,避免频繁刷盘
- 数据状态:不可搜索
2. 写入 Translog(Transaction Log)
- 同时追加一条记录到 Translog 文件
- 目的:持久化,防止节点宕机导致数据丢失
- 同步刷盘策略:
PUT /logs/_settings { "index.translog.durability": "request", // 默认:每次请求都 fsync // "index.translog.durability": "async" // 异步,性能更高但可能丢数据 }
✅ 此时数据已安全:即使 JVM 崩溃,重启后可从 Translog 恢复。
五、Step 4:副本分片复制(Replica Shards)
复制协议
Elasticsearch 使用同步复制(Sync Replication)确保数据一致性。
流程
- 主分片向所有副本分片并行发送写请求
- 副本分片执行相同操作:
- 写入自己的 In-Memory Buffer
- 写入自己的 Translog
- 副本分片返回 ACK 给主分片
超时与重试
// 设置复制超时(默认 1min)
PUT /logs/_settings
{
"index.write.wait_for_active_shards": "all" // 或 2, default
}
- 如果副本未在超时内 ACK,主分片会重试或返回错误。
六、Step 5:确认与响应
条件
只有当以下条件满足时,写入才成功:
- ✅ 主分片写入成功
- ✅ 足够数量的副本返回 ACK
计算“足够数量”
minimum_required = int( (primary + replica) / 2 ) + 1
例如:1 主 + 2 副 → 至少 2 个分片成功(主 + 1 副)
响应客户端
{
"_index": "logs",
"_id": "1",
"_version": 1,
"result": "created", // or "updated"
"forced_refresh": false
}
⚠️ 注意:此时数据仍不可搜索,需等待
refresh。
七、Step 6:刷新(Refresh)— 数据可搜索
目标
使新写入的文档可被搜索。
默认机制
- 每 1 秒 执行一次
refresh - 将 In-Memory Buffer 中的文档构建成新的 Lucene Segment
- 清空 Buffer
手动触发
# 立即刷新(影响性能,慎用)
POST /logs/_refresh
# 写入时立即刷新
PUT /logs/_doc/1?refresh=true
性能权衡
| 刷新间隔 | 搜索实时性 | 写入吞吐 |
|---|---|---|
| 1s(默认) | 高 | 中 |
| 30s | 低 | 高 |
| 关闭 | 极低 | 最高 |
PUT /logs/_settings
{
"index.refresh_interval": "30s"
}
八、Step 7:持久化(Flush)— Translog 归档
目标
- 清理 Translog 文件
- 确保 Lucene Index 完全持久化
触发条件(任一满足)
- 每 30 分钟(默认)
- Translog 太大(
index.translog.flush_threshold_size,默认 512MB) - Buffer 中文档数太多
操作内容
- 对所有分片执行
Lucene Commit - 将内存中的 Segments 持久化到磁盘
- 清空 Translog 文件
重要性
- 是真正意义上的“落盘”
- 防止 Translog 无限增长
九、高级配置与优化
1. 控制写入耐久性
PUT /logs/_settings
{
"index.translog.durability": "request", // 强一致性(默认)
"index.translog.sync_interval": "5s", // async 模式下每 5s sync
"index.refresh_interval": "30s" // 降低 refresh 频率
}
2. 批量写入优化(Bulk API)
POST /_bulk
{ "index" : { "_index" : "logs", "_id" : "1" } }
{ "message": "Log 1" }
{ "index" : { "_index" : "logs", "_id" : "2" } }
{ "message": "Log 2" }
- 减少网络开销
- 更高效利用内存和磁盘 IO
3. 写入限流
PUT /_cluster/settings
{
"transient": {
"thread_pool.write.queue_size": 1000, // 写入队列大小
"indices.breaker.total.limit": "70%" // 内存熔断
}
}
十、故障场景分析
场景 1:主分片失败
- 协调节点收到失败响应
- 返回
500 Internal Server Error - 集群尝试选举新主分片
场景 2:副本分片超时
- 主分片继续服务
- 集群状态变为
yellow - 后台继续尝试同步
场景 3:节点宕机
- Translog 保证数据不丢失
- 恢复后自动回放 Translog
- 分片重新分配(如果需要)
🔚 结语
你已经完整掌握了 Elasticsearch 数据写入流程的 7 个步骤:
- 接收 → 2. 路由 → 3. 主分片写入(Buffer + Translog)
- 副本复制 → 5. 确认响应 → 6. 刷新可搜索 → 7. 持久化归档
关键要点:
- ✅ Translog 是数据安全的生命线
- ✅ Refresh 决定搜索延迟
- ✅ Flush 确保最终持久化
- ✅ 同步复制 保障一致性
理解这个流程,你就能:
- 优化写入性能
- 设计合理的 refresh/flush 策略
- 快速诊断写入瓶颈
现在,检查你的索引设置:
GET /your-index/_settings?flat_settings
是否合理配置了 refresh_interval 和 translog?立即优化!
💬 评论区互动:你的生产环境
refresh_interval设置为多少?为什么?欢迎分享你的调优经验!
385

被折叠的 条评论
为什么被折叠?



