← 返回文章列表

Elasticsearch 底层原理系列(三):文档写入流程

2020-10-30·6 分钟阅读

前言

Elasticsearch 的写入流程是其高性能和近实时搜索能力的关键。理解写入流程中的 Buffer、Translog、Refresh、Flush 等机制,对于优化写入性能和保证数据可靠性至关重要。

本章将深入解析文档从写入到可搜索的完整流程。

技术亮点

技术点难度面试价值本文覆盖
写入流程⭐⭐⭐高频考点
Refresh 机制⭐⭐⭐⭐高频考点
Translog 原理⭐⭐⭐⭐高频考点
Flush vs Refresh⭐⭐⭐⭐常见误区

面试考点

  1. Elasticsearch 文档写入的完整流程是什么?
  2. Refresh 和 Flush 有什么区别?
  3. Translog 的作用是什么?如何保证数据不丢失?
  4. 如何优化 ES 的写入性能?

写入流程概览

整体架构

┌─────────────────────────────────────────────────────────────────────────┐
│                        文档写入完整流程                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Client                                                                 │
│     │                                                                   │
│     │  1. 发送写入请求                                                  │
│     ▼                                                                   │
│  ┌─────────────┐                                                       │
│  │ Coordinating│                                                       │
│  │    Node     │ ─── 路由解析、请求转发                                 │
│  └──────┬──────┘                                                       │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                        Primary Shard                             │   │
│  │                                                                  │   │
│  │  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐  │   │
│  │  │ Memory   │    │ Translog │    │ OS Cache │    │  Disk    │  │   │
│  │  │  Buffer  │───►│ (WAL)    │───►│ Segment  │───►│ Segment  │  │   │
│  │  │          │    │          │    │ (Refresh)│    │ (Flush)  │  │   │
│  │  └──────────┘    └──────────┘    └──────────┘    └──────────┘  │   │
│  │       │               │               │               │         │   │
│  │       │               │               │               │         │   │
│  │   写入内存        持久化日志      1秒后可见        持久化到磁盘   │   │
│  │                                                                  │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         │  同步到副本分片                                               │
│         ▼                                                               │
│  ┌─────────────┐                                                       │
│  │   Replica   │                                                       │
│  │   Shard     │ ─── 相同的写入流程                                    │
│  └─────────────┘                                                       │
│         │                                                               │
│         ▼                                                               │
│  返回写入成功响应                                                       │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

时间线视角

┌─────────────────────────────────────────────────────────────────────────┐
│                        写入时间线                                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  T0          T1 (约1秒)     T2 (约5分钟)     T3 (约30分钟)              │
│  │              │               │                │                     │
│  │              │               │                │                     │
│  ▼              ▼               ▼                ▼                     │
│  ┌────┐      ┌────┐        ┌────┐           ┌────┐                   │
│  │写入│      │可搜│        │Segment       │持久│                     │
│  │完成│      │索  │        │部分合并      │化  │                     │
│  └────┘      └────┘        └────┘           └────┘                   │
│                                                                         │
│  • T0: 文档写入 Memory Buffer + Translog                               │
│  • T1: Refresh 操作,Segment 在 OS Cache 中可搜索                      │
│  • T2: 小 Segment 开始合并                                              │
│  • T3: Flush 操作,Segment 持久化到磁盘                                 │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

详细流程解析

Step 1: 协调节点处理

┌─────────────────────────────────────────────────────────────────────────┐
│                    协调节点处理流程                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 请求解析                                                            │
│     • 解析 JSON 文档                                                    │
│     • 验证文档结构                                                      │
│     • 提取路由信息                                                      │
│                                                                         │
│  2. 路由计算                                                            │
│     shard_num = hash(_routing) % num_primary_shards                    │
│     • 默认 _routing = _id                                              │
│     • 可自定义 routing 参数                                             │
│                                                                         │
│  3. 转发到目标节点                                                       │
│     • 确定主分片所在节点                                                │
│     • 发送写入请求                                                      │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  Client ──► Coordinating Node ──► Primary Shard Node           │   │
│  │               (路由计算)           (实际写入)                   │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Step 2: 写入 Memory Buffer

