Skip to content

Elasticsearch 底层原理

学习 ES 不能只停留在会写 matchtermbool。真正能排查线上问题,必须知道 ES 为什么查得快、为什么写入后不是马上可见、为什么更新成本比数据库高、为什么深分页慢、为什么相关性排序会受字段长度和词频影响。

一句话理解:

ES 是把业务文档提前加工成适合搜索的数据结构,查询时不再逐条扫描,而是通过倒排索引、列式 doc values、分片并行和相关性评分快速找到结果。

学习目标

目标需要掌握什么
知道为什么快倒排索引、跳表、缓存、doc values、分片并行
知道为什么近实时refresh、segment、translog、flush 的关系
知道写入怎么工作路由、主分片、副本、内存 buffer、segment、merge
知道查询怎么工作query phase、fetch phase、协调节点合并 TopN
知道评分怎么来TF、IDF、字段长度、BM25、boost、filter 不评分
知道更新为什么贵Lucene segment 不可变,更新是删除旧文档再写新文档
会验证原理使用 _analyze_explainprofile、slowlog 排查

ES 为什么查询快

数据库里如果用:

sql
select * from product where product_name like '%降噪耳机%';

当数据量很大时,普通 B+Tree 索引很难高效支持前后都有 % 的模糊搜索,往往要扫描大量记录。ES 的思路不一样:写入时就把文本拆成词,并建立“词到文档”的映射。

mermaid
flowchart TD
    A["商品文档"] --> B["分词"]
    B --> C["降噪"]
    B --> D["耳机"]
    C --> E["倒排索引:降噪 -> 文档列表"]
    D --> F["倒排索引:耳机 -> 文档列表"]
    G["用户搜索 降噪耳机"] --> H["查询分词"]
    H --> I["查倒排索引"]
    I --> J["合并候选文档"]
    J --> K["评分和排序"]

ES 快主要来自这些点:

能力原理解决什么问题
倒排索引词到文档列表全文搜索不扫全表
分词把文本拆成 token中文、英文、型号词能被召回
postings list每个词对应有序文档列表快速求交集、并集和过滤
skip data文档列表中跳跃查找跳过不可能匹配的大段文档
doc values面向列的字段存储排序、聚合、脚本读取更高效
filter cache过滤条件可缓存状态、类目、权限范围重复过滤更快
分片并行多个分片同时查询数据量大时利用多节点 CPU 和 IO

注意:ES 查询快不是因为它“魔法般比数据库高级”,而是它为了搜索提前付出了写入、存储、同步和维护成本。

倒排索引内部有什么

倒排索引不只是:

text
词 -> 文档 ID 列表

为了支持评分、短语搜索、高亮,它通常还会保存更多信息。

信息作用
term分词后的词
doc id包含这个词的文档编号
term frequency词在文档里出现多少次
position词在文本中的位置,支持短语查询
offset词在原文中的字符位置,支持高亮
norm字段长度等归一化信息,影响评分

举个简化例子:

文档 IDproductName
1无线蓝牙降噪耳机
2入耳式蓝牙耳机
3机械键盘

倒排后可以理解成:

text
蓝牙 -> [1, 2]
耳机 -> [1, 2]
降噪 -> [1]
机械 -> [3]
键盘 -> [3]

用户搜“蓝牙降噪耳机”,ES 不需要逐条看所有商品名,而是先找到这些词对应的文档列表,再合并、评分、排序。

为什么 ES 适合搜索但不适合当主库

ES 为了搜索做了很多反数据库设计。

对比项MySQLElasticsearch
主要目标事务、关系、精确查询搜索、聚合、近实时分析
数据模型表、行、列、外键或关联JSON 文档
查询方式SQL、Join、事务Query DSL、倒排索引
一致性强事务能力更强搜索视图通常最终一致
更新方式原地更新行数据删除旧文档标记,再写新文档
复杂 Join擅长不擅长,推荐写入前冗余

商业系统里更稳的架构是:

mermaid
flowchart TD
    A["业务写入"] --> B["MySQL 保存事实数据"]
    B --> C["MQ / CDC / 补偿任务"]
    C --> D["ES 保存搜索视图"]
    E["交易判断"] --> B
    F["搜索和聚合"] --> D

