搜索引擎合并(搜索引擎合并方法)

前沿拓展:

搜索引擎合并

会,用HTML的FREAM+任意一门动态语言就可以。我有一个源码,整合了百度,google,soso,bing,中搜等很多引擎的,可以加我QQ,发给你。(底层语言是asp的)


2020悄然逝去,不知不觉已经迈入2021年的生活,跨年夜没有狂欢,甚至没有一丝丝躁动,总结下来就是将2020的遗憾不带到2021年,2021年希望所有的所有都将有所改变, 由于疫情的加重,我拒绝了许多小伙伴的邀约,在家干了这个ES,文章内容来自网上,我只是总结了一下,希望对大家有所帮助,有问题也欢迎大家指正。

发展简介

Shay Banon在2004年创造了Elasticsearch的前身,称为Compass。在考虑Compass的第三个版本时,他意识到有必要重写Compass的大部分内容,以“创建一个可扩展的搜索解决方案”。因此,他创建了“一个从头构建的分布式解决方案”,并使用了一个公共接口,即HTTP上的JSON,它也适用于Java以外的编程语言。Shay Banon在2010年2月发布了Elasticsearch的第一个版本。

ES(为简写,全称为Elasticsearch)基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。ES是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。

ES是与名为Logstash的数据收集和日志解析引擎以及名为Kibana的分析和可视化平台一起开发。这三个产品被设计成一个集成解决方案,称为“Elastic Stack”(以前称为“ELK stack”)。

Elasticsearch分布式搜索引擎架构(万字总结)

ES可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。

ES是分布式的,这意味着索引可以被分成分片,每个分片可以有0个或多个副本。每个节点托管一个或多个分片,并充当协调器将操作委托给正确的分片。再平衡和路由是自动完成的。相关数据通常存储在同一个索引中,该索引由一个或多个主分片和零个或多个复制分片组成。一旦创建了索引,就不能更改主分片的数量。

ES是一个搜索的服务框架,大部分的情况下可以利用resetful的特性,进行直接url操作通信。

倒排索引

什么是倒排索引? 倒排索引是搜索引擎的核心,是一种结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

倒排索引的组成:

单词词典(Term Dictionary)倒排列表(Posting List)

如图一,将语句分词后得到关键词,再根据关键词建立倒排索引,关键词构成词典,每个关键词都有一个列表与之对应,这个列表就是倒排文件。

Elasticsearch分布式搜索引擎架构(万字总结)

图一

我们知道了词典和倒排文件是Lucene的两种基本数据结构,但是它们的存储方式截然不同,词典存储在内存中,倒排文件在磁盘上。

ES集群架构

一个ES集群可以有多个节点构成,一个节点就是一个ES服务实例,通过配置集群名称cluster.name加入集群。节点为什么用过配置就能加入到集群中呢?我们来探究一下:

ES中节点有角色的区分,角色的区分是在conf/elasticsearch.yml中的配置来决定的

node.master: true/false
node.data: true/false

集群中的单个节点既可以是候选主节点也可以是数据节点,两两组合会有四种分类:

仅为候选主节点仅为数据节点既是候选主节点也是数据节点既不是候选主节点也不是数据节点

说到这,不得不提一下什么是主节点?什么是候选主节点?什么是数据节点?分别都是做什么的等等.....

Elasticsearch分布式搜索引擎架构(万字总结)

主节点

主节点负责索引的添加、删除,监控哪些节点是集群的一部分,对分片进行分配、收集集群中各个节点的状态等,稳定的主节点对集群的健康非常重要。

候选主节点

当主节点死掉,只有候选主节点才能参与选举投票产生,也就是说候选主节点才可以被选举为主节点。

数据节点

数据节点主要负责对数据的增删改查聚合等操作,数据的查询以及存储都是由数据节点负责的,涉及到数据节点的,必须对机器的CPU、IO和内存有着相对高的要求。

协调节点

还有一种节点--协调节点,其本身不是通过设置来分配的,用户的请求可以随机发往任何一个节点,并由该节点负责分发请求、收集结果等一系列操作,而不需要主节点的转发。这种节点称之为协调节点。集群中任意节点都充当着协调节点的角色,节点之间也相互保持着联系。

