前言

Elasticsearch 面试题通常不会只问“它是不是搜索引擎”。更常见的问题是:倒排索引是什么,写入为什么不是立刻可见,分片和副本怎么工作,查询如何路由,深分页为什么慢,聚合为什么占内存,Mapping 设计错了怎么办,集群变黄或变红怎么排查。

如果只把 Elasticsearch 理解成“可以全文搜索的数据库”,回答很容易停留在表面。更好的方式是把它理解成一条完整链路:

1
文档写入 Index -> 路由到 Primary Shard -> 写内存 Buffer 和 Translog -> Refresh 生成 Segment -> 查询时并发访问 Shard -> 合并排序和聚合结果

本文按面试中最常见的模块梳理 Elasticsearch 要点,适合作为面试前的复习清单。

Elasticsearch 整体定位

Elasticsearch 是基于 Lucene 的分布式搜索和分析引擎,擅长全文检索、结构化过滤、日志检索、指标分析、实时搜索和聚合分析。

它不是传统关系型数据库,也不适合替代所有主库场景。Elasticsearch 更适合作为搜索、检索、分析和可观测数据查询层,常与 MySQL、PostgreSQL、MongoDB、Kafka、Logstash、Beats 等系统配合使用。

面试中可以这样概括:

Elasticsearch 的核心能力来自 Lucene 倒排索引和分布式分片架构。倒排索引用于高效检索文本和字段,分片用于水平扩展,副本用于提升可用性和查询吞吐。

核心概念

Elasticsearch 常见概念如下:

  • Index:索引,类似一类文档的逻辑集合。
  • Document:文档,ES 存储和检索的基本单位,通常是 JSON。
  • Field:字段,文档中的属性。
  • Mapping:字段类型和索引方式定义。
  • Shard:分片,一个 Index 会拆分成多个 Shard。
  • Primary Shard:主分片,负责写入。
  • Replica Shard:副本分片,用于容灾和分担读请求。
  • Segment:Lucene 底层不可变索引段。
  • Refresh:把内存中的新数据打开为可搜索 Segment 的过程。
  • Translog:事务日志,用于异常恢复。
flowchart TD
    A[Index] --> B[Primary Shard 0]
    A --> C[Primary Shard 1]
    B --> D[Replica Shard 0]
    C --> E[Replica Shard 1]
    B --> F[Segment]
    C --> G[Segment]

倒排索引

倒排索引是 Elasticsearch 全文检索的基础。它不是从文档找词,而是从词找到包含这个词的文档。

例如有三篇文档:

1
2
3
doc1: Java search engine
doc2: Elasticsearch search
doc3: Java interview

倒排索引可以理解为:

1
2
3
4
java -> doc1, doc3
search -> doc1, doc2
engine -> doc1
interview -> doc3

查询 java search 时,ES 可以快速找到相关文档,再根据评分规则排序。

倒排索引通常包括:

  • Term Dictionary:词典,记录所有词项。
  • Posting List:倒排列表,记录词项出现在哪些文档中。
  • Term Frequency:词频,影响相关性评分。
  • Position:词项位置,用于短语查询。
  • Offset:字符偏移,用于高亮。

面试中要强调:倒排索引适合检索,不适合频繁原地更新。Lucene Segment 是不可变的,更新本质上是标记旧文档删除并写入新文档。

分词和 Analyzer

全文检索依赖分词。Analyzer 通常包括 Character Filter、Tokenizer 和 Token Filter。

1
原始文本 -> 字符过滤 -> 分词 -> 小写/停用词/同义词等过滤 -> Token 列表

常见问题:

  • 中文不能简单按空格分词,需要中文分词器。
  • 查询分词和索引分词要匹配,否则可能查不到。
  • text 字段会分词,适合全文检索。
  • keyword 字段不分词,适合精确匹配、排序和聚合。

例如:

1
2
3
4
5
6
7
8
9
10
{
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}

这个设计同时支持 title 全文检索和 title.keyword 精确聚合或排序。

