分布式搜索引擎Elasticsearch解析

Elasticsearch作为分布式开源的搜索引擎,广泛应用于搜索和实时分析场景。本文简要介绍ES的一些特性、索引执行的原理以及集群架构,以加深理解。


1、Elasticsearch基本概念

Elasticsearch是基于Apache Lucene的开源、分布式、可扩展、实时的数据搜索和分析引擎。ES不仅仅支持全文搜索,还是一个分布式文档数据库,每个字段都是被索引的数据并且可被搜索。总体来说有如下特性:

  • 一个分布式的实时文档存储引擎,每个字段都可以被索引与搜索
  • 一个分布式实时分析搜索引擎,支持各种查询和聚合操作
  • 能胜任上百个服务节点的扩展,并可以支持PB级别的结构化或者非结构化数据
1.1 全文搜索

现实中的数据分为结构化数据和非结构化数据,结构化数据主要通过关系型数据库进行存储和管理;非结构化数据又称为全文数据,包括各类文本、文档或者图片等。非结构化数据的搜索有两种方式:

  • 顺序扫描:按照顺序查找特定的关键字,这种方式是最低效的
  • 全文搜索:将非结构化数据中的部分数据提取出来变成有结构的,然后按照一定结构的数据再进行搜索

因此,全文搜索可以对每个词建立一个索引,指明该词在文本中出现的次数和位置。当用户查询时,根据事先建立的索引进行查找,并返回查找到的结果。

1.1.1 什么是Lucene

Elasticsearch是以Lucene为底层基础建立的开源全文搜索引擎,Lucene是现在最好的开源全文检索引擎工具,但是Lucene只是一个工具包,并不是一个完整的全文搜索引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。

1.1.2 倒排索引

Lucene能够实现全文搜索主要是因为实现了倒排索引的查询功能。倒排索引和正向索引不同,它不是由记录来确定属性的值而是由属性值来确定记录的位置。倒排索引包含两个部分:

  • 单词词典(Term Dictionary),记录所有文档的单词,记录单词倒排列表的关联关系。
    • 单词词典内每条索引项记载单词本身的信息以及指向“倒排列表”的指针
    • 单词词典一般比较大,可以通过B+树或者哈希拉链法实现,以满足高性能的插入与查询
  • 倒排列表(Posting List),记录了单词对应的文档结合,由倒排索引组成。倒排索引项包含:文档ID,词频TF(该单词在文档中出现的次数,用于相关性评分),位置(Position,单词在文档中分词的位置。用于语句搜索(phrase query)),偏移(Offset,记录单词的开始结束位置,实现高亮显示)

假如有以下两段文字,通过分词器将文档的内容拆分成单独的词,再创建倒排索引。

Java is the best programming language.
Python is the best programming language.

以上内容可以转换为以下的倒排索引信息

关键词 文章编号 出现频率 出现位置
Java 1 1 0
Python 2 1 0
is 1/2 1/1 5/7
the 1/2 1/1 8/10
best 1/2 1/1 12/14
programming 1/2 1/1 17/19
language 1/2 1/1 29/31

上表转换为倒排索引的图形结构信息

在这里插入图片描述

倒排索引有个很重要的特性是被写入磁盘后是不可改变的:它永远不会修改。不变性有重要的价值:

  • 不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。
  • 一旦索引被读入内核的文件系统缓存,便会留在那里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。
  • 其它缓存(像filter缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
  • 写入单个大的倒排索引允许数据被压缩,减少磁盘I/O 和需要被缓存到内存的索引的使用量。

不变索引的缺点就是不可变的它是不可变的,你不能修改它。如果需要让一个新的文档可被搜索,你需要重建整个索引。

1.2 Elasticsearch核心概念
1.2.1 Index索引

ES将数据存储于一个或多个索引中,索引是具有类似特性的文档的集合,相当于关系型数据库中的一个Database。索引由其名称(必须为全小写字符)进行标识,并通过引用此名称完成文档的创建、搜索、更新及删除操作。一个ES集群中可以按需创建任意数目的索引。

1.2.2 类型Type

类型是索引内部的逻辑分区,在一个索引内部可定义一个或多个类型(type),类似于传统数据库中的表。一般来说,类型就是为那些拥有相同的域的文档做的预定义。例如,在索引中,可以定义一个用于存储用户数据的类型,一个存储日志数据的类型,以及一个存储评论数据的类型。

1.2.3 文档Document

文档是索引和搜索的原子单位,它是存储在ES中的一个JSON格式的字符串,其中包含了一个或多个域(Field)的容器。在ES中每个文档都有一个类型和ID,每个文档由一个或多个域组成,每个域拥有一个名字及一个或多个值,有多个值的域通常称为“多值域”。每个文档可以存储不同的域集,但同一类型下的文档至应该有某种程度上的相似之处。

在这里插入图片描述

1.2.4 集群Cluster

集群由一个或多个节点组成,对外提供索引和搜索功能。在一个集群中所有的节点都有一个唯一的名称默认为“Elasticsearch”,当某个节点被设置为相同的集群名称时,会自动加入到集群。如果有多个集群,需要设置不同的名称,否则节点可能会加入到错误的集群。需要注意的是一个节点只能加入一个集群。

在这里插入图片描述

1.2.5 节点Node

一个运行中的Elasticsearch实例称为一个节点,它是一个逻辑上独立的服务,可以存储数据,是ES集群的一部分。ES集群由一个或者多个拥有相同cluster.name配置的节点组成,它们共同承担数据和负载的压力。ES集群中的节点有三种不同的类型:

  • 主节点:负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等,并决定哪些分片分配给相关的节点、追踪集群中节点的状态等。主节点并不需要涉及到文档级别的变更和搜索等操作,可以通过属性node.master进行设置。
  • 数据节点:存储数据和其对应的倒排索引,同时对数据进行增删改查和聚合等操作。默认每一个节点都是数据节点(包括主节点),可以通过node.data属性进行设置。通常随着集群的扩大,需要增加更多的数据节点来提高性能和可用性。
  • 协调节点:如果node.master和node.data属性均为false,则此节点称为协调节点,用来响应客户请求,均衡每个节点的负载。

在…/config/elasticsearch.yml配置文件中可以设置不同的节点类型

node.master: true //是否候选主节点
node.data: true //是否数据节点

在实际过程中,如果某个节点既是数据节的又是主节点,可能对主节点的性能产生影响。因此为了集群的健康性,需要对Elasticsearch集群中的角色进行划分和隔离,可以使用配置较低的机器作为主节点。

1.2.6 路由Routing

当存储一个文档的时候,它会存储在唯一的主分片中,具体哪个分片是通过散列值进行选择。默认情况下这个值由文档的ID生成,如果文档中指定了一个父文档,则从父文档ID中生成。注:Routing值和路由计算具体到哪个分片有关。

1.2.7 分片Sharding

ES中的索引数据量太大的时候,可以通过水平拆分的方式将一个索引上的数据拆分出来分配到不同的数据块上,这个数据块称为分片,相当于水平分表。一个分片便是一个Lucene的实例,ES中的index就是指向主分片和副本分片的逻辑空间。实际的文档数据被存储和索引到分片内,但是应用程序是直接与索引而不是与分片进行交互。

ES实际上就是利用分片来实现分布式,通过将索引分解成多个分片,分布在不同的节点上实现横向扩展。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。当集群规模扩大或者缩小时,ES会自动的在各节点中迁移分片,使得数据仍然均匀分布在集群里。下图是一个3主分片,1副本分片的ES集群。

在这里插入图片描述

1)主分片

每个文档都存储在一个分片中,当存储一个文档的时候,ES首先将数据存储在主分片中,然后复制到不同的副本中。ES默认为一个索引创建 5 个主分片, 并分别为每个分片创建一个副本,通过参数可以指定。在索引建立的时候就已经确定了主分片数,但是副本分片数可以随时修改。

"number_of_shards" : 5,
"number_of_replicas" : 1

2)副本分片

副本分片是主分片的复制,每个主分片有0个或者多个副本。当主分片异常的时候,可以从副本分片中选择一个作为主分片,提高可用性。同时,查询可以在副本分片进行,减轻主分片的压力,提高性能。另外副本分片必须和主分片部署在不同的节点上,如果集群中只有一个节点,则副本分片将不会被分配,此时集群健康状态为yellow,存在丢失数据的风险。

1.2.8 与关系型数据库对比

与关系型数据库对比,在ElasticSearch中的索引=数据库,类型=表,文档=行数据,如下表所示。

Elasticsearch 关系型数据库
Index Database
Type Table
Document Row
Field Column
Mapping Schema
Everything is indexed Index
Query DSL SQL
GET http://… SELECT
PUT http://… UPDATE

2、Elasticsearch索引原理

2.1 Elasticsearch读流程

分布式搜索的执行过程分为两个阶段:查询和取回。在搜索的API返回结果前,需要将多个分片的结果进行汇总放到一个有序列表中进行返回。

2.1.1 查询阶段

在这里插入图片描述

在初始化查询阶段,向索引中的每个分片副本进行广播,每个分片在本地执行搜索并且建立匹配document的优先队列。整个查询阶段包括以下步骤:

  1. 客户端发送一个search请求给Node 3,这个节点变成协调节点。Node 3创建一个长度为from+size(类似于{ “from”: 90, “size”: 10 } )的空优先级队列
  2. Node 3向节点里的每个分片副本广播搜索请求,每个分片在本地执行这个查询并将结果保存到本地大小为from+size的有序优先队列里。分片仅会返回一个轻量级的结果给协调节点,包含结果集中的每一个文档的ID和进行排序所需要的信息。
  3. 每个分片返回document的ID和优先队列里所有document的排序值给到协调节点Node 3。Node 3将这些值合并到自己的优先队列里并产生全局的排序结果。

在整个查询过程中,协调节点会将所有分片的结果汇总,并进行全局排序,得到最终的查询排序结果。

2.1.2 取回阶段

在这里插入图片描述

查询阶段得到哪些满足搜索请求的document,但是需要将这些document返回给客户端。取回阶段过程如下:

  1. 协调节点辨别出哪些document需要取回,并向相关分片发出Get请求
  2. 每个分片加载document并根据需要丰富这些文档,并将document返回给协调节点
  3. 当所有的document都被取回后,协调节点会将结果返回给客户端

为提高搜索效率,协调节点为每个持有相关document的分片建立并发的多点get请求然后发送请求到处理查询阶段的分片副本。

2.2 Elasticsearch更新流程

在这里插入图片描述

Elasticsearch更新document流程如图所示,主要分为几个阶段:

  1. ES首先将Document写入到In-memory buffer(内存缓冲区),并将这一操作写入一个translog文件(transaction log)中。此时如果执行搜索操作,这个文档还不能被索引到
  2. 默认每隔1s时间进行一次刷新操作(refresh),此时在这1秒时间内写入内存的文档都会被写入一个文件系统缓存Cache中,并构成一个分段(segment)。此时这个segment里的文档可以被搜索到,但是尚未写入硬盘,即如果此时发生断电,则这些文档可能会丢失。在执行刷新后清空内存,文档写入文件系统缓存Cache中。
  3. 不断有新的文档写入,则这一过程将不断重复执行。每隔一秒将生成一个新的segment,而translog文件将越来越大。
  4. 每隔30分钟或者translog文件变得很大,则执行一次flush操作。此时所有在文件系统缓存中的segment将被写入磁盘,而translog将被删除(此后会生成新的translog)。

由上面的流程可以看出,在两次fsync操作之间,存储在内存和文件系统缓存中的文档是不安全的,一旦出现断电这些文档就会丢失。所以ES引入了translog来记录两次fsync之间所有的操作,这样机器从故障中恢复或者重新启动,ES便可以根据translog进行还原。

2.3 Translog事务日志

Elasticsearch中的Translog和MySQL中的binlog类似,用来记录数据的变化用于故障恢复。

  • Document不断写入到In-memory buffer,此时也会追加translog
  • 当buffer中的数据每秒refresh到cache中时,translog并没有进入到刷新到磁盘,持续在追加的
  • translog每隔5s会fsync到磁盘
  • translog会继续累加变得越来越大,当translog大到一定程度或者每隔一段时间(默认30分钟),会执行flush

Flush操作会执行以下步骤:

  • In-memory buffer中的记录被清空
  • 记录commit point
  • Cache内的segment会fsync刷新到磁盘
  • Translog被删除

注意到translog是每5s刷新一次磁盘,所以故障重启时可能会丢失5s的数据。

2.4 Segment合并

由于每一秒就会生成一个新的segment,很快将会有大量的segment,segment数目太多会消耗文件句柄、内存和cpu运行周期。对于一个分片进行查询请求,将会轮流查询分片中的所有segment,这将降低搜索的效率。因此ES会自动启动合并segment的工作,将一部分相似大小的segment合并成一个新的大segment。合并的过程实际上是创建了一个新的segment,当新segment被写入磁盘,所有被合并的旧segment被清除。

在这里插入图片描述

在ES后台会有一个线程进行segment合并:

  • refresh操作会创建新的segment并打开以供搜索使用
  • 合并进程选择一小部分大小相似的segment,并且在后台将它们合并到更大的segment中。这个过程不会中断索引和搜索。
  • 当合并结束,老的segment会被删除。

注:在segment merge这块,那些被逻辑删除的document才会被真正的物理删除。

3、Elasticsearch高可用架构

3.1 集群主从架构

一个Elasticsearch集群由一个或多个拥有相同cluster.name配置的节点组成,这些节点共同承担数据和负载的压力。如下图所示为三个节点的ES集群:

在这里插入图片描述

节点1选为主节点时,负责管理集群范围内的所有变更操作,其它数据节点负责对文档的变更和搜索等操作。对客户端来说,请求发送到集群中的任何节点,每个节点都知道文档所处的位置,并将请求转发到对应的分片,并最终将数据返回给客户端。

3.2 路由计算

当客户端请求某个文档时,Elasticsearch通过路由计算文档具体落在哪个分片上。路由到分片位置的算法由下面的公式计算得到:

shard = hash(routing) % number_of_primary_shards

routing值默认是document的ID值,也可以自行指定。先对routing信息求hash值,然后将hash结果对primary_shard的数量求模,比如说primary_shard是5,那么结果肯定落在[0,4]区间内,这个结果值就是该document的分片位置,如示意图所示:

在这里插入图片描述

这个求模公式间接的解释了为什么了索引创建时指定了主分片的值,后续就不让改了。因为主分片值作为模数修改了,之前路由的document再执行该公式时,值就可能跟改之前得到的值不一致,这样document就找不到了。

3.3 集群发现机制

在同一个网络环境下,当启动一个Elasticsearch实例并且cluster.name配置和ES集群一致,该实例就会自动加入到集群中。这依赖于Elasticsearch的自动发现机制Zen Discovery,Zen Discovery是Elasticsearch内置的发现模块,提供单播和基于文件的发现。Elasticsearch中默认使用单播模式,依赖Transport模块实现,节点使用Ping方式查找其它节点。

如果同一台机器上运行不同的节点,这些节点会自动加入到集群。如果集群的节点运行在不同的机器上,使用单播模式可以为Elasticsearch节点配置一个尝试连接的列表,用discovery.zen.ping.unicast.hosts指定。