Elasticsearch分布式搜索引擎架构(万字总结)

ES发现机制

该模块主要负责集群中节点的自动发现和Master节点的选举。节点之间使用p2p的方式进行直接通信,不存在单点故障的问题。Elasticsearch中,Master节点维护集群的全局状态,比如节点加入和离开时进行shard的重新分配。 自动发现机制在目前版本(1.3.1)提供了四种选择,一种是默认实现,其他都是通过插件实现。

Azure discovery 插件方式,多播EC2 discovery 插件方式,多播Google Compute Engine (GCE)discovery 插件方式多播zen discovery默认实现 多播/单播

多播也叫组播,指一个节点可以向多台机器发送请求。生产环境中ES不建议使用这种方式,对于一个大规模的集群,组播会产生大量不必要的通信。

单播,当一个节点加入一个现有集群,或者组建一个新的集群时,请求发送到一台机器。当一个节点联系到单播列表中的成员时,它就会得到整个集群所有节点的状态,然后它会联系Master节点,并加入集群。

多播配置下,节点向集群发送多播请求,其他节点收到请求后会做出响应。配置参数如下:
discovery.zen.ping.multicast.group:224.2.2.4 组地址
discovery.zen.ping.multicast.port:54328 端口
discovery.zen.ping.multicast.ttl:3 广播消息ttl
discovery.zen.ping.multicast.address:null绑定的地址,null表示绑定所有可用的网络接口
discovery.zen.ping.multicast.enabled:true 多播自动发现禁用开关
单播配置下,节点向指定的主机发送单播请求,配置如下:
discovery.zen.ping.unicast.hosts:host1:port1,host2:port2

选举Master节点: 在ping主节点过程中,节点会加入到集群中或者会被选举为主节点,发送主节点的超时时间由参数discovery.zen.join_timeout来控制,默认为3s,对于配置node.master为false的节点启动后不会作为主节点的候选。discovery.zen.minimum_master_nodes配置当前集群中最少的主节点数,对于多于两个节点的集群环境,建议配置大于1。

故障检测: 一般存在两个故障检测过程。第一个是主节点周期性的ping其他节点。第二就是其他节点周期的ping主节点。相关参数:

ping_interval:1s节点被ping的频率
ping_timeout:30s等待ping返回的时间
ping_timeout:3重试次数,超过该次,就认为该节点不可用

分布式以及 Elastic

分布式系统要解决的第一个问题就是节点之间互相发现以及选主的机制。如果使用了 Zookeeper/Etcd 这样的成熟的服务发现工具,这两个问题都一并解决了。但 Elasticsearch 并没有依赖这样的工具,带来的好处是部署服务的成本和复杂度降低了,不用预先依赖一个服务发现的集群,缺点当然是将复杂度带入了 Elasticsearch 内部。

服务发现以及选主ZenDiscovery

节点启动后先ping(这里的ping是 Elasticsearch 的一个RPC命令。如果 discovery.zen.ping.unicast.hosts 有设置,则ping设置中的host,否则尝试ping localhost 的几个端口, Elasticsearch 支持同一个主机启动多个节点) Ping的response会包含该节点的基本信息以及该节点认为的master节点。

选举开始,先从各节点认为的master中选,规则很简单,按照id的字典序排序,取第一个。如果各节点都没有认为的master,则从所有节点中选择,规则同上。这里有个限制条件就是 discovery.zen.minimum_master_nodes,如果节点数达不到最小值的限制,则循环上述过程,直到节点数足够可以开始选举。 最后选举结果是肯定能选举出一个master,如果只有一个local节点那就选出的是自己。如果当前节点是master,则开始等待节点数达到 minimum_master_nodes,然后提供服务。如果当前节点不是master,则尝试加入master。

