作者:今天是星期天嘛_512 | 来源:互联网 | 2023-09-08 15:56
一、认识Apache Lucene
为了更深入地理解ElasticSearch的工作原理,特别是索引和查询这两个过程,理解Lucene的工作原理至关重要。本质上,ElasticSearch是用Lucene来实现索引的查询功能的。如果没有用过Lucene,下面的几个部分将为您介绍Lucene的基本概念。
为什么es使用Lucene作为底层框架呢?是因为Lucene是一个成熟的、高性能的、可扩展的、轻量级的,而且功能强大的搜索引擎包。Lucene的核心jar包只有一个文件,而且不依赖任何第三方jar包。更重要的是,它提供的索引数据和检索数据的功能开箱即用。当然,Lucene也提供了多语言支持,具有拼写检查、高亮等功能;但是如果你不需要这些功能,你只需要下载Lucene的核心jar包,应用到你的项目中就可以了。
下面直奔主题:说说Lucene的总体架构:
尽管我希望直奔主题,介绍Lucene的架构,但是首先必须理解一些概念才能更好地理解Lucene的架构,这些概念是:
- Document(类比数据库中的行): 它是在索引和搜索过程中数据的主要表现形式,或者称“载体”,承载着我们索引和搜索的数据,它由一个或者多个域(Field)组成。
- Field(类比数据库的列): 它是Document的组成部分,由两部分组成,名称(name)和值(value)。
- Term(分词后的一个词): 它是搜索的基本单位,其表现形式为文本中的一个词。
- Token(记录词的信息): 它是单个Term在所属Field中文本的呈现形式,包含了Term内容、Term类型、Term在文本中的起始及偏移位置。
Apache Lucene把所有的信息都写入到一个称为**倒排索引**的数据结构中。这种数据结构把索引中的每个Term(词)与相应的Document(行,即一条数据)映射起来,这与关系型数据库存储数据的方式有很大的不同。然后每个词会对应一个文档号(Document Number/Document ID)。这样就可以通过词倒排索引去找到对应的文档。此外,每个词映射着一个数值(Count),它代表着Term在文档集中出现的频繁程度。
所以倒排索引(以一种直观的形式)展现如下:
词(Term) | 词出现次数(count) | 文档号(Document Number/Document ID)(词对应的文档号) |
花 | 3 | <1> <5> |
天 | 2 | <3> <1> <2> |
而每次生成的一个倒排索引,Lucene把他称为一个段(segment)(例如:上面的两行数据),然后用一个文件commit来记录索引(index,类比库)中的段,而段的来源则是来自内存的buffer。而每次lucene为了动态更新的 Lucene 索引,采用了一个方法:新收到的数据写到新的索引文件里。
es中各种操作的实现原理:
1、es中写数据整体流程:
1、实时写入数据到内存buffer(缓存),在buffer里的时候数据是搜索不到的;同时将数据写入translog日志文件。
(translog日志文件是怕数据在没写入segment file磁盘文件时因为宕机等原因而导致的数据丢失)
2、如果buffer快满了/每隔1秒,就会将buffer的数据生成一个新的segment,然后refresh到os cache
(此时es则可以实时检索新加入的数据(refresh的过程需要1s影响不大))如果此时buffer如果没有数据则不会refresh
(操作系统里面,磁盘文件其实都有一个东西,叫做os cache,即操作系统缓存,就是说数据写入磁盘文件之前,会先进入os cache,先进入操作系统级别的
一个内存缓存中去。只要buffer中的数据被refresh 操作刷入os cache中,这个数据就可以被搜索到了。)
数据被输入os cache中,
就会被清空了,因为不需要保留buffer了,数据在translog里面已经持久化到磁盘去一份了。
3、重复上面的步骤,新的数据不断进入buffer和translog,不断将buffer数据写入一个又一个新的segment file中去,每次refresh完buffer清空,translog保留。
随着这个过程的推进,translog会变得越来越大。当translog达到一定长度的时候,就会触发commit操作。
3.1commit操作:
commit操作发生的第一步,就是将buffer中现有的数据refresh到os cache中去,清空buffer。然后将一个commit point写入磁盘文件,里面标识者这个commit point 对应的所有segment file,同时强行将os cache中目前所有的数据都fsync到磁盘文件中去。最后清空现有 translog日志文件,重启一个translog,此时commit操作完成。
(这个commit操作叫做flush。默认30分钟自动执行一次flush,但如果translog过大,也会触发flush。flush操作就对应着commit的全过程,我们可以通过es api,手动执行flush操作,手动将os cache中数据fsync强刷到磁盘上去。)
上面即写入的基本过程:
那么
(1)为什么叫es是准实时的?
NRT,全称 near real-time。默认是每隔1秒refresh一次的,所以es是准实时的,因为写入的数据1s之后才能被看到。
可以通过es的restful api或者 java api,手动执行一次 refresh操作,就是手动将buffer中的数据刷入os cache中,让数据立马就可以被搜索到。只要
数据被输入os cache中,buffer 就会被清空了,因为不需要保留buffer了,数据在translog里面已经持久化到磁盘去一份了。
(2)ranslog日志文件的作用是什么?
执行commit 操作之前,数据要么是停留在buffer中,要么是停留在os cache中,无论是buffer 还是os cache都是内存,一旦这台机器死了,内存中的数据就全丢了。
所以需要将数据对应的操作写入一个专门的日志文件translog中,一旦此时机器宕机了,再次重启的时候,es会自动读取translog日志文件中的数据,恢复到内存buffer和os cache中去。
translog其实也是先写入os cache的,默认每隔5秒刷一次到磁盘中去,所以默认情况下,可能有5s的数据会仅仅停留在buffer或者translog文件的os cache中,如果此时机器挂了,会丢失5秒钟的数据。但是这样性能比较好,最多丢5秒的数据(该数据不是segment,不是倒排索引结构,原数据)。
也可以将translog设置成每次写操作必须是直接fsync(该操作是比较耗性能的)到磁盘,但是性能会差很多。
(es第一是准实时的,数据写入1秒后就可以搜索到,可能会丢失数据的,有5秒的数据,停留在buffer、translog os cache 、segment file os cache中,而不在磁盘上,此时如果宕机,会导致5秒的数据丢失。)
稍微的总结一下:
数据写入buffer--------buffer有数据时,每秒生成一个segment,并放入os cache(放进cache后buffer的数据删除)。每5秒将源数据写入translog文件中(防止数据丢失)---------es可以利用os cache中的segment进行准即时检索------------30分钟/translog大小达到一定程度,触发commit操作-------强行将buffer的数据refresh到os cache中并清空,后将一个commit point写入磁盘文件,里面标识者这个commit point 对应的所有segment file,同时强行将os cache中目前所有的数据都fsync到磁盘文件中去。最后清空现有 translog日志文件,重启一个translog,此时commit操作完成。
2、es中的读操作基本流程
可以通过doc id 来查询,会根据doc id进行hash,判断出来当时把doc id分配到了哪个shard上面去,从那个shard去查询完整的document数据。
1、客户端发送请求到任意一个node,成为coordinate node
2、coordinate node 对doc id进行哈希路由,将请求转发到对应node,此时会使用round-robin随机轮询算法,在primary shard 以及其所有replica中随机选择一个,让读请求负载均衡。
3、接收请求的node返回document给coordinate node。
4、coordinate node返回document给客户端。
3、es中的删除/更新操作基本流程
如果是删除操作,commit的时候会生成一个 .del文件,里面将某个doc标识为 deleted状态,那么搜索的时候根据 .del文件就知道这个doc是否被删除了。
如果是更新操作,就是将原来的doc标识为deleted状态,然后重新写入一条数据。
buffer 每refresh一次,就会产生一个segment file,所以默认情况下是1秒钟一个segment file,这样下来segment file会越来越多,此时会定期执行merge(合并)。
每次merge的时候,会将多个segment file合并成一个,同时这里会将标识为 deleted的doc给物理删除掉,然后将新的segment file写入磁盘,这里会写一个
commit point,标识所有新的 segment file,然后打开segment file供搜索使用,同时删除旧的segment file。
二、文档读写模型实现原理
1、简介
ElasticSearch,每个索引(库)被分成多个分片(默认每个索引5个主分片primary shard),每个分片又可以有多个副本。当一个文档被添加或删除时(主分片中新增或删除),其对应的复制分片之间必须保持同步。那如何保持分片副本同步呢?这就是本篇重点要阐述的,即数据复制模型。
ElasticSearch的数据复制模型是基于主从备份模型的。每一个复制组中会有一个主分片,其他分片均为复制分片。主分片服务器是所有索引操作的主要入口点(索引(创建)、更新、删除操作)。一旦一个索引操作被主服务器接受之后主分片服务器会将其数据复制到其他副本。
2、基本写模型(可用shard过半就可以写入)
ElasticSearch每个索引操作首先会进行路由选择定位(随机轮询)到一个节点,如果请求的是非主节点,此时则将请求转给主节点(master)然后主节点进行分析看属于哪个分片,默认基于文档ID(routing),其基本算法为hash(routing) % (primary count),知道是哪个分片了则看该分片的主分片位于哪个节点,然后将请求发到该节点的主分片进行处理,主节点处理完后,成功后会发送消息给master(2 个副本分片只要有 1 个成功(默认一半,可以自己设置),就可以返回给客户端了)
由于副本可以离线(一个复制组并不是要求所有复制分片都在线才能运作,过半即可),可能不需要复制到所有副本。ElasticSearch会维护一个当前在线的副本服务器列表,这个列表被称为in-sync副本,由主节点维护。也就是当主分片接收一个文档后,需要将该文档复制到in-sync列表中的每一台服务器。
主分片的处理流程如下:
验证请求是否符合Elasticsearch的接口规范,如果不符合,直接拒绝。
在主分片上执行操作(例如索引、更新或删除一个文档)。如果执行过程中出错,直接返回错误。
将操作转发到当前同步副本集的每个副本。如果有多个副本,则并行执行。(in-sync当前可用、激活的副本)。
一旦所有的副本成功地执行了操作并对主服务器进行了响应,主服务器向客户端返回成功。
索引写操作在集群上的操作:(有0分片:P0(主分片) R0 R0 ;1分片:P1 R1 R1 )
假设我们集群如上面所示:这上面每个节点都可以接收请求,那么新建的一个流程如下所示:
以下是在主副分片和任何副本分片上面 成功新建,索引和删除文档所需要的步骤顺序:
(1)客户端向Node1
发送新建、索引或者删除请求。
(2)节点使用文档的_id
确定文档属于分片 0。请求会被转发到Node3
因为分片 0 的主分片目前被分配在 Node3
上。
(3)Node 3
在主分片上面执行请求。如果成功了,它将请求并行转发到 Node 1
和 Node 2
的副本分片上。一旦所有的副本分片都报告成功, Node 3
将向协调节点报告成功,协调节点向客户端报告成功。
在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。
异常处理机制:
在索引过程中,许多情况会引发异常,例如磁盘可能会被破坏、节点之间网络彼此断开,或者一些配置错误可能导致一个副本的操作失败,尽管它在主服务器上是成功的。上述原因虽然很少会发生,但我们在设计时还是必须考虑如果发生错误了该如何处理。
另外一种情况异常情况也不得不考虑。如果主服务器不可用,ES集群该如何处理呢?
此时会触发复制组内的主服务器选举,选举后新的主节点会向master服务器发送一条消息,然后,该请求会将被转发到新的主服务器进行处理。此过程该请求在主节点选主期间会阻塞(等待),默认情况下最多等待1分钟。
注:主服务器(master)会监控各个节点的健康状况,并可能决定主动降级主节点。这通常发生在持有主节点的节点通过网络问题与集群隔离的情况下。
为了更好的理解master服务器与主分片所在服务器的关系,下面给出一个ElasticSearch的集群说明图:
其中NODE1为整个集群的master服务器,而第一个复制组(P0,R0,RO,其主分片所在服务器NODE3),第二个复制组(P1,R1,R1,其主分片所在服务器NODE1)。
一旦在主服务器上成功执行了操作,主服务器就必须确保数据最终一致,即使由于在副本上执行失败或由于网络问题导致操作无法到达副本(或阻止副本响应)造成的。
为了避免数据在复制组内数据的不一致性(例如在主分片中执行成功,但在其中一两个复制分片中执行失败),主分片如果未在指定时间内(默认一分钟)未收到复制分片的成功响应或是收到错误响应,主分片会向Master服务器发送一个请求,请求从同步副本中删除有问题的分片,最终当主分片服务器确向Master服务器的确认要删除有问题的副本时,Master会指示删除有问题的副本。同时,master还会指示另一个节点开始构建新的分片副本,以便将系统恢复到一个健康状态。
主分片将一个操作转发到副本时,首先会使用副本数来验证它仍然是活动的主节点。如果由于网络分区(或长GC)而被隔离,那么在意识到它已经被降级之前,它可能会继续处理传入的索引操作并转发到从服务器。来自陈旧的主服务器的操作将会被副本拒绝。当主接受到来自副本的响应为拒绝它的请求时,此时的主分片会向Master服务器发送请求,最终将知道它已经被替换了,后续操作将会路由到新的主分片服务器上。
如果没有副本,那会发生什么呢?
这是一个有效的场景,可能由于配置而发生,或者是因为所有的副本都失败了。在这种情况下,主分片要在没有任何外部验证的情况下处理操作,这可能看起来有问题。另一方面,主分片服务器不能自己失败其他的分片(副本),而是请求master服务器代表它这样做。这意味着master服务器知道主分片是该复制组唯一的可用拷贝。因此,我们保证master不会将任何其他(过时的)分片副本提升为一个新的主分片,并且任何索引到主分片服务器的操作都不会丢失。当然这样意味着我们只使用单一的数据副本,物理硬件问题可能导致数据丢失。请参阅Wait For Active Shards,以获得一些缓解选项,(该参数项将在下一节中详细描述)。
注:在一个ElasticSearch集群中,存在两个维度的选主。Master节点的选主、各个复制组主分片的选主。
3、基本读模型
在Elasticsearch中,可以通过ID进行非常轻量级的查找,也可以使用复杂的聚合来获取非平凡的CPU能力。主备份模型的优点之一是它使所有的分片副本保持相同(除了异常情况恢复中)。通常,一个副本就足以满足读取请求。
当一个节点接收到read请求时,该节点根据路由规则负责将其转发给相应的数据节点,对响应进行整理,并对客户端作出响应。我们称该节点为该请求的协调节点。基本流程如下:
将读请求路由到到相关的分片节点。注意,由于大多数搜索条件中不包含分片字段,所以它们通常需要从多个分片组中读取数据,每个分片代表一个不同的数据子集(默认5个数据子集,因为ElasticSearch默认的主分片个数为5个)。
从每个分片复制组中选择一个副本。读请求可以是复制组中的主分片,也可以是其副本分片。在默认情况下,ElasticSearch分片组内的读请求负载算法为轮询。
根据第二步选择的各个分片,向选中的分片发送请求。
汇聚各个分片节点返回的数据,然后返回个客户端,注意,如果带有分片字段的查询,将之后转发给一个节点,该步骤可省略。
异常处理:
当一个碎片不能响应一个read请求时,协调节点将从同一个复制组中选择另一个副本,并向其发送查询请求。重复的失败会导致没有分片副本可用。在某些情况下,比如搜索,ElasticSearch会更倾向于快速响应(失败后不重试),返回成功的分片数据给客户端 ,并在响应包中指明哪些分片节点发生了错误。
读取文档
以下是从主分片或者副本分片检索文档的步骤顺序:
(1)客户端向Node1发送获取请求。
(2)节点使用文档的_id来确定文档属于分片0,分片0的数据在三个节点上都有。 在这种情况下,它会根据负载均衡策略将请求转发到其中一个,比如Node2 。
(3)Node2将文档返回给Node1然后将文档返回给客户端。
在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。
在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。
4、Elasticsearch主备模型隐含含义
在正常操作下,每个读取操作一次为每个相关的复制组执行一次。只有在失败条件下,同一个复制组的多个副本执行相同的搜索。
由于数据首先是在主分片上进行索引后,然后才转发请求到副本,在转发之前数据已经在主分片上发生了变化,所以在并发读时,如果读请求被转发到主分片节点上,那该数据在它被确认之前(主分片再等待所有副本全部执行成功)就已经看到了变化。【有点类似于数据库的读未提交】。
主备模型的容错能力为两个分片(1主分片,1副本)。、
5、ElasticSearch 读写模型异常时可造成的影响
在失败的情况下,以下是可能的:
1个分片节点可能减慢整个集群的索引性能
因为在每次操作期间(索引),主分片在本地成功执行索引动作后,会转发请求到期复制分片节点上,此时主分片需要等待所有同步副本节点的响应,单个慢分片可以减慢整个复制组的速度。当然,一个缓慢的分片也会减慢那些被路由到它的搜索。
脏读
一个孤立的主服务器可以公开不被承认的写入。这是由于一个孤立的主节点只会意识到它在向副本发送请求或向主人发送请求时被隔离。在这一点上,操作已经被索引到主节点,并且可以通过并发读取读取。Elasticsearch可以通过在每秒钟(默认情况下)对master进行ping来减少这种风险,并且如果没有已知的主节点,则拒绝索引操作。
本文详细介绍了ElasticSearch文档的读写模型的设计思路,涉及到写模型及其异常处理、读模型及其异常处理、主备负载模型背后隐含的设计缺陷与ElasticSearch在异常情况带来的影响。