一、分布式节点

1、client 客户端节点(协调节点)

当主节点和数据节点配置都设置为false的时候,该节点只能处理路由请求,处理搜索,分发索引操作等,从本质上来说该客户节点表现为智能负载平衡器。独立的客户端节点在一个比较大的集群中是非常有用的,协调主节点和数据节点,客户端节点加入集群可以得到集群的状态,根据集群的状态可以直接路由请求。大多数情况下不需要专用的协调节点,协调节点的功能可以由主节点或数据节点来完成,中小型集群中,专门的协调节点的并没有专用的数据节点必要。

  • 主要功能:负责任务分发和结果汇聚,分担数据节点压力。
  • 配置要点:大内存,最好是独占的机器

2. data 数据节点

数据节点主要是存储索引数据的节点,主要对文档进行增删改查操作,聚合操作等。数据节点对cpu,内存,io要求较高, 在优化的时候需要监控数据节点的状态,当资源不够的时候,需要在集群中添加新的节点。

  • 主要功能:负责数据的写入与查询,压力大。
  • 配置要点:大内存,大磁盘,最好是独占的机器。

3. maste 主节点

主资格节点的主要职责是和集群操作相关的内容,如创建或删除索引,跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点。稳定的主节点对集群的健康是非常重要的,默认情况下任何一个集群中的节点都有可能被选为主节点,索引数据和搜索查询等操作会占用大量的cpu,内存,io资源,为了确保一个集群的稳定,分离主节点和数据节点是一个比较好的选择。

  • 主要功能:维护元数据,管理集群节点状态;不负责数据写入和查询。
  • 配置要点:内存可以相对小一些,但是机器一定要稳定,最好是独占的机器。

4.Master-eligible node 候选节点

​ 可以通过选举成为Master的节点,官方推荐设置为所有的节点为master-eligible node。

5、mixed- 混合节点(不建议)

  • 主要功能:综合上述三个节点的功能,容易有单点问题。
  • 配置要点:大内存,最好是独占的机器

6. 节点选取建议

在一个生产集群中我们可以对这些节点的职责进行划分,建议集群中设置3台以上的节点作为master节点,这些节点只负责成为主节点,维护整个集群的状态。再根据数据量设置一批data节点,这些节点只负责存储数据,后期提供建立索引和查询索引的服务,这样的话如果用户请求比较频繁,这些节点的压力也会比较大,所以在集群中建议再设置一批client节点(node.master: false node.data: false),这些节点只负责处理用户请求,实现请求转发,负载均衡等功能。

7、elasticsearch master节点的选取

只有候选主节点(master:true)的节点才能成为主节点。当一个节点发现包括自己在内的多数派的master-eligible节点认为集群没有master时,就可以发起master选举。7.X之后的ES,采用一种新的选主算法,实际上是 Raft 的实现,但并非严格按照 Raft 论文实现,而是做了一些调整。

为了避免产生脑裂,ES采用了常见的分布式系统思路,保证选举出的master被多数派(quorum)的候选节点认可,以此来保证只有一个master。

cluster state是全局性信息, 包含了整个群集中所有分片的元信息(规则, 位置, 大小等信息), 并保持每个每节的信息同步,只有变化的cluster state信息才会被广播.。先根据节点的clusterStateVersion比较,clusterStateVersion越大,优先级越高。clusterStateVersion相同时,进入compareNodes,其内部按照节点的Id比较(Id为节点第一次启动时随机生成)。

  1. 当clusterStateVersion越大,优先级越高。这是为了保证新Master拥有最新的clusterState(即集群的meta),避免已经commit的meta变更丢失。因为Master当选后,就会以这个版本的clusterState为基础进行更新。(一个例外是集群全部重启,所有节点都没有meta,需要先选出一个master,然后master再通过持久化的数据进行meta恢复,再进行meta同步)。
  2. 当clusterStateVersion相同时,节点的Id越小,优先级越高。即总是倾向于选择Id小的Node,这个Id是节点第一次启动时生成的一个随机字符串。之所以这么设计,应该是为了让选举结果尽可能稳定,不要出现都想当master而选不出来的情况。
8、elasticsearch 节点主分片的选举

