文章目录
最近在项目中用到了ElasticSearch,但只是学了一下怎么用,这里对于ElasticSearch的底层原理进行一个学习,后续如果有其他的学习点再进行学习吧
本文的学习是在公众号看到了一个叫做小白debug的博主写的文章,看过之后学到了很多,因此在这里总结一下,顺便也对之前的项目进行一个更加深入的理解
写在前面
本人的第一个项目就是一个搜索引擎的项目,那会会的东西不多,所以只是简单搭建了一个框架,对着开源代码进行了一些学习,随着学习的深入,逐渐进行了架构的升级和改造,到现在已经完成了很多的改变
当我接触到ElasticSearch后,才发现里面的很多概念和项目里面的思想是一样的,不过想来也是,搜索引擎的概念本就是这些,只不过ElasticSearch做的要比本人的那个项目要全的很多,不过因为已经有了相应的概念,对于一些倒排索引这些概念就不过多讲解了,这里贴一个链接,感兴趣可以看关于该项目的一些介绍
常见的概念
倒排索引
这个概念在前面的项目中已经有概念了,这里简单提一下,可以理解为是一个关键字在哪些文档中出现过,这个就是倒排索引
Term Index
下面来讲的是Term Index的概念,这个概念其实也还好,就有点类似于一个目录树的感觉,至少在我写下这篇文章前,我是这样进行理解的,目录树的节点中存放这些词项在磁盘中的偏移量,也就是指向磁盘中的位置。这个目录树结构,体积小,适合放内存中,它就是所谓的 Term Index。可以用它来加速搜索
当我们需要查找某个词项的时候,只需要搜索 Term Index,就能快速获得词项在 Term Dictionary 中的大概位置。再跳转到 Term Dictionary,通过少量的检索,定位到词项内容
Stored Fields
这个概念其实也比较简单,就是之前概念中的正排索引,根据一个ID值来查询到某个文档的具体内容,而在这里它有一个新的名字,叫做行式存储:需要有个地方,存放完整的文档内容,它就是 Stored Fields(行式存储)
Doc Values
当我们用ID来查询到文章的内容后,下一步该怎么做呢?
用户经常需要根据某个字段排序文档,比如按时间排序或商品价格排序。但问题就来了,这些字段散落在文档里。也就是说,我们需要先获取 Stored Fields 里的文档,再提取出内部字段进行排序
也不是说不行。但其实有更高效的做法
我们可以用空间换时间的思路,再构造一个列式存储结构,将散落在各个文档的某个字段,集中存放,当我们想对某个字段排序的时候,就只需要将这些集中存放的字段一次性读取出来,就能做到针对性地进行排序。这个列式存储结构,就是所谓的 Doc Values
Segment
在上文中,我们介绍了四种关键的结构:倒排索引用于搜索,Term Index 用于加速搜索,Stored Fields 用于存放文档的原始信息,以及 Doc Values 用于排序和聚合。这些结构共同组成了一个复合文件,也就是所谓的"segment", 它是一个具备完整搜索功能的最小单元
Lucene
多个文档可以生成一个Segment,如果新增文档时,还是写入到这份 segment,那就得同时更新 segment 内部的多个数据结构,这样并发读写时性能肯定会受影响,所以一种解决的方案是,直接用一个新的Segment来进行存储,老的Segment只能进行读,如果要写就生成新的,如果想要读取的话,就遍历每一个Segment即可
但是太多的Segment也会带来性能的下降,所以我们可以不定期合并多个小 segment,变成一个大 segment,也就是段合并(segment merging)。这样文件数量就可控了
到此,就结束了一个简单的架构,这个架构也叫做是一个单机文本检索库,而它有一个名字,叫做Lucene
那Lucene和ElasticSearch有什么关系呢?ElasticSearch就是基于Lucene的,这里我们学习完了Lucene,那么就要过渡到ElasticSearch了
在讲ElasticSearch前,先看看Lucene的问题:单点架构。这就意味着在进行高性能,高扩展,高可用都不行,于是就要对它进行优化,我们一个一个来看
Lucene优化
高性能
lucene 作为一个搜索库,可以写入大量数据,并对外提供搜索能力。
多个调用方同时读写同一个 lucene 必然导致争抢计算资源。抢不到资源的一方就得等待
那怎么解决呢?实际上解决的措施是这样的:首先是对写入 lucene 的数据进行分类,比如体育新闻和八卦新闻数据算两类,每一类是一个 Index Name,然后根据 Index Name 新增 lucene 的数量,将不同类数据写入到不同的 lucene 中,读取数据时,根据需要搜索不同的 Index Name 。这就大大降低了单个 lucene 的压力
之后,还可以对于index Name进行数据的分片,每一个分片本质上就是一个独立的Lucene库
高拓展性
随着 分片 变多,如果 分片 都在同一台机器上的话,就会导致单机 cpu 和内存过高,影响整体系统性能
于是我们可以申请更多的机器,将 分片 分散部署在多台机器上,这每一台机器,就是一个 Node。我们可以通过增加 Node 缓解机器 cpu 过高带来的性能问题
高可用
如果其中一个 Node 挂了,那 Node 里所有分片 都无法对外提供服务了。我们需要保证服务的高可用。我们可以给 分片 多加几个副本。将 分片 分为 Primary shard 和 Replica shard,也就是主分片和副本分片 。主分片会将数据同步给副本分片,副本分片既可以同时提供读操作,还能在主分片挂了的时候,升级成新的主分片让系统保持正常运行,提高性能的同时,还保证了系统的高可用
这个理论其实就和Kafka是一样的,Kafka对于高可用的解决措施之一就是采用了分区和副本的机制,同时也有研究出一个类似于raft算法的去中心化算法,保证了副本之间数据的协调和一致性
Node 角色分化
搜索架构需要支持的功能很多,既要负责管理集群,又要存储管理数据,还要处理客户端的搜索请求。如果每个 Node 都支持这几类功能,那当集群有数据压力,需要扩容 Node 时,就会顺带把其他能力也一起扩容,但其实其他能力完全够用,不需要跟着扩容,这就有些浪费了。
因此我们可以将这几类功能拆开,给集群里的 Node 赋予角色身份,不同的角色负责不同的功能
比如负责管理集群的,叫主节点(Master Node), 负责存储管理数据的,叫数据节点(Data Node), 负责接受客户端搜索查询请求的叫协调节点(Coordinate Node)
集群规模小的时候,一个 Node 可以同时充当多个角色,随着集群规模变大,可以让一个 Node 一个角色
ElasticSearch
讲完了前面的前置内容,来回头看看,ElasticSearch到底是什么呢?其实,如果把Lucene进行高性能,高可用,高可拓展性优化之后,并提供一定的Http接口,得到的这个东西,其实就是ElasticSearch,换句话说,ElasticSearch底层就是借助Lucene,并进行了很多优化而变成的中间件,这里还有一些没提及,比如raft算法等,这些以后再谈
ElasticSearch写入流程
- 当客户端应用发起数据写入请求,请求会先发到集群中协调节点
- 协调节点根据 hash 路由,判断数据该写入到哪个数据节点里的哪个分片(Shard),找到主分片并写入。分片底层是 lucene,所以最终是将数据写入到 lucene 库里的 segment 内,将数据固化为倒排索引和 Stored Fields 以及 Doc Values 等多种结构
- 主分片 写入成功后会将数据同步给 副本分片
- 副本分片 写入完成后,主分片会响应协调节点一个 ACK,意思是写入完成
- 最后,协调节点响应客户端应用写入完成
ElasticSearch查询流程
- 当客户端应用发起搜索请求,请求会先发到集群中的协调节点
- 协调节点根据 index name 的信息,可以了解到 index name 被分为了几个 分片,以及这些分片 分散哪个数据节点上,将请求转发到这些数据节点的 分片 上面
- 搜索请求到达分片后,分片 底层的 lucene 库会并发搜索多个 segment,利用每个 segment 内部的倒排索引获取到对应文档 id,并结合 doc values 获得排序信息。分片将结果聚合返回给协调节点
- 协调节点对多个分片中拿到的数据进行一次排序聚合,舍弃大部分不需要的数据