简介
ES
是使用 Java
编写的一种开源搜索引擎,它在内部使用 Lucene
做索引与搜索,通过对 Lucene
的封装,隐藏了 Lucene
的复杂性,取而代之的提供一套简单一致的 RESTful API
然而,Elasticsearch
不仅仅是 Lucene
,并且也不仅仅只是一个全文搜索引擎。
它可以被下面这样准确的形容:
一个分布式的实时文档存储,每个字段可以被索引与搜索。
一个分布式实时分析搜索引擎。
能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。
官网对 Elasticsearch
的介绍是 Elasticsearch
是一个分布式、可扩展、近实时的搜索与数据分析引擎。
其中主要有如下几个核心术语需要理解:
- 词条(
Term
): 索引里面最小的存储和查询单元,对于英文来说是一个单词,对于中文来说一般指分词后的一个词。 - 词典(
Term Dictionary
): 或字典,是词条 Term 的集合。搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向倒排列表的指针。 - 倒排表(
Post list
):一个文档通常由多个词组成,倒排表记录的是某个词在哪些文档里出现过以及出现的位置。每条记录称为一个倒排项(Posting
)。倒排表记录的不仅是文档编号,还存储了词频等信息。 - 倒排文件(
Inverted File
): 所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件被称之为倒排文件
,倒排文件是存储倒排索引的物理文件
由属性值来确定记录的位置的结构就是倒排索引。带有倒排索引的文件称为倒排文件
词典
和倒排表
是 Lucene
中很重要的两种数据结构,是实现快速检索的重要基石。词典和倒排文件是分两部分存储的,词典在内存
中而倒排文件存储在磁盘上
分片,副本,映射
分片(Shards)
ES
支持PB
级全文搜索,当索引上的数据量太大的时候,ES
通过水平拆分的方式将一个索引上的数据拆分出来分配到不同的数据块上,拆分出来的数据库块称之为一个分片。
这类似于 MySQL 的分库分表,只不过 MySQL 分库分表需要借助第三方组件而 ES 内部自身实现了此功能。
在一个多分片的索引中写入数据时,通过路由来确定具体写入哪一个分片中,所以在创建索引的时候需要指定分片的数量,并且分片的数量一旦确定就不能修改。
分片的数量和下面介绍的副本数量都是可以通过创建索引时的 Settings 来配置,ES 默认为一个索引创建 5 个主分片, 并分别为每个分片创建一个副本。
PUT /myIndex
{
"settings" : {
"number_of_shards" : 5,
"number_of_replicas" : 1
}
}
ES
通过分片的功能使得索引在规模上和性能上都得到提升,每个分片都是 Lucene 中的一个索引文件,每个分片必须有一个主分片和零到多个副本
。
副本(Replicas)
副本就是对分片的 Copy
,每个主分片都有一个或多个副本分片,当主分片异常时,副本可以提供数据的查询等操作。
主分片和对应的副本分片是不会在同一个节点上的,所以副本分片数的最大值是 N-1
(其中 N 为节点数)。
对文档的新建、索引和删除请求都是写操作,必须在主分片上面完成之后才能被复制到相关的副本分片。
ES
为了提高写入的能力这个过程是并发写的,同时为了解决并发写的过程中数据冲突的问题,ES
通过乐观锁的方式控制,每个文档都有一个 _version
(版本)号,当文档被修改时版本号递增。
一旦所有的副本分片都报告写成功才会向协调节点报告成功,协调节点向客户端报告成功
从上图可以看出为了达到高可用,Master
节点会避免将主分片和副本分片放在同一个节点上。
假设这时节点 Node1
服务宕机了或者网络不可用了,那么主节点上主分片 S0
也就不可用了。幸运的是还存在另外两个节点能正常工作,这时 ES
会重新选举新的主节点,而且这两个节点上存在我们所需要的 S0
的所有数据。我们会将 S0
的副本分片提升为主分片,这个提升主分片的过程是瞬间发生的。此时集群的状态将会为 Yellow
。
为什么我们集群状态是 Yellow
而不是 Green
呢?虽然我们拥有所有的 2 个主分片,但是同时设置了每个主分片需要对应两份副本分片,而此时只存在一份副本分片。所以集群不能为 Green
的状态。
如果我们同样关闭了 Node2
,我们的程序依然可以保持在不丢失任何数据的情况下运行,因为 Node3 为每一个分片都保留着一份副本。
如果我们重新启动 Node1 ,集群可以将缺失的副本分片再次进行分配,那么集群的状态又将恢复到原来的正常状态。
如果 Node1
依然拥有着之前的分片,它将尝试去重用它们,只不过这时 Node1
节点上的分片不再是主分片而是副本分片了,如果期间有更改的数据只需要从主分片上复制修改的数据文件即可。
小结:
- 将数据分片是为了提高可处理数据的容量和易于进行水平扩展,为分片做副本是为了提高集群的稳定性和提高并发量。
- 副本是乘法,越多消耗越大,但也越保险。分片是除法,分片越多,单分片数据就越少也越分散。
- 副本越多,集群的可用性就越高,但是由于每个分片都相当于一个
Lucene
的索引文件,会占用一定的文件句柄、内存及 CPU。并且分片间的数据同步也会占用一定的网络带宽,所以索引的分片数和副本数也不是越多越好。
映射(Mapping)
映射是用于定义ES
对索引中字段的存储类型、分词方式和是否存储等信息,就像数据库中的 Schema
,描述了文档可能具有的字段或属性、每个字段的数据类型。
只不过关系型数据库建表时必须指定字段类型,而 ES
对于字段类型可以不指定然后动态对字段类型猜测,也可以在创建索引时具体指定字段的类型。
对字段类型根据数据格式自动识别的映射称之为动态映射(Dynamic Mapping
),我们创建索引时具体定义字段类型的映射称之为静态映射或显示映射(Explicit Mapping
)。
在讲解动态映射和静态映射的使用前,我们先来了解下ES
中的数据有哪些字段类型?之后我们再讲解为什么我们创建索引时需要建立静态映射而不使用动态映射。
ES(v6.8)
中字段数据类型主要有以下几类:
类别 | 数据类型 |
---|---|
核心类型 | text,keywords,long,integer,short,double,data,boolean |
复杂类型 | Object,Nested |
地理类型 | geo_point,geo_shape |
特殊类型 | ip,completion,token_count,join |
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"
}
}
}
}
}
ES机制原理
写索引原理
下图描述了 3 个节点的集群,共拥有 12 个分片,其中有 4 个主分片(S0、S1、S2、S3)和 8 个副本分片(R0、R1、R2、R3),每个主分片对应两个副本分片,节点 1 是主节点(Master
节点)负责整个集群的状态。
写索引
是只能写在主分片上
,然后同步到副本分片。这里有四个主分片,一条数据 ES 是根据什么规则写到特定分片上的呢?
这条索引数据为什么被写到 S0 上而不写到 S1 或 S2 上?那条数据为什么又被写到 S3 上而不写到 S0 上了?
首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。实际上,这个过程是根据下面这个公式决定的:
shard = hash(routing) % number_of_primary_shards
Routing
是一个可变值,默认是文档的 _id
,也可以设置成一个自定义的值。
Routing
通过 Hash
函数生成一个数字,然后这个数字再除以 number_of_primary_shards
(主分片的数量)后得到余数。这个在0
到 number_of_primary_shards-1
之间的余数,就是我们所寻求的文档所在分片的位置。
这就解释了为什么我们要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了
由于在ES
集群中每个节点通过上面的计算公式都知道集群中的文档的存放位置,所以每个节点都有处理读写请求的能力。
在一个写请求被发送到某个节点后,该节点即为前面说过的协调节点,协调节点会根据路由公式计算出需要写到哪个分片上,再将请求转发到该分片的主分片节点上
假如此时数据通过路由计算公式取余后得到的值是
shard=hash(routing)%4=0
则具体流程如下:
- 客户端向
ES1
节点(协调节点)发送写请求,通过路由计算公式得到值为0
,则当前数据应被写到主分片S0
上。 ES1
节点将请求转发到S0
主分片所在的节点ES3
,ES3
接受请求并写入到磁盘。- 并发将数据复制到两个副本分片
R0
上,其中通过乐观并发控制数据的冲突。一旦所有的副本分片都报告成功,则节点ES3
将向协调节点报告成功,协调节点向客户端报告成功。