Elasticsearch 将以上服务发现以及选主的流程叫做 ZenDiscovery 。由于它支持任意数目的集群(1-N),所以不能像 Zookeeper/Etcd 那样限制节点必须是奇数,也就无法用投票的机制来选主,而是通过一个规则,只要所有的节点都遵循同样的规则,得到的信息都是对等的,选出来的主节点肯定是一致的。但分布式系统的问题就出在信息不对等的情况,这时候很容易出现脑裂(Split-Brain)的问题,大多数解决方案就是设置一个quorum值,要求可用节点必须大于quorum(一般是超过半数节点),才能对外提供服务。而 Elasticsearch 中,这个quorum的配置就是 discovery.zen.minimum_master_nodes 。 说到这里要吐槽下 Elasticsearch 的方法和变量命名,它的方法和配置中的master指的是master的候选节点,也就是说可能成为master的节点,并不是表示当前的master。

弹性伸缩 Elastic

Elasticsearch 的弹性体现在两个方面:

服务发现机制让节点很容易加入和退出。丰富的设置以及allocation API。

Elasticsearch 节点启动的时候只需要配置discovery.zen.ping.unicast.hosts,这里不需要列举集群中所有的节点,只要知道其中一个即可。当然为了避免重启集群时正好配置的节点挂掉,最好多配置几个节点。节点退出时只需要调用 API 将该节点从集群中排除 (Shard Allocation Filtering),系统会自动迁移该节点上的数据,然后关闭该节点即可。当然最好也将不可用的已知节点从其他节点的配置中去除,避免下次启动时出错。

分片(Shard)以及副本(Replica)

分布式存储系统为了解决单机容量以及容灾的问题,都需要有分片以及副本机制。ES 没有采用节点级别的主从复制,而是基于分片。它当前还未提供分片切分(shard-splitting)的机制,只能创建索引的时候静态设置。

Elasticsearch分布式搜索引擎架构(万字总结)

比如上图所示,开始设置为5个分片,在单个节点上,后来扩容到5个节点,每个节点有一个分片。如果继续扩容,是不能自动切分进行数据迁移的。官方文档的说法是分片切分成本和重新索引的成本差不多,所以建议干脆通过接口重新索引。

Elasticsearch 的分片默认是基于id 哈希的,id可以用户指定,也可以自动生成。但这个可以通过参数(routing)或者在mapping配置中修改。当前版本默认的哈希算法是MurmurHash3。

Elasticsearch 禁止同一个分片的主分片和副本分片在同一个节点上,所以如果是一个节点的集群是不能有副本的。

恢复以及容灾

分布式系统的一个要求就是要保证高可用。前面描述的退出流程是节点主动退出的场景,但如果是故障导致节点挂掉,Elasticsearch 就会主动allocation。但如果节点丢失后立刻allocation,稍后节点恢复又立刻加入,会造成浪费。Elasticsearch的恢复流程大致如下:

集群中的某个节点丢失网络连接 master提升该节点上的所有主分片的在其他节点上的副本为主分片 cluster集群状态变为 yellow ,因为副本数不够 等待一个超时设置的时间,如果丢失节点回来就可以立即恢复(默认为1分钟,通过 index.unassigned.node_left.delayed_timeout 设置)。如果该分片已经有写入,则通过translog进行增量同步数据。 否则将副本分配给其他节点,开始同步数据。 但如果该节点上的分片没有副本,则无法恢复,集群状态会变为red,表示可能要丢失该分 片的数据了。

分布式集群的另外一个问题就是集群整个重启后可能导致不预期的分片重新分配(部分节点没有启动完成的时候,集群以为节点丢失),浪费带宽。所以 Elasticsearch 通过以下静态配置(不能通过API修改)控制整个流程,以10个节点的集群为例:

gateway.recover_after_nodes: 8
gateway.expected_nodes: 10
gateway.recover_after_time: 5m

比如10个节点的集群,按照上面的规则配置,当集群重启后,首先系统等待 minimum_master_nodes(6)个节点加入才会选出master, recovery操作是在 master节点上进行的,由于我们设置了 recover_after_nodes(8),系统会继续等待到8个节点加入, 才开始进行recovery。当开始recovery的时候,如果发现集群中的节点数小于expected_nodes,也就是还有部分节点未加入,于是开始recover_after_time 倒计时(如果节点数达到expected_nodes则立刻进行 recovery),5分钟后,如果剩余的节点依然没有加入,则会进行数据recovery。