discovery.zen.ping.unicast.hosts: ["host1", "host2","host3:port"]

当节点启动后,如果设置了discovery.zen.ping.unicast.hosts,会Ping其中的host,否则的话会ping本地的几个端口。当新的节点联系单播列表中的成员时,会得到整个集群所有节点的状态,然后联系Master节点,加入到集群中。

3.3.1 主节点选举

当主节点发生问题的时候,现有的节点会通过Ping的方式重新选举一个新的主节点。

  1. 选举开始时,从各节点认为的Master中选择,按照ID的字典顺序排序,取第一个
  2. 如果各节点没有认为的Master,则从所有节点中选择,规则同上
  3. 如果节点数达不到discovery.zen.minimum_master_nodes设定的最小值,则循环上述过程直到节点数足够开始选举
  4. 如果当前节点是Master,则等待节点数达到minimum_master_nodes,开始提供服务
  5. 为避免网络异常,配置discovery.zen.ping_timeout设置超时时间

需要注意的是Elasticsearch中支持任意数目的集群,没有限定节点数必须是奇数,而是通过一定的规则来约定。但是在分布式系统中,很容易出现脑裂,解决方案是设置一个Quorum值,并且要求可用节点数超过Quorum才能对外提供服务。

3.3.2 故障检测

Elasticsearch节点的故障检测有两种方式:第一种是Master节点到所有其它节点,证明它们还活着;另一种是每个节点Ping主节点进行验证,当主节点故障时会启动主节点重新选举过程。故障检测的频率由以下参数控制:

  • discovery.zen.fd.ping_interval:ping检查的频率,默认是1s
  • discovery.zen.fd.ping_timeout:ping的超时时间,默认是30s
  • discovery.zen.fd.ping_retries:ping失败或超时重试的次数,默认为3次
3.4 集群扩展

Elasticsearch集群的扩容分为垂直扩容和水平扩容,垂直扩容是增加单台服务器的CPU、内存和磁盘等资源;水平扩容即增加服务器数量,组成计算能力强大的分布式集群。水平扩容是最常用的方法,支撑PB级别的数据规模,当执行扩容操作后,新增加的节点会触发索引分片的重新分配。

每个索引的primary shard数量在索引创建的时候已经确定,如果想调整主分片需要重建索引,但是replica shard是可以动态调整的。如下图所示Elasticsearch集群有两个节点,primary shard设置为3,replica shard设置为1,这样1个索引就有3个primary shard,3个replica shard,P表示primary shard,R表示replica shard。

在这里插入图片描述

当加入新的节点Node 3时,触发分片的重新分配,P2和R2迁移到Node 3。如下图所示:

在这里插入图片描述

集群扩容时需要注意的是:

  • 同一个index的primary shard和replica shard不能分配到一个节点上
  • 尽量保证shard均匀分布在不同的节点上达到负载均衡

4、总结

Elasticsearch作为分布式架构下的搜索引擎已广泛应用到大数据分析、日志搜索等领域。本文中先介绍了ES中的基本概念,包括索引、文档、集群、路由和分片等;再从索引执行的流程角度,分析Elasticsearch读取和更新索引的流程,以及其中的Translog机制;最后解析ES的集群架构、集群的自动发现机制和水平扩容。


参考资料:

  1. Elasticsearch权威指南,路小磊等译
  2. Elasticsearch技术解析与实战,朱林编著
  3. https://blog.csdn.net/weixin_43495317/article/details/104490129
  4. https://blog.csdn.net/zkyfcx/article/details/79998197
  5. https://blog.csdn.net/qq_29595629/article/details/114289509
  6. Elasticsearch高级篇:核心概念和实现原理
  7. 十张图说清Elasticsearch原理

猜你喜欢

转载自blog.csdn.net/solihawk/article/details/127756599