Elasticsearch 原理大纲

这份你要建立的不是“记几个 DSL”,而是一整条 Elasticsearch 原理链:

文本 -> 分词 -> 倒排索引 -> segment -> refresh / merge -> shard / replica -> query / fetch -> 为什么适合搜索但不适合做主库

一、总纲

Elasticsearch 原理层最核心的 6 条主线:

  1. 倒排索引与全文检索
  2. analyzer / mapping / text / keyword
  3. segment 与近实时搜索
  4. shard / replica 与分布式检索
  5. query / fetch 流程
  6. 为什么它适合搜索、不适合主事务库

二、为什么 Elasticsearch 能做全文检索

数据库最常见的 B+ 树索引,更适合:

  • 等值查询
  • 范围查询
  • 排序

但全文检索更关注的是:

  • 一段文本里有哪些词
  • 哪些文档包含某个词
  • 多个词如何组合查询

这时倒排索引就更合适。

你要抓住的核心

不是“文档里有什么词”,而是“词出现在哪些文档里”。

这就是为什么 ES 对关键词搜索、全文检索、复杂筛选会比数据库 like '%xx%' 自然得多。


三、倒排索引原理链

1. 什么是倒排索引

倒排索引会把文本拆成 term,然后按 term 建索引。

例如:

  • 文档 1:apple phone
  • 文档 2:apple watch
  • 文档 3:android phone

倒排结构更像:

  • apple -> [1, 2]
  • phone -> [1, 3]
  • watch -> [2]
  • android -> [3]

2. 为什么它快

因为查 apple 时,不需要全表扫所有文档,而是直接命中:

  • apple 这个 term
  • 它对应的 posting list

3. 为什么它适合搜索

因为搜索本质上经常是:

  • 查一个词
  • 查多个词组合
  • 查词频、相关性
  • 查包含关系

倒排索引天然就是为这类场景设计的。


四、分词与 analyzer 原理

搜索效果很多时候不只是 DSL,而是分词是否合理。

analyzer 通常包含几步

  1. character filter
  • 先做字符级处理
  1. tokenizer
  • 把文本切成 token
  1. token filter
  • 再做小写化、停用词、同义词等处理

为什么这块重要

因为一旦 analyzer 设计不对:

  • 搜索不到
  • 搜索太宽
  • 排序不合理
  • 中英文效果差

很多“ES 不准”的问题,根因不是查询语句,而是 mapping 和 analyzer。


五、mapping、text、keyword 原理

1. 为什么 mapping 很重要

因为 ES 不是简单“来什么字段就存什么”,字段类型会直接影响:

  • 是否分词
  • 是否能聚合
  • 是否适合排序
  • 索引结构怎么建

2. text

  • 会分词
  • 适合全文检索
  • 不适合直接拿来精确聚合和排序

3. keyword

  • 不分词
  • 适合精确匹配
  • 适合排序、聚合、过滤

4. 工程上的常见做法

一个字段经常会同时有:

  • text 版本负责搜
  • keyword 子字段负责过滤 / 排序 / 聚合

这和数据库设计一样,本质上也是“按查询模式设计索引”。


六、segment、refresh、flush、merge

这块是 ES 很容易被问到的近实时原理。

1. 写入后为什么不是立刻可搜索

因为 ES 写入后,不是每一条都立刻变成“完全可检索”的最终状态。 它通常会先进入内存缓冲和 translog,再经过 refresh 才对搜索可见。

2. refresh 是什么

refresh 的核心是:

  • 把新写入的数据生成新的可搜索 segment
  • 让这些数据对搜索可见

所以 refresh 更偏“搜索可见性”,不是“彻底完成所有底层整理”。

3. flush 是什么

flush 更偏:

  • 把内存和 translog 状态做更稳定的落盘整理

4. merge 是什么

segment 会不断增多,如果一直不整理:

  • 查询要扫更多 segment
  • 管理成本变高
  • 碎片变多

所以后台会不断 merge,把多个小 segment 合并成更大的 segment。

5. 为什么说 ES 是近实时

因为:

  • 写入成功
  • 不等于立刻搜索可见

通常要等 refresh 之后才稳定被检索到。 这就是它和事务数据库“提交即强可见”的重要差异。


七、shard 与 replica 原理

1. shard 在解决什么

一个索引太大时,不可能总靠单机单分片承载。 所以 ES 会把索引拆成多个 shard。

shard 主要解决:

  • 容量问题
  • 并行问题
  • 分布式扩展问题

2. replica 在解决什么

副本主要解决:

  • 高可用
  • 读扩展
  • 主分片故障恢复

3. 为什么分片不是越多越好

因为分片越多,协调成本也会越高:

  • 更多元数据
  • 更多内存占用
  • 更多 query / fetch 汇总成本

所以分片设计本质上是:

  • 容量
  • 并发
  • 运维成本

三者的平衡。


八、查询流程原理

一次搜索请求,常见会经过两个阶段:

1. query phase

协调节点把请求发到相关分片,各分片先本地算:

  • 哪些文档匹配
  • 分数是多少
  • 本分片 top N 是谁

2. fetch phase

协调节点汇总各分片结果,再去取真正需要返回的文档详情。

3. 为什么要这样设计

