深入底层:ES 8.x 的读写原理与倒排索引

这篇文章的内容非常硬核,是面试中用来区分“调包侠”和“研发工程师”的分水岭。

面试题

ES 写入数据的工作原理是什么?查询数据的工作原理是什么?底层的 Lucene 和倒排索引了解吗?

面试官心理分析

这个问题是问 “黑盒” 里的东西。
平时我们只管调 API PUTGET,但如果不懂底层,一旦遇到写入慢、数据丢了、搜索不准等问题,你就两眼一抹黑。面试官想知道你是否理解数据在内存和磁盘间是如何流转的,以及 ES 是如何保证高可用近实时的。


1. 核心大前提:副本去哪了?

在 ES 的分布式架构中,有一个铁律:同一个 Shard 的 Primary(主分片)和 Replica(副本分片)绝对不会存放于同一台机器(节点)上。

  • 为什么? 如果主分片和副本分片都在机器 A,那机器 A 一旦宕机,主副全没,数据就彻底丢失了。
  • 怎么做? ES 会自动实施“反亲和性(Anti-affinity)”策略,强制把副本分散到其他机器。如果集群只有 1 台机器,副本将无法分配(Unassigned),集群状态变黄。

2. ES 写数据过程(Write Path)

场景:客户端发起请求,新增或修改一条数据。

  1. 协调节点(Coordinating Node)接收请求
    客户端可以向集群中任意节点发送请求,该节点就充当“协调节点”。
  2. 路由(Routing)
    协调节点根据 hash(_id) % primary_shard_num 计算出该数据属于哪个分片(比如 Shard 0)。
  3. 转发给主分片
    协调节点查表找到 Shard 0 的 Primary Shard 所在的节点,将请求转发过去。
  4. 主分片写入
    Primary Shard 处理写入请求,写入成功后。
  5. 并发同步副本
    Primary Shard 并行将数据发送给所有的 Replica Shard
  6. 响应客户端
    当所有的(或者满足 wait_for_active_shards 配置的)Replica Shard 都报告写入成功,Primary Shard 向协调节点报告成功,协调节点再返回给客户端“写入成功”。

3. ES 读数据过程(Read / Get Path)

场景:客户端根据 ID 查询一条数据(GET /index/_doc/1)。

  1. 协调节点接收:客户端发请求到任意节点(协调节点)。
  2. 路由计算:协调节点计算 hash,知道数据在 Shard 0。
  3. 智能负载均衡(ARS)
    • 老版本:简单的随机轮询(Round-Robin)。
    • ES 7/8 新特性ARS (Adaptive Replica Selection)。协调节点会记录每个节点的健康状况和响应速度,智能选择一个响应最快、负载最低的副本(或者是主分片)来查询。
  4. 返回结果:持有数据的节点将文档返回给协调节点,协调节点转发给客户端。

4. ES 搜索数据过程(Search Path)

场景:全文检索(比如搜索关键词 “Java”)。
这是一个两阶段过程:Query Then Fetch

第一阶段:Query Phase(查询)

  1. 协调节点将搜索请求广播到所有的分片(每个分片随机选一个副本或主分片)。
  2. 每个分片在本地进行搜索,但不返回完整数据,只返回Doc Id排序打分值
  3. 协调节点汇总所有分片返回的 ID 和分数,进行全局排序、分页,筛选出最终要返回的那一页数据的 ID。

第二阶段:Fetch Phase(取数)

  1. 协调节点拿着筛选出来的 ID,去对应的分片上拉取完整的 Document 数据(_source 内容)。
  2. 协调节点拼装最终结果,返回给客户端。

5. 写数据底层原理(Buffer, Translog, Segment)

这是面试最爱问的细节,涉及数据会不会丢!

ES 的写入不是直接写硬盘,而是经过了复杂的内存缓冲。

核心组件

  1. Memory Buffer:内存缓冲区,写入的数据先放这。
  2. OS Cache:操作系统的文件系统缓存。
  3. Translog:预写日志(Write Ahead Log),用于灾难恢复。
  4. Segment File:Lucene 的倒排索引文件(磁盘/OS Cache 中)。

详细步骤

  1. 写入 Buffer 和 Translog
    数据先写入 Memory Buffer,同时追加写入 Translog 文件(防止内存断电丢失)。此时数据搜索不到
  2. Refresh(默认 1秒)
    • 每隔 1 秒,ES 将 Buffer 里的数据刷新OS Cache 中,并生成一个新的 Segment
    • 关键点:一旦进入 OS Cache,数据就可以被搜索到了。
    • 这就是为什么 ES 被称为“近实时(Near Realtime)”搜索,因为有 1 秒延迟。
    • Refresh 后,清空 Memory Buffer,但 Translog 保留。
  3. Flush(默认 30分钟 或 Translog 过大)
    • 随着时间推移,Translog 越来越大。触发 Flush 操作。
    • 执行 Commit:强行将 OS Cache 里的所有 Segment fsync(物理落盘)到磁盘。
    • 清空 Translog:因为数据已经安全落盘,旧日志可以删了。

关于丢数据(面试加分项)

  • :ES 会丢数据吗?
  • 可能会,但只有 5 秒。
    Translog 默认每 5 秒(异步)刷一次盘。如果机器在数据写入 Buffer 且 Translog 还没刷盘的那几秒宕机,内存里的数据就没了。
    • 怎么解决? 金融级业务可以把 Translog 设置为 request(同步),每次写入必须落盘才返回成功,但性能会由于频繁 IO 而下降。

6. 删除/更新原理

Lucene 的 Segment 文件是**不可变(Immutable)**的。

  • 删除:不是真删,而是写一个 .del 文件,标记某个 Doc ID 为“已删除”。搜索时虽然能查到,但在返回前会被过滤掉。
  • 更新:本质是 Delete + Insert。先在 .del 文件里标记旧版本删除,然后写入一个新版本的 Document。
  • Segment Merge:后台线程会定期把很多小的 Segment 合并成大的,这时候才会真正物理清除那些被标记删除的数据。

7. 底层 Lucene 与倒排索引

Elasticsearch 是汽车,Lucene 是引擎。ES 的 Shard 本质上就是一个 Lucene Index。

倒排索引 (Inverted Index)

正排索引:ID -> 内容 (MySQL 的主键查询)。
倒排索引:关键词 -> ID列表 (搜索引擎的核心)。

举例
文档 A (ID:1): “Google Map”
文档 B (ID:2): “Google Search”

倒排索引结构

Term (关键词) Doc IDs (倒排表)
Google [1, 2]
Map [1]
Search [2]
  • 词项字典 (Term Dictionary):记录所有关键词,通常用 B+树或 FST (Finite State Transducers) 存储,查询极快。
  • 倒排表 (Posting List):记录包含该词的文档 ID、出现频率、位置等。

面试总结
当你搜 “Google” 时,Lucene 直接去字典里查,瞬间找到 ID [1, 2],而不需要像 MySQL LIKE %Google% 那样全表扫描。这就是 ES 快的原因。