Elasticsearch 原理与用法

Elasticsearch 原理与用法

一、倒排索引

是什么?

Term(词项)→ 包含该 Term 的文档 ID 列表。写入时对文档分词,建立「词 → 文档」的关联,查询时直接按词定位文档。

创建流程

  1. 建立文档列表,每个文档有唯一 ID
  2. 分词器对文档分词,生成 <词语, 文档 ID> 数据
  3. 以词语为索引关键字,记录词与文档的对应关系(可附带位置、词频,用于高亮和排序)

检索流程

  1. 根据分词查找对应的文档 ID
  2. 根据文档 ID 找到文档

由什么组成?

  • 单词字典(Term Dictionary):存储单词列表,一般用 B+Tree 或 Hash 拉链法,提高查询效率
  • 倒排列表(Posting List) :
    • DocID:文档 ID
    • TF:词频
    • Position:词在文档中的位置,用于检索
    • Offset:字符偏移量,用于高亮显示

倒排索引 vs 正排索引

正排索引 倒排索引
关联方向 文档 ID → 分词 分词 → 文档 ID
优点 维护成本低,新增数据只需在正排表末尾加 ID->内容 查询效率高
缺点 查询要扫描所有词逐一比较,效率低 建立成本高;文档每次更新都要重建;有精度问题(dogs 和 dog 想匹配一致需要合适的分词器)

可以针对某些字段不做索引吗?

可以。设 index: false 后该字段不能被检索(但结果中仍会返回),能节省存储空间。


二、写入流程(NRT)

1. 核心概念

  • refresh :默认每 1 秒执行一次,将内存 Buffer 中的数据写入 Lucene 段,使数据可被搜索。这就是 ES 被称为 Near Real-Time(NRT) 的原因。
  • translog:预写日志(WAL),每次写入同时记录 translog,防止宕机丢数据。
  • flush:将 Lucene 段持久化到磁盘,并清空 translog。默认每 30 分钟或 translog 达到阈值时触发。

2. 完整写入流程(了解)

  1. 客户端发送写请求到协调节点。
  2. 协调节点根据路由规则找到对应的主分片。
  3. 主分片将数据写入内存 Buffer,同时写入 translog。
  4. 主分片将请求同步到副本分片。
  5. 默认每 1 秒执行 refresh,将 Buffer 中的数据生成新的 Lucene 段,此时数据可被搜索。
  6. 定期执行 flush,将段落盘,清空 translog。

3. 为什么是 NRT

  • 数据写入后不会立即被搜索到,需要等待 refresh(默认 1 秒)。
  • 所以 ES 是"近实时",不是实时。

4. 一句话

"ES 写入时先写内存 Buffer 和 translog,然后默认每秒 refresh 一次生成 Lucene 段,数据才能被搜索。translog 保证宕机不丢数据,flush 负责落盘。所以 ES 是近实时搜索。"


三、查询流程(Query Then Fetch)

1. 两阶段流程

ES 查询分为两个阶段:

第一阶段:Query
  1. 客户端发送查询请求到协调节点。
  2. 协调节点将查询广播到索引的所有分片(或相关分片)。
  3. 每个分片在本地执行查询,返回文档 ID 和排序值(不返回文档内容)。
  4. 协调节点收集所有分片的结果,进行全局排序,选出需要返回的文档 ID。
第二阶段:Fetch
  1. 协调节点根据选出的文档 ID,向对应的分片发送获取请求。
  2. 各分片返回完整的文档内容。
  3. 协调节点将结果返回给客户端。

2. 为什么分两阶段

  • Query 阶段只返回 docId 和排序值,数据量小,减少网络传输。
  • Fetch 阶段才取回完整文档,避免不必要的 IO。
  • 这就是"Query Then Fetch"的由来。

3. 一句话

"ES 查询分两阶段:Query 阶段协调节点广播查询,各分片返回 docId 和排序值,协调节点合并排序;Fetch 阶段再根据 docId 去各分片取完整文档。这样设计是为了减少网络传输,先拿到 ID,再取内容。"


四、附:query 与 filter 的区别(高频)

维度 query filter
是否计算评分 是 否
是否可缓存 否 是
适用场景 全文检索、相关性排序 精确匹配、范围过滤
性能 较低 较高

面试话术:

"filter 不计算评分,可以被缓存,所以性能更好。精确匹配、范围过滤尽量用 filter,全文检索和需要相关性排序的用 query。"


五、分词器(Analyzer)

由什么组成?

Character Filters(0 个或多个,处理原始文本,如去 HTML 标签)→ Tokenizer(1 个,按规则切词)→ Token Filters(0 个或多个,转小写、去停用词、加近义词)

IK Analyzer

  • 为什么中文要装插件? ES 内置分词器按空格 / 字符切分,中文没空格,默认的 Standard 会退化成单字切分,效果很差
  • ik_max_word(细粒度)模式:尽可能多切。以「数据库索引可以大幅提高查询速度」为例 → 数据库 / 数据 / 索引 / 可以 / 大幅 / 提高 / 查询 / 速度
  • ik_smart(粗粒度)模式:已切出的词不再被占用 → 数据库 / 索引 / 可以 / 大幅 / 提高 / 查询 / 速度
  • 最佳实践 :建索引用 ik_max_word(召回优先),查询用 ik_smart(精准优先)
  • 搜不到 / 搜不准怎么排查? 先用 _analyze 接口看分词结果,再考虑加自定义词典(扩展词 / 停用词)、同义词
  • 拼音搜索 :装 elasticsearch-analysis-pinyin,做汉字与拼音互转,一般配合 IK 一起用

其余分词器知道名字即可(了解)

  • ES 内置:Standard(默认,英文转小写,中文单字切分)、Simple、Stop、Whitespace、Keyword、Fingerprint
  • 中文第三方:Ansj、ICU、THULAC、Jcseg
  • 这些实际项目几乎不单独用

六、数据类型与 Mapping

常见数据类型

  • 关键词:keyword 、constant_keyword、wildcard
  • 数值:long、integer、short、byte、double、float、half_float、scaled_float
  • 其他:boolean、date、date_nanos、binary、ip、version
  • 文本:text 、annotated-text、completion
  • 对象与关系:object、flattened、nested、join
  • 范围:integer_range、float_range、long_range、double_range、date_range
  • 空间:geo_point、geo_shape

你说项目中都是用keyword,为什么不用text

keyword 和 text 的区别?

keyword 不走分词器,查询效率高,用于精确匹配、排序、聚合;text 会走分词器,用于全文检索。常见做法是在 fields 里给 text 字段挂一个 keyword 子字段,兼顾两种用途。

有专门的数组类型吗?

没有。默认任何字段都可以包含零个或多个值,但数组内所有值必须是同一数据类型。

字段类型能改吗?

不能。Mapping 的字段类型只能增加不能修改,要改只能 reindex 重建索引,或重新建模导入数据。

Nested 类型是什么?有什么用?

object 类型的特殊版本,允许对象数组被独立查询 。

默认情况下数组会被扁平化,多个数组字段之间会做笛卡尔积,导致查出实际不存在的数据;改用 nested 可以避免。

怎么把多个字段合并成一个?

用 copy_to。例如把 first_name 和 last_name 合并为 full_name(full_name 不会出现在查询结果里)。

什么是 Mapping?动态和显式有什么区别?

Mapping 定义字段名称、数据类型、是否索引、分词器等,一个 Index 对应一个 Mapping。

  • 动态 Mapping:写入时自动推断、自动建立映射
  • 显式 Mapping:手动控制字段的存储和索引方式

实际项目应尽量手动定义映射。

为什么插入数据不用指定 Mapping?

因为写入文档时如果索引不存在,ES 会根据数据类型自动推断(Dynamic Mapping),但推断结果经常不准确。

自定义 Mapping 的实操步骤

  1. 创建临时 Index,插入一批临时数据
  2. 调用 Mapping API 拿到自动生成的映射定义
  3. 在此基础上修改(补 keyword、nested 等)
  4. 删除临时 Index

动态 Mapping 有几种属性配置?

4 种,默认 true:

  • true:新字段被加入映射
  • runtime:新字段作为运行时字段加入,不索引,查询时从 _source 加载
  • false:忽略新字段,不索引、不可搜索
  • strict:检测到新字段直接抛异常拒绝文档,新字段必须显式加入映射

怎么防止字段无限膨胀(映射爆炸)?

用映射限制设置,常用的几个:

  • index.mapping.total_fields.limit:字段总数上限,默认 1000
  • index.mapping.depth.limit:字段最大深度,默认 20
  • index.mapping.nested_fields.limit:nested 字段数上限,默认 50
  • index.mapping.nested_objects.limit:单文档 nested 对象数上限,默认 10000
  • index.mapping.field_name_length.limit:字段名最大长度,默认无限制

想让某个字段不被索引怎么做?

Mapping 中设 index: false。

相关属性 index_options 控制倒排索引记录的内容:docs(只记 DocID)→ freqs(+词频)→ positions(默认,+位置)→ offsets(+字符偏移量)。记录越多,占用空间越大,检索越精确。


七、查询 DSL

查询语句分几类?

  1. 请求体查询(最常用) :即 Query DSL,分叶子查询(term、match、match_phrase)和复合查询(bool、dis_max)
  2. 请求 URI:条件放 URL 里,不常用
  3. 类 SQL 检索:功能不完备,不推荐

term 查询和全文检索的区别?

  • term:不做分词处理,查询词与文档中的词精确匹配才命中,用于非文本字段
  • 全文检索:走对应分词器,流程为「分词 → 逐词查询 → 汇总得分」,用于文本字段

如何实现范围查询?

用 range,支持 gt / gte / lt / lte,可作用于数值、日期、字符串字段。只能查询单个字段,不能作用于多个字段。

match 和 match_phrase 的区别?

  • match:多个检索词之间默认是 or 关系,可用 operator 改为 and
  • match_phrase:多个检索词之间默认是 and ,且词的位置关系会影响搜索结果(短语匹配)

multi_match(单条件多字段匹配) 有几种匹配策略?

  • best_fields(默认):包含任一条件即可,最终得分取最佳匹配字段的得分
  • most_fields:包含任一条件即可,最终得分合并所有匹配字段,条件之间默认 or
  • cross_fields:跨字段匹配,解决了 most_fields 查询词无法用 and 连接的问题,相当于把多个字段整合成一个字段,但不像 copy_to 那样占用存储

bool 有哪几种查询子句?

子句 含义
must 必须匹配
should 应该匹配一个或多个
must_not 必须不能匹配
filter 必须匹配,可走缓存,效率更高

八、总结速查表

概念 要点
倒排索引 Term → DocId,全文检索快
写入流程 Buffer + translog → refresh(1s)→ 可搜索 → flush 落盘
查询流程 Query(取 docId)→ Fetch(取文档)
NRT 近实时,默认 1 秒可见
translog 防宕机丢数据
filter 不评分、可缓存,性能优于 query
相关推荐
马剑威(威哥爱编程)1 小时前
【AI全栈后端12-02】Spring Boot 跑通第一个 AI 对话接口:HR 政策问答机器人实战
java·人工智能·spring boot·机器人
IT枫斗者枫哥1 小时前
AI返回合法JSON,字段就可信吗?给抽取结果补一道业务校验
java·人工智能·后端
IT枫斗者枫哥1 小时前
同一个requestId换了参数,为什么不能直接返回旧结果?
java·后端
天天被压力1 小时前
【别再到处找免费股票数据API了:官方204个接口,32篇一次讲透 #06】Python实时行情总报错?五档盘口+逐笔一次跑通
java·人工智能·python
今年下半年1 小时前
【网站连通性检测】java项目telnet、curl命令的使用
java·curl·telnet
摇滚侠1 小时前
《Spring Boot 3:高级与架构设计》第 2 章 IOC 容器的高级机制 编程式驱动 IOC 阅读笔记 7
java·spring boot·笔记
IT枫斗者枫哥1 小时前
ORDER BY时间还会漏单?用相同时间和插入记录测一次分页
java·后端
IT枫斗者枫哥1 小时前
UPDATE影响0行,接口却返回成功:把版本冲突接回业务
java·后端
IT大白鼠1 小时前
搜索系列 · 第 01 篇——认知入门:Elasticsearch 是什么
elasticsearch·nosql