写入流程

Elasticsearch 写入流程的关键是 Primary Shard、Replica Shard、Buffer、Translog 和 Refresh。

sequenceDiagram
    participant Client
    participant Coord as Coordinating Node
    participant Primary as Primary Shard
    participant Replica as Replica Shard
    participant Translog
    participant Buffer as Index Buffer

    Client->>Coord: Index Document
    Coord->>Primary: 路由到主分片
    Primary->>Translog: 写 Translog
    Primary->>Buffer: 写内存 Buffer
    Primary->>Replica: 同步到副本
    Replica-->>Primary: 确认
    Primary-->>Coord: 返回结果
    Coord-->>Client: 写入成功

写入大致过程:

  1. 客户端请求任意节点,该节点成为协调节点。
  2. 协调节点根据文档 ID 或 routing 计算目标 Shard。
  3. 请求先到 Primary Shard。
  4. Primary 写入内存 Buffer 和 Translog。
  5. Primary 把写入复制到 Replica。
  6. 副本确认后返回客户端。
  7. 后续 Refresh 后文档才可被搜索到。

常见追问:为什么写入后马上搜索不到?

因为 ES 是近实时搜索,写入后需要 Refresh 生成新的可搜索 Segment。默认 Refresh 间隔通常是 1 秒,所以不是严格实时可见。

Refresh、Flush 和 Merge

Elasticsearch 中有几个容易混淆的概念:

  • Refresh:让内存 Buffer 中的数据变成可搜索 Segment,代价相对较轻。
  • Flush:提交 Lucene Commit,并清理 Translog,保证恢复成本可控。
  • Merge:后台合并多个小 Segment,减少文件数量和删除标记。
1
2
3
Index Buffer -> Refresh -> Segment 可搜索
Translog -> Flush -> 清理旧日志
多个 Segment -> Merge -> 更少更大的 Segment

Refresh 太频繁会影响写入吞吐;Refresh 太慢会影响数据可见性。Merge 跟不上会导致 Segment 过多,查询开销变大。

查询流程

Elasticsearch 查询是典型的分布式查询。一个查询通常分为 Query 阶段和 Fetch 阶段。

sequenceDiagram
    participant Client
    participant Coord as Coordinating Node
    participant S1 as Shard 1
    participant S2 as Shard 2
    participant S3 as Shard 3

    Client->>Coord: Search Request
    Coord->>S1: Query
    Coord->>S2: Query
    Coord->>S3: Query
    S1-->>Coord: Top N doc ids
    S2-->>Coord: Top N doc ids
    S3-->>Coord: Top N doc ids
    Coord->>S1: Fetch docs
    Coord->>S2: Fetch docs
    Coord-->>Client: Merge and return

Query 阶段:

  • 协调节点把查询请求发到相关 Shard。
  • 每个 Shard 在本地执行查询和排序。
  • 每个 Shard 返回 Top N 的文档 ID 和排序值。
  • 协调节点合并全局 Top N。

Fetch 阶段:

  • 协调节点根据最终命中的文档 ID 到对应 Shard 拉取 _source
  • 合并后返回客户端。

这也是深分页慢的原因之一:每个 Shard 都要返回较多候选结果,协调节点再做全局排序和丢弃。

Query 和 Filter

ES 查询语句中要区分 Query Context 和 Filter Context。

  • Query:关注匹配程度,会计算相关性评分 _score
  • Filter:只判断是否匹配,不计算评分,通常可以缓存。

例如状态、类型、时间范围等条件更适合放到 filter 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Elasticsearch 面试" } }
],
"filter": [
{ "term": { "status": "published" } },
{ "range": { "created_at": { "gte": "2025-01-01" } } }
]
}
}
}

面试中可以回答:需要相关性排序的条件放 Query,只做精确限制的条件放 Filter。

相关性评分