因为如果一上来就让所有分片把大量完整文档都传回来,网络和内存成本都会很高。 所以 ES 更像是:

  • 先局部筛选
  • 再全局归并
  • 最后按需取详情

九、写入流程原理

简化理解通常是:

  1. 请求写到主分片
  2. 主分片写内存结构和 translog
  3. 再同步到副本分片
  4. refresh 后新文档对搜索可见

这条链路很重要,因为它解释了:

  • 为什么 ES 更偏近实时
  • 为什么它更像“查询镜像”
  • 为什么不能把它当成主事务库

十、为什么 ES 适合搜索,但不适合主事务库

它适合什么

  • 全文检索
  • 搜索排序
  • 多条件筛选
  • 检索型聚合

它不适合什么

  • 强事务主状态
  • 支付 / 订单 / 退款主事实
  • 需要严格提交即强一致可见的核心业务

最重要的工程理解

在真实项目里,更稳的做法通常是:

  • MySQL 做主状态
  • ES 做查询镜像
  • 通过 MQ / binlog / 异步任务去同步

接受短暂延迟,但不让 ES 决定主事实。


十一、搜索慢时原理上优先怀疑什么

1. mapping / analyzer 设计错了

  • 本该 keyword 的做成了 text
  • 本该 text + keyword 双字段的只做了一种
  • 分词器选错

2. 查询 DSL 太重

  • 全文检索叠很多过滤和聚合
  • 排序过重
  • 脚本查询过多

3. 分片设计不合理

  • 分片过多
  • 热点索引
  • segment 太碎

4. 深分页

  • from + size 太大
  • 排序归并成本过高

十二、为什么深分页慢

因为深分页不是“只拿第 10001 到 10020 条”这么简单。 它往往意味着:

  • 每个分片都要先找到前面大量候选结果
  • 协调节点再做全局归并
  • 前面大量结果最后被丢弃

所以深分页常用:

  • search_after
  • scroll
  • 限制最大页数

1. 为什么 from + size 会随着页码变深而越来越贵

因为 ES 的搜索通常是分片并行的。

当你查很靠后的页时,常见过程是:

  1. 每个 shard 先找自己的前 from + size 个候选
  2. 协调节点把这些候选再做一次全局排序
  3. 前面的 from 大量结果最后被丢弃

所以深分页真正贵的地方在于:

  • shard 本地 topN 变大
  • 协调节点 merge sort 压力变大
  • 内存和 CPU 一起上涨

这也是为什么 ES 默认会用:

  • index.max_result_window = 10000

去限制 from + size 深度。

2. search_after 在原理上为什么更合适

search_after 的核心思路不是“跳过前面很多条”,而是:

  • 用上一页最后一条的 sort 值作为游标
  • 从这个游标之后继续取下一页

这意味着它更像:

  • 顺着排好序的结果继续往后拿

而不是:

  • 每次从头开始再丢一大堆

所以它特别适合:

  • 连续向后翻页
  • 无限滚动
  • “加载更多”

3. 为什么 search_after 最好配合 PIT

如果翻页过程中索引刷新了,结果顺序可能变化。

这时候即便你用了 search_after,也可能出现:

  • 某些文档重复
  • 某些文档漏掉

PIT,point in time,本质上就是:

  • 在这次分页期间固定一个索引视图

所以可以这样理解:

  • search_after 解决“不要反复丢前面的结果”
  • PIT 解决“翻页过程中结果集漂移”

4. scroll 应该怎么理解

scroll 更偏:

  • 批量遍历
  • 数据导出
  • 离线处理

而不是给用户做实时搜索翻页。

因为它本质上会维护一段搜索上下文,更像扫描数据,而不是前台交互分页。

5. 真正完整的优化思路

深分页优化不能只停在“换个 API”,更完整的是:

  1. 普通浅分页继续 from + size
  2. 连续翻页改 search_after
  3. 需要稳定视图时配 PIT
  4. 批量导出用 scroll
  5. 限制最大深度
  6. 从产品交互上减少“无限翻页”的需求

6. 一句最稳的总结

ES 深分页慢,本质上是分片查询下的大量候选结果归并和丢弃成本过高;真正的优化不是简单调大窗口,而是根据场景在 from+size、search_after+PIT、scroll 和交互限制之间做正确选择。


十三、ES 在你的项目里应该怎么讲

你更适合的表达不是“我会搭 ES 集群”,而是:

我知道 ES 在系统里应该放哪一层。像搜索、后台筛选、导入后检索这类场景,ES 很适合;但订单、支付、退款、订阅这些主状态还是 MySQL。我的重点是让 ES 成为高效查询层,而不是让它和主事务库职责混乱。


十四、你至少要讲顺的一条原理链

文档写入 -> analyzer 分词 -> 倒排索引 -> segment 生成 -> refresh 后搜索可见 -> 多 shard 查询 -> query/fetch 汇总 -> 返回结果

只要这条链能讲顺,ES 原理层就不会显得空。


十五、最适合你的结尾表达

我对 Elasticsearch 的理解不是停留在“会不会写 DSL”,而是理解它为什么适合全文检索、倒排索引为什么高效、segment 和 refresh 为什么让它成为近实时搜索系统,也知道它和 MySQL 的职责边界应该怎么划。