大数据-180 Elasticsearch 近实时搜索:Segment、Refresh、Flush、Translog 全流程解析

TL;DR

  • 场景:写入后立刻搜不到、节点重启怕丢数据,只知道"近实时"却搞不清 ES 底层是怎么保证性能和可靠性的。
  • 结论:Segment 不可变、Refresh 负责"可搜"、Flush+Translog 负责"可恢复",一套链路共同定义了 Elasticsearch 的性能上限和数据安全边界。
  • 产出:梳理索引写入到 NRT 搜索的全链路流程,给出 refresh_interval、flush、translog.durability 等关键参数的调优思路和常见故障模式速查表。

版本矩阵

版本范围 已验证 说明
Elasticsearch 7.x 部分 写入/Refresh/Flush/Translog 原理与文中描述一致,API 略有差异
Elasticsearch 8.x(2025) 部分 核心机制相同,安全与默认配置需以 8.x 官方文档为准
Elasticsearch 6.x 及以下 仅在原理层面大致适用,具体参数名与默认值可能不同

索引文档写入和近实时搜索原理

基本概念

Segments in Lucene

众所周知,Elasticsearch存储的基本单元是Shard,ES中的一个Index可能分为多个Shard,事实上每个Shard都是一个Lucence的Index,并且每个LucenceIndex由多个Segment组成,每个Segment事实上是一些倒排索引的集合,每次创建一个新的Document,都会归属于一个新的Segment,而不会去修改原来的Segment。且每次的文档删除操作,会仅仅标记Segment中该文档为删除状态,而不会真正的立马物理删除,所以说ES的index可以理解为一个抽象的概念。就像下图所示:

Translog-Hbase WAL(Write Ahead Log)

Write Ahead Log 预写入日志 新文档被索引意味着文档会被首先写入内存buffer和translog文件,每个shard都对应一个translog文件

Refresh In Elasticsearch

在Elasticsearch中,_refresh操作默认每秒执行一次,意味着将内存buffer的数据写入到一个新的Segment中,这个时候索引变成了可检索的,写入新Segment后会清空内存buffer。

Flush In Elasticsearch

Flush 操作意味着将内存buffer的数据全部写入到新的Segment中,并将内存中所有Segments全部刷盘,并且清空translog日志的过程。

近实时搜索

基本流程

Elasticsearch写入流程,当一个写请求到达Elasticsearch后,ES将数据写入MemoryBuffer中,并添加事务日志(translog)。如果每次一条数据写入内存后立即写到硬盘上,由于写入的数据肯定是离散的,因此写入磁盘的操作也就是随机写入了。硬盘随机写入的效率相当低,会严重降低ES的性能。 因此ES在设计时在MemoryBuffer和硬盘之间加入了高速缓存(FileSystemCache)来提高ES的写效率。 当写请求发送到ES后,ES将数据写入MemoryBuffer中,此时写入的数据还不能查询到。默认设置下,ES每1秒钟将MemoryBuffer中的数据Refresh到Linux的FileSystemCache,并清空MemoryBuffer,此时写入的数据就可以被查询到了。

Refresh API

在Elasticsearch中,写入和打开一个新段的轻量的过程叫做Refresh,默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说Elasticsearch是"近"实时搜索:文档的变化并不是立即对搜索可见,但会在一秒之内变成可见。 这些行为可能会对新用户操作困惑,他们索引了一个文档然后尝试搜索它,但却没有搜索到。这个问题的解决方法是用 Refresh API 执行一次手动刷新:

shell 复制代码
POST /_refresh

POST /my_blogs/_refresh

POST /my_blogs/_doc/1?refresh
{"xxx": "xxx"}

PUT /test/_doc/2?refresh=true
{"xxx": "xxx"}
  • 刷新(Refresh)所有的索引
  • 只刷新(Refresh)blogs 索引
  • 只刷新文档