┌─────────────────────────────────────────────────────────────────────────┐
│                    Memory Buffer 写入                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  IndexWriter (Lucene) 处理流程:                                        │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  Document                                                       │   │
│  │     │                                                           │   │
│  │     ▼                                                           │   │
│  │  ┌─────────────┐                                               │   │
│  │  │  Analyzer   │ ─── 分词、归一化                               │   │
│  │  └──────┬──────┘                                               │   │
│  │         │                                                       │   │
│  │         ▼                                                       │   │
│  │  ┌─────────────┐                                               │   │
│  │  │ In-Memory   │ ─── 构建倒排索引                              │   │
│  │  │ Index Buffer│     (Term Dictionary + Postings)              │   │
│  │  └──────┬──────┘                                               │   │
│  │         │                                                       │   │
│  │         ▼                                                       │   │
│  │  ┌─────────────┐                                               │   │
│  │  │  Doc Values │ ─── 构建列式存储                              │   │
│  │  │   Buffer    │     (用于排序聚合)                            │   │
│  │  └─────────────┘                                               │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  此时数据:                                                             │
│  • 在内存中,尚未可搜索                                                 │
│  • 丢失风险:节点崩溃会丢失                                             │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Step 3: 写入 Translog

┌─────────────────────────────────────────────────────────────────────────┐
│                    Translog(事务日志)                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Translog = Transaction Log,类似于数据库的 WAL(Write-Ahead Log)      │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  写入流程:                                                     │   │
│  │  1. 文档写入 Memory Buffer 的同时                               │   │
│  │  2. 将操作追加到 Translog 文件                                  │   │
│  │  3. 根据 sync 设置决定是否 fsync                                │   │
│  │                                                                 │   │
│  │  Translog 内容:                                                │   │
│  │  ┌──────────────────────────────────────────────────────┐      │   │
│  │  │ Op Type │ Doc ID │ Source JSON │ Timestamp │ ...     │      │   │
│  │  ├─────────┼────────┼─────────────┼───────────┼─────────┤      │   │
│  │  │ INDEX   │ doc_1  │ {...}       │ T1        │         │      │   │
│  │  │ INDEX   │ doc_2  │ {...}       │ T2        │         │      │   │
│  │  │ DELETE  │ doc_3  │ -           │ T3        │         │      │   │
│  │  │ UPDATE  │ doc_1  │ {...}       │ T4        │         │      │   │
│  │  └──────────────────────────────────────────────────────┘      │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  作用:                                                                 │
│  • 数据可靠性保证(节点崩溃后可恢复)                                    │
│  • 支持实时 GET 操作(从 Translog 读取)                                │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Step 4: Refresh(刷新)

┌─────────────────────────────────────────────────────────────────────────┐
│                    Refresh 操作                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Refresh 是实现"近实时搜索"的关键!                                     │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  Before Refresh:                                                │   │
│  │  ┌──────────────┐  ┌──────────────┐                            │   │
│  │  │ Memory Buffer│  │   Translog   │                            │   │
│  │  │ [docs...]    │  │ [ops...]     │                            │   │
│  │  └──────────────┘  └──────────────┘                            │   │
│  │         │                                                       │   │
│  │         │ Refresh (默认每秒执行)                                │   │
│  │         ▼                                                       │   │
│  │  After Refresh:                                                 │   │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │   │
│  │  │ Memory Buffer│  │   Translog   │  │ OS Cache     │          │   │
│  │  │ (清空)       │  │ (保留)       │  │ New Segment  │          │   │
│  │  │              │  │              │  │ (可搜索)     │          │   │
│  │  └──────────────┘  └──────────────┘  └──────────────┘          │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  关键点:                                                               │
│  • Segment 写入 OS Cache,不是磁盘!                                    │
│  • 操作轻量,不影响性能                                                 │
│  • 数据立即可搜索                                                       │
│  • Buffer 被清空,Translog 保留                                         │
│                                                                         │
│  默认配置:                                                             │
│  • refresh_interval = 1s                                               │
│  • 每秒自动执行一次                                                     │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Refresh 配置优化

// 动态调整 refresh 间隔
PUT /my_index/_settings
{
  "index": {
    "refresh_interval": "30s"    // 批量导入时可调大
  }
}

// 或完全禁用(批量导入完成后手动 refresh)
PUT /my_index/_settings
{
  "index": {
    "refresh_interval": "-1"     // 禁用自动 refresh
  }
}

// 手动触发 refresh
POST /my_index/_refresh

Step 5: Flush(刷盘)

