当你在 Kibana 中查询刚生成的日志,或在电商网站搜索刚上架的商品时,可能会好奇:数据写入后不到 1 秒就能搜到,这是如何实现的?
这背后就是 Elasticsearch (ES) 的“近实时”(Near Real-Time, NRT)能力。本文将通俗地解析从写入到可查的完整链路。
🎯 什么是“近实时” (NRT)?
- 实时 (Real-time):数据写入后,立刻对所有操作可见。
- 近实时 (Near Real-time):数据写入后,通常在1 秒内即可被搜索到。
ES 官方也强调其提供的是近实时搜索,默认情况下文档在写入后 1 秒内可被检索。这里的“秒级延迟”正是实现高性能写入和高并发查询的关键设计权衡。
🚀 为什么能这么快?
ES 的速度源于其精巧的架构设计:
- 分布式分片:一个索引被拆分为多个主分片(Primary Shard),分布在不同节点上并行处理请求,极大提升了吞吐量。
- 倒排索引:这是搜索引擎的核心。它将文档内容分词,形成“词 → 文档列表”的映射,查询时直接定位词,无需全表扫描,效率极高。
- Lucene 与 Segment:每个分片本质上是一个 Lucene 实例,由多个不可变的Segment (段)构成。查询时,ES 会并行搜索所有相关分片的所有 Segment,并合并结果。
🗺️ 数据写入与可查的完整链路
一条文档从写入到可被搜索,会经历以下关键阶段:
- 请求路由:客户端请求到达任一节点(协调节点),该节点根据文档 ID 计算出目标主分片,并将请求转发过去。
- 写入内存与日志:主分片节点将文档写入Index Buffer (内存缓冲区),同时追加写入Translog (事务日志)。此时数据在内存中,不可搜索。
- Translog 刷盘:为保证数据安全,Translog 会定期(默认每 5 秒)或每次请求后(默认策略)通过
fsync强制写入磁盘。这确保了即使节点宕机,数据也能从 Translog 中恢复。 - Refresh (刷新) - 变为可查:这是实现“近实时”的核心。默认每秒,ES 会将 Index Buffer 中的文档生成一个新 Segment,并写入文件系统缓存 (OS Cache)。这个新 Segment 会立即被打开,其中的文档从此刻起可被搜索。此过程不保证数据已落盘。
- Flush (刷盘) - 持久化:当 Translog 达到一定大小(默认 512MB)或时间(默认 30 分钟)时,会触发 Flush。Flush 会执行一次 Refresh,然后调用
fsync将所有 Segment 强制写入磁盘,并更新提交点 (Commit Point),最后清空旧的 Translog。至此,数据才完成了真正的持久化。 - 副本同步:主分片写入成功后,会将操作并行发送给所有副本分片。副本分片重复上述写入、Refresh 和 Flush 流程。只有当所有副本都确认成功后,主分片才会向客户端返回成功响应。
💡 更新与删除是如何实现的?
由于 Segment 是不可变的,ES 采用“标记-删除”和“新增”的策略:
- 删除:在
.del文件中标记该文档为已删除。查询时,结果会过滤掉这些被标记的文档。 - 更新:本质是“先删除,后新增”。旧文档被标记删除,新版本的文档作为一个新文档被索引到新的 Segment 中。
🧩 Segment 合并 (Merge)
频繁的 Refresh 会产生大量小 Segment,影响查询效率。为此,ES 后台会启动Merge (合并)任务:
- 将多个小 Segment 合并成少数几个大 Segment。
- 在合并过程中,物理删除那些已被标记删除的文档。
- 更新提交点,并删除旧的小 Segment 文件。
由于 Segment 不可变,合并过程不会阻塞写入和查询,但会消耗 I/O 和 CPU 资源。
🔍 补充:按 ID 查询为何更快?
你可能会发现,有时刚写入的文档,通过GET /index/_doc/id能立刻查到,但搜索却搜不到。这是因为:
- 按 ID 查询:会先检查 Translog 中是否有该文档的最新版本,因此能读到最新数据。
- 全文搜索:依赖倒排索引,必须等待 Refresh 生成新的 Segment 后才能被搜索到。
⚙️ 相关参数与调优建议
理解以下参数有助于你根据业务场景进行调优:
index.refresh_interval:控制 Refresh 频率。调大(如30s)可提升写入吞吐;调小(如100ms)可加快搜索可见性,但会增加开销。批量导入时可临时设为-1关闭,完成后再恢复。index.translog.durability和sync_interval:控制 Translog 的刷盘策略。request模式(默认)每次请求后刷盘,最安全但性能稍低;async模式(如每 5s 刷盘)性能更高,但有数据丢失风险。index.translog.flush_threshold_size:控制 Translog 触发 Flush 的大小阈值(默认 512MB)。
🔥 关注公众号【云技纵横】,目前正在更新分布式缓存进阶技巧和干货
🎯 总结
Elasticsearch 的“近实时”并非魔法,而是通过以下设计实现的:
- 近实时可见:依靠Refresh机制,将内存数据快速生成 Segment 并提供搜索。
- 数据不丢失:依靠Translog预写日志,确保数据安全。
- 最终持久化:依靠Flush和Merge机制,将数据稳定写入磁盘并优化查询性能。
正是这种精巧的平衡,使得 ES 能够在亿级数据量下,依然实现毫秒级的查询响应。