面试知识库
基础

ES架构与核心概念#

一句话答案#

ES 是基于 Lucene 的分布式搜索引擎,核心概念:Index(类似数据库表)→ Shard(分片,Lucene 实例)→ Document(JSON 文档);通过倒排索引实现全文检索,通过分片+副本实现水平扩展和高可用。

核心要点

核心概念类比#

ES 概念MySQL 类比说明
IndexTable一类文档的集合
DocumentRow一条 JSON 数据
FieldColumn文档中的字段
MappingSchema字段类型定义
ShardPartition数据分片
ReplicaSlave分片副本

架构分层#

┌─────────────────────────────────────────┐
│              ES Cluster                  │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐│
│  │  Node 1  │ │  Node 2  │ │  Node 3  ││
│  │(Master)  │ │(Data)    │ │(Data)    ││
│  │ P0  R1   │ │ P1  R2   │ │ P2  R0   ││
│  └──────────┘ └──────────┘ └──────────┘│
└─────────────────────────────────────────┘
P = Primary Shard, R = Replica Shard
plaintext

节点角色#

角色职责配置建议
Master集群元数据管理、分片分配3 个专用 Master 节点
Data存储数据、执行查询高磁盘/内存
Coordinating请求路由、结果聚合高 CPU
Ingest数据预处理(pipeline)可选

写入流程#

1. 客户端发请求到任意节点(Coordinating Node)
2. 路由计算: shard = hash(doc._id) % number_of_shards
3. 请求转发到对应 Primary Shard 所在节点
4. Primary 写入成功后,并行复制到所有 Replica
5. 所有 Replica 确认后返回客户端成功
6. 文档先写入 In-Memory Buffer → refresh(1s) → Segment(可搜索)
7. Segment → flush → 持久化到磁盘
plaintext

查询流程(Query-then-Fetch)#

1. Coordinating Node 收到查询请求
2. Query Phase: 广播到所有相关 Shard
   - 每个 Shard 本地查询,返回 Top-N 的 doc_id + score
3. Fetch Phase: Coordinating Node 汇总排序
   - 取全局 Top-N 的 doc_id
   - 去对应 Shard 获取完整文档内容
4. 返回结果给客户端
plaintext

为什么要拆成两阶段(深挖):

  • Query 阶段只回元数据:每个分片本地查询后,只返回 Top-N 的 文档 id + 排序值(score 或 sort 字段),不返回 _source。协调节点据此做全局归并排序,选出真正的全局 Top-N。
  • Fetch 阶段才取正文:协调节点拿到全局 Top-N 的 id 后,只去对应分片按 id 拉取这几条的完整 _source
  • 核心动机:避免网络放大。若一阶段就让每个分片把 Top-N 的全部 _source 都传回,假设 N=10、5 个分片,就要传 50 份完整文档,而最终只要 10 份——大量正文在网络上传了又被丢弃。两阶段把”排序”和”取正文”解耦,只在最后对真正需要的几条取 _source,传输量从 分片数×N×文档大小 降到 N×文档大小。文档越大、分片越多,省得越多。

深分页(from+size)为什么爆炸:

查 from=10000, size=10(要第 1 万条后的 10 条):
  每个分片都必须返回 from+size = 10010 条 doc_id+score
  (因为分片不知道自己的第 10001 条会不会进全局 Top)
  协调节点要在内存里归并 分片数 × (from+size) 条 = 5 × 10010 ≈ 5 万条
  → from 越深,每个分片回传越多、协调节点排序内存越大 → O(分片数×(from+size))
plaintext
  • ES 默认 index.max_result_window = 10000 就是为了拦住深分页。
  • 替代方案
    • search_after:用上一页最后一条的排序值做游标,每个分片只需返回 size 条(不再是 from+size),翻页代价恒定,适合无状态深翻页/导出。
    • scroll:生成快照游标,适合一次性全量遍历(如 reindex),但占资源且看不到快照后的新数据,不适合实时分页。

dfs_query_then_fetch(小数据集打分失真):

  • 默认 query_then_fetch 下,每个分片用自己本地的文档统计独立算 IDF(IDF 依赖 N=文档总数、n=含该词文档数)。数据量大时各分片统计接近、影响可忽略;但数据量小或分布不均时,同一个词在不同分片的 IDF 差很多,导致相同文档因落在不同分片而打分不一致,排序失真。
  • dfs_query_then_fetch 在 Query 前多加一个 DFS(Distributed Frequency Search) 阶段:先收集全局的词频/文档频率统计,再用统一的全局 IDF 打分,结果更准。代价是多一轮网络往返,一般只在小索引或测试时用。

近实时搜索(NRT)#

写入 → Buffer → refresh(1s) → 新 Segment → 可搜索

                         flush(30min) → 持久化磁盘

- refresh 默认 1s:写入后约 1s 可搜到(非实时)
- translog:crash 恢复用,类似 MySQL redo log
plaintext
面试回答(2分钟版)

ES 是基于 Lucene 的分布式搜索引擎。核心概念:Index 相当于 MySQL 的表,Document 是一条 JSON 记录,Shard 是数据分片(每个 Shard 是独立的 Lucene 实例)。架构上分 Master 节点管元数据和分片分配、Data 节点存数据执行查询、Coordinating 节点路由请求和聚合结果。写入时按 hash(doc_id) % shard_num 路由到 Primary Shard,写完后并行复制到 Replica。查询采用 Query-then-Fetch 两阶段:先广播到所有 Shard 各自查 Top-N 返回 doc_id+score,Coordinating 节点汇总全局排序后再去取完整文档。ES 是近实时的:文档写入后经过 refresh(默认 1 秒)才可搜索,不是强实时。通过 translog 保证 crash 恢复不丢数据,类似 MySQL 的 redo log。

追问与易错

追问方向:

  • “ES 和 MySQL 的区别?”→ ES 擅长全文检索和聚合分析,MySQL 擅长事务和关联查询
  • “为什么 ES 查询快?”→ 倒排索引 + 内存缓存 + 并行分片查询
  • “refresh 和 flush 区别?”→ refresh 写入内存 Segment 变可搜索,flush 持久化到磁盘
  • “分片数能改吗?”→ 创建后不能改(需要 reindex),所以要提前规划
  • “为什么查询要分 Query 和 Fetch 两阶段?”→ Query 阶段只回 id+排序值做全局排序,Fetch 阶段才取正文,避免每个分片回传全部 _source 的网络放大
  • “深分页为什么慢?怎么解决?”→ from+size 下每个分片要回 from+size 条、协调节点归并 分片数×(from+size) 条;用 search_after(游标翻页)或 scroll(全量遍历)替代

易错点:

  • ❌ “ES 是实时的”——是近实时(NRT),有 1s refresh 延迟
  • ❌ “ES 可以替代 MySQL”——ES 不支持事务、JOIN、强一致,不是关系数据库
  • ❌ “分片越多越好”——每个分片有开销,建议单个 shard 10-50GB