之前的文章中,我们已经知道如何存储数据到索引中以及如何检索它。但是我们掩盖了数据存储到集群中以及从集群中获取数据的具体实现的技术细节。
当你索引一篇文档时,它会存储到一个主分片中。但是 ElasticSearch 如何知道文档是属于哪个分片呢?当我们创建一个新的文档,它是怎么知道它是应该存储到分片1上还是分片2上?
数据存储到分片的过程是有一定规则的,并不是随机发生的,因为我们日后还需要从分片中检索出文档。数据存储过程取决于下面的公式:
shard = hash(routing) % number_of_primary_shardsRouting 值是一个任意字符串,默认为 文档的id,也可以设置为一个用户自定义的值。Routing 这个字符串通过一个 hash 函数处理,并返回一个数值,然后再除以索引中主分片的数目 number_of_primary_shards,所得的余数作为主分片的编号,取值一般在 0 到 number_of_primary_shards - 1 之间的余数范围中。通过这种方法计算出该数据是存储到哪个分片中。
这就解释了为什么主分片个数在创建索引之后就不能再更改了:如果主分片个数在创建之后可以修改,那么之前所有通过公式得到的值都会失效,之前存储的文档也可能找不到。
有的人可能认为,拥有固定数量的主分片会使以后很难对索引进行扩展。实际上,有一些技术可以让你在需要的时候很轻松的扩展。可以参阅 Designing for Scale。
所有的文档API(get , index , delete , bulk , update , 和 mget)都可以接受一个 routing 参数,来自定义文档与分片之间的映射。一个自定义的路由参数可以用来确保所有相关的文档,例如所有属于同一个用户的文档都被存储到同一个分片中。我们会在 Designing for Scale中详细讨论为什么要这样做。
假设我们有一个三个节点的集群。集群里有一个名称为 blog 的索引,有两个主分片(primary shards)。每个主分片都有两个副本。相同节点的副本不会分配到同一节点,最后如下图展示:

我们可以发送请求到集群中的任何一个节点,每个节点都有能力处理我们的请求。每个节点都知道集群中每个文档的存储位置,所以可以直接将请求转发到对应的节点上。
在下面的例子中,我们将请求都发送到节点 1 上,我们将其称为协调节点(coordinating node)。
创建,索引和删除请求都是写操作,所以必须在主分片上写操作完成之后才能被复制到相关的副本分片上。
交互过程如下图所示:

下面是成功在主分片和副本分片上创建,索引以及删除文档所必须的步骤:
在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。
有一些可选的请求参数允许您影响这个过程,可能以数据安全为代价提升性能。这些选项很少使用,因为Elasticsearch已经很快,但是为了完整起见,在这里阐述如下:
默认情况下,在尝试进行写操作之前,主分片需要规定数量(quorum)或大多数(majority)的分片拷贝 shard copies (其中分片副本可以是主分片或副本分片)。这是为了防止将数据写入网络分区的“错误的一边wrong side”。 规定数量quorum定义如下:
int( (primary + number_of_replicas) / 2 ) + 1一致性consistency值可以是one(只有主分片),all(主分片和所有的副本),或者默认值quorum,或者大多数的分片副本(The allowed values for consistency are one (just the primary shard), all (the primary and all replicas), or the default quorum, or majority, of shard copies.)。
请注意,number_of_replicas是索引设置中指定的副本数,而不是当前活跃的副本数。 如果指定索引有三个副本,则quorum将如下定义:
int( (primary + 3 replicas) / 2 ) + 1 = 3但是,如果仅启动两个节点,则活跃的分片副本不满足规定数量,您将无法对任何文档进行索引或删除。
如果没有足够的副本分片会发生什么? Elasticsearch会等待,希望更多的分片出现。默认情况下,它最多等待1分钟。 如果你需要,你可以使用 timeout 参数 使它更早终止: 100 100毫秒,30s 是30秒。
新索引默认有 1 个副本分片,这意味着为满足 规定数量 应该 需要两个活动的分片副本。 但是,这些默认的设置会阻止我们在单一节点上做任何事情。为了避免这个问题,要求只有当 number_of_replicas 大于1的时候,规定数量才会执行。
我们可以从一个主分片(primary shard)或者它们任一副本中检索文档,流程如下图:

下面是从主分片或者副本分片上检索文档所需要的一系列步骤:
对于读请求,对于每一次请求,请求节点都会选择一个不同的副本分本,达到负载均衡。通过轮询所有的副本分片。
在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的。
更新 API (Update API)融合了上面解释的两种读写模式,如下图所示:

下面是部分更新一篇文档所需要的一系列步骤:
_source 字段中的JSON,修改完毕之后试着重新索引文档到主分片(P0)上。如果有人已经修改了该文档,那么会重复步骤3,如果尝试 retry_on_conflict 次还没有成功则放弃。基于文档的复制:当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果Elasticsearch仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。
mget 和 bulk API的模式类似于单文档模式。 不同的是,协调节点知道每个文档存储在哪个分片中。 它将多文档请求分解成对每个分片的多文档请求,并将请求并行转发到每个参与节点。
一旦从每个节点接收到应答,将每个节点的响应整合到单个响应中,并返回给客户端
如下图所示:

以下是使用单个 mget 请求取回多个文档所需的步骤顺序:
bulk API,允许在单个批量请求中执行多个创建、索引、删除和更新请求,如下图所示:

bulk API 按如下步骤顺序执行:
bulk API 还可以在整个批量请求的最顶层使用 consistency 参数,以及在每个请求中的元数据中使用 routing 参数。
ElasticSearch版本: 2.x