读写文档

介绍

每个 Elasticsearch 中的索引都会被分成多个分片,并且每个分片都有多个副本。这些副本被称作 副本组,当有文档添加或删除,副本组必须要保持同步。如果做不到这点,从一个分片读取到的数据会和其它分片读取的数据有很大差异。保持分片副本同步并提供可靠地读服务的这一过程,我们称之为数据复制模型。

译者批注:其实副本组这个描述感觉不太好,应该叫分片组,因为文中的副本组不光只副本,还有主分片

Elasticsearch 的数据复制模型是基于主备模型的,这一理论在微软的 PacificA paper 这篇论文中有深入的说明。主备模型基于一个单主分片,其他的拷贝叫做副本。主分片提供所有索引操作的服务的入口,他主要掌管数据校验确保正确的工作。一旦主分片接受的索引操作,他还负责将数据同步到其他副本中。

这一章节的目的是介绍 Elasticsearch 中关于复制模型的高阶概述,并讨论它对读写操作之间的相互作用所带来的影响。

基本的写模型

每个文档操作首先要通过路由选择副本组。通常采用文档的 ID 来路由。一旦确定了在哪个副本组执行,操作就会从内部转发到当前组的主分片。主分片负责校验操作是否合法并把它转发到组内其它分片。由于副本可能会有下线的状态(译者批注:副本未分配或副本所在节点宕机等),不要求主分片复制到所有副本中。Elasticsearch 中维护了一组分片列表记录了操作应该到哪个分片,这个列表维护在主节点中,叫做 同步分片组。正如名字中暗示的,这是一组确保能够所有的索引和删除操作并且能响应给用户的 好的 分片,主分片负责维护这组不变的(译者批注:不变的 这一描述感觉不好,因为副本数是可变的,只是主分片数是不可变的)列表并向列表中的分片复制所有的操作。

主分片遵循以下基本流程:

  1. 校验所有进入主分片的操作,并拒绝请求结构上有误的操作(例如:数字类型的字段使用对象值)。
  2. 在本地执行操作,也就是索引或删除相关文档。这一过程也会校验字段的内容,有必要的话拒绝操作(例如:一个分词字段的值太长无法存储到Lucene索引中)
  3. 将操作转发到当前 同步列表 中的每个副本中, 如果列表中包含多个副本,这一操作是并行的。
  4. 当这一操作在所有副本都执行完毕,并响应给主分片,主分片将执行完毕的结果反馈给客户端。

失败处理

当索引文档的时候,有很多情况可能导致出错 —— 磁盘损坏,节点之间无法连接,或者因为配置错误的原因导致操作尽管在主分片执行成功却在副本上执行失败了,这些情况比较少见,但是主分片需要给予回应。

在主分片失败的情况下。持有主分片的节点将会给 master 节点发送一个消息,索引操作将会等待(默认 情况下1分钟左右)master 节点从副本中提升其中一个作为新的主分片,然后操作将会被转发到新的主分片上执行。注意 master 也会监控节点健康状况并决定主动降级主分片,通常这个过程在主分片所在节点出现网络故障的时候发生。详见这里

一旦操作在主分片上执行成功,主分片需要在副本执行的时候处理潜在的失败,可能由于在副本上确实执行失败了或者由于网络故障阻止操作抵达副本所在节点(或阻止副本节点发送响应)。所有这些都会有相同的结果:同步列表 中的副本错过了这个操作。为了避免影响这个不变的列表,主分片发送请求到 master 节点,要求 master 从同步列表删除有问题的分片。master 一次只处理主分片发送的一个删除操作。注意 master 也会使另一个节点构建新的分片,从而恢复系统健康状态。

当主分片向副本转发操作时,主分片会借助副本的响应验证自己是否还是活跃的,如果主分片由于网络问题(或GC太久)被隔离在系统外,可能它意识到被降级之前他还会接收索引操作。这些来自被隔离的分片的操作将会被副本拒绝,当主分片收到了拒绝的响应,因为他不再是主分片了,之后他会去 master 中得知自己被其他副本替换了,操作将会路由到新的主分片。

如果没有副本会怎样?

没有副本的情况可能是索引配置副本数为0或者仅仅是所有副本分配失败了,这个时候主分片执行操作时不会执行额外的检验,这样肯能会有问题。另一方面主分片自己无法告知副本失败,而请求 master 替它做,意味着 master 知道主分片是唯一可用的分片,因此我们可以保证 master 不会提升其它分片为主分片,并且所有在主分片执行的操作都不会丢失。当然因为我们的数据跑在一个节点上,硬件问题可能会导致数据丢失。看这里使用缓解方案。

译者批注:说白了单节点不会存在数据一致性问题,但是也放弃了高可用

基本的读模型

Elasticsearch 中的读请求可以是 很轻量级的通过 ID 查找,也可能是比较重的复杂的占用巨大CPU的聚合搜索请求。主备模式的好处在于它让所有分片数据一致,因此,一个副本也可以用来提供搜索服务。

当节点收到读请求后,该节点将负责把请求转发到所有相关的分片上,并整理响应结果,返回给客户端。我们称这个节点叫做协调节点,下面是基本流程:

  1. 相关分片解析读请求。注意因为大多数搜索将会被发送到一个或多个索引上,它们通常需要从多个分片进行读取,每个分片代表数据的不同子集。
  2. 从每个相关的分片的分片组中选择一个活跃的分片,这个分片可以是主分片也可以是副本,默认情况下 elasticsearch 通过轮询的方式选择一个分片。
  3. 发送分片级别的读请求到被选中的分片。
  4. 聚合搜索结果响应,注意如果使用 get ID 的查找方式,因为只有一个相关分片因此这一步可以跳过。

失败处理

当一个分片响应读请求失败了,那么协调节点将选择分片组中的另一个分片并发送分片级搜索请求到这个分片上。重复多次的失败将导致没有可用分片返回结果,在某些情况下,例如 _search 请求,elasticsearch 将会尽快返回结果,尽管只有部分结果,而不是等待问题解决(响应部分结果将在响应头部的 _shard 中体现)。

一些简单的含义

下面每个基本的流程都决定了 elasticsearch 作为读写系统的是如何工作的。进一步说,因为读写请求是可以并发执行的,这两个基本的流程相互影响着,这里有一些内在的暗示:

  • 有效的读:在正常情况下,每个分片组只对每个读操作执行一次。只有在失败的时候才会在多个分片下执行同一个搜索

  • 未响应读:因为主分片首先现在本地索引然后在拷贝到其他副本,所以在收到索引响应答复之前并发读是有可能看到文档改动的。

  • 默认两个分片:这个模型可以保证容错,同时只需要维护两个分片的数据。相比基于半数以上原则的系统,其容错的最小分片数为3(译者批注:使用大于2的单数防止系统脑裂

失败

发生故障是,可能是以下情况:

  • 单个节点降低索引速度:由于主分片上每个操作都要等待同步分片组中所有副本,因此一个分片如果慢了就会导致整个分片组变慢。这就是上面提到的读效率的代价。当然单个分片的缓慢也可能会影响一个被路由到该分片上的查询速度。

  • 脏读:因故障被隔离的主分片可以对外暴露写操作,但是不会有响应。这是由于因故障而孤立的主分片只有在向它的副本发送请求或请求主节点时,才会意识到自己失联了。在这时索引操作已经写到主分片并可以被读请求并发读到了。Elasticsearch 通过每秒钟(默认) ping 主节点一次,如果无法发现主节点就拒绝索引操作这一方法来降低风险。

冰山一角

这篇文档介绍了elasticsearch对数据处理的高阶概述,当然它底层还有很多很多细节例如 集群状态发布,主节点选举,都是来保证系统正常的,文档中并没有完全涵盖已知的和重要的bug,在下面这个文档中能够让你掌握第一手es的动态 resiliency page