介绍
ES里面的每一个索引(Index)由多个shard组成,每一个shard有多个副本。这些副本被称为同步组。当增加或者删除文档时,这些副本之间必须保持同步,以便让所有副本都能包含相同的文档。如果同步失败,有可能会导致从一个副本读的结果和从另外一个副本上读的结果不一致。在shard的所有副本之间保持数据同步并同时对外提供一致的读服务,我们这样的处理过程称之为“数据副本模型。
ES的“数据复制模型”是一种“主-备”式的模型,这种模型在微软的研究论文中有专门的介绍。基于这种模型,“同步组 的所有副本中间会有一个专门的“Primary shard”角色,除了”Primary shard“之外的其余副本都被称为”replica shard“。 Primary shard是所有文档的索引操作(文档写操作)的入口点, 其负责索引操作的校验,确保这些索引操作是正确且合法的。当索引操作校验通过后, primary shard会负责把索引操作复制并传递给replica shard,让“同步组”里面所有副本会进行相同的写操作,进而保持所有副本上包含的文档是一致的。
基本写模型
根据文档的doc ID,ES首先会识别出来一个文档的索引操作应该由哪个“同步组”来执行,这个“同步组”也就是存储该文档的目的容器。在“同步组被识别出来后,该文档的索引操作就被传递给“同步组”中的当前primary shard。Primary shard 负责校验并传递同样的文档索引操作给其他的relica shard。因为replica shard有可能会因为各种可能原因造成离线,所以ES并不要求primary shard一定要复制操作给所有的replica shard。事实上,primary shard维护了一个可以接收索引操作的replica shard列表。这个列表被称为“in-sync 副本列表”, 列表数据由master node来维护。 primary shard需要把文档的索引操作复制传递给in-sync列表中每一个relica shard。
下面是primary shard处理的基本流程:
校验操作请求,如果有结构类的错误(例如,object filed 却存储了一个数字),就拒绝此请求
如果校验通过,本地先执行索引操作,例如索引或者删除文档。在本地操作过程中,也会做一些校验,如果校验失败(例如keyword字段的值太长,超过Lucene索引长度限制),也会拒绝请求。
转发索引操作给当前in-sync里面的所有replica shard。如果有多个replica shard,转发操作会并行来执行
当所有replica 都成功执行了和本地一样的索引操作,并且给primary shard 发回了成功确认,primary shard 就会发成功确认给客户端。
异常处理
在执行索引操作的过程中,每一步都可能会出现问题,比如磁盘坏了、node down掉了、node之间网络不通了、或者配置错误了等等,也许会导致索引操作在primary shard执行成功,在一个或者多个replica shard 却执行失败了。这些都是失败的情况,虽然并不是经常出现,但是primary shard必须处理各种异常的情况。
一种场景是primary shard自己出问题了,在这种情况下 ,primary shard 所在的node会发消息给master node告知这种异常情况出现了 。索引操作会等待master node选出新的primary shard并把索引操作转发给新的primary shard。当然这种等待不是无限期的,缺省会等待1分钟。当然master node也不是被动等待通知,master node会主动持续监测node的健康状态,并根据健康状态来决定是否选出新的primary shard。一种典型的情况就是当primary shard所在node 网络不通的时候,master node会认为 primary shard所在node可能down死掉了,就会选出新的primary shard,并更新in-sync 列表。
另外一种场景就是索引操作在primary shard上执行成功,但是在replica shard执行却失败了。当然被定义失败的各种情况有很多,比如索引操作在replica上的确失败了(比如replica shard所在硬盘出问题了)、primary shard和replica shard之间网络原因导致索引操作请求没有到达replica shard、或者primary shard没有收到replica shard的成功确认等等。这些复杂的情况导致的共同结果就是:primary shard 没有收到in-sync列表里面的所有replica shard的索引操作成功确认。为了解决这样的问题,primary shard 会发消息给master node ,请求master node 删除出问题的replica shard。当primary shard 收到master node 成功删除问题replica shard的确认消息时,primary shard会发索引操作成功确认给客户端。要注意的是,master node同时会通知另外一个node会构建一个新的replica shard,以保证系统处在一个健康状态。
当转发操作请求给replica shards的时候,primary shard会根据replica shard的响应情况来确认自己仍然是活跃的primary shard。某些情况下,因为网络原因,primary shard 也许已经被master node给降级了,但是primary shard还没收到这种降级通知,所以其会继续处理进来的索引操作请求。当老的primary shard把索引操作请求转发给其他replica shard时,replica shard发现请求来自于一个已经不合法的primary shard,就会拒绝请求并发响应给这个不合法的primary shard。当这个不合法的primary shard收到拒绝的响应时候,就会和master node联系,获取最新的状态信息。在从master node发回的信息中,老的primary shard发现自己已经被推翻,就会把索引操作请求转发给新的primary shard。
没有replica shard时,情况会怎么样?
因为各种原因,所有的replica shards可能都会失效。在这种情况下,primary 会自己处理索引操作,而不会等待任何外部的确认(因为没有replica shards了)。看起来似乎有点怪怪的,这主要是因为primary shards不能自己定义而只能依赖master nodereplica shards是否失败。这就意味着,master node知道primary shards 是唯一的正常能工作的副本。我们要保证master node不会把其他任何过期的shard 副本定义成新的primary,也保证发到primary shard的索引操作请求不会丢失。但是不可否认,primary shard上的物理硬件出问题了,肯定也会导致数据丢失。
基本读模型
ES中的读可以是通过ID的轻量级查找,也可以是重量级的非常消耗CPU的复杂聚合计算。ES的“主-备”模型的优雅之处在于它保持所有副本的数据一致的,这样“同步组”中的任何单个副本都可以对外提供读服务。
当集群中的一个node收到读请求时,该node负责转发读请求给相关的shards、聚合shards的响应并把响应发送给客户端。我们这样的node称之为本次请求的协调node。协调node上的基本读的处理流程如下:
根据请求,解析出来要转发的“同步组”。多数搜索需要查询多个“同步组,每个同步组”是可能包含了搜索结果的一部分数据。但是如果是根据id检索一个文档,可以根据routing算法,计算出一个包含该文档的“同步组”。
从每一个要转发“同步组”里面选出一个活跃的shard。这个shard可以是primary,也可以是replica。缺省情况下,ES使用round robin的策略在“同步组里面选择shard。
把请求转发给选出来的shards
合并各个shard的响应并发响应给客户端。要注意的是,如果根据id检索文档,因为转发shard是一个,所以就不存在合并的过程了。
异常处理
当一个shard没有成功执行读请求、转发响应给协调node时,协调node会从该shard所在的”同步组“中选择另外一个shard,然后把读请求转发给它。重复性的“失败-选择”可能会导致最后“同步组里面没有shard 可用。在某些情况下,ES更青睐快速响应,而不是搜索结果的完整性,例如_search。即使缺失了部分查询结果,ES也希望能快速响应给客户端,而不是等待问题解决(搜索结果是否完整会在响应头有体现)。
一些优雅的点
基本读、基本写流程定义了ES作为一个系统,是如何支持读和写操作的。但是实践中,读写操作可能是同时并发执行的,所以读、写流程是相互影响的。此模型有一些优雅的点,如下:
高效读
正常情况下,每次读请求操作只会被“同步组”里面shard执行一次。在异常情况下,有可能会出现“同步组”里面的多个shard执行了多次读操作,例如协调node在shard没有响应的情况下,会重新选择另外一个shard,再次发出读请求操作。
未确认读
因为primary shard首先在本地执行索引操作,然后才转发索引操作给replica shard。在索引操作完全确认之前,primary shard执行了一个读请求操作,就能读到还没确认的数据。
缺省两个副本
在一些基于投票的系统中,要实现容错,有可能最少需要的副本数是3. 但是基于ES的模型,仅维护两个副本,就可以实现容错。
失败
失败的时候,会出现下面可能的情况:
单个shard有可能会让索引操作执行的更慢
因为primary shard需要等待in-sync里面所有replica shard执行完并响应才能发确认给客户端,所以任何一个处理慢的shard都会拉低本次索引操作的处理速度。这是为了实现前面提到的高效读而付出的成本代价。当然,单个慢的shard也会拉低路由到此shard上的读请求操作处理速度,进而影响那次search(因为search是由多个shard的读请求组成)。
脏读
一个孤立的primary shard可能会暴露将不会被确认的但是已经被写到本地的数据。这是因为孤立的primary只有在发索引操作请求给replica或者跟master node时才能知道自己已经被孤立了。因为数据已经被写入本地了,如果此时有读请求过来,读请求就可能会读到刚刚已经写进去的数据。为了降低这种风险,primary 会定期(缺省每1秒)ping master node,如果发现已经找不到master了,就会拒绝索引操作。