中间件系列之ElasticSearch-1-相关概念和基本原理

3,705 阅读12分钟

1.背景

1.1 定义

全文搜索引擎是Elasticsearch目前广泛应用的主流搜索引擎。它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置

当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

1.2 为什么使用

  • 数据类型

    全文索引搜索支持非结构化数据的搜索,可以更好地快速搜索大量存在的任何单词或单词组的非结构化文本。

    例如 Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回。还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。

  • 索引的维护

    一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对SQL的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。

1.3 什么时候使用

  1. 搜索的数据对象是大量的非结构化的文本数据。
  2. 文件记录量达到数十万或数百万个甚至更多。
  3. 支持大量基于交互式文本的查询。
  4. 需求非常灵活的全文搜索查询。
  5. 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
  6. 对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。

2.相关概念和基本原理

2.1 相关概念

Cluster

ES天生就是一个分布式的搜索引擎,允许多台服务器协同工作,每台服务器可以运行多个 Elastic 实例。单个 Elastic 实例称为一个节点(node)。一组节点构成一个集群(cluster)。

一个集群由一个唯一的名字标识,节点通过指定集群的名字来加入集群。可以在config/elasticsearch.yml配置文件中自定义

cluster.name: myes  

Node

关于Node,有以下一些概念需要注意:

  • Coordinating(使协调; 使相配合) Node
    • 处理请求的节点(路由请求到正确的节点,比如创建索引的请求,路由到MASTER节点)
    • 所有节点默认都是Coordinating Node
  • Data Node
    • 保存数据,节点启动后,默认就是Data Node,可以设置node.data:false禁止。由Master Node决定如何把分片分发到数据节点上
    • 通过增加数据节点,可以解决数据水平扩展问题
  • Master Node
    • 处理创建,删除索引等请求/决定分片被分配到哪个节点/维护并更新Cluster State
    • 实际使用,为一个集群配置多个Master Node
  • Cluster State
    • 集群状态信息,包括所有节点信息,所有的索引和相关的mapping,分片的路由信息
    • 每个节点都会保存Cluster State,但只有Master Node可以修改,并负责同步给其他节点

每个节点既可以是候选Master Node也可以是Data Node,通过在配置文件 ../config/elasticsearch.yml中设置即可,默认都为 true。需要强调,node.master: true 仅仅是代表该节点拥有被选举为Master Node的资格

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

Data Node负责数据的存储和相关的操作,例如对数据进行增、删、改、查和聚合等操作

候选Master Node可以被选举为主节点(Master Node),集群中只有候选主节点才有选举权和被选举权,其他节点不参与选举的工作。主节点负责创建索引、删除索引、跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点、追踪集群中节点的状态等

Document

Elasticsearch是**面向文档(document oriented)**的,这意味着索引或搜索的最小数据单元就是文档。 ELasticsearch使用JSON,作为文档序列化格式

索引

在Elasticsearch中存储数据的行为就叫做索引(indexing)

Elasticsearch集群可以包含多个索引(indices)(数据库),每一个索引可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。类比传统关系型数据库:

Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices   -> Types  -> Documents -> Fields

在7.x版本之前,可以用如下方式创建一个索引

PUT /megacorp/employee/1
{
    "first_name" : "John",
    "last_name" :  "Smith",
    "age" :        25,
    "about" :      "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}

我们看到path:/megacorp/employee/1包含三部分信息:

名字说明
megacorp索引名
employee类型名
1这个员工的ID

在7.x版本之后,ES取消了type的定义,默认使用_doc,在未来8.0的版本中,type将被彻底删除。

PUT /megacorp/_doc/1

Shard

一个索引可以存储超出单个结点硬件限制的大量数据,为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片(shard)。

有两种类型的分片:primary shard和replica shard。

  • Primary shard: 每个文档都存储在一个Primary shard。 索引文档时,它首先在Primary shard上编制索引,然后在此分片的所有副本上(replica)编制索引。索引可以包含一个或多个主分片。
  • Replica shard: 每个主分片可以具有零个或多个副本。副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高es的查询效率,es会自动对搜索请求进行负载均衡。

在旧版本中,默认Shards为5,replicas为1,在7.0之后,默认Shards为1,replicas为0。故一般都需要在创建时设置

{
    "settings" : {
        "index" : {
            "number_of_shards" : 3, 
            "number_of_replicas" : 2 
        }
    }
}

​ 注意:**主分片的数量只能在索引创建前指定,并且索引创建后不能更改。**这是由于ES的路由算法所致

shard = hash(_routing) % (num_of_primary_shards)

​ 其中,hash算法确保文档分散均匀,默认路由值_routing是文档ID,其对主分片数量取模,所以主分片数量不能修改,否则可能会找不到相应的shard。

2.2 基本原理

倒排索引不可变性

倒排索引不可变性

  • 倒排索引Immutable Design,一旦生成,不可更改
  • 优点
    • 无需考虑并发写文件的问题
    • 一旦读入内核,便会留在那里,只有有足够的空间,大部分请求都会直接请求内存
    • 缓存容易生成和维护,数据可以压缩
  • 缺点:如果搜索一个新文档,则需要重建整个索引

由于Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更。删除的文档信息保存在".del"文件中。

当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del文件中被标记为删除的文档将不会被写入新段。   在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉

Lucene Index

ES是基于Lucene开发的,在Lucene 中,单个倒排索引文件成为Segment,多个Segment汇总在一起,成为Lucene的Index,即ES的Shard。

当有新文档写入时,会生成新的Segment,查询时会查询所有的Segments,并对结果进行汇总。Lucene中有一个文件,用来记录所有Segments信息,叫做提交点(Commit Point)。

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

生命周期

  • Refresh

    在ES中,写入新文档时,先把文档写入自己的一个名为Index Buffer的内存空间,在内存和磁盘之间是文件系统缓存(操作系统的内存),当达到默认的时间(1秒钟)或者Index Buffer被占满(默认是JVM 10%)时,将内存中的数据生成到一个新的段上并缓存到文件缓存系统 上,这个过程就是一次Refresh。

    Refresh之后,数据就可以被检索到了。

    简单的说,Index Buffer把数据从Index Buffer写入Segment的过程就叫Refresh。频率默认1秒发生一次,可通过index.refresh_interval设置。

  • Transaction Log

    为了保证数据不丢失,在Index文档时,同时写入Transaction Log,Transaction Log默认落盘,每个分片都有一个Transaction Log。

    在ES Refresh时,Index Buffer会被清空,Transaction Log不会清空。

    由于Transaction Log的存在,保证持久性,断电恢复后可通过Transaction Log恢复。

  • Flush

    该阶段会做以下几件事:

    • 调用Refresh,清空Index Buffer;
    • 调用fsync,将文件系统中缓存中的Segment写入磁盘;
    • 清空Transaction Log;

    默认30分钟调用一次,或者Transaction Log写满时也会自动调用,默认大小512MB。

  • Merge

    由于自动刷新流程每秒会创建一个新的Segment,这样会导致短时间内的Segment数量暴增。而Segment数目太多会带来较大的麻烦。每一个Segment都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个Segment然后合并查询结果,所以Segment越多,搜索也就越慢。所以,需要定期合并,并且还能真正删除已经删除的文档(即.del文件的内容)

    默认自动进行,也可通过命令手动执行

    POST /index/_forcemerge
    

脑裂现象

如果由于网络或其他原因导致集群中选举出多个Master节点,使得数据更新时出现不一致,这种现象称之为脑裂,即集群中不同的节点对于master的选择出现了分歧,出现了多个master竞争。

​ “脑裂”问题可能有以下几个原因造成:

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

为了避免脑裂现象的发生,我们可以从原因着手通过以下几个方面来做出优化措施:

  • 适当调大响应时间,减少误判通过参数 discovery.zen.ping_timeout设置节点状态的响应时间,默认为3s,可以适当调大,如果master在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数(如6s,discovery.zen.ping_timeout:6),可适当减少误判。

  • 选举触发我们需要在候选集群中的节点的配置文件中设置参数 discovery.zen.munimum_master_nodes的值,这个参数表示在选举主节点时需要参与选举的候选主节点的节点数,默认值是1,官方建议取值 (master_eligibel_nodes/2)+1,其中 master_eligibel_nodes为候选主节点的个数。这样做既能防止脑裂现象的发生,也能最大限度地提升集群的高可用性,因为只要不少于discovery.zen.munimum_master_nodes个候选节点存活,选举工作就能正常进行。当小于这个值的时候,无法触发选举行为,集群无法使用,不会造成分片混乱的情况。

    7.0版本之后,移除该参数,交由ES自己选择

  • 角色分离即是上面我们提到的候选主节点和数据节点进行角色分离,这样可以减轻主节点的负担,防止主节点的假死状态发生,减少对主节点“已死”的误判。

选举机制

一个集群,支持配置多个Master Eligible节点,可以设置master:false禁止,这些节点在必要时会参与选主流程,成为Master节点。

节点互相PING,根据nodeId字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点。

如果对某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。