仅插入工作负载的分布式本地写入
MongoDB 标签感知分片允许管理员通过定义分片键的范围并对其标记到一个或多个分片来控制分片集群中的数据分布。分片键
本教程使用区域、多数据中心分片集群部署和应用程序端逻辑来支持分布式本地写入,以及在副本集选举或数据中心故障事件中的高写入可用性。
在分片一个空集合或不存在集合之前定义区域和区域范围,分片集合操作将为定义的区域范围创建数据块,以及任何额外的数据块以覆盖分片键值的整个范围,并根据区域范围执行初始数据块分配。这种初始的数据块创建和分配允许快速设置区域分片。在初始分配之后,平衡器将管理未来的数据块分配。
请参阅为空或不存在集合预定义区域和区域范围的示例。
重要
场景
考虑一个高插入负载的应用,其中读取很少且相对于写入来说是低优先级的。该应用将文档写入到分片集合中,并且需要数据库的几乎恒定在线时间来支持其SLAs或SLOs。
以下是对应用写入数据库的文档格式的部分视图
{ "_id" : ObjectId("56f08c447fe58b2e96f595fa"), "message_id" : 329620, "datacenter" : "alfa", "userid" : 123, ... } { "_id" : ObjectId("56f08c447fe58b2e96f595fb"), "message_id" : 578494, "datacenter" : "bravo", "userid" : 456, ... } { "_id" : ObjectId("56f08c447fe58b2e96f595fc"), "message_id" : 689979, "datacenter" : "bravo", "userid" : 789, ... }
分片键
该集合使用{ datacenter : 1, userid : 1 }复合索引作为分片键。
每个文档中的datacenter字段允许在每个不同的数据中心值上创建一个标签范围。如果没有datacenter字段,就无法将文档与特定数据中心关联起来。
userid字段提供了相对于datacenter的分片键的高基数和低频率组件。
有关选择分片键的更多一般性说明,请参阅选择分片键。
架构
部署由两个数据中心组成,分别为 alfa 和 bravo。有两个分片,分别为 shard0000 和 shard0001。每个分片都是一个包含三个成员的 副本集。 shard0000 在 alfa 有两个成员,在 bravo 有一个 优先级 0 成员。 shard0001 在 bravo 有两个成员,在 alfa 有一个优先级 0 成员。
标签
此应用程序需要每个数据中心一个标签。每个分片根据其副本集成员的大多数位于的数据中心分配一个标签。有两个标签范围,每个数据中心一个。
alfa数据中心将大多数成员位于此数据中心的分片标记为
alfa。创建一个标签范围,
下限为
{ "datacenter" : "alfa", "userid" : MinKey },上限为
{ "datacenter" : "alfa", "userid" : MaxKey },标签为
alfa
bravo数据中心将大多数成员位于此数据中心的分片标记为
bravo。创建一个标签范围,
下限为
{ "datacenter" : "bravo", "userid" : MinKey },上限为
{ "datacenter" : "bravo", "userid" : MaxKey },标签为
bravo
注意
MinKey 和 MaxKey 是保留的特殊值,用于比较。
根据配置的标签和标签范围,mongos 将具有 datacenter : alfa 的文档路由到 alfa 数据中心,将具有 datacenter : bravo 的文档路由到 bravo 数据中心。
写入操作
如果插入或更新的文档与配置的标签范围匹配,则只能将其写入具有相关标签的分区。
MongoDB 可以将不匹配配置标签范围的文档写入集群中的任何分区。
注意
上述行为要求集群处于稳定状态,没有违反配置标签范围的分区。有关更多信息,请参阅以下关于平衡器 的部分。
平衡器
平衡器平衡器 将标记的分区迁移到适当的分区。在迁移之前,分区可能包含违反配置标签范围和标签的分区。一旦平衡完成,分区应仅包含其范围不违反其分配的标签和标签范围的分区。
添加或删除标签或标签范围可能导致分区迁移。根据数据集的大小和受影响的标签范围的数量,这些迁移可能会影响集群性能。考虑在特定的计划时间段运行您的 平衡器。有关如何设置计划窗口的教程,请参阅 安排平衡窗口。
应用程序行为
默认情况下,应用程序将写入最近的数据中心。如果本地数据中心宕机,或者在该数据中心的写入在设定的时间间隔内未被确认,应用程序将通过在尝试将文档写入数据库之前更改datacenter字段的值来切换到其他可用的数据中心。
应用程序支持写入超时。应用程序使用写入关注点为每个写入操作设置超时。
如果应用程序遇到写入或超时错误,它将修改每个文档中的datacenter字段并执行写入。这会将文档路由到其他数据中心。如果两个数据中心都宕机,则无法成功写入。请参阅解决写入失败。
应用程序会定期检查标记为“宕机”的任何数据中心的连接性。如果连接性恢复,应用程序可以继续执行正常的写入操作。
考虑到切换逻辑,以及任何用于处理数据中心之间客户端流量的负载均衡器或其他类似机制,应用程序无法预测特定文档写入的是哪个数据中心。为了确保在读取操作中不遗漏任何文档,应用程序必须通过不包括datacenter字段作为查询的一部分来执行广播查询。
尽管报告了超时错误,写入操作仍可能成功。应用程序通过尝试将文档重新写入其他数据中心来响应错误 - 这可能导致文档在两个数据中心之间重复。应用程序在读取逻辑中解决重复项。
切换逻辑
应用程序具有逻辑,如果一次或多次写入失败,或者写入在设定时间内未被确认,则会切换数据中心。应用程序根据目标数据中心的标签修改数据中心字段,以将文档指向该数据中心。
例如,尝试写入alfa数据中心的程序可能会遵循以下一般步骤
尝试写入文档,指定
datacenter : alfa。在写入超时或错误时,记录
alfa暂时不可用。尝试写入相同的文档,修改
datacenter : bravo。在写入超时或错误时,记录
bravo暂时不可用。如果
alfa和bravo都不可用,则记录并报告错误。
程序
配置分片标签
您必须连接到与目标mongos相关的分片集群,才能继续操作。您不能通过直接连接到分片副本集成员来创建标签。
为每个分片打标签。
使用alfa标签为alfa数据中心中的每个分片打标签。
sh.addShardTag("shard0000", "alfa")
使用bravo标签为bravo数据中心中的每个分片打标签。
sh.addShardTag("shard0001", "bravo")
您可以通过运行sh.status()来查看分配给任何给定分片的标签。
为每个标签定义范围。
使用sh.addTagRange()方法定义alfa数据库的范围并将其与alfa标签关联。此方法需要
目标集合的完整命名空间。
范围的包含下限。
范围的排他上限。
标签的名称。
sh.addTagRange( "<database>.<collection>", { "datacenter" : "alfa", "userid" : MinKey }, { "datacenter" : "alfa", "userid" : MaxKey }, "alfa" )
使用sh.addTagRange()方法定义bravo数据库的范围并将其与bravo标签关联。此方法需要
目标集合的完整命名空间。
范围的包含下限。
范围的排他上限。
标签的名称。
sh.addTagRange( "<database>.<collection>", { "datacenter" : "bravo", "userid" : MinKey }, { "datacenter" : "bravo", "userid" : MaxKey }, "bravo" )
《MinKey》和《MaxKey》值是用于比较的保留特殊值。《MinKey》始终比较为小于其他所有可能值,而《MaxKey》始终比较为大于其他所有可能值。配置的取值范围捕获了每个数据中心的每个用户。
审查变更。
下次运行均衡器时,它将在配置的区域中迁移分片间的数据。
一旦均衡完成,标记为《alfa》的分片应只包含具有《datacenter : alfa》的文档,而标记为《bravo》的分片应只包含具有《datacenter : bravo》的文档。
您可以通过运行sh.status()来审查数据块分布。
解决写入失败
当应用程序的默认数据中心断电或不可用时,应用程序将《datacenter》字段更改为其他数据中心。
例如,应用程序默认尝试将以下文档写入《alfa》数据中心
{ "_id" : ObjectId("56f08c447fe58b2e96f595fa"), "message_id" : 329620, "datacenter" : "alfa", "userid" : 123, ... }
如果应用程序在尝试写入时收到错误,或者写入确认时间过长,应用程序将记录数据中心不可用,并将《datacenter》字段更改为指向《bravo》数据中心。
{ "_id" : ObjectId("56f08c457fe58b2e96f595fb"), "message_id" : 329620, "datacenter" : "bravo", "userid" : 123, ... }
应用程序定期检查《alfa》数据中心的连通性。如果数据中心再次可达,应用程序可以恢复正常写入。
在读取时解决重复文档
应用程序的切换逻辑允许潜在的文档重复。当执行读取时,应用程序会在应用程序层解决任何重复的文档。
以下查询搜索 userid 为 123 的文档。请注意,虽然 userid 是分片键的一部分,但查询不包括 datacenter 字段,因此不执行定向读取操作。
db.collection.find( { "userid" : 123 } )
结果显示,具有 message_id 为 329620 的文档已插入MongoDB两次,这可能是由于延迟的写入确认造成的。
{ "_id" : ObjectId("56f08c447fe58b2e96f595fa"), "message_id" : 329620 "datacenter" : "alfa", "userid" : 123, data : {...} } { "_id" : ObjectId("56f08c457fe58b2e96f595fb"), "message_id" : 329620 "datacenter" : "bravo", "userid" : 123, ... }
应用程序可以忽略重复项,选择其中的一个文档,或者尝试修剪重复项,直到只剩下一个文档。
修剪重复项的一种方法是使用ObjectId.getTimestamp() 方法从 _id 字段提取时间戳。然后应用程序可以保留第一个插入的文档或最后一个插入的文档。这假设 _id 字段使用MongoDB的ObjectId()。
例如,对具有 ObjectId("56f08c447fe58b2e96f595fa") 的文档使用getTimestamp() 返回
ISODate("2016-03-22T00:05:24Z")
对具有 ObjectId("56f08c457fe58b2e96f595fb") 的文档使用getTimestamp() 返回
ISODate("2016-03-22T00:05:25Z")