Elasticsearch学习
2026/7/17 4:30:00 网站建设 项目流程

一、ES的简单了解


(一)直面Elasticsearch
Elasticsearch(通常简写为ES)是一个开源的分布式搜索和分析引擎,它被设计用于快速、实时地搜索和分析大规模数据。以下是对Elasticsearch的简单了解:

分布式搜索引擎:Elasticsearch是一个分布式系统,它可以在多个节点上运行,允许你存储和搜索大量的数据。这使得它非常适合处理日志、文档、地理空间数据等各种类型的信息。

文本搜索:Elasticsearch是一个强大的文本搜索引擎,它可以高效地搜索和匹配文本数据。它支持全文搜索、模糊搜索、多字段搜索等功能,使得用户可以轻松地构建搜索引擎、推荐系统和数据分析应用。

实时性:Elasticsearch支持实时搜索,这意味着当你添加、更新或删除文档时,你可以立即在搜索结果中看到变化,这对于监控、日志分析和实时报告非常有用。

多种数据类型支持:Elasticsearch不仅支持文本数据搜索,还支持地理空间数据、数值数据、日期和时间数据等多种数据类型的搜索和分析。

弹性和可扩展性:Elasticsearch是弹性的,你可以根据需要添加或删除节点,以适应不断增长的数据和负载。它还具有自动分片和复制机制,以确保数据的高可用性和可扩展性。

RESTful API:Elasticsearch提供了一个易于使用的RESTful API,使得与其交互变得简单。你可以使用HTTP请求来执行各种操作,例如索引文档、执行搜索查询、管理索引和节点等。

生态系统:Elasticsearch是Elastic公司的一个产品,它是ELK(Elasticsearch、Logstash和Kibana)堆栈的一部分,用于日志收集、分析和可视化。此外,有丰富的插件和工具,可以扩展Elasticsearch的功能。

二、基本概念回顾

当使用Elasticsearch时,有一些基本概念是很重要的,下面是一些ES基本概念的回顾:

索引(Index)

概念:索引是Elasticsearch中的一个逻辑容器,用于组织和存储相关的文档数据。它类似于关系数据库中的表,但更灵活。
理解:可以将索引看作是一个大型数据集的容器,用于存储相似类型的数据。例如,你可以创建一个名为"products"的索引,用于存储产品信息。


文档(Document)

概念:文档是存储在索引中的基本数据单元,通常以JSON格式表示。每个文档代表了一个独立的数据记录。
理解:文档类似于关系数据库中的一行记录。例如,在"products"索引中,每个文档可以代表一个产品,包括产品的名称、描述、价格等信息。


字段(Field)

概念:字段是文档中的数据项,它们包含了文档的具体信息。每个字段都有一个名称和一个对应的值。
理解:在一个文档中,每个字段代表了一个属性或特征,例如,一个产品文档可以包含字段如“productName”、“productDescription”、“price”等,每个字段存储相关信息。

综合起来,可以将这些概念视为组织和存储数据的层次结构:

一个索引可以包含多个文档,这些文档代表了不同类型的数据。
每个文档包含多个字段,每个字段存储文档的具体数据。


这种层次结构的弹性和灵活性使得Elasticsearch非常适合处理大规模、异构数据,并且具有强大的搜索和分析功能。通过理解这些概念,你可以更好地组织、检索和分析你的数据

(二)映射

Mapping 定义了一个索引中每个字段的数据类型、索引方式以及查询规则,相当于 Elasticsearch 的"表结构(Schema)"。

如果你有 MySQL 基础,可以这样类比:

MySQLElasticsearch
DatabaseIndex
Table(ES 7+ 已取消 Type,可理解为 Index)
ColumnField
表结构(DDL)Mapping
Row

Document

例如,MySQL 中建表:

CREATE TABLE user ( id BIGINT, name VARCHAR(100), age INT, birthday DATE );

对应到 ES,大概就是:

PUT user_index { "mappings": { "properties": { "id": { "type": "long" }, "name": { "type": "text" }, "age": { "type": "integer" }, "birthday": { "type": "date" } } } }

这里的Mapping就是在告诉 ES:

  • id 是 long
  • name 是 text
  • age 是 integer
  • birthday 是 date

Mapping 最容易考的几个点

① text 和 keyword 有什么区别?

text会分词,keyword是整体

一个是全文搜索一个是精确查询

② Mapping 可以自动生成吗?

可以。

ES 默认:

Dynamic Mapping。

例如:

第一次:

{ "age":18 }

ES 自动:

"age":"long"

第二次:

{ "name":"Tom" }

自动:

"name":"text"

但是不建议这么做,因为如果由text->long 会冲突报错。

③ Mapping 能修改吗?

text 改 keywork是不行的,因为底层的倒排索引已经建好了,改意味着Segment重建,一般都是重建索引

Mapping 是 Elasticsearch 中对索引结构的定义,类似于关系型数据库中的表结构(Schema)。它不仅定义字段的数据类型,如 text、keyword、integer、date 等,还决定字段是否建立索引、采用什么分词器、是否支持排序和聚合等。不同的 Mapping 会决定底层采用不同的数据结构,例如 text 字段建立倒排索引,数值和日期字段采用 BKD Tree,排序和聚合依赖 Doc Values。因此 Mapping 不仅影响数据存储方式,也直接影响查询性能和功能。由于底层索引结构已经生成,大部分字段类型创建后不能直接修改,通常需要重新创建索引并进行 Reindex。


映射(Mapping)是Elasticsearch中的一个重要概念,它用于定义索引中文档的结构和字段的属性。映射指定了每个字段的数据类型、如何分析文本、是否存储原始数据等信息。以下是关于映射的详细介绍:

字段的数据类型:映射确定了每个字段的数据类型,例如文本、数值、日期、布尔值等。指定正确的数据类型有助于Elasticsearch正确地索引和搜索数据。

分析器(Analyzer):对于文本字段,映射可以指定使用哪种分析器来处理文本数据。分析器决定了如何将文本拆分成词条,以及如何处理这些词条,例如小写化、删除停用词等。

存储选项:映射可以指定是否将字段的原始值存储在索引中。存储原始值可以提高检索性能,但会占用更多的存储空间。

多值字段:映射允许你指定字段是否可以包含多个值,这对于数组或多选字段非常有用。

日期格式:对于日期字段,映射可以定义日期的格式,以确保正确的日期解析和排序。

自定义字段属性:你还可以在映射中定义自定义字段属性,例如字段的权重、是否可搜索、是否可排序等。

嵌套对象:映射允许你在文档中包含嵌套对象,这些对象可以具有自己的字段和映射。

映射的正确定义对于Elasticsearch的性能和数据质量非常重要。它确保了索引中的文档被正确地存储和检索,并允许执行高级的搜索和分析操作。通常,映射可以自动创建,但在需要更精细的控制时,也可以手动定义映射。

Lucene 负责“单机搜索能力”,ES 负责“把多个 Lucene 组成一个可用的分布式搜索服务”。

可以按照职责拆分:

Elasticsearch | -------------------------------- | | 分布式系统能力 搜索引擎能力 | | | | Cluster管理 Lucene Shard分配 倒排索引 副本复制 Segment 节点通信 BKD Tree REST API FST DSL解析 BM25 权限 translog 查询执行

一、Lucene 负责什么?

Lucene 是搜索核心。

1. 索引构建

比如:

文档:

{ "title":"Java技术", "price":100 }

Lucene负责:

text字段

分词:

Java 技术

建立:

倒排索引:

Java | doc1 技术 | doc1

2. 倒排索引

这是 Lucene 最核心能力。

例如:

10亿篇文章。

搜索:

Java

不是扫描。

而是:

Java | Posting List | docId列表

快速找到文档。


3. 数值索引