Elasticsearch分布式搜索引擎架构(万字总结)

上面说的比较深,下面咱们简单介绍一下选主和脑裂:

选主

Master的选主过程中要确保只有一个master,ES通过一个参数quorum的代表多数派阈值,保证选举出的master被至少quorum个的候选主节点认可,以此来保证只有一个master。

选主的发起由候选主节点发起,当前候选主节点发现自己不是master节点,并且通过ping其他节点发现无法联系到主节点,并且包括自己在内已经有超过minimum_master_nodes个节点无法联系到主节点,那么这个时候则发起选主。

脑裂

提到分布式系统选主,不可避免地会提到脑裂这样一个现象,什么是脑裂呢?如果集群中选举出多个Master节点,使得数据更新时出现不一致,这种现象称之为脑裂。

简而言之集群中不同的节点对于 Master的选择出现了分歧,出现了多个Master竞争。

一般而言脑裂问题可能有以下几个原因造成:

网络问题:集群间的网络延迟导致一些节点访问不到Master,认为Master 挂掉了,而master其实并没有宕机,而选举出了新的Master,并对Master上的分片和副本标红,分配新的主分片。节点负载:主节点的角色既为Master又为Data,访问量较大时可能会导致 ES 停止响应(假死状态)造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。内存回收:主节点的角色既为Master又为Data,当Data节点上的ES进程占用的内存较大,引发JVM的大规模内存回收,造成ES进程失去响应。

如何避免脑裂:我们可以基于上述原因,做出优化措施:

适当调大响应超时时间,减少误判。通过参数 discovery.zen.ping_timeout 设置节点ping超时时间,默认为 3s,可以适当调大。选举触发,我们需要在候选节点的配置文件中设置参数 discovery.zen.munimum_master_nodes 的值。这个参数表示在选举主节点时需要参与选举的候选主节点的节点数,默认值是 1,官方建议取值(master_eligibel_nodes/2)+1,其中 master_eligibel_nodes 为候选主节点的个数。这样做既能防止脑裂现象的发生,也能最大限度地提升集群的高可用性,因为只要不少于 discovery.zen.munimum_master_nodes 个候选节点存活,选举工作就能正常进行。当小于这个值的时候,无法触发选举行为,集群无法使用,不会造成分片混乱的情况。角色分离,即是上面我们提到的候选主节点和数据节点进行角色分离,这样可以减轻主节点的负担,防止主节点的假死状态发生,减少对主节点宕机的误判。ES存储机制

在配置集群时,数据存储模块是众多模块中不需要过多关注的一个模块,但是却是非常重要的一个模块。它允许用户控制索引数据的存储方式:持久化存储(在硬盘上)或者临时存储(在内存中)。ElasticSearch中绝大部分的存储方式都会映射到合适的Apache Lucene Directory类。

directory模块用来存取所有索引文件,因此合理配置就显得尤为重要了。

存储类型ElasticSearch给用户提供了4种存储类型,下面看看它们提供了什么特性以及用户该如何使用用这些特性。

Elasticsearch分布式搜索引擎架构(万字总结)

简单的文件系统存储

directory类对外最简单的实现基于文件的随机读写,对于简单的应用来说,这种实现方式足够了。它主要的瓶颈是在文件的多线程存取时性能很差。在ElasticSearch中,通常建议使用基于新IO的系统存储来替代简单的文件系统存储。只是如果用户希望使用简单的文件系统存储,可以设置index.store.type属性值为simplefs。

新IO文件系统存储

这种存储类型使用的directory类是基于java.nio包中的FileChannel类实现的,该类映射到Apache Lucene的NIOFSDirectory类这种实现方式使得多个线程同时读写文件时不会出现性能下降的问题。通过设置index.store.type属性值为niofs使用该存储类型。

MMap文件系统存储

