Elasticsearch 为什么是近实时搜索?refresh、flush、translog 有什么区别?
简化版
Elasticsearch 是近实时搜索,因为文档写入成功后通常不会立刻对搜索可见,要等 refresh 创建新的可搜索段后才能被搜到。refresh 让新数据可搜索,translog 保障故障恢复,flush 会提交 Lucene 数据并开启新的 translog 世代。
详细版
写入 Elasticsearch 后,有几个容易混淆的概念:
- index 操作成功:文档已被写入内存缓冲和 translog,具备恢复基础。
- refresh:把内存缓冲中的数据生成新的 Lucene segment,并打开新的 searcher,让文档能被搜索到。
- flush:执行更重的提交操作,把 Lucene segment 提交到磁盘,并截断或开启新的 translog。
- translog:事务日志,用于节点故障后恢复尚未完全提交的写入。
所以:
- 写入成功不等于马上能被搜索;
- 需要立即搜索可用时可以用
refresh=wait_for,但不要对高并发写入滥用; - 缩短
refresh_interval会提升可见性,但会增加小 segment 和 IO 压力; - 提高写入吞吐时,常见做法是批量写入,并适当调大或临时关闭 refresh。
完整版教学
一、近实时是什么意思
近实时不是实时。实时意味着写入成功后立刻可查;近实时意味着写入成功后通常有一个很短延迟,数据才对搜索可见。
Elasticsearch 面向搜索和分析,底层 Lucene 通过 segment 提供高效检索。为了避免每写一条就做昂贵的完整提交,Elasticsearch 会把写入和搜索可见性拆开。
典型现象:
写入一篇文章 -> 立刻搜索可能查不到 -> 等一次 refresh 后可以查到
这不是数据丢了,而是搜索视图还没刷新。
二、refresh 让数据变得可搜索
refresh 的核心作用是创建新的可搜索 segment,并让 searcher 看到它。
可以手动 refresh:
POST /articles/_refresh
也可以在写入时指定:
POST /articles/_doc?refresh=wait_for
{
"title": "Elasticsearch refresh"
}
refresh=wait_for 表示等待下一次 refresh 后再返回,适合写后立刻读的少量关键场景。
但 refresh 不是越频繁越好。过于频繁会产生很多小 segment,增加合并压力、IO 开销和查询开销。
三、translog 负责什么
translog 可以理解为写入操作日志。文档写入时,除了进入内存缓冲,还会记录到 translog。节点异常重启时,可以通过 translog 回放恢复尚未完整提交到 Lucene commit 的数据。
它解决的是“写入持久性和恢复”问题,不是“搜索可见性”问题。
这也是常见误区:
- 文档进入 translog,不代表已经能被 search 查到;
- 文档能被 search 查到,也不代表发生了 flush;
- refresh 和 translog 是不同维度。
四、flush 和 refresh 的区别
refresh 更轻,关注搜索可见;flush 更重,关注提交和 translog 管理。
| 操作 | 主要目的 | 影响 |
|---|---|---|
| refresh | 让新写入对搜索可见 | 创建可搜索 segment |
| flush | 提交 Lucene 数据并管理 translog | 降低恢复时需要回放的日志量 |
| merge | 合并小 segment | 减少段数量,回收删除空间 |
实际使用中,大多数业务不需要手动 flush,Elasticsearch 会根据条件自动处理。手动 refresh 也要谨慎,只在明确需要写后可查时使用。
五、如何优化写入吞吐
如果是日志、埋点、批量导入场景,写入吞吐比秒级可见更重要,可以考虑:
- 使用 bulk API 批量写入。
- 调大
refresh_interval,降低 refresh 频率。 - 导入期间临时关闭 refresh,导入后再恢复并 refresh。
- 合理设置副本数量,批量导入时可在可接受范围内临时降低副本。
- 控制 mapping 和字段数量,避免写入时动态字段膨胀。
但这些优化都要结合数据可靠性、查询实时性和业务窗口来定,不能机械套用。
六、常见误区与追问
| 概念 | 关注点 | 面试区分 |
|---|---|---|
| refresh | 搜索可见性 | 新 segment 对 searcher 可见 |
| translog | 故障恢复 | 记录写操作,节点重启后回放 |
| flush | 提交和日志截断 | Lucene commit,并开启新的 translog generation |
| merge | segment 合并 | 降低小 segment 数量,回收删除空间 |
T0 写入成功
T0+10ms GET by id 可能能查到
T0+10ms search 可能查不到
T0+1s refresh 后 search 可见
记忆钩子:refresh 解决“搜不搜得到”,translog 解决“崩了能不能恢复”,flush 解决“提交点和日志负担”。
如果一个日志导入任务每秒写 5 万条,还强制每条都 refresh=true,就会产生大量小 segment 和频繁 searcher reopen;如果把 refresh_interval 调到 30s 或导入期临时关闭 refresh,吞吐通常会明显改善,但代价是搜索可见延迟变长。
- 误区:写入成功就一定能立刻被搜索到。 写入成功说明进入写入链路和恢复机制,搜索可见要等 refresh。
- 误区:手动 refresh 能提高数据可靠性。 refresh 管可见性,不负责故障恢复;可靠性主要和 translog、副本确认、磁盘刷写策略等相关。
- 误区:flush 越频繁越好。 flush 是更重的提交和 translog 管理动作,通常由 ES 自动控制,业务代码不应频繁手动触发。
- 追问:
refresh=wait_for和refresh=true有什么区别?wait_for等待下一次 refresh 后返回,通常比立即强制 refresh 更温和;true会触发刷新,滥用成本更高。 - 追问:为什么近实时不是 bug? ES 为了写入吞吐和搜索性能,把写入确认与搜索视图刷新解耦,这是搜索引擎架构取舍。
- 追问:写入吞吐优化最常见手段是什么? bulk 批写、合理调大 refresh interval、控制副本和 mapping 复杂度,并在导入窗口接受一定可见性延迟。
七、加强记忆
记住“refresh 管可搜,translog 管恢复,flush 管提交”。写入成功后数据不一定马上能搜到,这是 Elasticsearch 近实时的核心;需要写后读用 refresh=wait_for,高吞吐写入则要减少过度 refresh。