并不是所有的情况都需要每秒刷新,可能你正在使用Elasticsearch索引大量的文件,你可能想优化索引速度而不是近实时搜索,可以通过设置 refrsh_interval,降低每个索引的刷新频率。

json 复制代码
PUT /my_logs
{
  "settings": {
    "refresh_interval": "30s"
  }
}

refresh_interval可以在既存索引上动态更新,在生产环境中,当你正在建立一个大的索引时,可以先关比自动刷新,待开始使用该索引时,再把他们调回来。

json 复制代码
PUT /my_logs/_settings
{
  "refresh_interval": -1
}

PUT /my_logs/_settings
{
  "refresh_interval": "1s"
}

持久化变更

基本流程

持久化变更flush 即使通过每秒刷新(Refresh)实现了近实时搜索,仍然要经常进行完整提交来确保从失败中恢复。但在两次提交之间发生变化的文档怎么办?我们也不希望丢掉这些数据。 Elasticsearch增加了一个Translog,叫做事务日志,在每一次对Elasticsearch操作时都会进行日志记录,通过translog,整个流程是下面这个样子:

第一步:一个文档被索引之后,就会被添加到内存缓冲区中,并且追加到了translog,如下图描述一样: 新的文档被添加到内存缓冲区并且追加到了事务日志: 第二步:刷新(refresh)使分片处于下图描述的状态,分片每秒刷新(refresh)一次:

  • 这些内存缓冲区的文档被写入到一个新的段中,且没有进行fsync操作
  • 这个段被打开,使其可被搜索。
  • 内存缓存区被清空

刷新(refresh)完成后,缓存被清空但是事务日志不会。

第三步:这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志,事务日志不断积累文档: 每隔一段时间:列如translog 变得越来越大,索引被刷新(flush),一个新的translog被创建,并且一个全量提交被执行。

  • 所有在内存缓冲区的文档都被写入一个新的段(Segment)
  • 缓冲区被清空
  • 一个提交点被写入硬盘
  • 文件系统缓存通过fsync被刷新(flush)
  • 老的translog被删除

translog提供所有还没有被刷到磁盘的操作的一个持久化记录,当Elasticsearch启动的时候,它会从磁盘中使用最后一个提交点去恢复已经得段,并且会重放translog中所有在最后一次提交后发生的变更操作。 translog也被用来提供实时CRUD,当你试着通过ID查询、解析、删除一个文档,它会在尝试从相应的段中检索之前,首先检查translog任何最近的变更。这意味着它总是能够实时的获取到文档的最新版本。在刷新(flush)之后,段被全量提交,并且事务日志被清空。

flush API

这个执行一个提交并且截断translog的行为在Elasticsearch被称作一次flush,分片每30分钟被自动刷新(flush),或者在translog太大(512M)的时候也会刷新。 flush API可以被用来执行一个手工的刷新(flush):

shell 复制代码
POST /blogs/_flush

POST /_flush?wait_for_ongoin
  • 刷新(flush)blogs索引
  • 刷新(flush)所有的索引并且等待所有刷新在返回前完成,我们很少需要自己手动执行一个flush操作,通常情况下,自动刷新就够了。

这就是说,在重启节点或者关闭之前执行flush有益于你的索引,当Elasticsearch尝试恢复或重新打开一个索引的时候,它需要重放translog中所有的操作,所以如果日志越短,恢复的会越快。

Translog安全问题

Translog有多安全? Translog的目的是保证操作不会丢失,但是却引出了对应的问题: 在文件被fsync到磁盘前,被写入的文件在重启之后就会丢失。这个过程在主分片和复制分片都会发生。最终,基本上,这意味着在整个请求被fsync到主分片和复制分片的translog之前,你的客户端不会得到一个200的OK响应,在每次写请求后执行一个fsync会带来性能上的损失,尽管实践表明这个损失并不大(特别是bluk导入,在一次请求时平摊了大量的文档开销) 但是对于一些大容量的偶尔丢失几秒数据问题并不眼中的集群,使用异步的fsync还是比较有益的。比如,写入的数据被缓存到内存中,再每5秒执行一次fsync。 这个行为可以通过设置durability参数为async来启动。