它使用mmap系统调用来通过读取和随机方式完成写文件操作。在进程中,它将文件映射到相同尺寸的虚拟内存地址空间中。由于没有任何的锁操作,多线程存取索引文件时就程序就具有可伸缩性了(可伸缩性是指当增加计算资源时,程序的吞吐量或者处理能力相应的增加)。当我们使用mmap读取索引文件,在操作系统看来,该文件已经被缓存(文件会被映射到虚拟内存中)。基于这个原因,从Lucene索引中读取一个文件时,文件不必加载到操作系统的缓存中,读取速度就会快一些。这基本上就是允许Lucene,也就是ElasticSearch直接操作I/O缓存,索引文件的存取当然会快很多。

内存存储

这种存储类型是几种类型中唯一不基于Apache Lucene directory实现的(当然也可以用Lucene的RAMDirectory类来实现)。内存存储类型允许用户直接把索引数据存储到内存中,所以硬盘上不会存储索引数据。记住这一点至关重要,因为这意味着数据并没有持久化:只要整个集群重启,数据就会丢失。然而,如果你的应用需要一个微型的、存取快速的,能有多个片分和分片副本的而且重建过程很快的索引,内存存储类型可能是你需要的。把index.store.type属性值设置为memeory即可使用该存储类型。

存储在内存中的索引数据,与其它存储类型相似,也会在允许存数据的节点上保留分片副本。

默认存储类型

默认情况下,ElasticSearch会使用基于文件系统的存储。尽管不同的存储类型用于不同的操作系统,被选定的存储类型依然基于文件系统。

ES恢复机制

cluster-level 恢复机制的配置

绝大多数恢复机制都是定义在集群层面的,用户通过为恢复模块设置通用的规则来控制恢复模块的工作。这些设置项如下: indices.recovery.concurrent_streams:该属性的默认值为3,表明从数据源恢复分片数据时,允许同时打开的数据流通道。该值越大,网络层的压力也就越大,同时恢复的速度也就越快,当然恢复速度与网络使用量和总的吞吐量也有关系。 indices.recovery.max_bytes_per_sec:该属性默认设置为20MB,表示分片在恢复过程中每秒传输的最大数据量。要想关闭传输量的限制,用户需要设置该属性值为0。与并发流的功能相似,该属性允许用户在恢复过程中控制网络的流量。设置该属性一个比较大的值会导致网络变得繁忙,当然恢复过程也会加快。 indices.recovery.compress:该属性值默认为true,允许用户自行决定在恢复过程是否对数据进行压缩。设置该属性值为false可以降低CPU的压力,与此同时,会导致网络需要传输更多的数据。 indices.recovery.file_chunk_size:该属性值指定了用于从源数据复制到分片时,每次复制的数据块的大小。默认值是512KB,同时如果indices.recovery.compress属性设置为true,数据会被压缩。 indices.recovery.translog_ops:该属性值默认为1000,指定了在恢复过程中,单个请求中分片间传输的事务日志的行数。 indices.recovery.translog_size: 该属性指定了从源数据分片复制事务日志数据时每次处理的数据块的大小。默认值为512KB,同时如果如果indices.recovery.compress属性设置为true,数据会被压缩。

在ElasticSearch 0.90.0版本前,曾用到indices.recovery.max_size_per_sec属性,但是在随后的版本中,该属性值被废弃了,由indices.recovery.max_bytes_per_sec属性替代。然而,如果使用0.90.0版本前的ElasticSearch,还是有必要记住这个属性。 上述的所有属性都可以通过集群的update API或者elasticsearch.yml来设置生效。

index-level 恢复机制的配置

除了前面提到的属性,还有一个可以作用于单个索引的属性。该属性可以在elasticsearch.yml文件中设置,也可以用索引更新的API来设置,它是index.recovery.initial_shards。通常情况下,当集群中还存在着不低于quorum数量的分片,并且这些分片都可进行分配时,ElasticSearch只会恢复一个特殊的分片。quorum数量是指总的分片数量加一。通过使用index.recovery.initial_shards属性时,用户可以改变ElasticSearch中quorum的实际分片数量。该属性可设置如下值:

quorum: 该值意味着集群中至少有(分片总数的50%+1)个分片存在并且可分配。quorum-1:该值意味着集群中至少有(分片总数的50%-1)个分片存在并且可分配。full:该值意味着集群中所有的分片都必须存在并且可分配。full-1:该值意味着集群中至少有(分片总数-1)个分片必须存在并且可分配。

