ElasticSearch 为什么快?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
-
原理深度:光答 “倒排索引” 这四个字不够,面试官要听的是你能不能讲清它内部的查询结构(Term Index、Term Dictionary、Posting List),这一条直接区分 “背答案的” 和 “真懂的”。
-
对比思维:考察你能不能把 ES 和 MySQL 的
B+树做对比,说清楚各自擅长什么场景。能做对比的人,通常真用过这两个东西。 -
架构视野:除了单机数据结构,面试官还想看你能否从分布式(分片并行)、近实时(NRT)、缓存这些架构层面来分析性能。答到这层,冲高级岗就有底气了。
核心答案
先给结论:ES 快是一堆优化叠出来的,没有单点魔法,核心可以拆成三层看:
| 层面 | 关键设计 | 一句话解释 |
|---|---|---|
| 数据结构层 | 倒排索引 | 从 “关键词” 直接定位 “文档”,跳过全表扫描 |
| 数据结构层 | FST + Term Index |
词典的 “目录页” 常驻内存,磁盘最少寻址一次 |
| 数据结构层 | FOR 压缩 + 跳表 + Roaring 位图 | Posting List 压缩存储,多条件求交集飞快 |
| 存储层 | Segment 不可变 | 无锁读取、友好压缩、吃满 Page Cache |
| 存储层 | Doc Values 列式存储 | 排序聚合不用回源解析原文 |
| 架构层 | 分片并行查询 | 一份查询拆到 N 个节点同时算 |
| 架构层 | NRT 近实时写入 | 1 秒可见性,换来写入高吞吐 |
面试时建议按 “数据结构 → 存储 → 架构” 这个顺序答,层次感一下就出来了。
深度解析
一、倒排索引:一切的起点
先搞清楚正排和倒排的区别。MySQL 里存的是 “文档 → 内容”,这就是正排。你想搜包含 “关注” 两个字的文章,数据库只能一条条翻,like '%关注%' 直接全表扫描,数据量一大就是灾难。
倒排索引反过来,存的是 “关键词 → 文档列表”:
上图的对比就是本质区别:
- 正排索引:从文档找内容容易,但从内容找文档,只能逐条匹配
- 倒排索引:写入时先分词(“关注犬小哈” 会拆成 “关注”、“犬小哈” 两个 Term),然后建立 “Term → 文档 ID 列表” 的映射
- 查询时:搜 “犬小哈” 直接拿到 Posting List
[1, 2],O(1) 级别定位,根本不用扫描原文
一句话:写入时多干活(分词、建索引),查询时少干活。搜索引擎 20 年前的核心思想,到现在依然能打。
但光有倒排索引还不够。假设 Term 有上亿个(英文单词、中文词、各种数字组合),怎么快速找到目标 Term 本身就是个问题。总不能每个查询都在磁盘上二分查找几万次吧?
二、Term Index + Term Dictionary:字典的 “目录页”
Lucene 把词典查询拆成了三层结构,这是我认为整道题最出彩的部分:
这三层的文字讲解:
- 第一层 Term Index:可以理解成字典的 “目录页”,只存 Term 的前缀,不存完整内容,所以体积极小,可以整个放进内存。它用的是
FST(Finite State Transducer,有限状态转换器)结构 - 第二层 Term Dictionary:真正有序的词典,存在磁盘上。通过内存里的 Term Index,直接定位到目标 Term 所在的磁盘块,最少只需要一次磁盘寻址
- 第三层 Posting List:拿到了 Term,也就拿到了对应的文档 ID 列表,后面就是算交集、取原文
FST 这玩意值得单独说两句。它有三个漂亮的特点:
- 前缀共享:
cat、catalog、catalogue共用cat前缀,重复部分只存一份,压缩率极高 - 查询 O(len):时间复杂度只跟查询词长度有关,跟词典总量无关
- 内存占用小:正是够小,Term Index 才能常驻堆内存
这三层结构像不像你查字典的过程?先翻目录(内存里的 FST)找到页码,再翻到那一页(磁盘上的 Term Dictionary),最后看到词条解释(Posting List)。
三、存储层的暗器:Segment 不可变 + Doc Values
Segment 不可变设计。Lucene 底层由一个个 Segment 组成,每个 Segment 生成后就不再修改(删除只是打个标记)。这个设计带来三个性能红利:
- 读取无锁:不可变数据天生线程安全,并发查询不需要任何锁竞争
- 压缩率高:内容不变就能放心用激进的压缩算法,省下来的空间全是 IO
- 吃满 Page Cache:不可变文件可以被操作系统放心缓存,热数据几乎全部驻留内存,查询基本不打磁盘
Doc Values 列式存储。倒排索引擅长 “找到文档”,但排序和聚合需要 “按字段值” 来算,用倒排就得把所有文档原文拉出来解析一遍,太浪费了。所以 ES 写入时会额外存一份列式存储的 Doc Values,排序聚合直接读它,快得多。
另外提一句,数值类型和地理位置用的是 BKD-Tree(空间多维索引),范围查询效率高,跟倒排索引各管一摊。
顺带补充一个容易忽视的点:Posting List 在磁盘上也不是裸存的,文档 ID 会先做 delta 编码,再用 Frame of Reference 压缩成块,配上跳表(Skip List)支持快速跳块。多个条件 AND 求交集时不用一个个遍历。而 Roaring Bitmap 主要用在 filter 缓存上,缓存下来的 DocIdSet 求交并集飞快。
四、写入侧:为什么说是 “近实时”?
ES 写入不是立刻落盘可查的,中间走了一套流水线:
这张图的文字讲解:
- 第一步:数据先进内存 Buffer,同时写 Translog(事务日志,防止宕机丢数据)
- 第二步 refresh:默认每 1 秒(
index.refresh_interval可调),Buffer 里的数据生成一个新 Segment,直接放进文件系统缓存,这时数据就能被搜到了。注意,这一步没有落盘,这就是 “近实时(NRT)” 的由来:放弃强实时的落盘,换来 1 秒可见性 + 高吞吐写入 - 第三步 flush:Segment 真正 fsync 到磁盘,清空 Translog,默认 30 分钟或 Translog 太大(默认 512MB)时触发
所以严格来说,ES 不是实时搜索引擎,是近实时(Near Real-Time)。这个追问频率极高,答不上来会很减分。
五、架构层:分片并行 + 缓存体系
单机再快也有天花板,ES 是天生的分布式架构:
- 分片并行:一个索引拆成 N 个分片,散落在不同节点。查询时协调节点把请求广播到所有分片并行执行,各自算完再汇总(scatter-gather 模式)。数据量翻倍,加机器就是了,查询延迟基本不涨
- 缓存体系:文件系统缓存(Page Cache)扛大头,加上 ES 自己的 Shard Request Cache(缓存分片级聚合结果)、Query Cache(缓存过滤查询的位图),热点数据的查询几乎就是纯内存操作
面试高频追问
-
ES 是实时的吗?
不是,是近实时。写入到可搜索之间默认有 1 秒的 refresh 间隔。要强一致场景(比如秒杀扣库存)别用 ES,那是 MySQL 的事。
-
ES 为什么深分页慢?
from + size查询,每个分片都要取from + size条数据到协调节点排序。翻到第 1000 页时,每个分片都扛不住了。生产环境用search_after,导出场景用 Scroll。 -
refresh、flush、force merge 分别干什么?
refresh 是 Buffer 生成 Segment 进缓存(可搜索),flush 是 Segment 落盘并清 Translog,force merge 是把多个小 Segment 合并成大的(查询提速,但很吃资源,建议低峰期做)。
-
既然 Segment 不可变,那更新和删除怎么办?
打标记(.del 文件记录删除的文档号),查询时过滤掉,真正的物理删除要等 Segment 合并时才发生。
常见面试变体
- “ES 的写入流程说一下?”(本文第四节展开答)
- “倒排索引是什么?画一下结构”
- “MySQL 的
B+树和 ES 的倒排索引有什么区别?各自适合什么场景?” - “为什么用 ES 不直接用 MySQL 的
like?”
记忆口诀
结构看三层:目录(Term Index/FST)→ 词典(Term Dictionary)→ 倒排列表(Posting List);存储两板斧:不可变 Segment + 列式 Doc Values;架构一手牌:分片并行加缓存;写入近实时:1 秒 refresh 见。
总结
一句话:ES 快 = 倒排索引让查询避开全表扫描 + FST 目录常驻内存让定位最少一次磁盘寻址 + 不可变 Segment 与列式存储吃满缓存 + 分片并行把压力摊到多台机器。面试时把这四层讲清楚,从数据结构聊到架构,这道题就是你的送分题。
