仅插入工作负载的分布式本地写入

在本页面

MongoDB 标记感知分片允许 Management 员通过定义shard key的范围并将其标记为一个或多个分片来控制分片群集中的数据分发。

本教程使用Zones以及多数据中心分片群集部署和应用程序侧逻辑来支持分布式本地写操作,并在选择副本集或数据中心出现故障时支持高写可用性。

Important

本教程中讨论的概念需要特定的部署体系结构以及应用程序级逻辑。

这些概念要求熟悉 MongoDB sharded clustersreplica sets以及zones的一般行为。

本教程假定仅插入或需要大量插入的工作负载。本教程中讨论的概念和策略不适用于需要快速读取或更新的用例。

Scenario

考虑一个插入密集型应用程序,其中与写入相比,读取不频繁且优先级较低。该应用程序将文档写入分片集合,并且需要数据库保持近乎恒定的正常运行时间以支持其 SLA 或 SLO。

以下是应用程序写入数据库的文档格式的部分视图:

{
   "_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,
   ...
}

Shard Key

集合使用{ datacenter : 1, userid : 1 }复合索引作为shard key

每个文档中的datacenter字段允许在每个不同的数据中心值上创建标签范围。没有datacenter字段,将无法将文档与特定数据中心关联。

userid字段相对于datacenter为分片键提供高cardinality和低frequency的分量。

有关选择分片键的更多常规说明,请参见选择分片键

Architecture

部署包含两个数据中心alfabravo。有两个分片shard0000shard0001。每个分片都是具有三个成员的replica setshard0000alfa上有两个成员,在bravo上有一个优先级 0 成员shard0001bravo上具有两个成员,在alfa上具有一个优先级 0 成员

分片集群 Schema,以实现高可用性

Tags

此应用程序每个数据中心需要一个标签。每个分片都有一个标签,该标签基于包含大部分副本集成员的数据中心分配给它。有两个标签范围,每个数据中心一个。

  • alfa数据中心

    • 在该数据中心上具有大多数成员的标记分片为alfa

使用以下方法创建标签范围:

  • 下限{ "datacenter" : "alfa", "userid" : MinKey }

  • 上限{ "datacenter" : "alfa", "userid" : MaxKey },以及

  • 标签alfa

  • bravo数据中心

    • 在该数据中心上具有大多数成员的标记分片为bravo

使用以下方法创建标签范围:

  • 下限{ "datacenter" : "bravo", "userid" : MinKey }

  • 上限{ "datacenter" : "bravo", "userid" : MaxKey },以及

  • 标签bravo

Note

MinKeyMaxKey值是保留的特殊值,用于比较

根据配置的标记和标记范围,mongos将带有datacenter : alfa的文档路由到alfa数据中心,并将带有datacenter : bravo的文档路由到bravo数据中心。

Write Operations

如果插入或更新的文档与配置的标签范围匹配,则只能将其与相关标签一起写入分片。

MongoDB 可以将与配置的标签范围不匹配的文档写入群集中的任何分片。

Note

上述行为要求群集处于稳定状态,且没有块违反配置的标签范围。有关更多信息,请参见balancer的以下部分。

Balancer

balancer migrates标记的块发送到适当的分片。在迁移之前,碎片可能包含违反已配置标签范围和标签的块。平衡完成后,分片应仅包含其范围不违反其分配的标签和标签范围的块。

添加或删除标签或标签范围可能导致块迁移。根据数据集的大小和标签范围影响的块数,这些迁移可能会影响群集性能。考虑在特定的 sched 时段内运行balancer。有关如何设置计划窗口的教程,请参见安排平衡窗口

Application Behavior

默认情况下,应用程序将写入最近的数据中心。如果本地数据中心已关闭,或者在设定的时间段内未确认对该数据中心的写入,则应用程序将在尝试将文档写入数据库之前,通过更改datacenter字段的值来切换到另一个可用的数据中心。

该应用程序支持写超时。该应用程序使用Write Concern为每个写操作设置timeout

如果应用程序遇到写或超时错误,它将修改每个文档中的datacenter字段并执行写操作。这会将文档路由到另一个数据中心。如果两个数据中心都关闭,则写入将无法成功。参见解决写入失败