json 复制代码
PUT /my_index/x_settings
{
  "index.translog.durability": "async",
  "index.translog.sync_interval": "5s"
}

这个选项可以针对索引单独设置,并且可以动态修改,如果你决定使用异步translog的话,你需要保证在发生crash时,丢失掉sync_interval时间段的数据也无所谓。请在决定前知晓这个特性。 如果你不确定这个行为的后果,最好使用默认参数:"index.translog.durability": "request" 来避免数据丢失。

错误速查

症状 根因 定位 修复
写入返回 201/200,但立即搜索不到文档 近实时语义:refresh_interval 为 1s,尚未触发 Refresh 查看索引 settings 中 refresh_interval,监控 refresh 频率 关键链路可临时调用 _refresh,或缩短 refresh_interval;批量写入阶段相反应调大或关闭
节点重启后丢失几秒钟内的写入 translog 使用 async 模式,sync_interval 内的数据未 fsync 查看 index.translog.durability 和 index.translog.sync_interval 对关键业务使用 durability=request,缩短 sync_interval;重要变更前手动 _flush
集群恢复或分片重启时间异常长 长期未 flush,translog 体积过大,恢复时需要重放大量操作 查看索引的 translog 大小、RECOVERING 状态耗时 调整 flush 策略;在流量低谷执行 _flush;必要时使用滚动索引减小单索引体量
写入吞吐低,磁盘 IO 持续偏高 refresh_interval 过小或频繁手工 _refresh / _flush 监控每秒 refresh/flush 次数与 segment 生成频率 导入阶段设置 refresh_interval=-1,导入完成后再恢复;减少不必要的手工 _refresh/_flush
磁盘空间增长快,segment 数量爆炸 删除/更新频繁、merge 压力大,flush/滚动策略不当 通过 _cat/segments、store size 观察 segment 数量与大小 采用滚动索引治理冷数据,定期对只读索引用 force merge,优化删除/更新模式

其他系列

🚀 AI篇持续更新中(长期更新)

AI炼丹日志-29 - 字节跳动 DeerFlow 深度研究框斜体样式架 私有部署 测试上手 架构研究 ,持续打造实用AI工具指南! AI研究-132 Java 生态前沿 2025:Spring、Quarkus、GraalVM、CRaC 与云原生落地

💻 Java篇持续更新中(长期更新)

Java-180 Java 接入 FastDFS:自编译客户端与 Maven/Spring Boot 实战 MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务已完结,Dubbo已完结,MySQL已完结,MongoDB已完结,Neo4j已完结,FastDFS 已完结,OSS正在更新... 深入浅出助你打牢基础!

📊 大数据板块已完成多项干货更新(300篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解

相关推荐
阿桂天山2 小时前
阿桂的数据资产灵动实战 (一) 开发框架
大数据·python·软件工程
武子康2 小时前
Java-189 Guava Cache 源码剖析:LocalCache、Segment 与 LoadingCache 工作原理全解析
java·redis·后端·spring·缓存·guava·guava cache
踏浪无痕2 小时前
彻底搞懂微服务 TraceId 传递:ThreadLocal、TTL 与全链路日志追踪实战
后端·微服务·面试
程序员小假2 小时前
我们来说一说 Redis 主从复制的原理及作用
java·后端
海上彼尚2 小时前
Go之路 - 1.gomod指令
开发语言·后端·golang
TDengine (老段)2 小时前
TDengine 查询引擎设计与最佳实践
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
总会落叶2 小时前
🧪 JUnit单元测试完全指南:从入门到企业级应用
后端
源码获取_wx:Fegn08952 小时前
基于springboot + vue图书商城系统
java·vue.js·spring boot·后端·spring·课程设计
未秃头的程序猿2 小时前
解决ShardingSphere分片算法在Devtools热重启后SpringUtil.getBean()空指针问题
java·后端