当主分片不可用时,es就会重新进行选举,把最新的副本分片提高到主分片的地位,这里es的master节点实现了主副本选举的逻辑。


二、节点发现和master选举

2.1 节点发现:(Zen Discovery机制)

Node启动后,首先要通过节点发现功能加入集群。ZenDiscovery是ES自己实现的一套用于节点发现和选主等功能的模块,没有依赖Zookeeper等工具。规模较大的elasticsearch集群应用存在十几个甚至几十个节点,Elasticsearch会将配置有相同的cluster.name的节点加入集群中。所有节点间通讯必须用trasport模块来完成。

集群由cluster.name设置项相同的节点自动连接而成,同一个网段中存在多个独立的集群.Zen 发现机制是ElasticSearch中默认的用来发现新节点的功能模块,而且集群启动后默认生效。Zen发现机制默认配置是用多播来寻找其它的节点。如果各个模块工作正常,该节点就会自动添加到与节点中集群名字(cluster.name)一样的集群,同时其它的节点都能感知到新节点的加入。

多播: 多播是需要看服务器是否支持的,由于其安全性,其实现在基本的云服务(比如阿里云)是不支持多播的,所以即使你开启了多播模式,你也仅仅只能找到本机上的节点。单播模式安全,也高效,但是缺点就是如果增加了一个新的机器的话,就需要每个节点上进行配置才生效了。ES 不建议生产环境使用这种方式,对于一个大规模的集群,组播会产生大量不必要的通信。组播是一对多的网络通讯方式,组播的通讯机制允许将一台主机发送的数据通过路由器和交换机将数据复制到整个组中。主机如果想接收组播数据,组播要求主机首先要加入到某个组中。组播的IP地址是D类IP地址,即224.0.0.0至239.255.255.255之间的IP地址。节点刚刚启动或者重启 ,它会发送一个多播的ping请求到网段中,该请求只是用来通知所有能连接到节点和集群它已经准备好加入到集群中。

discovery.zen.ping.multicast.address:(用于通信的网络接口)
discovery.zen.ping.multicast.port:(通信端口)
discovery.zen.ping.multicast.group:(组播消息发送的地址)
discovery.zen.ping.multicast.buffer_size:(缓冲区大小)
discovery.zen.ping.multicast.enable:(是否开启组播,默认为true,使用单播,应该关闭组播,设为false)

单播:一个节点加入一个现有集群,或者组件一个新的集群时,发送请求到一台主机。当一个节点联系到单播列表中的成员时,它就会得到整个集群所有节点的状态,然后它会联系 master 节点,并加入集群。一个节点加入一个现有集群,节点会发送一个ping请求到事先设置好的地址中,来通知集群它已经准备好加入到集群中了。当一个节点联系到单播列表中的成员时,它就会得到整个集群所有节点的状态,然后它会联系 master 节点,并加入集群。

discovery.zen.ping.unicast.hosts:(集群初始节点列表)需要提供一个主机列表作为路由列表
discovery.zen.ping.unicast.concurrent_connections:(单播发现使用的最大并发链接数,默认为10)
2.2 master选举:

选举时机: master选举是由master-eligible节点发起,Elasticsearch在满足如下时间点的时候会触发选举, 即当一个节点发现包括自己在内的多数派的master-eligible节点认为集群没有master时,就可以发起master选举。

  1. 集群启动初始化
  2. 集群的Master崩溃的时候
  3. 任何一个节点发现当前集群中的Master节点没有得到n/2 + 1节点认可的时候,触发选举.

7.x之前版本: 只有一个 Leader将当前版本的全局集群状态推送到每个节点。 ZenDiscovery(默认)过程就是这样的:

  • 每个节点计算最低的已知节点ID,并向该节点发送领导投票
  • 如果一个节点收到足够多的票数,并且该节点也为自己投票,那么它将扮演领导者的角色,开始发布集群状态。
  • 所有节点都会参数选举,并参与投票,但是,只有有资格成为 master 的节点的投票才有效.
  • 有多少选票赢得选举的定义就是所谓的法定人数。 在弹性搜索中,法定大小是一个可配置的参数。 (一般配置成:可以成为master节点数n/2+1,防止脑裂)。为了避免产生脑裂,ES采用了常见的分布式系统思路,保证选举出的master被多数派(quorum)的候选节点认可,以此来保证只有一个master。

7.x之后版本: 7.X之后的ES,采用一种新的选主算法,实际上是 Raft 的实现,但并非严格按照 Raft 论文实现,而是做了一些调整


三、倒排索引原理和FST

Lucene在对文档建立索引的时候,会给词典的所有的元素排好序,在搜索的时候直接根据二分查找的方法进行筛选就能够快速找到数据。ES希望把这个词典“搬进”内存,直接从内存读取数据不就比从磁盘读数据要快很多。问题在于对于海量的数据,索引的空间消耗十分巨大,直接搬进内存肯定不合适,所以需要进一步的处理,建立词典索引(term index)。通过词典索引可以直接找到搜索词在词典中的大致位置,然后从磁盘中取出词典数据再进行查找。所以大致的结构图就变成了这样:

在这里插入图片描述

有限状态转换器(Finite State Transducers)相当于是一个Trie前缀树,可以直接根据前缀就找到对应的term在词典中的位置。这个树并不会包含所有的term,而是很多term的前缀,通过这些前缀快速定位到这个前缀所属的磁盘的block,再从这个block去找文档列表。为了压缩词典的空间,实际上每个block都只会保存block内不同的部分,比如mop和moth在同一个以mo开头的block,那么在对应的词典里面只会保存p和th,这样空间利用率提高了一倍。
在这里插入图片描述

由于索引数量巨大,ES无法直接把全部索引放入内存,转而建立词典索引,构建有限状态转换器(FST)放入内存,进一步提高搜索效率。为了加速查询,FST 永驻堆内内存,无法被 GC 回收。用户查询时,先通过关键词(Term)查询内存中的 FST ,找到该 Term 对应的 Block 首地址。再读磁盘上的分词表,将该 Block 加载到内存,遍历该 Block ,查找到目标 Term 对应的 文档ID。 数据文档的id在词典内的空间消耗也是巨大的,ES使用了索引帧(Frame of Reference)技术压缩posting list,带来的压缩效果十分明显。

倒排词典的索引需要常驻内存,无法GC,ES的Segment Memory——本质上就是segment中加到内存的FST数据,因此segment越多,该内存越大。需要监控data node上segment memory增长趋势。


四、segment的数据结构

ES 在架构上主要分为集群、节点、索引、分片、段这五层结构。集群(cluster)包含了若干个 ES 节点,节点(node)角色分为 master 与 数据节点等;一个数据节点包含了若干个索引的部分分片数据,索引(index)可类比于关系型数据库中的库;一个索引包含若干个分片,ES 7.X 之前默认为5个分片,ES 7.X之后默认为1个分片;一个分片(shard)是一个完整的 lucene Index , 其中包含了若干个段(segment);架构图如下所示:

为了加速数据的访问,每个segment都有会一些索引数据驻留在heap里。因此segment越多,瓜分掉的heap也越多,并且这部分heap是无法被GC掉的。一个segment是一个完备的lucene倒排索引,而倒排索引是通过词典(Term Dictionary)到文档列表(Postings List)的映射关系,快速做查询的。由于词典的size会很大,全部装载到heap里不现实,因此Lucene为词典做了一层前缀索引(Term Index),这个索引在Lucene4.0以后采用的数据结构是FST (Finite State Transducer)。这种数据结构占用空间很小,Lucene打开索引的时候将其全量装载到内存中,加快磁盘上词典查询速度的同时减少随机磁盘访问次数。

段中则包含着 ES 最底层的数据结构如 倒排索引、docValue、stored field、cache 等 。

1、Inverted Index: Inverted Index就是我们常见的倒排索引, 当我们搜索的时候,首先将搜索的内容分解,然后在字典里找到对应 Term,从而查找到与搜索相关的文件内容。主要包括两部分:

  • 一个有序的数据字典 Dictionary(包括单词 Term 和它出现的频率)。
  • 与单词 Term 对应的 Postings(即存在这个单词的文件)

2、Stored Field: 本质上,Stored Fields 是一个简单的键值对 key-value。默认情况下,Stored Fields是为false的,ElasticSearch 会存储整个文件的 JSON source。哪些情形下需要显式的指定store属性呢?大多数情况并不是必须的。从_source中获取值是快速而且高效的。如果你的文档长度很长,存储 _source或者从_source中获取field的代价很大,你可以显式的将某些field的store属性设置为yes。缺点如上边所说:假设你存 储了10个field,而如果想获取这10个field的值,则需要多次的io,如果从Stored Field 中获取则只需要一次,而且_source是被压缩过 的。这个时候你可以指定一些字段store为true,这意味着这个field的数据将会被单独存储(实际上是存两份,source和 Stored Field都存了一份)。这时候,如果你要求返回field1(store:yes),es会分辨出field1已经被存储了,因此不会从_source中加载,而是从field1的存储块中加载。

3、Document Values Doc_values 本质上是一个序列化的 列式存储,这个结构非常适用于聚合(aggregations)、排序(Sorting)、脚本(scripts access to field)等操作。而且,这种存储方式也非常便于压缩,特别是数字类型。这样可以减少磁盘空间并且提高访问速度,ElasticSearch 可以将索引下某一个 Document Value 全部读取到内存中进行操作。Doc_values是存在磁盘的,在es中text类型字段默认只会建立倒排索引,其它几种类型在建立倒排索引的时候还会建立正排索引,当然es是支持自定义的。在这里这个正排索引其实就是Doc Value。


五、段合并

Elasticsearch 存储的基本单元是shard, ES中一个Index 可能分为多个shard, 事实上每个shard 都是一个Lucence 的Index,并且每个Lucence Index 由多个Segment组成, 每个Segment事实上是一些倒排索引的集合, 每次创建一个新的Document, 都会归属于一个新的Segment, 而不会去修改原来的Segment;在 Lucene 中,单个倒排索引文件被称为 Segment。Segment 是自包含的,不可变更的。 多个 Segments 汇总在一起,称为 Lucene 的 Index,其对应的就是 ES 中的 Shard,当有新文档写入时,并且执行 Refresh,就会生成一个新 Segment。 Lucene 中有一个文件,用来记录所有 Segments 信息,叫做 Commit Point。查询时会同时查询所有 Segments,并且对结果汇总。

由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。段数目太多会带来较大的麻烦。

  • 消耗资源:每一个段都会消耗文件句柄、内存和cpu运行周期;
  • 搜索变慢:每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。

ES 通过后台合并段解决这个问题。小段被合并成大段,再合并成更大的段。这时旧的文档从文件系统删除的时候,旧的段不会再复制到更大的新段中。段合并的意图是减少段的数量(通常减少到一个),来提升搜索性能。最终分成几个 segments 比较合适呢,越少越好,最好可以 force merge 成 1 个,但是,Force Merge 会占用大量的网络,IO 和 CPU。

在这里插入图片描述

合并的过程中不会中断索引和搜索。段合并在进行索引和搜索时会自动进行,合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中,这些段既可以是未提交的也可以是已提交的。合并结束后老的段会被删除,新的段被 flush 到磁盘,同时写入一个包含新段且排除旧的和较小的段的新提交点,新的段被打开可以用来搜索。段合并流程:

  • 选择一些有相似大小的segment,merge成一个大的segment
  • 将新的segment flush到磁盘上去
  • 写一个新的commit point,包括了新的segment,并且排除旧的那些segment
  • 将新的segment打开供搜索
  • 将旧的segment删除

六、ES的并发版本机制

  • 在ES中通过_version版本号的方式进行乐观锁并发控制。在es内部第次一创建document的时候,它的_version默认会是1,之后进行的删除和修改的操作_version都会增加1。可以看到删除一个document之后,再进行同一个id的document添加操作,版本号是加1而不是初始化为1,从而可以说明document并不是正真地被物理删除,它的一些版本号信息一样会存在,而是会在某个时刻一起被清除。

  • 每一次从es中拿数据的时候顺带拿到一个version,进行数据修改的时候进行version的对比,如果一致,进行修改操作,如果不一致,否则失败需要重新获取数据再进行更新操作。其优点就是不需要给数据加锁,并发能力高。缺点就是每一次的修改操作都需要进行version的对比,然后才能进行数据的修改,但可能出现多次对比不正确,对此进行数据的多次重新加载操作。