整数值:表示可设置为任意的整数值,比如1,2或者5,表示至少需要存在且可分配的分片数。比如,属性值为2,表示最少需要2个分片存在,同时ElasticSearch中至少需要2个分片可分配。 了解该属性值的作用总会有用上的一天,尽管在绝大多数场景中使用默认值就足够了。

Elasticsearch分布式搜索引擎架构(万字总结)

索引写入

分片

ES支持PB级全文搜索,通常我们数据量很大的时候,查询性能都会越来越慢,我们能想到的一个方式的将数据分散到不同的地方存储,ES也是如此,ES通过水平拆分的方式将一个索引上的数据拆分出来分配到不同的数据块上,拆分出来的数据库块称之为一个分片Shard,很像MySQL的分库分表。

不同的主分片分布在不同的节点上,那么在多分片的索引中数据应该被写入哪里?肯定不能随机写,否则查询的时候就无法快速检索到对应的数据了,这需要有一个路由策略来确定具体写入哪一个分片中,怎么路由我们下文会介绍。在创建索引的时候需要指定分片的数量,并且分片的数量一旦确定就不能修改。

副本

副本就是对分片的复制,每个主分片都有一个或多个副本分片,当主分片异常时,副本可以提供数据的查询等操作。主分片和对应的副本分片是不会在同一个节点上的,避免数据的丢失,当一个节点宕机的时候,还可以通过副本查询到数据,副本分片数的最大值是 N-1(其中 N 为节点数)。

对doc的新建、索引和删除请求都是写操作,这些写操作是必须在主分片上完成,然后才能被复制到对应的副本上。ES为了提高写入的能力这个过程是并发写的,同时为了解决并发写的过程中数据冲突的问题,ES通过乐观锁的方式控制,每个文档都有一个 _version号,当文档被修改时版本号递增。

Elasticsearch分布式搜索引擎架构(万字总结)

ES写索引流程

上面提到了写索引是只能写在主分片上,然后同步到副本分片,那么如图4所示,这里有四个主分片分别是S0、S1、S2、S3,一条数据是根据什么策略写到指定的分片上呢?这条索引数据为什么被写到S0上而不被写到 S1 或 S2 上?这个过程是根据下面这个公式决定的。

shard = hash(routing) % number_of_primary_shards

以上公式的值是在0到number_of_primary_shards-1之间的余数,也就是数据档所在分片的位置。routing通过Hash函数生成一个数字,然后这个数字再除以number_of_primary_shards(主分片的数量)后得到余数。routing是一个可变值,默认是文档的_id ,也可以设置成一个自定义的值。

在一个写请求被发送到某个节点后,该节点按照前文所述,会充当协调节点,会根据路由公式计算出写哪个分片,当前节点有所有其他节点的分片信息,如果发现对应的分片是在其他节点上,再将请求转发到该分片的主分片节点上。

在ES集群中每个节点都通过上面的公式知道数据的在集群中的存放位置,所以每个节点都有接收读写请求的能力。

Elasticsearch分布式搜索引擎架构(万字总结)

当前一个数据通过路由计算公式得到的值是 shard=hash(routing)%4=0,则具体流程如下:

数据写请求发送到 node1 节点,通过路由计算得到值为1,那么对应的数据会应该在主分片S1上。node1节点将请求转发到 S1 主分片所在的节点node2,node2 接受请求并写入到磁盘。并发将数据复制到三个副本分片R1上,其中通过乐观并发控制数据的冲突。一旦所有的副本分片都报告成功,则节点 node2将向node1节点报告成功,然后node1节点向客户端报告成功。

这种模式下,只要有副本在,写入延时最小也是两次单分片的写入耗时总和,效率会较低,但是这样的好处也很明显,避免写入后单个机器硬件故障导致数据丢失,在数据完整性和性能方面,一般都是优先选择数据,除非一些允许丢数据的特殊场景。

在ES里为了减少磁盘IO保证读写性能,一般是每隔一段时间(比如30分钟)才会把数据写入磁盘持久化,对于写入内存,但还未flush到磁盘的数据,如果发生机器宕机或者掉电,那么内存中的数据也会丢失,这时候如何保证?

