本文翻译自Elasticsearch官方指南的distributed document store一章。
分布式文档存储
在上一章中,我们一直在介绍索引数据和获取数据的方法。但是我们省略了很多关于数据是如何在集群中被分布(Distributed)和获取(Fetched)的技术细节。这实际上是有意为之 - 你真的不需要了解数据在ES中是如何被分布的。它能工作就足够了。
在本章中,我们将会深入到这些内部技术细节中,来帮助你了解你的数据是如何被存储在一个分布式系统中的。
路由一份文档(Document)到一个分片(Shard)
当你索引一份文档时,它会被保存到一个主要分片(Primary Shard)上。那么ES是如何知道该文档应该被保存到哪个分片上呢?当我们创建了一份新文档,ES是如何知道它究竟应该保存到分片1或者分片2上的呢?
这个过程不能是随机的,因为将来我们或许还需要获取该文档。实际上,这个过程是通过一个非常简单的公式决定的:
shard = hash(routing) % number_of_primary_shards
以上的routing的值是一个任意的字符串,它默认被设置成文档的_id字段,但是也可以被设置成其他指定的值。这个routing字符串会被传入到一个哈希函数(Hash Function)来得到一个数字,然后该数字会和索引中的主要分片数进行模运算来得到余数。这个余数的范围应该总是在0和number_of_primary_shards - 1之间,它就是一份文档被存储到的分片的号码。
这就解释了为什么索引中的主要分片数量只能在索引创建时被指定,并且将来都不能在被更改:如果主要分片数量在索引创建后改变了,那么之前的所有路由结果都会变的不正确,从而导致文档不能被正确地获取。
用户有时会认为将主要分片数量固定下来会让将来对索引的水平扩展(Scale Out)变的困难。实际上,有些技术能够让你根据需要方便地进行水平扩展。我们会在Designing for scale中介绍这些技术。
所有的文档API(get, index, delete, buli, update和mget)都接受一个routing参数,它用来定制从文档到分片的映射。一个特定的routing值能够确保所有相关文档 - 比如属于相同用户的所有文档 - 都会被存储在相同的分片上。我们会在Designing for scale中详细介绍为什么你可能会这样做。
主要分片(Primary Shard)和副本分片(Replica Shard)是如何交互的
为了解释这个问题,假设我们有一个包含3个节点(Node)的集群(Cluster)。它含有一个拥有2个主要分片的名为blogs的索引。每个主要分片有2个副本分片。拥有相同数据的两个分片绝不会被分配到同一个节点上,所以这个集群的构成可能会像下图这样:
vcC0vavH68fz0sC0zreiy821vcO/uPa92rXjyc+jrLTTtvjX9rW9t9a1o7i61NihozwvcD4KPHA+Cjxicj4KPC9wPgo8cD4KPC9wPgo8aDI+Cs7EtbW1xLS0vaijrMv30v26zcm+s/08L2gyPgo8cD48L3A+CjxwPgq0tL2oo6zL99L9us3JvrP9tcTH68fztrzKx9C0stnX9yhXcml0ZSBPcGVyYXRpb25zKaOsy/zDx7a806a4w8rXz8jU2tb30qq31sasKFByaW1hcnkgU2hhcmQpyc+zybmmzeqzyaOsyLu687LFxNyxu7+9sbS52MGqtcS4sbG+t9bGrChSZXBsaWNhIFNoYXJkKcnPoaM8L3A+CjxwPgo8aW1nIHNyYz0="https://www.cppentry.com/upload_files/article/76/1_an9er__.png" alt="\">
这个过程如上图所示。下面我们列举出图中用来完成创建,索引和删除文档的每个步骤:
- 客户端(Client)向节点1发送了一个用于创建,索引或是删除的请求。
- 节点使用该文档的_id字段来决定了它应该属于分片0。因此请求会被转发到节点3,因为分片0的主要分片目前被分配在节点3上。
- 节点3会在文档对应的主要分片上执行这个请求。如果执行成功了,那么它会将该请求并行地转发到对应副本分片所在的节点1和节点2上。一旦所有的副本分片都成功完成了该请求,那么节点3就会向请求节点(Requesting Node)报告执行成功,然后节点3就能够向客户端发送一个请求执行成功的响应了。
当客户端接收到了执行成功的响应时,在主要分片和其关联的所有副本分片中,发送的文档已经被成功更新了。至此,你的修改就完成了。
在这个过程中还存在一些可选的参数用来对此过程进行调整,可能地如以牺牲数据安全的代价来增加性能。因为ES本身已经足够快了,所以这些可选参数很少被使用,但是为了完整性还是会对它们进行解释:
replication
replication的默认值是sync。它会导致主要分片将等待副本分片上的执行成功响应,然后才会将执行成功响应发送到请求节点。 如果你将replication设置成async,那么它会导致成功响应会在请求在主要分片上成功执行后就会被发送到客户端。它仍然会将请求转发到副本分片所在的节点上,只是你将无法得知请求在副本分片上是否能成功执行。 提到这个选项的目的是为了让你不要使用它。默认的sync值能够让ES处理各种系统中的数据压力。而使用了async可能会因为发送了过多无需等待其完成的请求而让ES处于过载的状态。consistency
默认情况下,主要分片需要通过仲裁(Quorum),即确认大部分分片拷贝(分片拷贝可以使主要分片或者副本分片,两者均可)有效时,才会发起一个写操作。这样做的目的是为了防止将数据写入到网络中"错误的一侧(Wrong Side)"。仲裁的定义如下:
int( (primary + number_of_replicas) / 2 ) + 1
consistency的值可以是one(仅主要分片),all(主要分片和所有副本分片),或者是默认的quorum- 大部分分片拷贝。注意
number_of_replicas是指定在索引设置中的副本分片的数量,不是当前处于活动状态的副本分片数量。如果你在索引中指定了有3个副本分片的话,那么quorum的值就是:int( (primary + 3 replicas) / 2 ) + 1 = 3
那么当只启动了两个节点时,那么就无法满足
quorum,从而导致无法索引或者删除任何文档。timeout
如果没有足够的分片拷贝会如何呢?ES会等待,希望有更多的分片会出现。默认它会等待1分钟。如果需要可以将这个时间设置的短一些:100表示的是100毫秒,30s表示的是30秒。
NOTE 一个新的索引默认会有1个副本分片,那么为了满足
quorum则需要有两个活动的分片拷贝。但是,当ES运行在一个单一节点的集群上时,这些默认设置会阻止用户做任何有用的操作(比如索引等写操作)。为了防止这个问题,只有当number_of_replicas大于1时,quorum才需要被满足。
获取文档
文档能够通过主要分片(P