es底层原理

分布式节点

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

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

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

data数据节点

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

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

master节点

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

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

Master-eligible node 候选节点

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

mixed混合节点(不建议)

主要功能:综合上述三个节点的功能,容易有单点的问题

大内存,独占机器

节点选举建议

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

节点选取

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

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越大,优先级越高。这是为了保证新matser拥有最新的clusterState(即集群的mate),避免已经commit的mata变更丢失。因为master当选后,就会以这个版本的cluster为基础进行更新(一个例外是集群全部重启,所有节点没有meta,需要先选出一个master,当master通过持久化的数据进行meta恢复,再进行meta同步)
  2. clusterStateVersion相同时,节点的ID越小,优先级越高。即总是倾向于选择Id小的Node,这个Id是节点第一次启动时生成的一个随机字符串。之所以这么设计,应该是为了让选举结果尽可能稳定,不要出现都想当matser而选不出来的情况
  3. 当主分片不可用时,es就会重新进行选举,把最新的副本分片提高到主分片的地位,这里es的masyer节点实现了主副本选举的逻辑

节点的发现和master选举

节点发现 Zen Discovery机制

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

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

多播

多播是需要看服务器是否支持的,由于其安全性,其实现在基本的云服务器(比如阿里云)是不支持多播的,即使开启了多播模式,你也仅仅只能找到本机上的节点。单播模式安全,也高效,但缺点就是如果增加了一个新机器的话,就需要每个节点上进行配置才生效了。多播也叫组播,指一个节点想多太机器发送请求。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节点,并加入集群

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。

raft

正常情况下集群中只有一个leader,其他节点全是follower。follower都是被动接受请求,不发送任何请求。
candidate候选人,补者,从Follower到Leader中间的状态

Raft中引入了任期(term)的概念,每个term内最多只有一个leader。term在raft算法中充当逻辑时钟的作用。服务器之间通信的时候会携带这个term,如果节点中发现消息中的term小于自己的term,则拒绝这个消息,如果大于本节点的term,则更新自己的term。如果一个condidate或者leader发现自己的任期过了,会立即回到follower状态

每个term节点最多投一票,票数对等时,该轮选举会失败

Raft选举的流程为

  • 增加当前本地的currentterm,切换到candidate状态
  • 当前节点投自己一票,并且并行给其他节点发送requestvote RPC(让大家投他)

然后等待其他节点的响应,会有如下三种结果

  • 如果接收到大多数服务器的选票,那么就变成leader。称为leader后,向其他节点发送心跳来确定自己的地位,并且阻止新的选举
  • 如果收到了别人的请求,且别人的term比自己的大,那么候选者退化为follower
  • 如果选举算法超时,再发起一轮选举

7.0之后的raft选主流程

流程

es 7.X重构了新的集群协调层,它实际上是raft的实现,但是做了一些调整

节点初始化为candidate角色,便于更快地发起选举

选举超时也没了,follower没了leader会立刻发起选举

ES实现中,后渲染不先投自己,而是直接并行发起requestvote(投票决议),这相当于后渲染有投票给其他人的机会。这样的好处是可以在一定程度上避免三个节点同时成为候选人时,都投自己,无法成功选主的情况

ES不限制每个节点在某个term上只能投一票,节点可以多投票,这样会产生选出多个的情况

  • Node2被选为主,收到的投票为Node2,Node3
  • Node3被选为主,收到的投票为Node3,Node1

对于这种情况,ES的处理是让最后当选的Leader成功,作为leader。如果收到RequestVote请求,他会无条件退出leader状态。

在本例中,Node2先被选为主,随后收到Node3的requestVote请求,那么他退出leader状态,切换为CANDIDATE(候选人),并同意发起RequestVote候选人投票。因此最终Node3成功当选Leader

这样就能更快的发起选举,减少集群的不可用时间

es允许term每个任期多投票,当票数对等时,A,B会轮流当选Leader,后当选的节点会打断当前的节点,成为最后的Leader

如果一轮选举周期内参与的节点过多,可能导致死循环

整体流程如下

  1. 节点初始化为candidate
  2. 执行preBote流程,发起预选举:如果集群中已经有leader或者更高term的version则终止
  3. 当收到的选票过半,则发起新的选举
  4. 执行RequestVote流程,发起选举:想其他节点告知自己的term和version并索要join选票
  5. 收到选票过半,当选新leader:需要发布新的cluster state:elected-as-master,告知其他节点自己是leader

如果elected-as-master元数据发布耗时超过一轮选举超时,(选举持续被新的选举打断)有可能会陷入死循环

raftelasticsearchb
投票机制每个节点只能投一票每个节点可以投多票
优点可以避免激烈的竞争能更快的发起选举,并选举成功,缩短选举停服时间
一轮内如果有多个节点发起选举,最后一个当选leader
缺点容易选举失败 (通过随时产生选举超时缓解)容易导致选举非常
1.如果一轮选举期内参与竞争的节点过多,有可能会陷入死循环
2.Leader当选之前需要发布elected-as-master任务成功才能真正当选,如果由于这种原因导致一轮选举周期没有顺利发布,那就有可能被下一轮选举的节点打断,导致最终无法当选

腾讯的优化方案

考虑异常常见,大规模场景下

如果elected-as-master发布由于网络或者磁盘原因,需要较长时间,超过一轮选举超时,那还是会导致无法选出主

优点:通过增加等待一轮选举超时,cluster.election.duration,并适当增加改参数,可确保异常场景下,也可以快速选出主,避免陷入竞争死循环

开源estencent ES
优点1.规模集群能更快选出主
2.选举耗时更稳定
1.能动态合法节点当选master
2.高压力场景下,也可以通过调节election.timeout让集群选主
缺点无法动态指定master,需要重启节点
大集群切主不可服务时间长,容易陷入竞争死循环
有一定的开发复杂度
大集群正常重启不可服务的时间3s或者持续无法选主300ms-500ms

倒排索引原理和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查询内存文件中的term对应文档的ID。数据文档的ID在词典的内的空间消耗也是巨大的,ES使用了索引帧(frame of reference)技术压缩posting list,带来的压缩效果十分显著

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

segment的数据结构

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

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

段中包含着ES最底层的数据结构 如倒排索引,docvalue,stored fieid,cache等

Inverted Index

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

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

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存储快中加载

Document Values

Doc_value本质上是一个序列化的列式存储,这个结构非常适用聚合(aggregations),排序(soirting)脚本(script access to field)等操作

而且这种存储方式也非常便于压缩,特别是数字类型。这样可以减少磁盘空间并且提高访问速度,ElasticSearch可以将索引下某一个Document value全部读取到内存中进行操作。Doc_values是存在磁盘的,在es中text类型字只会建立倒排索引,其他几种类型在建立倒排索引的时候,还会建立正排索引,当然es是支持自定义的。在这里正排索引其实就是DocValue

段合并

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

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

ES的高可用和故障恢复

高可用

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

故障检测和恢复

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选举的步骤。

分布式节点

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

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

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

data数据节点

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

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

master节点

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

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

Master-eligible node 候选节点

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

mixed混合节点(不建议)

主要功能:综合上述三个节点的功能,容易有单点的问题

大内存,独占机器

节点选举建议

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

节点选取

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

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越大,优先级越高。这是为了保证新matser拥有最新的clusterState(即集群的mate),避免已经commit的mata变更丢失。因为master当选后,就会以这个版本的cluster为基础进行更新(一个例外是集群全部重启,所有节点没有meta,需要先选出一个master,当master通过持久化的数据进行meta恢复,再进行meta同步)
  2. clusterStateVersion相同时,节点的ID越小,优先级越高。即总是倾向于选择Id小的Node,这个Id是节点第一次启动时生成的一个随机字符串。之所以这么设计,应该是为了让选举结果尽可能稳定,不要出现都想当matser而选不出来的情况
  3. 当主分片不可用时,es就会重新进行选举,把最新的副本分片提高到主分片的地位,这里es的masyer节点实现了主副本选举的逻辑

节点的发现和master选举

节点发现 Zen Discovery机制

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

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

多播

多播是需要看服务器是否支持的,由于其安全性,其实现在基本的云服务器(比如阿里云)是不支持多播的,即使开启了多播模式,你也仅仅只能找到本机上的节点。单播模式安全,也高效,但缺点就是如果增加了一个新机器的话,就需要每个节点上进行配置才生效了。多播也叫组播,指一个节点想多太机器发送请求。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节点,并加入集群

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。

raft

正常情况下集群中只有一个leader,其他节点全是follower。follower都是被动接受请求,不发送任何请求。
candidate候选人,补者,从Follower到Leader中间的状态

Raft中引入了任期(term)的概念,每个term内最多只有一个leader。term在raft算法中充当逻辑时钟的作用。服务器之间通信的时候会携带这个term,如果节点中发现消息中的term小于自己的term,则拒绝这个消息,如果大于本节点的term,则更新自己的term。如果一个condidate或者leader发现自己的任期过了,会立即回到follower状态

每个term节点最多投一票,票数对等时,该轮选举会失败

Raft选举的流程为

  • 增加当前本地的currentterm,切换到candidate状态
  • 当前节点投自己一票,并且并行给其他节点发送requestvote RPC(让大家投他)

然后等待其他节点的响应,会有如下三种结果

  • 如果接收到大多数服务器的选票,那么就变成leader。称为leader后,向其他节点发送心跳来确定自己的地位,并且阻止新的选举
  • 如果收到了别人的请求,且别人的term比自己的大,那么候选者退化为follower
  • 如果选举算法超时,再发起一轮选举

7.0之后的raft选主流程

流程

es 7.X重构了新的集群协调层,它实际上是raft的实现,但是做了一些调整

节点初始化为candidate角色,便于更快地发起选举

选举超时也没了,follower没了leader会立刻发起选举

ES实现中,后渲染不先投自己,而是直接并行发起requestvote(投票决议),这相当于后渲染有投票给其他人的机会。这样的好处是可以在一定程度上避免三个节点同时成为候选人时,都投自己,无法成功选主的情况

ES不限制每个节点在某个term上只能投一票,节点可以多投票,这样会产生选出多个的情况

  • Node2被选为主,收到的投票为Node2,Node3
  • Node3被选为主,收到的投票为Node3,Node1

对于这种情况,ES的处理是让最后当选的Leader成功,作为leader。如果收到RequestVote请求,他会无条件退出leader状态。

在本例中,Node2先被选为主,随后收到Node3的requestVote请求,那么他退出leader状态,切换为CANDIDATE(候选人),并同意发起RequestVote候选人投票。因此最终Node3成功当选Leader

这样就能更快的发起选举,减少集群的不可用时间

es允许term每个任期多投票,当票数对等时,A,B会轮流当选Leader,后当选的节点会打断当前的节点,成为最后的Leader

如果一轮选举周期内参与的节点过多,可能导致死循环

整体流程如下

  1. 节点初始化为candidate
  2. 执行preBote流程,发起预选举:如果集群中已经有leader或者更高term的version则终止
  3. 当收到的选票过半,则发起新的选举
  4. 执行RequestVote流程,发起选举:想其他节点告知自己的term和version并索要join选票
  5. 收到选票过半,当选新leader:需要发布新的cluster state:elected-as-master,告知其他节点自己是leader

如果elected-as-master元数据发布耗时超过一轮选举超时,(选举持续被新的选举打断)有可能会陷入死循环

raftelasticsearchb
投票机制每个节点只能投一票每个节点可以投多票
优点可以避免激烈的竞争能更快的发起选举,并选举成功,缩短选举停服时间
一轮内如果有多个节点发起选举,最后一个当选leader
缺点容易选举失败 (通过随时产生选举超时缓解)容易导致选举非常
1.如果一轮选举期内参与竞争的节点过多,有可能会陷入死循环
2.Leader当选之前需要发布elected-as-master任务成功才能真正当选,如果由于这种原因导致一轮选举周期没有顺利发布,那就有可能被下一轮选举的节点打断,导致最终无法当选

腾讯的优化方案

考虑异常常见,大规模场景下

如果elected-as-master发布由于网络或者磁盘原因,需要较长时间,超过一轮选举超时,那还是会导致无法选出主

优点:通过增加等待一轮选举超时,cluster.election.duration,并适当增加改参数,可确保异常场景下,也可以快速选出主,避免陷入竞争死循环

开源estencent ES
优点1.规模集群能更快选出主
2.选举耗时更稳定
1.能动态合法节点当选master
2.高压力场景下,也可以通过调节election.timeout让集群选主
缺点无法动态指定master,需要重启节点
大集群切主不可服务时间长,容易陷入竞争死循环
有一定的开发复杂度
大集群正常重启不可服务的时间3s或者持续无法选主300ms-500ms

倒排索引原理和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查询内存文件中的term对应文档的ID。数据文档的ID在词典的内的空间消耗也是巨大的,ES使用了索引帧(frame of reference)技术压缩posting list,带来的压缩效果十分显著

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

segment的数据结构

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

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

段中包含着ES最底层的数据结构 如倒排索引,docvalue,stored fieid,cache等

Inverted Index

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

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

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存储快中加载

Document Values

Doc_value本质上是一个序列化的列式存储,这个结构非常适用聚合(aggregations),排序(soirting)脚本(script access to field)等操作

而且这种存储方式也非常便于压缩,特别是数字类型。这样可以减少磁盘空间并且提高访问速度,ElasticSearch可以将索引下某一个Document value全部读取到内存中进行操作。Doc_values是存在磁盘的,在es中text类型字只会建立倒排索引,其他几种类型在建立倒排索引的时候,还会建立正排索引,当然es是支持自定义的。在这里正排索引其实就是DocValue

段合并

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

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

ES的高可用和故障恢复

高可用

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

故障检测和恢复

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选举的步骤。

Last modification:November 17, 2023
如果觉得我的文章对你有用,请随意赞赏