┌─────────────────────────────────────────────────────────────────────────┐
│                    Flush 操作                                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Flush = Refresh + 持久化 + 清理 Translog                               │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  Flush 执行时机:                                               │   │
│  │  1. Translog 达到大小阈值(默认 512MB)                         │   │
│  │  2. 定时触发(默认 30 分钟)                                    │   │
│  │  3. 手动触发                                                    │   │
│  │                                                                 │   │
│  │  Flush 执行步骤:                                               │   │
│  │  ┌──────────────────────────────────────────────────────────┐  │   │
│  │  │ 1. 执行 Refresh(确保所有内存数据变成 Segment)           │  │   │
│  │  │ 2. 调用 Lucene commit(fsync Segment 到磁盘)            │  │   │
│  │  │ 3. 清理旧的 Translog 文件                                 │  │   │
│  │  │ 4. 更新 commit point                                       │  │   │
│  │  └──────────────────────────────────────────────────────────┘  │   │
│  │                                                                 │   │
│  │  Before Flush:                                                  │   │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐          │   │
│  │  │ Segment1 │ │ Segment2 │ │ Segment3 │ │ Translog │          │   │
│  │  │ (cache)  │ │ (cache)  │ │ (cache)  │ │ (大)     │          │   │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘          │   │
│  │                                                                 │   │
│  │  After Flush:                                                   │   │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐          │   │
│  │  │ Segment1 │ │ Segment2 │ │ Segment3 │ │ Translog │          │   │
│  │  │ (disk)   │ │ (disk)   │ │ (disk)   │ │ (新文件) │          │   │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘          │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  重要:Flush 是重量级操作,不要频繁调用!                                │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Refresh vs Flush 对比

┌─────────────────────────────────────────────────────────────────────────┐
│                    Refresh vs Flush                                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌───────────────────────┬───────────────────────┐                     │
│  │       Refresh         │        Flush          │                     │
│  ├───────────────────────┼───────────────────────┤                     │
│  │ 目的:使数据可搜索    │ 目的:数据持久化      │                     │
│  ├───────────────────────┼───────────────────────┤                     │
│  │ 频率:默认 1 秒       │ 频率:默认 30 分钟    │                     │
│  ├───────────────────────┼───────────────────────┤                     │
│  │ 目标:OS Cache        │ 目标:磁盘            │                     │
│  ├───────────────────────┼───────────────────────┤                     │
│  │ 重量:轻量            │ 重量:重量级          │                     │
│  ├───────────────────────┼───────────────────────┤                     │
│  │ Translog:保留        │ Translog:清理        │                     │
│  ├───────────────────────┼───────────────────────┤                     │
│  │ Buffer:清空          │ Buffer:不涉及        │                     │
│  └───────────────────────┴───────────────────────┘                     │
│                                                                         │
│  记忆技巧:                                                             │
│  • Refresh = 刷新可见性(内存 → OS Cache)                              │
│  • Flush = 刷盘持久化(OS Cache → Disk)                                │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Translog 配置详解

Translog 配置参数

PUT /my_index/_settings
{
  "index": {
    "translog": {
      "durability": "async",        // 或 "request"
      "sync_interval": "5s",        // async 模式下的同步间隔
      "flush_threshold_size": "512mb"  // 触发 flush 的阈值
    }
  }
}

Durability 模式

┌─────────────────────────────────────────────────────────────────────────┐
│                    Translog Durability 模式                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. request(默认)                                                     │
│     • 每次写入请求后 fsync                                              │
│     • 保证数据不丢失                                                    │
│     • 性能较低                                                          │
│                                                                         │
│  2. async                                                               │
│     • 定期 fsync(默认 5 秒)                                           │
│     • 可能丢失最近 5 秒的数据                                           │
│     • 性能更高                                                          │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                     性能 vs 可靠性权衡                          │   │
│  │                                                                 │   │
│  │  request 模式:                                                 │   │
│  │  ┌────────────────────────────────────────────────────────┐    │   │
│  │  │ 写入 → Translog → fsync → 返回确认                     │    │   │
│  │  │              ↑                                         │    │   │
│  │  │         每次请求都同步                                 │    │   │
│  │  └────────────────────────────────────────────────────────┘    │   │
│  │                                                                 │   │
│  │  async 模式:                                                   │   │
│  │  ┌────────────────────────────────────────────────────────┐    │   │
│  │  │ 写入 → Translog → 返回确认                             │    │   │
│  │  │              │                                         │    │   │
│  │  │              └── 后台每 5 秒 fsync 一次                │    │   │
│  │  └────────────────────────────────────────────────────────┘    │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

写入性能优化

批量写入优化

