面试追问地图
| 主问题 | 必讲关键点 | 下一层追问 |
|---|---|---|
| 倒排索引 | 词典、倒排表、文档列表 | 分词、跳表/压缩、为何适合全文搜索 |
| Mapping | 字段类型、分词、doc_values | 动态字段爆炸、类型错误能否修改 |
| 分片副本 | 路由、扩展、容灾 | 分片过多/过大、如何重新规划 |
| 写入流程 | 主分片、复制、translog、refresh | 写成功为何暂时搜不到 |
| 查询流程 | Query Then Fetch、协调合并 | 分片数对查询延迟的影响 |
| text/keyword | 分词与精确值 | 排序聚合为何通常用 keyword |
| match/term | 查询分析与精确词项 | analyzer 不一致为何搜不到 |
| 深分页 | 各分片 Top N、协调节点丢弃 | search_after、PIT、唯一排序键 |
| 聚合 | bucket、metric、pipeline | 高基数、内存和准确性 |
| 数据同步 | 消息、CDC、补偿 | 乱序更新、全量重建、别名切换 |
| 集群与选主 | 健康状态、脑裂、gossip | green/yellow/red、选主机制 |
| 性能优化 | 写入/查询调优、分片规划、段合并 | forcemerge、堆内存设置、bulk 策略 |
ES 题要区分“写入确认”“持久化”“可搜索”三个时间点,并说明它通常是派生索引而非事实源。
一、基础概念
Elasticsearch 适合什么场景?
难度 🟢
- 全文检索。
- 多条件过滤和聚合分析。
- 日志和可观测数据检索。
- 商品、地点等复杂搜索。
它不适合替代所有关系数据库。事务、多表强一致关联和高频随机更新通常仍由数据库承担。
Index、Document、Field 分别是什么?
- Index:逻辑数据集合。
- Document:JSON 文档,是基本检索单位。
- Field:文档字段,由 Mapping 定义类型和索引方式。
现代 Elasticsearch 已弱化 Type 概念,一个 Index 中通常使用统一文档结构。
分片和副本有什么作用?
- 主分片:将数据水平拆分到多个节点。
- 副本分片:提供容灾和读扩展。
主分片数量创建后调整成本较高。分片过多会增加内存、文件句柄和集群状态开销,分片过大又会影响迁移和恢复。
ES 路由机制(Routing)是如何工作的?
频次 ★★★★ · 难度 🟡 · 高频:美团/字节
ES 通过路由计算决定文档写入哪个分片:
shard = hash(routing) % number_of_primary_shards
默认 routing 值为文档 _id,也可在请求中显式指定:
PUT /my_index/_doc/1?routing=user123
{
"title": "ES 教程"
}为什么用 Routing:
- 默认行为:按
_id哈希均匀分布到各分片,绝大部分场景够用 - 自定义路由:将相关文档路由到同一分片,查询时指定
routing参数可跳过广播(仅扫描目标分片),显著提升查询性能;典型场景是按用户 ID 路由,查询该用户的所有文档只需扫描一个分片 - 数据倾斜风险:若 routing 值分布不均匀(如按「大客户 vs 小客户」路由),某些分片数据量远超其他分片,导致集群负载不均和查询延迟波动
面试追问:
- Routing 写错了能改吗?→ 主分片数创建后不可变,routing 目标是固定的;要重新分布只能 reindex 到新索引
- 不指定 routing 查询会怎样?→ 广播到索引的全部主分片(或副本分片),协调节点合并结果
- 自定义 routing 后还想按
_id查询怎么办?→ 要么在请求中同时指定 routing(需已知 routing 值),要么在文档中存储 routing 值再搜索——没有 routing 时 ES 只能广播
二、倒排索引
倒排索引是什么?
难度 🔴
倒排索引建立“词项到文档”的映射:
高德 → 文档 1、文档 3
地图 → 文档 1、文档 2全文检索时无需扫描所有文档,而是通过词典找到词项对应的文档列表,再计算相关性。
text 和 keyword 的区别?
text | keyword | |
|---|---|---|
| 是否分词 | 是 | 否 |
| 场景 | 全文检索 | 精确匹配、排序、聚合 |
| 示例 | 标题、正文 | 状态、编码、标签 |
同一字段可通过 multi-fields 同时建立 text 和 keyword 两种索引。
分词器由什么组成?
通常包括:
- Character Filter:字符预处理。
- Tokenizer:切分词元。
- Token Filter:小写、停用词、同义词等处理。
索引时和查询时的分析方式需要兼容,否则可能出现“数据存在但搜不到”。
Mapping 为什么重要?
Mapping 决定字段类型、分词和索引能力。错误类型会导致:
- 数字或日期范围查询异常。
- 精确匹配字段被分词。
- 动态字段爆炸,集群状态膨胀。
生产索引应使用显式 Mapping 和模板,谨慎依赖动态映射。
三、写入与查询流程
文档写入流程?
难度 🔴
- 请求根据文档 ID 路由到目标主分片。
- 主分片执行写入并记录操作。
- 操作复制到副本分片。
- 满足确认条件后响应客户端。
- Refresh 后文档才对搜索可见。
写入确认、持久化和搜索可见不是同一个时间点。
Refresh、Flush、Translog 的区别?
- Refresh:生成新的可搜索 Segment,使近期写入可见。
- Translog:记录尚未安全落盘到 Lucene 的操作,用于恢复。
- Flush:提交 Lucene Commit,开启新的 Translog generation。
频繁手动 Refresh 会产生大量小 Segment,降低写入性能。
搜索请求的 Query Then Fetch 流程?
- 协调节点将查询发送到相关分片。
- 各分片本地计算并返回候选文档 ID 和排序值。
- 协调节点合并全局 Top N。
- 再到对应分片获取完整文档。
分片越多,协调和合并成本越高。
Filter 和 Query 有什么区别?
- Query Context:计算匹配程度和相关性分数。
- Filter Context:只判断是否匹配,适合状态、范围等精确条件,也更容易缓存。
业务查询应把不需要算分的条件放入 filter。
ES 如何实现并发控制(版本控制/乐观锁)?
频次 ★★★ · 难度 🟡 · 高频:阿里/腾讯
ES 使用基于版本号的乐观锁来避免并发更新冲突:
内部版本控制(_version):
- 每个文档有递增的
_version号,更新/删除时版本号随之递增 - 写操作可指定
version参数,要求当前版本必须匹配才执行:
PUT /my_index/_doc/1?version=5
{
"title": "new title"
}
// 若当前 _version ≠ 5,返回 409 Conflict外部版本控制(version_type=external):
- 使用业务系统自己的版本号(如数据库行记录的版本),ES 只做「新版本号 > 旧版本号」的判断
- 适合业务侧已有乐观锁的场景(如 MySQL 行版本 + CDC 同步到 ES):
PUT /my_index/_doc/1?version=10&version_type=external
{
"title": "new title"
}if_seq_no + if_primary_term(7.x+ 推荐方式):
_seq_no是分片级的递增序号,_primary_term是主分片的任期号- 先 GET 获取文档拿到当前
_seq_no和_primary_term,更新时带回去做条件:
PUT /my_index/_doc/1?if_seq_no=5&if_primary_term=1
{
"title": "new title"
}对比:
| 方式 | 粒度 | 适用场景 |
|---|---|---|
_version | 文档全局 | 简单并发控制 |
version_type=external | 文档全局 | 业务已有版本号 |
if_seq_no + if_primary_term | 分片级 | 精确 CAS(推荐) |
常见追问:
- 高并发写入场景版本冲突怎么办?→ 冲突概率不高时直接重试;冲突频繁应检讨并发模型(是否不必同时写同一文档),或用
retry_on_conflict参数(自动重试,需幂等)
四、相关性与查询
match 和 term 的区别?
match:会分析查询文本,适合text全文检索。term:不分词,按精确词项匹配,适合keyword、数字和状态字段。
对 text 字段直接使用 term 是常见的“查不到数据”原因。
BM25 是什么?
BM25 是常用相关性算法,主要考虑:
- 词项在当前文档中的频率。
- 词项在全部文档中的稀有程度。
- 文档长度归一化。
业务排序通常还会结合时间、热度、距离和业务权重。
must、should、filter、must_not 如何理解?
must:必须匹配,参与算分。should:可选匹配,可提升得分。filter:必须匹配,不参与算分。must_not:必须不匹配。
五、分页与聚合
Elasticsearch 深分页为什么慢?
难度 🔴
使用 from + size 时,每个分片都要取出足够多的候选结果,协调节点再全局排序并丢弃前面的数据。
页数越深,CPU、内存和网络开销越大。
深分页如何优化?
- 普通页面限制最大页数。
- 连续翻页使用
search_after。 - 需要稳定视图时结合 Point in Time。
- 离线全量遍历使用适合的批处理方式。
search_after 需要稳定且唯一的排序字段,通常增加 _id 或业务唯一字段作为最后排序条件。
Aggregation 有哪些常见类型?
- Bucket:按条件分组,如 terms、date histogram。
- Metric:计算指标,如 avg、sum、cardinality。
- Pipeline:基于其他聚合结果继续计算。
高基数字段 terms 聚合可能消耗大量内存,应控制桶数量和字段设计。
六、性能优化
写入性能如何优化?
- 使用 Bulk 批量写入。
- 控制批次大小和并发,避免压垮节点。
- 批量导入期间适当增大 Refresh Interval。
- 减少不必要字段和副本,导入后恢复配置。
- 避免频繁更新同一文档。
- 使用稳定且分布均匀的路由。
查询性能如何优化?
- 精确条件使用 filter。
- 避免通配符前缀和高开销脚本。
- 控制返回字段和结果数量。
- 合理设计 Mapping、分片和路由。
- 对慢查询使用 Profile API 和慢日志分析。
- 预计算高频复杂业务字段。
分片数量如何规划?
没有固定公式,应结合:
- 总数据量和增长速度。
- 单分片大小。
- 查询并发和聚合复杂度。
- 节点数量、堆内存和恢复时间。
可使用 rollover 按大小或时间滚动索引,避免单个索引无限增长。
为什么不建议大量使用脚本查询?
脚本通常无法充分利用索引,需要逐文档计算,CPU 成本高。高频查询应把可计算字段在写入时预处理。
段合并与 forcemerge 对写入性能的影响
频次 ★★ · 难度 🟡 · 高频:阿里
ES 的段(Segment)机制:
- 写入 → 生成新的不可变段文件(类似 LSM-Tree)
- 段过多 → 查询需要合并多个段的倒排表 → 性能下降
- 后台线程定期合并段:小段合并成大段
段合并的过程:
- 后台线程选择合适大小的段(按大小分层)
- 读取段中的文档,合并排序,写入新段
- 旧段标记删除,查询切换到新段
- 释放旧段占用的文件句柄和内存
合并对写入的影响:
| 影响 | 原因 | 缓解措施 |
|---|---|---|
| IO 压力飙升 | 合并读取多个段再写入,产生大量磁盘 IO | 限制合并线程数、调大 indices.store.throttle.max_bytes_per_sec |
| 写入延迟波动 | 合并期间占用 IO 资源,写入请求排队 | 错峰写入、使用 SSD |
| 大段反复合并 | 达到阈值后大段继续合并,越合越大 | 合理设置 segments.merge.policy,对大索引用 forcemerge |
forcemerge 用于索引只读场景(如日志索引归档):
POST /my_index/_forcemerge?max_num_segments=1- 强制合并到指定段数(通常 1),减少段数量
- 期间 IO 压力巨大,必须在业务低峰期手动执行
- 只读索引的 segment 不再变化,forcemerge 后查询性能显著提升
关联概念: 段合并本质是空间换时间——更多段 → 写入更快(不需要合并)、查询更慢(更多文件需扫描);更少段 → 写入变慢(合并开销)、查询更快。和 LSM-Tree(LevelDB/RocksDB)的 compaction 机制同源。
七、集群与故障
集群健康状态 green、yellow、red 表示什么?
- Green:主分片和副本分片全部已分配。
- Yellow:主分片可用,但部分副本未分配。
- Red:至少一个主分片不可用,部分数据无法访问。
为什么分片会 unassigned?
- 节点故障或磁盘不足。
- 分配过滤和 awareness 约束。
- 副本数超过可用节点数。
- 分片恢复失败。
- 集群磁盘水位限制。
应查看 Allocation Explain,而不是盲目重启节点。
脑裂如何避免?
现代集群通过选主和法定人数机制避免多个 Master 同时提交集群状态。部署时仍应:
- 使用至少三个符合条件的主节点。
- 跨故障域部署。
- 保持稳定网络。
- 不随意修改发现和选举配置。
Elasticsearch 选主机制
频次 ★★★ · 难度 🟡 · 高频:阿里
ES 使用 Zen Discovery 模块实现选主(7.x 后默认用基于集群状态版本的选主)。
7.x 前的选主(Zen Discovery):
- 节点发现其他节点后发起选举
- 每个节点投票给比自己集群状态版本高的节点,版本相同则比 nodeId(最小优先)
- 候选节点得票数 ≥
minimum_master_nodes时成为 Master
- 该参数推荐公式:
quorum = N/2 + 1(N = 有资格成为 Master 的节点数) - 设置不当是脑裂的核心原因:设置得太小(如 1)→ 分区后两个子集都选出 Master
7.x 后的选主(改良版):
- 内置法定人数检查(不再依赖手动配置
minimum_master_nodes) - 基于集群状态版本 + 节点加入顺序,更稳定
- Master 选举完成后定期续期 lease,旧 Master 重新加入时自动降级
选主触发条件:
- 集群启动时
- 当前 Master 失联(超时
discovery.zen.ping_timeout,默认 3s) - 节点无法连接 Master,发起新一轮选举
关联知识点: ZooKeeper 的 ZAB 协议也是多数派选主,但 ZK 用「事务 ID(zxid)」比较新旧,ES 用「集群状态版本」。
数据库与 Elasticsearch 如何保持一致?
常见方案:
- 数据库写入后发送可靠消息,异步更新 ES。
- 使用 CDC 订阅数据库变更。
- 定时校验和补偿。
- 查询不到或异常时降级回源。
ES 通常作为派生索引,不应成为核心业务唯一事实来源。
八、场景题
如何设计商品搜索?
- 标题、描述使用
text。 - 品牌、类目、状态使用
keyword。 - 价格、销量和时间使用数值或日期类型。
- 使用 bool query 组合全文检索与过滤。
- 使用 function score 融合文本相关性、销量和新鲜度。
- 使用聚合生成品牌、类目和价格筛选项。
如何设计地理位置搜索?
- 使用
geo_point存储经纬度。 - 使用 geo distance 查询附近数据。
- 按距离排序或衰减打分。
- 先用业务条件过滤,再执行地理查询。
- 对高频固定区域可考虑预计算区域编码。
参见 LBS 地理信息系统。