Elasticsearch:基于Apache Lucene并使用Java开发的分布式开源搜索和分析引擎。是 Elastic Stack 的核心,它集中存储您的数据。
Elastic Stack:包括 Elasticsearch、Logstash 、 Kibana 和Beats (也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。
ES是一个分布式、可扩展的、近实时的数据搜索、分析与存储引擎。支持全文搜索、结构化搜索、半结构化搜索、数据分析、地理位置和对象间关联关系搜索等功能。其底层基于Lucene,但Lucene比较复杂,面向普通应用开发者而言,易用性不是很好,同时对于目前的主流分布式架构支持也不好,所以就诞生了ES。ES使用Java编写,它的内部使用Lucene做索引与搜索,隐藏了Lucene的复杂性,面向开发者暴露了一套即使是不同编程语言也基本一致的API和client,方便大家将搜索功能快速植入到日常应用中。
Elasticsearch |
关系型数据库 |
索引(index) |
数据库(Database) |
文档类型(type) |
表(Table) |
文档(document) |
一行数据(Row) |
字段(field) |
一列数据(Column) |
映射(mapping) |
数据库的组织和结构(Schema) |
Query DSL |
SQL |
GET http:// |
select * from table |
PUT http:// |
update table |
集群(cluster)
一个Elasticsearch集群由一个或多个ES节点组成,并提供集群内所有节点的联合索引和搜索能力(所有节点共同存储数据)。一个集群被命名为唯一的名字(默认为elasticsearch),集群名称非常重要,因为节点需要通过集群的名称加入集群。
请确保在不同的环境使用不同的集群名称,否则会导致节点添加到错误的集群中。
节点(node)
一个节点是集群中的一个服务器,用来存储数据并参与集群的索引和搜索。和集群类似,节点由一个名称来标识,默认情况下,该名称是在节点启动时分配给节点的随机通用唯一标识符(UUID)。您也可以自定义任意节点的名称,节点名称对于管理工作很重要,因为通过节点名称可以确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。
一个节点可以被添加到指定名称的集群中。默认情况下,每个节点会被设置加入到名称为elasticsearch的集群中,这意味着,如果在您在网络中启动了某些节点(假设这些节点可以发现彼此),它们会自动形成并加入名称为elasticsearch的集群中。
一个集群可以拥有任意多的节点。此外,如果在您的网络中没有运行任何Elasticsearch节点,此时启动一个节点会创建一个名称为elasticsearch的单节点集群。
索引(index)
一个索引是一个拥有一些相似特征的文档的集合(相当于关系型数据库中的一个数据库)。例如,您可以拥有一个客户数据的索引,一个商品目录的索引,以及一个订单数据的索引。一个索引通常使用一个名称(所有字母必须小写)来标识,当针对这个索引的文档执行索引、搜索、更新和删除操作的时候,这个名称被用来指向索引。
类型(type)
一个类型通常是一个索引的一个逻辑分类或分区,允许在一个索引下存储不同类型的文档(相当于关系型数据库中的一张表),例如用户类型、博客类型等。目前已经不支持在一个索引下创建多个类型,并且类型概念已经在后续版本中删除,详情请参见Elasticsearch官方文档。
文档(document)
一个文档是可以被索引的基本信息单元(相当于关系型数据库中的一行数据)。例如,您可以为一个客户创建一个文档,或者为一个商品创建一个文档。文档可以用JSON格式来表示。在一个索引中,您可以存储任意多的文档,且文档必须被索引。
字段(field)
组成文档的最小单位。相当于关系型数据库中的一列数据。
映射(mapping)
用来定义一个文档以及其所包含的字段如何被存储和索引,例如在mapping中定义字段的名称和类型,以及所使用的分词器。相当于关系型数据库中的Schema。
分片(shards)
代表索引分片,Elasticsearch可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上,构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。
一个分片可以是主分片或副本分片。Elasticsearch 7.0以下版本默认为一个索引创建5个主分片,并分别为每个主分片创建1个副本分片,7.0及以上版本默认创建1个主分片和1个副本分片。两者区别如下:
分片类型 |
支持处理的请求 |
数量是否可修改 |
其他说明 |
主分片 |
支持处理查询和索引请求。 |
在创建索引时设定,设定后不可更改。 |
索引内任意一个文档都存储在一个主分片中,所以主分片的数量和大小决定着索引能够保存的最大数据量。 |
副本分片 |
支持处理查询请求,不支持处理索引请求。 |
可在任何时候添加或删除副本分片。 |
副本分片对搜索性能非常重要,主要体现在以下两个方面:
|
注意 主分片不是越多越好,因为主分片越多,Elasticsearch性能开销也会越大。
recovery
代表数据恢复或数据重新分布,Elasticsearch在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。
gateway
代表Elasticsearch索引快照的存储方式,Elasticsearch默认优先将索引存放到内存中,当内存满时再将这些索引持久化存储至本地硬盘。gateway对索引快照进行存储,当这个Elasticsearch集群关闭再重新启动时就会从gateway中读取索引备份数据。Elasticsearch支持多种类型的gateway,有本地文件系统(默认)、分布式文件系统、Hadoop的HDFS和阿里云的OSS云存储服务。
discovery.zen
代表Elasticsearch的自动发现节点机制,Elasticsearch是一个基于p2p的系统,它先通过广播寻找存在的节点,再通过多播协议进行节点之间的通信,同时也支持点对点的交互。
Transport
Transport代表Elasticsearch内部节点或集群与客户端的交互方式,默认使用TCP协议进行交互。同时,通过插件的方式集成,也支持使用HTTP协议(JSON格式)、thrift、servlet、memcached、zeroMQ等传输协议进行交互。
master |
负责保存和更新集群的一些元数据信息,之后同步到所有节点,所以每个节点都需要保存全量的元数据信息:
|
datanode |
负责实际的数据存储和查询 |
coordinator |
|
ingestor |
类似于logstash,对输入数据进行处理和转换 |
ES集群的高可用性不需要依赖于其他外部组件,例如Zookeeper、HDFS等,master节点的主备依赖于ES内部自带的选举算法,通过副本分片的方式实现了数据的备份的同时,也提高了并发查询的能力。
一般一个物理节点的缺省配置是将master和datanode两属性集为一体。对于3-5个节点的小集群来讲,通常让所有节点存储数据(datanode)和具有获得主节点(master)的资格。
专用协调节点(也称为client节点或路由节点)从数据节点中消除了聚合/查询的请求解析和最终阶段,随着集群写入以及查询负载的增大,可以通过协调节点减轻数据节点的压力,可以让数据节点更多专注于数据的写入以及查询。
ES master节点选举不需要依赖于ZK之类的外部服务,而是实现了一套自己的选举策略:
选举时机
集群启动:后台启动线程去ping集群中的节点,按照上述策略从具有master资格的节点中选举出master
现有的master离开集群:后台一直有一个线程定时ping master节点,超过一定次数没有ping成功之后,重新进行master的选举
选举策略
两张列表。master列表用来记录现在的master节点,存储0或1个节点信息。候选列表,用来存储有资格成为master但不是master的节点。
如果集群中存在master,认可该master加入集群。如果集群中不存在master,从具有master资格的节点中选id最小的节点作为master。
具体的选举流程如下:
脑裂问题是采用master-slave模式的分布式集群普遍需要关注的问题,脑裂一旦出现,会导致集群的状态出现不一致,导致数据错误甚至丢失。
ES避免脑裂的策略:过半原则,可以在ES的集群配置中添加一下配置,避免脑裂的发生:
##选举时需要的节点连接数,N为具有master资格的节点数量
discovery.zen.minimum_master_nodes=N/2+1
对于网络波动比较大的集群来说,适当增加ping的间隔时间和ping的次数,一定程度上可以增加集群的稳定性,例如:
##一个节点多久ping一次,默认1s
discovery.zen.fd.ping_interval: 5s
##等待ping返回时间,默认30s
discovery.zen.fd.ping_timeout: 30s
##ping超时重试次数,默认3次
discovery.zen.fd.ping_retries: 5
Green
所有主分片和备份分片都准备就绪,分配成功, 即使有一台机器挂了(假设一台机器实例),数据都不会丢失(但是会变成yellow状态)。
Yellow
所有主分片准备就绪,但至少一个主分片(假设是A)对应的备份分片没有就绪,此时集群处于告警状态,意味着高可用和容灾能力下降.如果刚好A所在的机器挂了,并且你只设置了一个备份(且已处于未就绪状态), 那么A的数据就会丢失(查询不完整),此时集群将变成Red状态。
Red
至少有一个主分片没有就绪(直接原因是找不到对应的备份分片成为新的主分片),此时查询的结果会出现数据丢失(不完整)。
分片(shard)
数据副本(replica)
存储架构总结
状态 |
描述 |
详情 |
初始状态 |
一个Lucene索引包含一个提交点和三个段 ES基于Lucene, ES引入了按段搜索概念,每一段(segment)本身都是一个倒排索引, 但索引在Lucene中除表示所有段的集合外, 还增加了提交点(commit point)的概念: 一个列出了所有已知段的文件。 |
|
追加日志 |
新的文档被添加到内存缓冲区并且被追加到了事务日志
|
内存缓冲区和translog具有相同的数据内容。 |
Refresh |
新的文档可被搜索
|
|
积累文档 |
随着新数据的不断进入,更多的文档被添加到in-memory buffer和追加到translog中。 随着数据进入,in-memory buffer中的数据会被定期生成segment,但Translog中会持续记录数据。 |
|
Commit/Flush |
超过固定的时间,或者translog文件过大之后,触发flush操作:
|
|
Merge |
由于每次refresh的时候,都会在文件系统缓冲区中生成一个segment,后续flush触发的时候持久化到磁盘。所以,随着数据的写入,尤其是refresh的时间设置的很短的时候,磁盘中会生成越来越多的segment。segment数目太多会带来较大的负担, 每一个segment都会消耗文件句柄、内存和cpu运行周期,更重要的是,每个搜索请求都必须轮流检查每个segment,所以segment越多,搜索也就越慢。因此,需要对这些segment进行merge操作, merge的过程大致描述如下:
|
从上面的写入流程可知,所有的segment一但生成之后,其数据是不可变的,如果要改变segment的数据,则是采用将旧的segment销毁,重新生成一个新的segment的方式来进行的。segment的不可变性有如下好处:
磁盘上的每个segment都有一个.del文件与它相关联。当发送删除请求时,该文档未被真正物理删除,而是在.del文件中标记为已删除。此文档可能仍然能被搜索到,但会从结果中过滤掉。当segment合并时,在.del文件中标记为已删除的文档不会被包括在新的segment中,在merge的时候才会真正物理删除被删除的文档。
创建新文档时,ES将为该文档分配一个版本号。对文档的每次更改都会产生一个新的版本号。当执行更新时,旧版本在.del文件中被标记为已删除,并且新版本在新的segment中写入索引。旧版本可能仍然与搜索查询匹配,但是从结果中将其过滤掉。
ES最关键的一个特性就是提供了强大的索引能力。ES索引的精髓:”一切设计都是为了提高搜索的性能“。从另一方面讲,为了提高搜索的性能,难免会牺牲某些其他方面,比如插入/更新等。
ES的设计中引入了倒排索引,这种索引比关系型数据库的B-Tree索引更快。
二叉树查找效率是logN,同时插入新的节点不必移动全部节点,所以用树型结构存储索引,能同时兼顾插入和查询的性能。因此在这个基础上,再结合磁盘的读取特性(顺序读/随机读),传统关系型数据库采用了B-Tree/B+Tree这样的数据结构:
B-Tree 和 B+Tree的区别如下:
为了提高查询的效率,减少磁盘读取次数,将多个值作为一个数组通过连续区间存放,一次读取多个数据,同时也降低树的高度。
倒排索引也找反向索引。正向索引是通过key找value,反向索引则是通过value找key。
其中,几个基本概念:
下面用一个表举个例子:
假设有个user索引,它有四个字段:分别是name,gender,age,address。其跟关系型数据库表结构如下:
ES建立的索引大致如下:
结合以上实例再来理解倒排索引的设计:
Term index补充说明。由于Term dictionary是一个已经排好序的结构,因此Term index不会存储全部的Term,只会存储部分。通过Term index可以快速定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。再加上一些压缩技术(搜索 Lucene Finite State Transducers) term index 的尺寸可以只有所有term的尺寸的几十分之一,使得用内存缓存整个term index变成可能。
ES在提高查询速度的同时,牺牲了删除和更新性能。