Elasticsearch 默认相关性评分基于 BM25。面试不一定要推公式,但要知道影响因素:

  • 查询词是否命中。
  • 查询词在文档中出现的频率。
  • 查询词在整体语料中是否稀有。
  • 字段长度,短字段中命中通常更有代表性。
  • boost 权重设置。

常见优化方式:

  • 给标题、标签、核心字段设置更高 boost。
  • multi_match 控制多个字段权重。
  • function_score 加入时间、热度、业务权重。
  • minimum_should_match 控制匹配质量。

Mapping 设计

Mapping 是 Elasticsearch 使用效果的基础。字段类型选错,后续查询、排序、聚合和存储都会受影响。

常见原则:

  • 需要全文搜索的字段用 text
  • 需要精确匹配、排序、聚合的字段用 keyword
  • 数值字段用对应 numeric 类型,不要用字符串。
  • 时间字段用 date
  • 对象结构明确时使用 object,需要独立嵌套匹配时使用 nested
  • 不需要检索的字段可以关闭 index
  • 控制动态字段,避免 Mapping 爆炸。

Mapping 通常不能随意修改已有字段类型。字段类型设计错了,常见处理方式是新建索引、定义正确 Mapping、Reindex 数据,再切换别名。

分片和副本

分片决定水平扩展能力,副本决定高可用和读吞吐。

Primary Shard 数量通常在索引创建时确定,后续调整成本较高。Replica 数量可以动态调整。

设计分片时要考虑:

  • 数据总量。
  • 单 Shard 大小。
  • 查询并发。
  • 节点数量。
  • 写入吞吐。
  • 后续增长空间。

分片不是越多越好。分片过多会增加集群元数据、文件句柄、内存和调度成本;分片过少会限制扩展能力。

常见经验是让单个 Shard 保持在合理大小范围内,并结合实际集群压测调整,而不是机械套固定数字。

集群状态

Elasticsearch 集群状态常见三种:

  • Green:主分片和副本分片都正常分配。
  • Yellow:主分片正常,部分副本未分配。
  • Red:存在主分片未分配,部分数据不可用。

常见原因:

  • 节点数量不足,副本无法分配。
  • 磁盘水位过高,Shard 无法迁移或分配。
  • 节点宕机。
  • 分片过多,集群负载过高。
  • 索引损坏或恢复失败。

排查时可以先看集群健康、未分配分片原因、节点磁盘、节点状态和恢复进度。

脑裂问题

早期 Elasticsearch 集群可能因为 Master 选举配置不合理出现脑裂:网络分区后多个节点都认为自己是 Master,导致元数据冲突。

现代版本通过集群协调机制降低了这类风险,但面试中仍可能问到。回答重点是:

  • Master 负责集群元数据管理,不直接承担所有数据读写。
  • Master 选举需要多数派原则。
  • Master eligible 节点建议至少 3 个。
  • 避免网络不稳定和错误的节点角色配置。

深分页问题

ES 的 from + size 深分页会越来越慢。

原因是每个 Shard 都要取出 from + size 条候选结果,协调节点再做全局排序,最后丢弃前面的 from 条。页数越深,浪费越大。

优化方式:

  • 普通浅分页可以使用 from + size
  • 深分页或滚动导出使用 scroll
  • 用户连续翻页使用 search_after
  • 结合 Point In Time 保证分页过程中视图稳定。
  • 避免允许用户任意跳到极深页。

聚合分析

Elasticsearch 聚合适合做日志统计、指标分析、分组计数、时间直方图等。

但聚合也容易消耗内存和 CPU,尤其是高基数字段聚合。

常见优化方式:

  • 对聚合字段使用 keyword 或数值类型。
  • 避免对 text 字段直接聚合。
  • 控制 bucket 数量。
  • 使用时间范围过滤减少数据量。
  • 对高基数字段谨慎使用 terms aggregation。
  • 必要时做预聚合或离线统计。

写入优化

批量写入 ES 时,重点关注吞吐、Refresh、Replica、Mapping 和 Bulk 大小。