对于这种问题,ES借鉴数据库中的处理方式,增加CommitLog模块,在ES中叫transLog,在下面的ES存储原理中会介绍。

存储原理

上面介绍了在ES内部的写索引处理流程,数据在写入到分片和副本上后,目前数据在内存中,要确保数据在断电后不丢失,还需要持久化到磁盘上。

我们知道ES是基于Lucene实现的,内部是通过Lucene完成的索引的创建写入和搜索查询,Lucene 工作原理如下图所示,当新添加一片文档时,Lucene进行分词等预处理,然后将文档索引写入内存中,并将本次操作写入事务日志(transLog),transLog类似于mysql的binlog,用于宕机后内存数据的恢复,保存未持久化数据的操作日志。

Elasticsearch分布式搜索引擎架构(万字总结)

默认情况下,Lucene每隔1s(refresh_interval配置项)将内存中的数据刷新到文件系统缓存中,称为一个segment(段)。一旦刷入文件系统缓存,segment才可以被用于检索,在这之前是无法被检索的。

因此refresh_interval决定了ES数据的实时性,因此说ES是一个准实时的系统。segment 在磁盘中是不可修改的,因此避免了磁盘的随机写,所有的随机写都在内存中进行。随着时间的推移,segment越来越多,默认情况下,Lucene每隔30min或segment 空间大于512M,将缓存中的segment持久化落盘,称为一个commit point,此时删掉对应的transLog。

当我们在进行写操作的测试的时候,可以通过手动刷新来保障数据能够被及时检索到,但是不要在生产环境下每次索引一个文档都去手动刷新,刷新操作会有一定的性能开销。一般业务场景中并不都需要每秒刷新。

可以通过在 Settings 中, 调大 refresh_interval = "30s" 的值,来降低每个索引的刷新频率,设置时需要注意后面带上时间单位,否则默认是毫秒。当 refresh_interval=-1 时表示关闭索引的自动刷新。

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

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

segment被设定为不可修改具有一定的优势也有一定的缺点。

优点:

不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。

一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升.

其它缓存(像 Filter 缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。

写入单个大的倒排索引允许数据被压缩,减少磁盘 I/O 和需要被缓存到内存的索引的使用量。

缺点:

当对旧数据进行删除时,旧数据不会马上被删除,而是在 .del 文件中被标记为删除。而旧数据只能等到段更新时才能被移除,这样会造成大量的空间浪费。

若有一条数据频繁的更新,每次更新都是新增新的,标记旧的,则会有大量的空间浪费。

每次新增数据时都需要新增一个段来存储数据。当段的数量太多时,对服务器的资源例如文件句柄的消耗会非常大。

在查询的结果中包含所有的结果集,需要排除被标记删除的旧数据,这增加了查询的负担。

段合并

由于每当刷新一次就会新建一个segment(段),这样会导致短时间内的段数量暴增,而segment数目太多会带来较大的麻烦。大量的segment会影响数据的读性能。每一个segment都会消耗文件句柄、内存和CPU 运行周期。

更重要的是,每个搜索请求都必须轮流检查每个segment然后合并查询结果,所以segment越多,搜索也就越慢。

因此Lucene会按照一定的策略将segment合并,合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档不会被拷贝到新的大segment中。

合并的过程中不会中断索引和搜索,倒排索引的数据结构使得文件的合并是比较容易的。

段合并在进行索引和搜索时会自动进行,合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中,这些段既可以是未提交的也可以是已提交的。

合并结束后老的段会被删除,新的段被刷新到磁盘,同时写入一个包含新段且排除旧的和较小的段的新提交点,新的段被打开,可以用来搜索。段合并的计算量庞大,而且还要吃掉大量磁盘 I/O,并且段合并会拖累写入速率,如果任其发展会影响搜索性能。

ES在默认情况下会对合并流程进行资源限制,所以搜索性能可以得到保证。

Elasticsearch分布式搜索引擎架构(万字总结)

总结

文章出处:

vivo官网商城开发团队

https://coyotey.gitbooks.io/elasticsearch/content/

拓展知识: