ElasticSearch基本原理

一.ES基本概念介绍

1.ES简介

ES是一个分布式可扩展的、近实时的数据搜索分析与存储引擎。支持全文搜索、结构化搜索、半结构化搜索、数据分析、地理位置和对象间关联关系搜索等功能。其底层基于Lucene,但Lucene比较复杂,面向普通应用开发者而言,易用性不是很好,同时对于目前的主流分布式架构支持也不好,所以就诞生了ES。ES使用Java编写,它的内部使用Lucene做索引与搜索,隐藏了Lucene的复杂性,面向开发者暴露了一套即使是不同编程语言也基本一致的API和client,方便大家将搜索功能快速植入到日常应用中。

2.ES使用场景

  • 全文检索
  • es的主要应用场景之一,类似于Google搜索,百度搜索和维基百科等,对全文关键字进行检索。
  • 日志分析
  • 海量数据进行近实时的处理,对复杂的日志进行分析,得到我们想要的结果。
  • 复杂条件查询
  • 数据量很大,又需要各种条件查询,实时的返回查询数据结果,如管理后台
  • 相似搜索,模糊匹配,地理位置聚合等

3.ES基本概念

  • 文档(Document)
  • 是在Elasticsearch中可以被索引的基本单位,以JSON表示,类似于关系数据库中的一行。
  • 类型(Type)
  • 表示一类相似的文档,作为一个元数据来实现逻辑划分,类似于关系数据库中的表。
  • 索引(Index)
  • 索引是具有相似特征的文档的集合,类似于关系数据库中的数据库 。

4.ES的节点,分片

默认情况下,每个索引由5个主要分片组成,每份主要分片又有一个副本,一共10个分片。

分片是ElasticSearch所处理的最小单元,一份分片是Lucene的索引,包含倒排索引的文件目录,分片仅保存了全部数据中的一部分。副本分片是主分片的拷贝,副本分片作为硬件故障时数据的备份,并为搜索和返回文档等读操作提供服务。副本分片可以提高可用性和性能。

默认的,文档在分片上均匀分布,通过文档ID字符串的散列决定分布到哪个分片,每份分片拥有相同的散列范围,接收新文档的机会均等。一旦目标分片确定,接收请求的节点将文档转发到该分片所在的节点,随后索引操作在所有目标分片的所有副本分片中进行。

写索引只能写在主分片上,然后同步到副本分片,ES写入哪个分片是由文档id的hash值决定的,公式如下:

shard = hash(routing) % number_of_primary_shards

一次文档写入流程大致如下:

(1)客户端向节点2(协调节点)发送写请求,通过路由计算公式得到分片值为0,则文档应该写到主分片0上

(2)协调节点将请求转发到分片0所在的节点1上,节点1接收请求并写入到磁盘

(3)并发将数据复制到节点2的副本分片0上,通过乐观锁解决数据冲突,一旦所有副本分片都报告成功,则节点1向协调节点报告成功,协调节点向客户端报告成功。

5.倒排索引

倒排索引是一种类似于HashMap的数据结构,不会直接存储字符串,而是将每个文档拆分为单个搜索词,然后将每个搜索词映射到这些搜索词出现过的文档。

id

content

1

elasticsearch in action

2

redis in action

如上表格标识我们关系型数据库的两条数据,转变为倒排索引的形式如下(词频主要用于打分后面介绍)

content

id

词频

elasticsearch

1

id1->1次

redis

2

id2->1次

in

1,2

id1->1次 id2->1次

action

1,2

id1->1次 id2->1次

二.分析数据

分析是在文档发送并加入倒排索引之前,Elasticsearch在其主体上进行的操作。在文档被加入索引之前,Elasticsearch让每个被分析字段经过一系列的处理步骤。

  • 字符过滤-使用字符过滤器转变字符
  • 文本切分-将文本切分为单个或多个分词
  • 分词过滤-使用分词过滤器转变每个分词
  • 分词索引-将这些分词存储到索引中

三.数据写入