例如:

{ "price":100 }

Lucene:

使用:

BKD Tree

支持:

  • range查询
  • 数值term查询
  • geo查询

例如:

price 100~200

4. 相关性计算

例如:

搜索:

iphone

哪个文档排前面?

Lucene:

计算:

  • TF-IDF(老)
  • BM25(现在)

得到:

doc1 score=10.2 doc2 score=8.5

5. Segment管理

Lucene:

索引不是一个大文件。

而是:

Index | +-- Segment1 | +-- Segment2 | +-- Segment3

每个 Segment:

类似一个不可变的小索引。

Lucene负责:

  • 创建 Segment
  • 查询 Segment
  • Merge Segment

6. 文件存储

Lucene定义:

自己的文件格式:

例如:

.tim .tip .doc .pos .dvd .dvm

管理:

  • 写入
  • 压缩
  • 读取

ES的查询链路

你前面已经把 ES 搜索链路基本串起来了:

Mapping | Analyzer 完整的一个分词 | Tokenizer 负责把词语分开 | Token Filter 负责把分开的词语做后处理,比如转小写、去掉无意义词,同义词等等 | 倒排索引 | 召回 | BM25 | 排序

同义词就在Token Filter 这一层。你现在问的已经不是“会不会用 ES”,而是在理解搜索引擎内部机制了。

Segment(段)是 Lucene 最核心的数据组织单位。

简单理解:

Segment 是 Lucene 生成的一份不可变的小型索引。一个 Lucene Index 不是一个完整的大文件,而是由多个 Segment 组成。

你可以把它理解成:

  • MySQL:一个表里面有很多页(Page)
  • Lucene:一个索引里面有很多 Segment

Segment(段)是 Lucene 最核心的数据组织单位。

简单理解:

Segment 是 Lucene 生成的一份不可变的小型索引。一个 Lucene Index 不是一个完整的大文件,而是由多个 Segment 组成。

你可以把它理解成:

  • MySQL:一个表里面有很多页(Page)
  • Lucene:一个索引里面有很多 Segment

1. 为什么需要 Segment?

因为搜索引擎面对两个矛盾:

写入要求

希望:

  • 快速写入
  • 顺序写磁盘
  • 不频繁修改已有数据结构

查询要求

希望:

  • 快速查找
  • 有完整倒排索引
  • 有BKD Tree

如果每写一条数据就修改整个索引:

新增一条数据 | 修改倒排索引 | 修改BKD Tree | 磁盘随机写

性能会很差。

所以 Lucene 设计:

数据先积累,然后批量生成一个新的 Segment。


2. Segment生成过程

例如写入数据:

{ "id":1, "title":"Java开发" }

流程:

写入 | v 内存缓冲区 | refresh | v 生成Segment

生成:

Segment_1 倒排索引 BKD Tree DocValues Stored Fields

继续写:

Segment_1 已有 新增数据 ↓ Segment_2

于是:

Index +-------------+ | Segment_1 | +-------------+ | Segment_2 | +-------------+ | Segment_3 | +-------------+

3. Segment里面有什么?

一个 Segment 里面包含很多 Lucene 数据结构:

① 倒排索引

负责文本搜索:

例如:

文档:

doc1: Java Spring doc2: Java Elasticsearch

倒排:

Java doc1 doc2 Spring doc1 Elasticsearch doc2

② BKD Tree

负责:

数值、地理查询。

例如:

price > 100 location within 5km

③ Stored Fields

保存原始文档。

例如:

搜索命中:

doc1

返回:

{ "title":"Java Spring" }

这个数据来自 Stored Fields。


④ DocValues

用于:

  • 排序
  • 聚合

例如:

按照价格排序 统计品牌数量

4. Segment为什么叫不可变?

这是 Lucene 一个非常重要的设计。

Segment生成后:

不能修改。

例如:

已有:

Segment_1 doc1 doc2 doc3

现在:

修改doc2:

doc2: Java 改成 Python

不会直接改:

Segment_1。

而是:

Segment_1 doc2 标记删除 + Segment_2 新的doc2

为什么?

因为:

如果允许修改:

需要维护:

  • 倒排索引
  • BKD Tree
  • DocValues

代价巨大。


5. 那Segment越来越多怎么办?

比如:

持续写入:

Segment_1 Segment_2 ... Segment_1000

查询:

需要查:

1000个索引。

性能下降。

所以 Lucene 有:

Segment Merge

后台合并:

Segment_1 Segment_2 Segment_3 | v Segment_big

合并过程:

重新:

  • 构建倒排索引
  • 构建BKD Tree
  • 清理删除数据

Segment Merge 是 Lucene 自动执行的,不需要业务代码手动触发。

但是它不是“每生成一个 Segment 马上合并”,而是由 Lucene 的Merge Policy(合并策略)和后台Merge Scheduler(合并调度器)控制。


1. Segment Merge 的整体流程

假设持续写入:

写入数据 | v Segment_1 继续写 | v Segment_2 继续写 | v Segment_3

此时:

Lucene Index Segment_1 Segment_2 Segment_3

后台线程发现:

这些 Segment 太多了,需要合并。

于是:

Segment_1 Segment_2 Segment_3 | v Segment_4

生成新的大 Segment。

旧 Segment 删除。


2. 谁决定什么时候合并?

两个核心组件:

① Merge Policy(合并策略)

负责决定:

哪些 Segment 应该合并?

Lucene 默认:

TieredMergePolicy

它主要考虑:

  • Segment数量
  • Segment大小
  • Segment之间大小差异

例如:

现在:

Segment A 10MB Segment B 12MB Segment C 15MB Segment D 500MB

可能:

优先:

A+B+C

而不是:

C+D

因为小 Segment 合并收益更高。


② Merge Scheduler

负责:

什么时候执行合并?用几个线程执行?

例如:

后台:

Merge Thread | | 读取多个Segment | | 生成新Segment

3. Merge 是不是实时发生?

不是。

例如:

大量写入:

Segment_1 Segment_2 Segment_3 ... Segment_100

可能短时间存在。

原因:

Merge 本身也消耗资源:

  • CPU
  • IO
  • 磁盘空间

如果疯狂merge:

写入性能会下降。

所以 Lucene 会控制节奏。


4. Merge 会影响查询吗?

会,但是影响可控。

例如:

正在查询:

Segment_1 Segment_2 Segment_3

后台开始merge:

Segment_1 + Segment_2 生成 Segment_4

查询不会被阻塞。

因为:

Segment是不可变的。

旧查询继续读:

Segment_1 Segment_2

新查询:

可能读:

Segment_4 Segment_3

5. Merge过程会不会丢数据?

不会。

因为:

流程:

旧Segment 读取数据 ↓ 生成新Segment ↓ 校验成功 ↓ 替换引用 ↓ 删除旧Segment

不是:

先删除再生成。


6. ES里面可以控制吗?

可以。

例如:

查看merge情况

GET _cat/segments

查看:

  • segment数量
  • 大小

手动触发

ES提供:

POST index/_forcemerge

例如:

POST my_index/_forcemerge?max_num_segments=1

强制合并。

但是:

生产环境慎用。

因为:

merge非常耗资源。

KD Tree(K-Dimensional Tree,K维树)是一种用于多维空间数据检索的数据结构,主要解决:

在多个维度的数据中,如何快速查找附近的点、范围内的点。

它和你前面问的BKD Tree有直接关系:BKD Tree 可以理解为 KD Tree 面向磁盘、大数据场景的工程化改进版本。


1. KD Tree解决什么问题?

假设有二维数据:

城市坐标: 北京 (116,39) 上海 (121,31) 广州 (113,23) 深圳 (114,22)

如果我要查询:

距离点 (115,30) 最近的城市?

暴力:

遍历所有点:

北京 上海 广州 深圳

计算距离。

复杂度:

O(N)

数据多了很慢。


KD Tree:

把空间组织起来。

查询:

不用访问所有点。


2. KD Tree的核心思想

一句话:

不断选择一个维度,把空间一分为二。

例如二维:

维度:

X轴 Y轴

假设数据:

y | A | | -----------x | |

第一次:

按照 X 轴切:

x < 100 x >=100

分左右。

然后下一层:

按照 Y 轴切。

再下一层:

继续 X。

所以:

维度轮流切。


3. KD Tree结构长什么样?

例如二维:

数据:

(3,6) (17,15) (13,15) (6,12) (9,1)

构建:

第一层:

按 X:

(9,1) / \ x<9 x>9

第二层:

按 Y:

(9,1) / \ (6,12) (17,15)

类似二叉树。


4. KD Tree查询怎么做?

假设:

找:

距离(10,10)最近的点

先走:

因为:

当前节点:

(9,1)

比较:

x:

10 > 9

所以:

优先走右边。

但是:

不能只走右边。

为什么?

因为:

左边可能也有更近的点。

所以:

需要判断:

另一边有没有可能更优。


这就是:

剪枝(Pruning)

核心:

如果另一边空间距离已经不可能更近:

直接舍弃。

否则:

继续搜索。


5. KD Tree为什么适合多维?

因为它利用:

空间划分。

例如二维:

全部空间 ---------------- 左半区 | 右半区 继续切: 上 下

最终:

每个节点代表:

一个空间区域。

查询时:

排除大量区域。


6. KD Tree的问题

KD Tree 理论很好。

但是:

实际大规模数据有问题。

BKD树 就是非点状的,而是一个点存很多数据

为什么KD树要做成二叉的,这样查询效率应该比多叉的慢吧

这是一个非常好的问题。你的直觉是对的:

如果只从“树的高度”来看,二叉树确实比多叉树更深,单次查找路径更长。

但是 KD Tree 选择二叉,不是因为二叉一定最快,而是因为它解决的是多维空间划分问题,二叉划分更符合它的数学和工程需求。

ES 查询快 ≠ BKD Tree 比 B+Tree 快。

实际上,ES 快的原因绝大部分不是 BKD Tree,而是倒排索引(Inverted Index)。BKD Tree 只是负责数值、日期、地理位置等字段的查询。

如果按照对 ES 查询性能的贡献排序,大概可以理解成:

倒排索引(70%~80%) ↓ Lucene Segment 不可变设计 ↓ 缓存(Query Cache、Request Cache、OS Page Cache) ↓ 分布式并行查询(多个 Shard) ↓ BKD Tree(数值/范围查询)

所以很多人学 ES,一直在研究 BKD Tree,其实真正让 ES 牛的是倒排索引

全文检索能力真正强的是 Lucene,而不是 Elasticsearch。ES 的全文检索能力,本质上就是 Lucene 提供的。

Lucene 解决的是"如何搜索",ES 解决的是"如何在生产环境大规模使用搜索"。

举个例子

假设你只有一台机器。

100 万篇文档。

用 Lucene:

IndexWriter ↓ 建立倒排索引 ↓ IndexSearcher ↓ 查询

查询速度:

和 ES几乎没有区别

为什么?

因为:

ES 底层:

最后也是:

IndexSearcher.search(...)

也是 Lucene 在查。


那为什么大家不用 Lucene,而用 ES?

因为 Lucene 太底层了。

Lucene 更像:

JDBC

ES 更像:

MyBatis

或者:

Netty

和:

Spring Boot

不是一个层级。


Lucene 只有搜索库

你自己要负责:

建立索引 打开索引 关闭索引 更新索引 Merge Commit Searcher 缓存 线程池

全部自己写。

例如:

Directory directory = FSDirectory.open(...); IndexWriter writer = ... writer.addDocument(...) writer.commit() DirectoryReader.open(...)

都是代码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询