ES原理
约 2173 字大约 7 分钟...
- 基于 lucence 引擎
- 通过关键词进行信息检索,搜索场景
基础:
倒排索引:
- 什么是倒排索引:
- 一个用于搜索的数据结构
- 倒排索引的核心概念:
词项 term:
- 将文本进行分词(LLM里面叫token化)后得到词项,词项就是一堆
词项字典 Term Dictionary:
- 将词项进行排序后得到词项字典,词项数量庞大时,可以通过二分法快速查询(此处可类比mysql的Page Dictionary作用也相似)
- 词项字典存在磁盘中,数据大
- 实际访问时先通过内存中的Term index,找到前缀匹配的词项位置,然后通过少量查找,定位到词项
Posting List:
- 作用:记录每个词项对应的文本ID的集合和其他信息
- Posting List包含:文本ID的集合、词项在每个文本内的offset、词频
Term Index:
- 作用:通过内存加速搜索
- 结构:基于词项前缀生成的一个精简的树结构(可对比参考跳表、b+树)
- 特点:词项索引存放在内存中,数据小,可以方便的快速查询
- 查询:先通过词项索引,找到词项在词项字典的大概位置,然后到词项字典中再查找
- 具体实现 FST 有限状态机:(可对比Tire树)
- FST的特点:(本质是有向图结构)
- 压缩性:FST通过共享公共前缀和后缀,大大减少了存储空间
- 高效查找:FST允许在子线性时间内查找词项,非常适合用于大规模的文本搜索
- 自动补全:FST还支持前缀查找和自动补全功能,这对于搜索引擎的实现非常有用
- FST的特点:(本质是有向图结构)
Stored Fields:
行式存储
- 作用:存放文件的原始信息
- 查询:可以通过文本ID,从Stored Fields,获取出整个文本内容(此处可类比MyISAM引擎)
Doc Values:
列式存储
- 作用:用于排序和聚合
- 说明:对某个字段排序文档,比如按时间、价格排序场景时,提升查询速度(此处可对比Mysql的二级索引)
- 原理:将散落在各个文档的某个字段集中存放在一起,
segment:
- 说明:segment是具备完整搜索功能的最小单元
- 组成:inverted index、Term index、stored fields、doc values共同构成一个segment
- segment读写规则:
- 写入:
- 将多个文档生成一个segment,生成后,segment不能再被修改
- 如果有新文档,则新生成一个 segment
- 读取:
- 并发同时读多个segment,并进行聚合操作
- 写入:
- segment合并:
- 避免segment过多影响性能、避免文件句柄耗尽
- 不定期将多个小segment合并
- segment合并过程:待补充
- segment合并中的查询:待补充
lucene:
- 单机文本检索库,由多个segment构成
高性能:
减少资源竞争:
对写入lucene 的数据进行分类:
- 每一类是一个独立的
Index Name
(此处类比Mysql的分库、消息队列的Topic) - 读取数据时,指定搜索的Index Name
- 每一类是一个独立的
对lucene进行拆分:
- 一个lucene拆分为多个
shard
(此处类比消息队列的分区) - 每个shard分片本质上就是一个独立的 lucene 库
- 一个lucene拆分为多个
可扩展:
- 横向扩展,从单个节点Node变成多个,提高CPU、内存上限
- 如何拆分?
- 按照shard粒度,将一个lucene对应的多个shard分散到多个Node
- 这样一个Node拥有多个lucene的部分shard
- 存疑?es集群的扩容如何进行?
高可用:
replication机制
- 按照shard粒度,设置replicas副本,这样shard就有了主分片、副分片的区分
- 一个主分片拥有多个副分片
Node职责/角色分化:
- 主节点 (Master Node)
- 负责管理集群的
- 数据节点 (Data Node)
- 负责存储管理数据
- 协调节点 (Coordinate Node)
- 负责接受客户端搜索查询请求,这个协调节点不做选主操作(该协调节点主要做路由转发、聚合操作,类比Mysql的server层)
- 负责接受客户端搜索查询请求,这个协调节点不做选主操作(该协调节点主要做路由转发、聚合操作,类比Mysql的server层)
- 主节点 (Master Node)
中心化/去中心化:
- 中心化:引入中心节点zookeeper,进行协调选主、Node间的通信(7.x版本开始被抛弃)
- 去中心化:(主流)
- 在每个Node中加入协调模块,用类似一致性算法 Raft 的方式,在节点间互相同步数据,让所有Node看到的集群数据状态都是一致的。(此处可类比redis的哨兵机制,只不过这个模块是跟Node放在一起的)
- 即内置的
Zen Discovery
和Cluster Coordination Module
去中心化整体架构:

es写入过程:
- 客户端请求:
- 请求会先发到集群中协调节点
- 请求路由到主分片:
协调节点
根据文档的ID计算hash,判断数据该写入到哪个哪个分片(Shard),将请求转发到对应的Node节点
- 主分片处理写请求:
- 分片底层是 lucene,最终是将数据写入到 lucene 库里的 segment 内,将数据固化为倒排索引和 Stored Fields 以及 Doc Values 等多种结构
- 同步到副本分片:
- 主分片将写操作及其相关的元数据(如版本号和操作序列号)同步发送到所有副本分片。这个过程是并行进行的。
- 副本分片确认写操作:
- 副本分片接收到写操作后,执行相同的写操作并更新自身的数据,完成写入给主分片确认信号ACK
- 主分片确认写操作完成:
- 主分片在
接收到所有副本分片
的确认消息后(同步复制策略下),认为写操作已经成功,给协调节点ACK确认信号 - 如果开启异步复制,则不等待副本写入,直接响应协调节点
- quorum 机制
- 主分片在
- 协调节点响应客户端:
- 响应写入成功
- 响应写入成功
es查询过程:
客户端请求:
- 请求会先发到集群中协调节点
请求路由到主分片:
协调节点
根据查询请求的类型(如搜索、聚合等)和索引的元数据,确定需要查询哪些分片- 根据index name,可以知道index被分到哪几个shard,分片在哪些node上
查询阶段 Query Phase:
- 协调节点将查询请求并行发送到所有相关分片
- 搜索请求到达分片后,shard底层的lucene库会并发搜索多个segment,利用每个segment内部的倒排索引获取到对应文档id,并结合doc values获得排序信息
- 分片将结果聚合后返回给协调节点
聚合阶段 Fetch Phase:
- 协调节点对多个分片中拿到的数据再进行一次排序聚合,判断哪些文档ID对应的数据需要返回客户端
- 协调节点再次拿着这些文档ID并发请求数据节点里的分片,分片底层的lucene库会从segment内的
Stored Fields
中取出完整文档内容,并返回给协调节点
返回结果给客户端
- 协调节点将查询好的数据返回给客户端
es的更新、删除过程:
- 重点:
- 删除和更新也都是写操作
- 删除:
- 磁盘上的每个段都有一个相应的.del 文件,当删除请求发送后,在.del文件中标记文档为删除。(delete_mask)
- 该文档依然能匹配查询,但是会在结果中被过滤掉。
- 当段合并时,在.del 文件中被标记为删除的文档将不会被写入新段,进行真正的删除。(delete)
- 更新:
- 当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。(add)
- 旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
- 当段合并时,在.del 文件中被标记为删除的文档将不会被写入新段,进行真正的删除。(delete)
查询举例
PUT /my_index // my_index 即index name
{
"mappings": {
"properties": {
"title": {
"type": "text",
"doc_values": false, // 是否使用doc_values进行聚合加速
"store": true,
}
}
}
}