库存、支付、订单状态、账户余额必须以数据库或业务服务为准。ES 适合回答“哪些文档符合搜索条件”,不适合负责强一致业务决策。

写入流程

一条文档写入 ES,不是简单放进一个文件。

mermaid
flowchart TD
    A["客户端写入文档"] --> B["协调节点接收"]
    B --> C["根据 _id 或 routing 计算分片"]
    C --> D["转发到主分片"]
    D --> E["写入内存 buffer"]
    D --> F["写入 translog"]
    D --> G["复制到副本分片"]
    G --> H["达到确认条件"]
    H --> I["返回写入成功"]
    E --> J["refresh 生成可搜索 segment"]
    J --> K["后续 merge 合并小 segment"]

关键点:

  1. 写入先到主分片,再复制到副本。
  2. translog 用于故障恢复。
  3. 写入成功不等于立刻能搜到。
  4. refresh 后生成新的可搜索 segment。
  5. segment 是不可变的,小 segment 后续会 merge。

为什么叫近实时搜索

ES 常被称为 Near Real-Time Search,近实时搜索。原因是:写入成功后,文档通常不会马上出现在搜索结果里,需要等 refresh。

mermaid
flowchart TD
    A["写入文档成功"] --> B["进入内存 buffer 和 translog"]
    B --> C{"是否 refresh"}
    C -- "未 refresh" --> D["搜索不到新文档"]
    C -- "已 refresh" --> E["生成 segment"]
    E --> F["搜索可见"]

默认情况下,常见配置会每隔一段时间自动 refresh。这个间隔越短,文档越快可见,但系统开销越大。

配置效果代价
refresh_interval: 1s写入后通常 1 秒左右可见适合普通搜索
调大 refresh写入吞吐更好新数据可见更慢
手动 refresh立刻可见开销大,不适合高频调用
批量导入时关闭或调大 refresh提升导入速度导入期间不适合实时搜索

所以商品改价、上下架、订单状态同步到 ES 后,搜索页短时间看到旧数据是可能的。业务上要设计最终一致和补偿,而不是要求 ES 当强一致数据库。

refresh、flush、merge 的区别

这三个词很容易混。

概念做什么解决什么问题
refresh让内存中的新数据生成可搜索 segment新文档什么时候能被搜索到
flush提交 Lucene commit,清理 translog故障恢复和持久化边界
merge把多个小 segment 合并成大 segment减少小文件,提高查询效率,清理删除标记
mermaid
flowchart TD
    A["写入数据"] --> B["内存 buffer"]
    A --> C["translog"]
    B --> D["refresh"]
    D --> E["新的 segment 可搜索"]
    E --> F["多个小 segment"]
    F --> G["merge 合并"]
    C --> H["flush 后 translog 可清理"]

如果写入量很大但 refresh 太频繁,会产生大量小 segment,查询和 merge 压力都会上升。

更新和删除为什么成本高

Lucene segment 是不可变的。不可变的好处是查询时结构稳定、缓存友好;代价是更新不能像数据库那样直接原地改。

更新文档可以理解成:

mermaid
flowchart TD
    A["更新文档 1001"] --> B["旧文档打删除标记"]
    B --> C["写入新版本文档"]
    C --> D["refresh 后新版本可见"]
    D --> E["merge 时真正清理旧文档"]

所以:

  1. 高频更新会产生更多删除标记和 merge 压力。
  2. 大字段文档局部更新,本质仍要重新索引文档。
  3. ES 不适合做高频强一致状态表。
  4. 商品、订单搜索可以同步变化,但事实仍应以主库为准。

查询流程:Query Phase 和 Fetch Phase

分布式查询通常分两阶段。

mermaid
flowchart TD
    A["客户端查询"] --> B["协调节点"]
    B --> C["Query Phase"]
    C --> D["每个分片本地查 TopN 文档ID和分数"]
    D --> E["协调节点合并全局 TopN"]
    E --> F["Fetch Phase"]
    F --> G["到对应分片取 _source"]
    G --> H["返回最终结果"]

Query Phase 负责找候选和评分,Fetch Phase 负责取原文档内容。深分页慢的原因就在这里:如果 from=10000&size=20,每个分片都要先拿出更多候选,协调节点合并后再丢弃前面大量结果。

filter 为什么通常更快

mustfilter 都能限制结果,但语义不同:

子句是否影响 _score常见用途
must影响关键词全文匹配
should影响加权、召回扩展
filter不影响状态、类目、品牌、权限、时间范围
must_not不影响或弱相关排除条件

过滤条件不需要计算相关性分数,并且相同过滤条件可能被缓存。因此品牌、类目、库存状态、租户权限、时间范围这类硬条件,应该尽量放在 filter

相关性评分:为什么有的结果排前面

ES 常见评分模型是 BM25。零基础可以先这样理解:

一个文档越多次命中用户搜索词,且这些词越稀有、字段越聚焦,它的相关性分数通常越高。

影响评分的常见因素:

因素含义例子
TF词在当前文档出现次数商品名多次出现“降噪”
IDF词在全局是否稀有“降噪”比“商品”更有区分度
字段长度字段越短且命中越集中,通常越相关标题命中比长详情命中更强
boost人工提高字段或条件权重productName^5
filter只过滤不评分stockStatus=IN_STOCK
json
GET /product_search/_search
{
  "query": {
    "multi_match": {
      "query": "无线降噪耳机",
      "fields": ["productName^5", "subTitle^2", "tags"]
    }
  }
}

这里 productName^5 表示商品标题命中更重要。商业搜索里通常还会结合销量、库存、运营标签、新品权重等业务规则。

_explain:看一条文档为什么被命中

当你觉得“这个结果为什么排这么前”时,可以用 _explain

json
GET /product_search/_explain/10001
{
  "query": {
    "match": {
      "productName": "无线降噪耳机"
    }
  }
}

它会返回该文档如何匹配、每部分分数怎么来的。生产排查时不要对大批量请求都开 _explain,它适合定位单条异常结果。

profile:看查询慢在哪里

profile 可以分析查询 DSL 每个部分的耗时。

json
GET /product_search/_search
{
  "profile": true,
  "query": {
    "bool": {
      "must": [
        { "match": { "productName": "无线降噪耳机" } }
      ],
      "filter": [
        { "term": { "stockStatus": "IN_STOCK" } }
      ]
    }
  }
}

profile 本身会增加开销,适合测试和故障定位,不适合长期对线上接口开启。

doc values 为什么重要

倒排索引适合“根据词找文档”,但排序和聚合经常需要“按文档读取某个字段的值”。这时 doc values 更合适。

数据结构访问方向适合
倒排索引term -> docs全文检索、精确过滤
doc valuesdoc -> field value排序、聚合、脚本读取

这也是为什么聚合、排序字段通常要用 keyword、数值、日期等类型,而不是直接对 text 字段聚合。

常见误区

误区正确理解
ES 写入成功就一定马上搜到要等 refresh 后才可搜索
更新只是改一个字段底层接近删除旧文档再写新文档
分片越多查询越快分片过多会增加协调、内存和恢复成本
_score 是固定不变的评分受查询、字段、分词、全局统计、boost 影响
filter 和 must 没区别filter 不评分,更适合硬过滤条件
ES 快所以可以替代 MySQLES 是搜索视图,不是事务事实源

关联知识点

知识点继续学习什么
核心概念文档、Mapping、倒排索引、refresh、segment
Mapping 与查询字段类型和 Query DSL 如何配合
分词中文分词、索引分词、查询分词
集群分片、副本、协调节点、分片分配
性能优化与排查慢查询、写入慢、堆内存、磁盘水位

小结

ES 查询快,是因为它在写入时提前分词、建立倒排索引和列式 doc values,查询时通过分片并行、过滤缓存、评分排序快速得到 TopN。它不是强一致事务库,而是近实时搜索引擎。理解 refresh、segment、translog、merge、query/fetch、BM25 和 doc values,才能真正解释 ES 为什么快、为什么有延迟、为什么更新贵、为什么深分页慢。