文章目录
一些基础知识的铺垫之后我们正式进入今天的主角 Elasticsearch 的介绍。
3.1 概述
Elasticsearch是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅是存储,还会索引(index)每个文档的内容使之可以被搜索。
在Elasticsearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。
3.2 核心概念
ES 是使用 Java 编写的一种开源搜索引擎,它在内部使用 Lucene 做索引与搜索,通过对 Lucene 的封装,隐藏了 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。
然而,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。
它可以被下面这样准确的形容:
- 一个分布式的实时文档存储,每个字段可以被索引与搜索。
- 一个分布式实时分析搜索引擎。
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。
官网对 Elasticsearch 的介绍是 Elasticsearch 是一个分布式、可扩展、近实时的搜索与数据分析引擎。
我们通过一些核心概念来看下 Elasticsearch 是如何做到分布式,可扩展和近实时搜索的。
index索引
一个索引就是一个拥有几分相似特征的文档的集合。
比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。
一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。可类比mysql中的数据库
type类型
在一个索引中,你可以定义一种或多种类型。
一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。
通常,会为具有一组共同字段的文档定义一个类型。
比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型。可类比mysql中的表
Filed字段
相当于是数据表的字段,对文档数据根据不同属性进行的分类标识 。
映射mapping
mapping是处理数据的方式和规则方面做一些限制。
映射是用于定义 ES 对索引中字段的存储类型、分词方式和是否存储等信息,就像数据库中的 Schema ,描述了文档可能具有的字段或属性、每个字段的数据类型。
如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,其它就是处理es里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。相当于mysql中的创建表的过程,设置主键外键等等
在讲解动态映射和静态映射的使用前,我们先来了解下 ES 中的数据有哪些字段类型?之后我们再讲解为什么我们创建索引时需要建立静态映射而不使用动态映射。
ES(v6.8)中字段数据类型主要有以下几类:
Text 用于索引全文值的字段,例如电子邮件正文或产品说明。
这些字段是被分词的,它们通过分词器传递 ,以在被索引之前将字符串转换为单个术语的列表。
分析过程允许 Elasticsearch 搜索单个单词中每个完整的文本字段。文本字段不用于排序,很少用于聚合。
Keyword 用于索引结构化内容的字段,例如电子邮件地址,主机名,状态代码,邮政编码或标签。它们通常用于过滤,排序,和聚合。Keyword 字段只能按其确切值进行搜索。
通过对字段类型的了解我们知道有些字段需要明确定义的,例如某个字段是 Text 类型还是 Keyword 类型差别是很大的,时间字段也许我们需要指定它的时间格式,还有一些字段我们需要指定特定的分词器等等。
如果采用动态映射是不能精确做到这些的,自动识别常常会与我们期望的有些差异。
所以创建索引的时候一个完整的格式应该是指定分片和副本数以及 Mapping 的定义,如下:
PUT my_index
{
"settings" : {
"number_of_shards" : 5,
"number_of_replicas" : 1
}
"mappings": {
"_doc": {
"properties": {
"title": { "type": "text" },
"name": { "type": "text" },
"age": { "type": "integer" },
"created": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
}
}
}
}
}
document文档
一个文档是一个可被索引的基础信息单元。
比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。
文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。
在一个index/type里面,你可以存储任意多的文档。
注意,尽管一个文档,物理上存在于一个索引之中,文档必须被索引/赋予一个索引的type。
插入索引库以文档为单位,类比与数据库中的一行数据
集群cluster
一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。
一个集群由 一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。
确保不同的环境中使用不同的集群名称,否则最终会导致节点加入错误的集群。
一个 Elasticsearch 服务启动实例就是一个节点(Node)。节点通过 node.name 来设置节点名称,如果不设置则在启动时给节点分配一个随机通用唯一标识符作为名称。
①发现机制
那么有一个问题,ES 内部是如何通过一个相同的设置 cluster.name 就能将不同的节点连接到同一个集群的?
答案是 Zen Discovery。
Zen Discovery 是 Elasticsearch 的内置默认发现模块(发现模块的职责是发现集群中的节点以及选举 Master 节点)。
它提供单播和基于文件的发现,并且可以扩展为通过插件支持云环境和其他形式的发现。
Zen Discovery 与其他模块集成,例如,节点之间的所有通信都使用 Transport 模块完成。节点使用发现机制通过Ping 的方式查找其他节点。
Elasticsearch 默认被配置为使用单播发现,以防止节点无意中加入集群。
只有在同一台机器上运行的节点才会自动组成集群。
如果集群的节点运行在不同的机器上,使用单播,你可以为 Elasticsearch 提供一些它应该去尝试连接的节点列表。
当一个节点联系到单播列表中的成员时,它就会得到整个集群所有节点的状态,然后它会联系Master 节点,并加入集群。
这意味着单播列表不需要包含集群中的所有节点, 它只是需要足够的节点,当一个新节点联系上其中一个并且说上话就可以了。
如果你使用Master 候选节点作为单播列表,你只要列出三个就可以了。这个配置在 elasticsearch.yml 文件中:
discovery.zen.ping.unicast.hosts: ["host1", "host2:port"]
节点启动后先 Ping ,如果 discovery.zen.ping.unicast.hosts 有设置,则 Ping 设置中的 Host ,否则尝试 ping localhost 的几个端口。
Elasticsearch 支持同一个主机启动多个节点,Ping 的 Response 会包含该节点的基本信息以及该节点认为的 Master 节点。
选举开始,先从各节点认为的 Master 中选,规则很简单,按照 ID 的字典序排序,取第一个。如果各节点都没有认为的 Master ,则从所有节点中选择,规则同上。
这里有个限制条件就是 discovery.zen.minimum_master_nodes ,如果节点数达不到最小值的限制,则循环上述过程,直到节点数足够可以开始选举。
最后选举结果是肯定能选举出一个 Master ,如果只有一个 Local 节点那就选出的是自己。
如果当前节点是 Master ,则开始等待节点数达到 discovery.zen.minimum_master_nodes,然后提供服务。
如果当前节点不是 Master ,则尝试加入 Master 。Elasticsearch 将以上服务发现以及选主的流程叫做 Zen Discovery 。
由于它支持任意数目的集群( 1- N ),所以不能像 Zookeeper 那样限制节点必须是奇数,也就无法用投票的机制来选主,而是通过一个规则。
只要所有的节点都遵循同样的规则,得到的信息都是对等的,选出来的主节点肯定是一致的。
但分布式系统的问题就出在信息不对等的情况,这时候很容易出现脑裂(Split-Brain)的问题。
大多数解决方案就是设置一个 Quorum 值,要求可用节点必须大于 Quorum(一般是超过半数节点),才能对外提供服务。
而 Elasticsearch 中,这个 Quorum 的配置就是 discovery.zen.minimum_master_nodes 。
②节点的角色
每个节点既可以是候选主节点也可以是数据节点,通过在配置文件 config/elasticsearch.yml 中设置即可,默认都为 true。
node.master: true //是否候选主节点
node.data: true //是否数据节点
数据节点负责数据的存储和相关的操作,例如对数据进行增、删、改、查和聚合等操作,所以数据节点(Data 节点)对机器配置要求比较高,对 CPU、内存和 I/O 的消耗很大。
通常随着集群的扩大,需要增加更多的数据节点来提高性能和可用性。
候选主节点可以被选举为主节点(Master 节点),集群中只有候选主节点才有选举权和被选举权,其他节点不参与选举的工作。
主节点负责创建索引、删除索引、跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点、追踪集群中节点的状态等,稳定的主节点对集群的健康是非常重要的。
一个节点既可以是候选主节点也可以是数据节点,但是由于数据节点对 CPU、内存核 I/O 消耗都很大。
所以如果某个节点既是数据节点又是主节点,那么可能会对主节点产生影响从而对整个集群的状态产生影响。
因此为了提高集群的健康性,我们应该对 Elasticsearch 集群中的节点做好角色上的划分和隔离。可以使用几个配置较低的机器群作为候选主节点群。
主节点和其他节点之间通过 Ping 的方式互检查,主节点负责 Ping 所有其他节点,判断是否有节点已经挂掉。其他节点也通过 Ping 的方式判断主节点是否处于可用状态。
虽然对节点做了角色区分,但是用户的请求可以发往任何一个节点,并由该节点负责分发请求、收集结果等操作,而不需要主节点转发。
这种节点可称之为协调节点,协调节点是不需要指定和配置的,集群中的任何节点都可以充当协调节点的角色。
③脑裂现象
同时如果由于网络或其他原因导致集群中选举出多个 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 个候选节点存活,选举工作就能正常进行。
当小于这个值的时候,无法触发选举行为,集群无法使用,不会造成分片混乱的情况。
- 角色分离。即是上面我们提到的候选主节点和数据节点进行角色分离,这样可以减轻主节点的负担,防止主节点的假死状态发生,减少对主节点“已死”的误判。
节点node
一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
和集群类似,一 个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的 时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对 应于Elasticsearch集群中的哪些节点。
一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫 做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此, 它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点, 这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
分片和复制 shards&replicas
一个索引可以存储超出单个结点硬件限制的大量数据。
比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。
当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。
分片很重要,主要有两方面的原因:
- 1)允许你水平分割/扩展你的内容容量。
- 2)允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。
至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。
在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。
为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。
复制之所以重要,有两个主要原因:
-
在分片/节点失败的情况下,提供了高可用性。
因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。 -
扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行。
总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。
分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。
默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。
副本(Replicas)
副本就是对分片的 Copy,每个主分片都有一个或多个副本分片,当主分片异常时,副本可以提供数据的查询等操作。
主分片和对应的副本分片是不会在同一个节点上的,所以副本分片数的最大值是 N-1(其中 N 为节点数)。
对文档的新建、索引和删除请求都是写操作,必须在主分片上面完成之后才能被复制到相关的副本分片。
ES 为了提高写入的能力这个过程是并发写的,同时为了解决并发写的过程中数据冲突的问题,ES 通过乐观锁的方式控制,每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。
一旦所有的副本分片都报告写成功才会向协调节点报告成功,协调节点向客户端报告成功。
从上图可以看出为了达到高可用,Master 节点会避免将主分片和副本分片放在同一个节点上。
假设这时节点 Node1 服务宕机了或者网络不可用了,那么主节点上主分片 S0 也就不可用了。
幸运的是还存在另外两个节点能正常工作,这时 ES 会重新选举新的主节点,而且这两个节点上存在我们所需要的 S0 的所有数据。
我们会将 S0 的副本分片提升为主分片,这个提升主分片的过程是瞬间发生的。此时集群的状态将会为 Yellow。
为什么我们集群状态是 Yellow 而不是 Green 呢?虽然我们拥有所有的 2 个主分片,但是同时设置了每个主分片需要对应两份副本分片,而此时只存在一份副本分片。所以集群不能为 Green 的状态。
如果我们同样关闭了 Node2 ,我们的程序依然可以保持在不丢失任何数据的情况下运行,因为 Node3 为每一个分片都保留着一份副本。
如果我们重新启动 Node1 ,集群可以将缺失的副本分片再次进行分配,那么集群的状态又将恢复到原来的正常状态。
如果 Node1 依然拥有着之前的分片,它将尝试去重用它们,只不过这时 Node1 节点上的分片不再是主分片而是副本分片了,如果期间有更改的数据只需要从主分片上复制修改的数据文件即可。
3.3 小结
将数据分片是为了提高可处理数据的容量和易于进行水平扩展,为分片做副本是为了提高集群的稳定性和提高并发量。
副本是乘法,越多消耗越大,但也越保险。分片是除法,分片越多,单分片数据就越少也越分散。
副本越多,集群的可用性就越高,但是由于每个分片都相当于一个 Lucene 的索引文件,会占用一定的文件句柄、内存及 CPU。
并且分片间的数据同步也会占用一定的网络带宽,所以索引的分片数和副本数也不是越多越好。