Elasticsearch 核心概念

Elasticsearch 核心概念

候选人小陈在字节面试时被问到:"你们项目里用 ES 做什么?索引结构是怎样的?"

小陈说:"我们建了一个订单索引,存了 2000 万订单数据。"

面试官追问:"你建了几个分片?副本怎么配的?为什么这么配?"

小陈愣了一下,说:"就默认的...5个分片,1个副本?"

面试官又问:"分片数和副本数是怎么算出来的?主分片和副本分片的区别是什么?"

小陈开始语无伦次,最后勉强说出"副本是用来做故障恢复的"。

【面试官心理】 我问他分片副本,不是想听他背定义。我想知道的是:他有没有经历过数据量增长后的容量规划,有没有踩过"分片过多导致 GC 压力大"或者"分片过少导致写入瓶颈"的坑。能说出"小分片多副本"还是"大分片少副本"这个 trade-off 的,基本都有生产经验。


一、Index / Type / Document 🔴

1.1 三层结构拆解

很多候选人把 ES 的数据和关系型数据库一一对应,这本身没错,但只对了一半:

关系型数据库ES说明
DatabaseIndex索引,逻辑命名空间
TableType(已废弃)ES 7.x 后 type 只有一个 _doc
RowDocument文档,JSON 格式的最小单元
ColumnField字段,支持多种类型

但这里有个大坑:ES 的 Index 不是"数据库",它更像是"一个预建好索引的表"。你在创建 Index 的同时,ES 就在背后为你建好了倒排索引结构。

一个文档的结构大概长这样:

{
  "_index": "orders",
  "_type": "_doc",
  "_id": "order_123456",
  "_version": 3,
  "_source": {
    "order_id": "order_123456",
    "user_id": "user_789",
    "amount": 299.00,
    "status": "completed",
    "create_time": "2024-11-11T10:30:00Z"
  }
}

1.2 追问链

第一层:怎么存? 面试官问:"ES 里一个文档是怎么存储的?" 候选人答:"JSON 格式,存到 _source 字段里..." 考察点:基本 API 使用

第二层:底层结构 面试官追问:"那倒排索引是怎么建的?" 候选人答:"根据字段值建索引..."(可能卡在这里) 考察点:数据结构理解

第三层:分片与副本 面试官追问:"2000万数据你分了几个分片?为什么?副本怎么配的?" 候选人答:...(P5/P6 分水岭) 考察点:容量规划、生产经验

第四层:集群角色 面试官追问:"你的集群有哪些节点角色?Master 节点挂了会怎样?" 候选人答:...(P7 区分点) 考察点:架构理解、故障容灾

1.3 错误示范

候选人原话:"ES 的 Index 就相当于 MySQL 的数据库,Type 相当于表。"

问题诊断

  • 把 ES 和 MySQL 的映射关系想得太死
  • 忽略了 Type 在 ES 7.x 已经被废弃的事实
  • 不知道 _source 和倒排索引是两回事

面试官内心 OS:"这个候选人可能用过 ES,但肯定没深入了解过它的底层结构。连 Type 废弃都不知道,说明他至少 3 年没更新过 ES 知识了。"


二、Shard 分片机制 🟡

2.1 主分片与副本分片

ES 的数据水平扩展靠的就是分片。每个 Index 创建时必须指定主分片数量(默认 5,ES 7.x 默认改为 1),副本分片数量可动态调整。

Index: orders
  ├── Primary Shard 0
  ├── Primary Shard 1
  ├── Primary Shard 2
  ├── Primary Shard 3
  └── Primary Shard 4

       ├── Replica 0 (副本分片 0 的副本)
       ├── Replica 1
       ├── Replica 2
       ├── Replica 3
       └── Replica 4

核心规则

  • 主分片数在 Index 创建后不可更改(这是最容易踩的坑)
  • 副本分片数可以随时调整
  • 主分片和副本不能同时存在于同一节点(否则该节点挂了副本也没了)

2.2 分片路由原理

文档写入时,ES 通过这个公式决定文档落到哪个分片:

shard_num = hash(_routing) % num_primary_shards

默认 _routing 就是文档的 _id,你也可以指定自定义 routing 字段。

💡

如果按 user_id 作为 routing key,所有相同用户的订单都会落在同一个分片内。这个技巧在"查询某个用户的所有订单"时非常有用,可以避免跨分片查询带来的 scatter-gather 开销。

2.3 分片数规划

这是面试中的高频深水区,也是 90% 候选人答不好的地方。

分片过小的风险

  • 单分片数据量过大,查询延迟飙升
  • 分片恢复时间长(故障后拉取 100GB 数据 vs 10GB)

分片过大的风险

  • 分片过多导致元数据压力大(ES 集群状态由 Master 节点管理,每个分片都有元数据)
  • 每次查询要扫描更多分片,GC 压力大
  • Lucene 段合并成本增加

经验公式(针对 HDD):

  • 每个分片数据量控制在 30GB~50GB 以内
  • 每个节点的分片数控制在 20~30 个 以内
  • 副本数通常设为 1~2,高可用要求高则设为 2

【面试官心理】 我问他分片规划,其实是在试探他有没有经历过"数据量从百万到千万"这个阶段。有这个经历的候选人,至少踩过一个坑:要么一开始分片设少了导致扩容,要么分片设多了导致集群抖动。


三、Segment 段存储 🟡

3.1 Lucene 的分段架构

很多候选人知道 ES 是基于 Lucene 的,但说不出 Lucene 的分段存储机制。

ES 的每个分片其实是一个 Lucene 索引,而每个 Lucene 索引又由多个 Segment(段)组成:

Shard (Lucene Index)
  ├── Segment _0 (已密封,只读)
  ├── Segment _1 (已密封,只读)
  ├── Segment _2 (活跃段,可写)
  └── Write Buffer (内存中的写入缓冲区)

关键点

  • 每个 Segment 都是一个独立的倒排索引
  • 新文档写入时先到 Write Buffer,定时 refresh 生成新 Segment
  • 老 Segment 永不修改,只通过合并(Merge)被删除

3.2 段合并的代价

这就是生产环境中的经典坑:

⚠️

在 ES 写入高峰期,如果触发了大量段合并,会出现明显的写入性能下降。合并过程是 CPU 和 IO 密集型操作,会占用大量资源。解决办法是:在业务低峰期执行 forcemerge,或者调整 merge 策略参数

【面试官心理】 我问他 Segment,其实是想看他有没有踩过"段合并导致集群抖动"这个生产事故。能说出 merge policy 参数调优的,基本都带过生产环境。


四、集群节点角色 🟡

4.1 四大节点角色

ES 7.x 的节点角色分为以下几种:

角色功能资源需求注意事项
Master管理集群元数据(索引创建/删除/分片分配)生产必须 3 个候选节点,避免脑裂
Data存储分片数据,处理查询和聚合高(CPU+内存+磁盘)热数据用 SSD,温数据用 HDD
Ingest数据预处理(Pipeline/富化)可选,非必须
Coordinating接收请求,分发到 Data 节点,汇总结果可扩展,用于大查询量场景

4.2 最小高可用集群配置

很多候选人简历上写"负责 ES 集群运维",但被问到"Master 挂了怎么办"就答不上来。

生产最小高可用

  • 3 个 Master 候选节点(奇数,防止脑裂)
  • 至少 2 个 Data 节点(保证副本能分配)
  • 不推荐单节点集群用于生产(ES 会自动把副本分片分配到同一节点,失去了副本的意义)

:::details 📖 点击展开:脑裂问题详解 ES 6.x 及之前版本使用 ZenDiscovery 机制,如果网络抖动导致 Master 和部分节点失联,部分节点可能重新选举出一个新 Master,形成"两个 Master"的脑裂情况。解决方案:

  1. 奇数个 Master 候选节点
  2. 设置 discovery.zen.minimum_master_nodes = (master_eligible_nodes / 2) + 1
  3. ES 7.x 后引入 cluster.coordination.join_stability_threshold 和 bootstrapping 流程,从根本上规避了脑裂 :::

五、生产避坑

5.1 容量规划翻车

线上后果:上线后发现单分片数据量超过 100GB,查询延迟从 50ms 飙升到 3s。

排查路径

# 查看各分片大小
GET _cat/shards?v&s=store:desc
# 查看分片分布
GET _cluster/allocation/explain

根本原因:创建 Index 时没有规划数据量增长,盲目使用默认分片数。

5.2 mapping 设计翻车

线上后果:某个字段类型设为 text,无法做范围查询和排序,只能用 script 字段解决,性能极差。

正确做法:需要排序/聚合的字段,类型设为 keyword;需要全文搜索的字段,设为 text + keyword 双类型:

{
  "mappings": {
    "properties": {
      "order_id": { "type": "keyword" },
      "amount": { "type": "double" },
      "description": {
        "type": "text",
        "fields": {
          "keyword": { "type": "keyword", "ignore_above": 256 }
        }
      }
    }
  }
}

六、工程选型

场景建议理由
数据量 < 1000万单 Index,单分片数据量小,不需要分片
数据量 1000万~1亿5~10 分片,1 副本平均每个分片 20~50GB
数据量 > 1亿,或需要按时间查询Time-based Index + 别名按月/按天建 Index,配合 Rollover
高写入场景加大主分片数,减少副本写入压力分散到更多分片

【面试官心理】 最后这道选型题,我是给他一个展示全局视野的机会。能说出"按时间分 Index + 别名"的,说明他对 ES 的数据生命周期管理有过实战经验。这种候选人,放在 P6+ 的水平线上。