1. 路由计算
1.1 路由解决的问题
当索引一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch
如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片 P0
还是P1
和P2
中呢?
首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。实际上,这个过程是根据下面这个公式决定的:
shard = hash(routing) % number_of_primary_shards
routing
是一个可变值,默认是文档的 id
,也可以设置成一个自定义的值。 routing
通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards
(主分片的数量)后得到余数 。这个分布在 0
到 number_of_primary_shards-1
之间的余数,就是我们所寻求的文档所在分片的位置。
这就解释了为什么我们要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。
你可能觉得由于Elasticsearch
主分片数量是固定的会使索引难以进行扩容。实际上当你需要时有很多技巧可以轻松实现扩容。
所有的文档 API
( get
、 index
、 delete
、 bulk
、 update
以及 mget
)都接受一个叫做 routing
的路由参数 ,通过这个参数我们可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档——例如所有属于同一个用户的文档——都被存储到同一个分片中。相同的路由值总是指向同一个分片。换个说法就是:“之前使用某个路由值将文档存放在特定的分片上,那么搜索时,也去相应的分片查找该文档。”
1.2 路由实战
通过路由控制Elasticsearch
,选择将文档发送到哪个主分片。此时需要指定路由参数routing
。路由参数值无关紧要,可以选择任何值。重要的是在将不同文档放到同一个分片上时,需要使用相同的值。简单地说,给不同的文档使用相同的路由参数值可以确保这些文档被索引到相同分片中。向Elasticsearch
提供路由信息有多种途径。最简单的办法是在索引文档时加一个routing URI
参数。例如:
查询时,请求会被发送至所有的分片,所以最关键的事情就是使用一个能均匀分发数据的算法,让每个分片都包含差不多数量的文档。并不希望某个分片持有99%
的数据,而另一个分片持有剩下的1%
,这样做极其低效。
2. 分片控制
索引一个问文档时,这个文档会被存储到主分片中,主分片再将数据拷贝到副本分片中,而主分片和各个副本分片都在不同的节点上,所以每个节点上都有zhangsan
这个文档数据,那我们要到哪个节点上获取这个文档数据呢?
实际上,我们可以发送请求到集群中的任一节点。 每个节点都有能力处理任意请求。 每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。 将所有的请求发送到节点 1
,我们将其称为协调节点。
但是,当发送请求的时候, 为了扩展负载,更好的做法是轮询集群中所有的节点。
3. 写文档流程
新建、索引和删除 请求都是写操作,必须在主分片上面完成之后才能被复制到相关的副本分片,如图所示:新建,索引和删除单个文档
以下是在主副分片和任何副本分片上面成功新建,索引和删除文档所需要的步骤顺序:
① 客户端向 节点 1
发送新建文档请求 (节点 1就是协调节点
)。
② 协调节点根据文档的 id
确定文档属于分片 0
(路由计算)。请求会被转发到 节点 2
,因为分片0
的主分片目前被分配在 节点 2
上。
③ 节点 2
在主分片上面执行请求写入文档。如果成功了,它将请求并行转发到 节点 1
和 节点 3
的副本分片上。一旦所有的副本分片都报告写入成功, 节点 2
将向协调节点报告成功,协调节点向客户端报告成功。
在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。
当协调节点接收到来自客户端对某个索引的写入文档请求时,该节点会根据路由算法将该文档映射到某个主分片上,然后将请求转发到该分片所在的节点。完成数据的存储后,该节点会将请求转发给该分片的其他副分片所在的节点,直到所有副分片节点全部完成写入,协调节点向客户端报告写入成功。
如图所示,一个包含3
个节点的ES
集群,假设索引中只有3
个主分片和6
个副分片,客户端向节点1
发起向索引写入一条文档的请求,在本次请求中,节点1
被称为协调节点。节点1
判断数据应该映射到哪个分片上。假设将数据映射到分片1
上,因为分片1
的主分片在节点3
上,因此节点1
把请求转发到节点3
上。节点3
接收客户端的数据并进行存储,然后把请求转发到副分片1
所在的节点1
和节点2
上,当所有副分片所在的节点全部完成存储后,协调节点也就是节点1
向客户端返回成功标志。
4. 读文档流程
可以从主分片或者从其它任意副本分片检索文档 ,如下图所示:取回单个文档
以下是从主分片或者副本分片检索文档的步骤顺序:
① 客户端向 节点 1
发送获取请求。
② 节点使用文档的 id
来确定文档属于分片 0
。分片 0
的副本分片存在于所有的三个节点上。 在这种情况下,它将请求转发到 节点 3
。
③ 节点 3
将文档返回给 节点 1
,然后将文档返回给客户端。
在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。
当协调节点接收到来自客户端的获取某个索引的某文档的请求时,协调节点会找到该文档所在的所有分片,然后根据轮询算法在主/副分片中选择一个分片并将请求转发给该分片所在的节点,该节点会将目标数据发送给协调节点,协调节点再将数据返回给客户端。
一个包含3
个节点的ES
集群,假设索引中只有3
个主分片和6
个副分片,客户端向节点1
发起向索引获取文档的请求,在本次请求中,节点1
被称为协调节点。节点1
判断数据应该映射到哪个分片上。假设将数据映射到分片1
上,分片1
有主/副两种分片,分别在节点2
、节点1
和节点3
上。假设此时协调节点的轮询算法选择的是节点3
,那么它会将请求转发到节点3
上,然后节点3
会把数据传输给协调节点,也就是节点1
,最后由节点1
向客户端返回文档数据。
在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的。
5. 更新文档流程
update API
结合了读取和写入模式。
以下是部分更新一个文档的步骤:
① 客户端向 节点 1
发送更新请求。
② 节点使用文档的 id
来确定文档属于分片 0
,它将请求转发到主分片所在的 节点 2
。
③ 节点 2
从主分片检索文档,修改 _source
字段中的 JSON
,并且尝试重新索引主分片的文档。 如果文档已经被另一个进程修改,它会重试步骤 3 ,超过 retry_on_conflict
次后放弃。
④ 如果 节点 2
成功地更新文档,它将新版本的文档并行转发到 节点 1
和 节点 3
上的副本分片,重新建立索引。 一旦所有副本分片都返回成功, 节点 2
向协调节点也返回成功,协调节点向客户端返回成功。
当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果Elasticsearch
仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。