七、ES的高可用和和故障恢复

7.1 高可用

数据节点的副本机制:ES会自动进行分片均衡,且冗余副本分片和主分片不能存在于同一台节点上面。ES不允许Primary和它的Replica放在同一个节点中(为了容错),并且同一个节点不接受完全相同的两个Replica,也就是说,因为同一个节点存放两个相同的副本既不能提升吞吐量,也不能提升查询速度,容易单点故障。es可以设置多个索引的副本,副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。

7.2 故障检测和恢复

ES在工作时会运行两个检测进程即有两类心跳错误检测,一类是Master定期ping检测集群内其他的Node,另一类是集群内其他的Node定期ping检测当前集群的Master。检查的方法就是定期执行ping请求。主节点和各节点之间都会进行心跳检测,比如mater要确保各节点健康状况、是否宕机等,而子节点也要要确保master的健康状况,一旦master宕机,各子节点要重新选举新的master。这种相互间的心跳检测就是cluster的faultdetection

在这里插入图片描述

当集群选举出master之后,master节点和集群其它节点会对集群状态进行检测,master会定期和其它节点发送ping,将失活的成员移除集群,同时将最新的集群状态发布到集群中,集群成员收到最新的集群状态后会进行相应的调整,比如重新选择主分片,进行数据复制等操作。而master节点也可能会宕机,所以集群中的节点会监听Master节点进行错误检测,如果成员节点发现master连接不上,重新加入新的Master节点,如果发现当前集群中有很多节点都连不上master节点,那么会重新发起选举。

节点通过周期性ping的方式和其它节点交互确认其它节点是否存活,master节点发现其它节点宕机,master会将其它节点移除出集群,当其它节点发现master宕机,则会尝试发起选举,我们可以通过如下参数修改ping的行为

//master向其它节点ping确认是否存活的时间范围,该值默认为1s
discovery.zen.fd. ping_interval
//等待ping回复的时间,默认为30s
discovery.zen.fd.ping_timeout
//ping失败超时次数,超过该次数则认定宕机,默认值为3
discovery.zen.fd. ping_retries

在这里插入图片描述

green

所有的主分片和副本分片都已分配。你的集群是 100% 可用的。

yellow

所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。不过,你的高可用性在某种程度上被弱化。如果 更多的 分片消失,你就会丢数据了。把 yellow 想象成一个需要及时调查的警告。

red

至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。

如果是Master宕机,ES的健康检查值变为Red。第一步:Master选举(假如宕机节点是Master)

  • 脑裂:可能会产生多个Master节点
  • 解决:discovery.zen.minimum_master_nodes=N/2+1

第二步:Replica容错,新的(或者原有)Master节点会将丢失的Primary对应的某个副本提升为Primary
第三步:Master节点会尝试重启故障机器
第四步:数据同步,Master会将宕机期间丢失的数据同步到重启机器对应的分片上去
如果宕机节点不是Master,将省去Master选举的步骤。


八、ES优化机制

1、定期对不再更新的索引做optimize (ES2.0以后更改为force merge api)。这Optimze的实质是对segment file强制做合并,可以节省大量的segment memory。例如每天凌晨定时对索引做 force_merge 操作,以释放空间;

2、采取冷热分离机制,热数据存储到 SSD,提高检索效率;冷数据定期进行 shrink操作,以缩减存储

3、采取 curator 进行索引的生命周期管理,定期删除无用索引;

4、仅针对需要分词的字段,合理的设置分词器;

5、倒排词典的索引需要常驻内存,无法GC,需要监控data node上segment memory增长趋势。

6、各类缓存,field cache, filter cache, indexing cache, bulk queue等等,要设置合理的大小,并且要应该根据最坏的情况来看heap是否够用,也就是各类缓存全部占满的时候,还有heap空间可以分配给其他任务吗?避免采用clear cache等“自欺欺人”的方式来释放内存。

7、避免返回大量结果集的搜索与聚合。确实需要大量拉取数据的场景,可以采用scan & scroll api来实现。

8、对于写操作,一致性级别支持quorum/one/all,默认为quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。

9、删除不用的索引,关闭索引 (文件仍然存在于磁盘,只是释放掉内存)。需要的时候可以重新打开。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