┌─────────────────────────────────────────────────────────────────────────┐
│                    批量写入最佳实践                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 使用 Bulk API                                                       │
│     • 批量大小:5-15MB 为佳                                             │
│     • 文档数量:1000-5000 个/批                                         │
│                                                                         │
│  2. 调整 Refresh 间隔                                                   │
│     • 批量导入时设置 refresh_interval = -1                              │
│     • 导入完成后恢复并手动 refresh                                      │
│                                                                         │
│  3. 调整 Translog 配置                                                  │
│     • 使用 async durability                                             │
│     • 增大 flush_threshold_size                                         │
│                                                                         │
│  4. 调整副本数量                                                        │
│     • 批量导入时设置 number_of_replicas = 0                             │
│     • 导入完成后恢复副本                                                │
│                                                                         │
│  优化配置示例:                                                          │
│  ┌────────────────────────────────────────────────────────────────┐    │
│  │ // 导入前设置                                                  │    │
│  │ PUT /my_index/_settings                                       │    │
│  │ {                                                              │    │
│  │   "index": {                                                   │    │
│  │     "refresh_interval": "-1",                                  │    │
│  │     "number_of_replicas": 0,                                   │    │
│  │     "translog.durability": "async"                             │    │
│  │   }                                                            │    │
│  │ }                                                              │    │
│  │                                                                │    │
│  │ // 执行 Bulk 导入...                                           │    │
│  │                                                                │    │
│  │ // 导入后恢复                                                  │    │
│  │ POST /my_index/_refresh                                        │    │
│  │ PUT /my_index/_settings                                        │    │
│  │ {                                                              │    │
│  │   "index": {                                                   │    │
│  │     "refresh_interval": "1s",                                  │    │
│  │     "number_of_replicas": 1                                    │    │
│  │   }                                                            │    │
│  │ }                                                              │    │
│  └────────────────────────────────────────────────────────────────┘    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

写入流程时序图

┌─────────────────────────────────────────────────────────────────────────┐
│                    写入流程时序图                                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Client      Coord Node     Primary      Replica       Memory     Disk  │
│     │            │            │            │            │          │    │
│     │──写入请求──►│            │            │            │          │    │
│     │            │──路由转发──►│            │            │          │    │
│     │            │            │──写入Buffer─►│           │          │    │
│     │            │            │──写入Translog───────────────────────►│   │
│     │            │            │──同步副本──►│            │          │    │
│     │            │            │            │──写入────►│          │    │
│     │            │◄──写入确认──│◄──确认──────│            │          │    │
│     │◄──成功响应──│            │            │            │          │    │
│     │            │            │            │            │          │    │
│     │            │            │            │            │          │    │
│     │            │         (1秒后)         │            │          │    │
│     │            │            │──Refresh──────────────►│          │    │
│     │            │            │            │            │ Segment  │    │
│     │            │            │            │            │ (Cache)  │    │
│     │            │            │            │            │          │    │
│     │            │         (30分钟后)      │            │          │    │
│     │            │            │──Flush───────────────────────────►│    │
│     │            │            │            │            │          │    │
│     │            │            │            │            │          │Disk│
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

数据恢复机制

节点崩溃后的恢复

┌─────────────────────────────────────────────────────────────────────────┐
│                    数据恢复流程                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  节点崩溃后重启:                                                        │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  1. 读取最后一个 Commit Point                                   │   │
│  │     • 确定哪些 Segment 已持久化                                  │   │
│  │                                                                 │   │
│  │  2. 加载已持久化的 Segment                                       │   │
│  │     • 从磁盘读取 Segment 文件                                    │   │
│  │     • 这些数据是安全的                                           │   │
│  │                                                                 │   │
│  │  3. 重放 Translog                                               │   │
│  │     • 从 Commit Point 之后开始重放                              │   │
│  │     • 恢复未 Flush 的数据                                        │   │
│  │                                                                 │   │
│  │  4. 恢复完成                                                    │   │
│  │     • 数据完整恢复                                               │   │
│  │     • 开始接受新请求                                             │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  重要:                                                                 │
│  • Translog 保证了数据不会丢失                                          │
│  • 恢复时间取决于 Translog 大小                                         │
│  • 定期 Flush 可以减少恢复时间                                          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

总结

本章详细解析了 ES 文档写入流程:

操作触发时机目标作用
写入 Buffer实时内存快速接收数据
写入 Translog实时磁盘数据可靠性
Refresh1秒OS Cache近实时搜索
Flush30分钟磁盘数据持久化

核心要点:

  1. 写入即持久:数据同时写入 Buffer 和 Translog
  2. 近实时搜索:Refresh 实现约 1 秒可见
  3. 最终持久化:Flush 将数据真正写入磁盘
  4. 可靠性保证:Translog 支持崩溃恢复

参考资料

下一章预告

下一章将探讨 Segment 合并机制,包括:

  • Merge Policy 详解
  • 自动合并触发条件
  • Force Merge 使用场景
  • 合并对性能的影响
分享: