Elasticsearch 对文档操作时的分片交互过程分析

路由文档到分片
  1. 文档路由到分片上:一个索引由多个分片构成,当添加(删除、修改)一个文档时,Elasticsearch就需要决定这个文档存储在哪个分片上,这个过程就称为数据路由(routing)。
  2. 路由算法:
shard = hash(routing) % number_of_primary_shards

示例:一个索引,3个 primary shard

  1. 每次增删改查时,都有一个 routing 值,默认是文档的 _id 的值。
  2. 对这个 routing 值使用 hash 函数进行计算。
  3. 计算出的值再和主分片个数取余数,余数的取值范围永远是(0 ~ number_of_primary_shards - 1)之间,文档就在对应的 shard 上。routing 值默认是文档的 _id 的值,也可以手动指定一个值,手动指定对于负载均衡以及提升批量读取的性能都有帮助。
  4. 正是这种路由机制,导致了 primary shard(主分片)的个数为什么在索引建立之后不能修改。对已有索引主分片数目的修改直接会导致路由规则出现严重问题,部分数据将无法被检索
增删改查时主分片与复制分片如何交互

假设有三个节点的集群。它包含一个叫做bblogs的索引并拥有两个主分片。每个主分片有两个复制分片。相同的分片不会放在同一个节点上,所以我们的集群是这样的:
这里写图片描述

我们能够发送请求给集群中任意一个节点。每个节点都有能力处理任意请求。每个节点都知道任意文档所在的节点,所以也可以将请求转发到需要的节点。下面的例子中,我们将发送所有请求给Node 1,这个节点我们将会称之为请求节点(requesting node)
这里写图片描述

新建、 索引与删除一个文档

新建、索引和删除请求都是写(write)操作,它们必须在主分片上成功完成才能复制到相关的复制分片上。
这里写图片描述

  1. 客户端发送了一个索引或者删除的请求给node 1。
  2. node 1通过请求中文档的 _id 值判断出该文档应该被存储在shard 0 这个分片中(node 1知道shard 0的primary shard位于node 3节点上),node 1会把这个请求转发到node 3。
  3. node 3在shard 0 的primary shard上执行请求。如果请求执行成功,它node 3将并行地将该请求发给shard 0的其余所有replica shard上,也就是存在于node 1和node 2中的replica shard。如果所有的replica shard都成功地执行了请求,那么将会向node 3回复一个成功确认,当node 3收到了所有replica shard的确认信息后,则最后向用户返回一个Success的消息。

客户端接收到成功响应的时候,文档的修改已经被应用于主分片和所有的复制分片。此时修改已经生效。

有很多可选的请求参数允许你更改这一过程。你可能想牺牲一些安全来提高性能。这一选项很少使用因为Elasticsearch已经足够快,下面将一一阐述。

replication
  • 复制默认的值是 sync(同步操作)。这将导致主分片得到复制分片的成功响应后才返回。
  • 当 replication 设置为 async(异步操作),请求在主分片上被执行后就会返回给客户端。它依旧会转发请求给复制节点,但你将不知道复制节点成功与否。
  • 该选项不建议使用。默认的sync复制允许Elasticsearch强制反馈传输。async复制可能会因为在不等待其它分片就绪的情况下发送过多的请求而使Elasticsearch过载。
consistency(写一致性原理和quorum机制)
  • 默认主分片在尝试写入时需要**规定数量(quorum)**或过半的分片(可以是主节点或复制节点)可用。这是防止数据被写入到错的网络分区。规定的数量计算公式如下:
int( (primary + number_of_replicas) / 2 ) + 1
  • consistency允许的值为one(只有一个主分片),all(所有主分片和复制分片)或者默认的quorum或过半分片。
  • 注意number_of_replicas是在索引中的的设置,用来定义复制分片的数量,而不是现在活动的复制节点的数量。如果你定义了索引有3个复制节点,那规定数量是:
int( (1 primary + 3 replicas) / 2 ) + 1 = 3
  • 但如果你只有2个节点,那你的活动分片不够规定数量,也就不能索引或删除任何文档。
timeout

当分片副本不足时会怎样?

  • Elasticsearch会等待更多的分片出现。默认等待一分钟。如果需要,你可以设置timeout参数让它终止的更早:100表示100毫秒,30s表示30秒。

注意
新索引默认有1个复制分片,这意味着为了满足**quorum(规定数量)**的要求需要两个活动的分片。当然,这个默认设置将阻止我们在单一节点集群中进行操作。为了避开这个问题,规定数量只有在number_of_replicas大于一时才生效。

局部更新文档

update API 结合了之前提到的读和写模式。
这里写图片描述

该过程可以分为四个阶段来描述:

  1. 客户端给node 1发送更新请求。
  2. 它转发请求到主分片所在节点node 3。
  3. node 3从主分片检索出文档,修改_source字段的JSON,然后在主分片上重建索引。如果有其他进程修改了文档,它以retry_on_conflict设置的次数重复步骤3,都未成功则放弃。
  4. 如果node 3成功更新文档,它同时转发文档的新版本到Node 1和Node 2上的复制节点以重建索引。当所有复制节点报告成功,node 3返回成功给请求节点,然后返回给客户端。

update API还接受 新建、索引和删除 提到的routing、replication、consistency和timout参数。

基于文档的复制
当主分片转发更改给复制分片时,并不是转发更新请求,而是转发整个文档的新版本。记住这些修改转发到复制节点是异步的,它们并不能保证到达的顺序与发送相同。如果Elasticsearch转发的仅仅是修改请求,修改的顺序可能是错误的,那得到的就是个损坏的文档。

检索文档

文档能够从主分片或任意一个复制分片被检索。
这里写图片描述

下面我们罗列在主分片或复制分片上检索一个文档必要的顺序步骤:

  1. 客户端给node 1发送get请求,node1节点就成了 coordinating node(协同节点),该节点使用路由算法算出文档所在的 primary shard;协调节点把请求转发给 primary shard ,也可以转发给 replica shard(使用轮询调度算法 [ Round-Robin Scheduling,把请求平均分配至 primary shard 和 replica shard ] ,场景在于多个相同请求进入时)。
  2. 节点使用文档的_id确定文档属于分片0。分片0对应的复制分片在三个节点上都有。此时,它转发请求到node 2。
  3. node 2返回文档(document)给node 1然后返回给客户端。

对于读请求,为了平衡负载,请求节点会为每个请求选择不同的分片——它会循环所有分片副本。

特殊情况:一个被索引的文档已经存在于主分片上却还没来得及同步到复制分片上。这时复制分片会报告文档未找到,主分片会成功返回文档。一旦索引请求成功返回给用户,文档则在主分片和复制分片都是可用的。

Query Phase & Fetch Phase

CRUD这些操作的过程中一般都是结合一些唯一的标记例如:_index,_type,以及routing的值,这就意味在执行操作的时候都是确切的知道文档在集群中的哪个node中,哪个shard中。

而检索过程往往需要更多的执行模式,因为我们并不清楚所要检索的文档具体位置所在, 它们可能存在于ES集群中个任何位置。因此,一般情况下,检索的执行不得不去询问index中的每一个shard。

但是,找到所有匹配检索的文档仅仅只是检索过程的一半,在向客户端返回一个结果列表之前,必须将各个shard发回的小片的检索结果,拼接成一个大的已排好序的汇总结果列表。正因为这个原因,检索的过程将分为查询阶段与获取阶段(Query Phase and Fetch Phase)。

Query Phase

在最初的查询过程中,查询请求会广播到index中的每一个primary shard和replica shard中,每一个shard会在本地执行检索,并建立一个优先级队列(priority queue)。这个优先级队列是一个根据文档匹配度这个指标所排序列表,列表的长度由分页参数from和size两个参数所决定。例如:

GET /_search
{
	"from": 90,
	"size": 10
}

下面从一个例子中说明这个过程:
这里写图片描述

Query Phase阶段可以再细分成3个小的子阶段:

  1. 客户端发送一个检索的请求给node 3,此时node 3会创建一个空的优先级队列并且配置好分页参数from与size。
  2. node 3将检索请求发送给该index中个每一个shard(这里的每一个意思是无论它是primary还是replica,它们的组合可以构成一个完整的index数据)。每个shard在本地执行检索,并将结果添加到本地优先级队列中。
  3. 每个shard返回本地优先级序列中所记录的_id与sort值,并发送node 3。Node 3将这些值合并到自己的本地的优先级队列中,并做全局的排序。
Fetch Phase

Query Phase主要定位了所要检索数据的具体位置,但是我们还必须取回它们才能完成整个检索过程。而Fetch Phase阶段的任务就是将这些定位好的数据内容取回并返回给客户端。

同样也用一个例子来说明这个过程:
这里写图片描述

Fetch Phase过程可以分为三个子过程来描述:

  1. node 3获取了所有待检索数据的定位之后,发送一个mget的请求给与数据相关的shard。
  2. 每个收到node 3的get请求的shard将读取相关文档_source中的内容,并将它们返回给node 3。
  3. 当node 3获取到了所有shard返回的文档后,node 3将它们合并成一条汇总的结果,返回给客户端。
多文档模式

mget和bulk API与单独的文档类似。差别是请求节点知道每个文档所在的分片。它把多文档请求拆成每个分片的对文档请求,然后转发每个参与的节点。

一旦接收到每个节点的应答,然后整理这些响应组合为一个单独的响应,最后返回给客户端。
这里写图片描述

下面我们将罗列通过一个mget请求检索多个文档的顺序步骤:

  1. 客户端向node 1发送mget请求。
  2. node 1为每个分片构建一个多条数据检索请求,然后转发到这些请求所需的主分片或复制分片上。当所有回复被接收,node 1构建响应并返回给客户端。

routing 参数可以被docs中的每个文档设置。
这里写图片描述

下面我们将罗列使用一个bulk执行多个create、index、delete和update请求的顺序步骤:

  1. 客户端向node 1发送bulk请求。
  2. node 1为每个分片构建批量请求,然后转发到这些请求所需的主分片上。
  3. 主分片一个接一个的按序执行操作。当一个操作执行完,主分片转发新文档(或者删除部分)给对应的复制节点,然后执行下一个操作。一旦所有复制节点报告所有操作已成功完成,节点就报告success给请求节点,后者(请求节点)整理响应并返回给客户端。

bulk API还可以在最上层使用replication和consistency参数,routing参数则在每个请求的元数据中使用。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值