数据的写入最终是要写到ES的磁盘上,但是如果每次写数据都直接更新磁盘,磁盘的I/O消耗会非常影响性能,当写数据量大的时候就会造成ES停顿卡死,查询也会收到严重影响,ES就无法成为近实时搜索引擎了。

1.分段存储

索引文件被拆分为多个子文件,则每个子文件叫作, 每一个段本身都是一个倒排索引,并且段具有不变性,一旦索引的数据被写入硬盘,就不可再修改。在底层采用了分段的存储模式,使它在读写时几乎完全避免了锁的出现,大大提升了读写性能。

段被写入到磁盘后会生成一个提交点,提交点是一个用来记录所有提交后段信息的文件。一个段一旦拥有了提交点,就说明这个段只有读的权限,失去了写的权限。相反,当段在内存中时,就只有写的权限,而不具备读数据的权限,意味着不能被检索。

索引文件分段存储并且不可修改,那么新增、更新和删除如何处理呢?

  • 新增,数据是新的,所以只需要对当前文档新增一个段就可以了。
  • 删除,由于不可修改,所以对于删除操作,不会把文档从旧的段中移除而是通过新增一个 .del文件,文件中会列出这些被删除文档的段信息。这个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除。
  • 更新,不能修改旧的段来进行反映文档的更新,其实更新相当于是删除和新增这两个动作组成。会将旧的文档在 .del文件中标记删除,然后文档的新版本被索引到一个新的段中。可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就会被移除。

2.刷新(refresh)

每当有新增的数据时,就将其先写入到内存中,在内存和磁盘之间是文件系统缓存,当达到默认的时间(1秒钟)或者内存的数据达到一定量时,会触发一次刷新(Refresh),将内存中的数据生成到一个新的段上并缓存到文件缓存系统 上,稍后再被刷新到磁盘中并生成提交点。

如上,数据会优先写入到内存缓存中,此时新写入的数据还不可以被检索,这也是为什么ES被称为近实时检索的原因,随着数据不断写入,ES会将内存缓存中的数据刷新到一个新的段中,这时新写的数据就可以被检索了。

3.冲刷(flush)

将内存中的分段提交到磁盘的过程叫做冲刷,在数据没有冲刷到磁盘之前,此时新写入的数据还在内存中,如果这时系统断电或者出现宕机异常,如何保证数据不会丢失?Elasticsearch 增加了一个 translog,或者叫事务日志,在每一次对 Elasticsearch 进行操作时均进行了日志记录。

数据refresh的时候,内存中的缓存数据被请求,但是事务日志不会

数据flush到磁盘之后,段被全量提交到磁盘,事务日志被清空

当满足以下条件之一就会触发刷新操作

  • 内存缓冲区已满
  • 自上次flush后超过了一定的时间
  • 事务日志达到了一定的阈值

4.段合并(merge)

段合并主要有两个目的:将分段的总数量保持在受控的范围内,保障查询的性能,因为每个搜索请求都必须轮流检查每个段,所以段越多,搜索也就越慢。第二个是真正的删除文档。

合并的最终目的是提升搜索的性能而均衡I/O和CPU计算能力。合并发生在写入、更新或者删除文档的时候,所以合并的越多,更新操作的成本就越高。

四.数据查询

1.索引优化

Elasticsearch为了能快速找到某个term,先将所有的term排个序,然后根据二分法查找term,时间复杂度为logN,就像通过字典查找一样,这就是Term Dictionary。现在再看起来,似乎和传统数据库通过B-Tree的方式类似。

但是如果term太多,term dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树。这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。

2.FST压缩

为了让内存中可以存储更多数据,ES在内存中用FST方式压缩term index,FST有两个优点:

  • 空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间。
  • 查询速度快。O(len(str))的查询时间复杂度。

我们对“cat”、 “deep”、 “do”、 “dog” 、“dogs”这5个单词进行插入构建FST:

FST压缩率一般在3倍~20倍之间,相对于TreeMap/HashMap的膨胀3倍,内存节省就有9倍到60倍。

3.打分机制

ElasticSearch使用搜索词条的频率以及它有多常见来影响得分,简单介绍为:一个词条出现在某个文档中的次数越多,就越相关;但是如果该词条出现在不同文档的次数越多,就越不相关。这一点被成为TF-IDF(TF是词频,IDF是逆文档频率)。

  • 词频:词条在文档出现的次数
  • 逆文档频率:如果一个分词在索引的不同文档中出现越多的次数,那么它就越不重要

比如我们要搜索词条"the score",单词the几乎出现在所有文档中,所有the不重要,如果不使用逆文档频率,那么the关键字将覆盖单词score的频率。

五.其他

1.Master选举

集群中可能会有多个可以成为,此时就要进行master选举,保证只有一个当选master。如果有多个node当选为master,则集群会出现脑裂,脑裂会破坏数据的一致性,导致集群行为不可控,产生各种非预期的影响。

为了避免产生脑裂,ES采用了常见的分布式系统思路,投票机制,保证选举出的master被多数节点认可,以此来保证只有一个master。配置名称:discovery.zen.minimum_master_nodes,为了避免脑裂,这个配置一般配置为sum(node)/2+1,保证超过一半的节点投票了主节点。

2.选举谁

public static int compare(MasterCandidate c1, MasterCandidate c2) {
    // we explicitly swap c1 and c2 here. the code expects "better" is lower in a sorted
    // list, so if c2 has a higher cluster state version, it needs to come first.
    int ret = Long.compare(c2.clusterStateVersion, c1.clusterStateVersion);
    if (ret == 0) {
        ret = compareNodes(c1.getNode(), c2.getNode());
    }
    return ret;
}

如上面源码所示,先根据节点的clusterStateVersion比较,clusterStateVersion越大,优先级越高。clusterStateVersion相同时,进入compareNodes,其内部按照节点的Id比较(Id为节点第一次启动时随机生成)。

为什么要有排序逻辑,主要是避免每次选举的节点不一致,导致达不到minimum_master_nodes数量,不要出现都想当master而选不出来的情况。

3.怎么保证不脑裂

基本原则还是多数派的策略,如果必须得到多数派的认可才能成为Master,那么显然不可能有两个Master都得到多数派的认可。

上述流程中,master候选人需要等待多数派节点进行join后才能真正成为master,就是为了保证这个master得到了多数派的认可。上述流程在绝大部份场景下没问题,听上去也非常合理,但是却是有bug的。

因为上述流程并没有限制在选举过程中,一个Node只能投一票,那么什么场景下会投两票呢?比如Node_B投Node_A一票,但是Node_A迟迟不成为Master,Node_B等不及了发起了下一轮选主,这时候发现集群里多了个Node_0,Node_0优先级比Node_A还高,那Node_B肯定就改投Node_0了。假设Node_0和Node_A都处在等选票的环节,那显然这时候Node_B其实发挥了两票的作用,而且投给了不同的人。

那么这种问题应该怎么解决呢,比如raft算法中就引入了选举周期(term)的概念,保证了每个选举周期中每个成员只能投一票,如果需要再投就会进入下一个选举周期,term+1。假如最后出现两个节点都认为自己是master,那么肯定有一个term要大于另一个的term,而且因为两个term都收集到了多数派的选票,所以多数节点的term是较大的那个,保证了term小的master不可能commit任何状态变更(commit需要多数派节点先持久化日志成功,由于有term检测,不可能达到多数派持久化条件)。这就保证了集群的状态变更总是一致的。

4.批量操作提升性能

Elasticssearch提供批量操作(插入,更新,删除),批量操作的API是_bulk,此功能非常强大,因为它提供了一种非常有效的机制,可以尽可能快地进行多个操作,并尽可能少地进行网络往返。批量查询使用mget。

参考文档:

全文搜索引擎Elasticsearch,这篇文章给讲透了 - 云+社区 - 腾讯云

Elasticsearch: 权威指南 | Elastic

  • 3
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值