常见优化:

  • 使用 Bulk API,减少单条写入开销。
  • 合理控制 Bulk 大小,避免请求过大导致内存压力。
  • 大批量导入时临时调大 Refresh Interval。
  • 初始导入时可以临时把 Replica 调为 0,导入完成后恢复。
  • 使用固定 Mapping,避免动态字段过多。
  • 避免频繁更新同一文档。
  • 监控写入线程池、拒绝数、Refresh、Merge 和磁盘 IO。

查询优化

查询优化重点是减少扫描范围、减少评分成本、减少返回数据量。

常见方式:

  • 精确条件放 filter。
  • 时间范围、状态、租户等条件尽量前置过滤。
  • 只返回必要字段,减少 _source 体积。
  • 避免通配符前缀查询,例如 *abc
  • 避免深分页。
  • 对排序字段使用合适类型。
  • 使用 Routing 缩小查询 Shard 范围。
  • 对慢查询使用 Profile API 分析执行成本。

常见排查思路

如果面试官问“ES 查询突然变慢怎么排查”,可以按以下路径回答:

  1. 看是单个索引慢,还是整个集群慢。
  2. 看查询类型,是全文检索、过滤、排序、聚合还是深分页。
  3. 看是否命中大量 Shard,是否存在跨大量索引查询。
  4. 看慢查询日志和 Profile 结果。
  5. 看 Segment 数量、Merge 是否堆积。
  6. 看 JVM Heap、GC、CPU、磁盘 IO 和搜索线程池。
  7. 看是否有大批量写入、Refresh 频繁、聚合高基数或磁盘水位过高。
  8. 看 Mapping 是否合理,例如 text 字段被用于排序或聚合。

如果是写入变慢,可以重点看 Bulk 大小、写线程池拒绝、Translog、Refresh、Merge、Replica 同步和磁盘 IO。

高频面试题

Elasticsearch 为什么搜索快?

核心原因是倒排索引。ES 不需要逐条扫描文档,而是通过词项快速定位包含该词的文档,再结合评分、过滤和排序返回结果。

Elasticsearch 写入后为什么不能立刻查到?

ES 是近实时搜索。文档写入后先进入内存 Buffer 和 Translog,只有 Refresh 生成可搜索 Segment 后,搜索请求才能查到新文档。

text 和 keyword 有什么区别?

text 会分词,适合全文检索;keyword 不分词,适合精确匹配、排序、聚合和去重。很多字符串字段会同时设计 text 和 keyword 子字段。

为什么深分页慢?

因为每个 Shard 都要返回 from + size 条候选结果,协调节点合并排序后再丢弃前面的数据。页数越深,Shard 和协调节点浪费越大。

分片越多越好吗?

不是。分片太少会限制扩展,分片太多会增加元数据、内存、文件句柄和调度成本,也会让查询 fan-out 更重。分片数量应结合数据量、单分片大小、节点数和查询写入压力设计。

ES 可以当主库吗?

通常不建议把 ES 当强一致业务主库。ES 更适合作为搜索和分析系统。业务主数据通常放在关系型数据库或其他主存储中,再同步到 ES 做检索。

ES 集群 Yellow 一定有问题吗?

Yellow 表示主分片正常,副本分片未完全分配,数据仍可读写,但容灾能力下降。单节点集群设置了副本时常见 Yellow,因为副本不能和主分片放在同一节点。

总结

Elasticsearch 面试的主线可以围绕四个问题展开:

  1. 为什么能搜得快:倒排索引、分词、BM25。
  2. 数据怎么写进去:Primary Shard、Replica、Buffer、Translog、Refresh。
  3. 查询怎么跑起来:协调节点、Query 阶段、Fetch 阶段、排序和聚合。
  4. 线上怎么优化和排查:Mapping、分片、深分页、Merge、GC、磁盘和线程池。

把这条链路讲清楚,再结合 Mapping 设计、深分页治理、分片规划和集群状态排查,Elasticsearch 相关问题就能从概念回答升级成工程化回答。