该应用程序会定期检查与标记为“关闭”的任何数据中心的连接。如果恢复了连接,则应用程序可以 continue 执行正常的写操作。

给定交换逻辑以及适当的负载平衡器或类似机制来处理数据中心之间的 Client 端流量,应用程序无法预测给定文档将写入两个数据中心中的哪个。为了确保在读取操作中不会遗漏任何文档,应用程序必须不能执行broadcast queries,而不能*将datacenter字段作为任何查询的一部分。

该应用程序使用read preferencenearest执行读取,以减少延迟。

尽管报告了超时错误,写操作仍可能成功。应用程序通过尝试将文档重新写入另一个数据中心来响应该错误-这可能导致文档在两个数据中心之间重复。该应用程序将重复项解析为read逻辑的一部分。

Switching Logic

如果一个或多个写入失败,或者在设定的时间段内写入不是acknowledged,则应用程序具有切换数据中心的逻辑。应用程序根据目标数据中心的tag修改datacenter字段,以将文档导向该数据中心。

例如,尝试写入alfa数据中心的应用程序可以遵循以下一般过程:

  • 尝试写文档,指定datacenter : alfa

  • 写入超时或错误时,暂时记录alfa

  • 尝试写相同的文档,修改datacenter : bravo

  • 写入超时或错误时,暂时记录bravo

  • 如果alfabravo都关闭,则记录并报告错误。

See 解决写入失败.

Procedure

配置分片标签

您必须连接到与目标sharded cluster关联的mongos才能 continue。您不能通过直接连接到shard副本集成员来创建标签。

标记每个分片。

使用alfa标签标记alfa数据中心中的每个碎片。

sh.addShardTag("shard0000", "alfa")

使用bravo标签标记bravo数据中心中的每个碎片。

sh.addShardTag("shard0001", "bravo")

您可以通过运行sh.status()查看分配给任何给定分片的标签。

定义每个标签的范围。

定义alfa数据库的范围,并使用sh.addTagRange()方法将其与alfa标记关联。此方法要求:

  • 目标集合的完整名称空间。

  • 范围的下限值。

  • 范围的唯一上限。

  • 标签名称。

sh.addTagRange(
  "<database>.<collection>",
  { "datacenter" : "alfa", "userid" : MinKey },
  { "datacenter" : "alfa", "userid" : MaxKey },
  "alfa"
)

定义bravo数据库的范围,并使用sh.addTagRange()方法将其与bravo标记关联。此方法要求:

  • 目标集合的完整名称空间。

  • 范围的下限值。

  • 范围的唯一上限。

  • 标签名称。

sh.addTagRange(
  "<database>.<collection>",
  { "datacenter" : "bravo", "userid" : MinKey },
  { "datacenter" : "bravo", "userid" : MaxKey },
  "bravo"
)

MinKeyMaxKey值是保留的特殊值,用于比较。 MinKey总是比较小于其他所有可能的值,而MaxKey总是比较大于其他所有可能的值。配置的范围捕获每个datacenter的每个用户。

查看更改。

balancer下次运行时,它会在分片上splitsmigrates块,遵守标记范围和标记。

平衡完成后,标记为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数据中心的连接性。如果数据中心再次可以访问,则应用程序可以恢复正常写入。

Note

原始写入datacenter : alfa的成功很可能,特别是如果错误与timeout有关。如果是这样,现在可以在两个数据中心之间复制带有message_id : 329620的文档。应用程序必须将重复项解析为read operations的一部分。

解决读取的重复文件

应用程序的切换逻辑允许潜在的文档重复。执行读取时,应用程序将解析应用程序层上的所有重复文档。

以下查询搜索userid123的文档。请注意,虽然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

例如,在文档上将getTimestamp()ObjectId("56f08c447fe58b2e96f595fa")一起使用将返回:

ISODate("2016-03-22T00:05:24Z")

在文档上使用getTimestamp()并与ObjectId("56f08c457fe58b2e96f595fb")一起返回:

ISODate("2016-03-22T00:05:25Z")