仅插入工作负载的分布式本地写入
在本页面
MongoDB 标记感知分片允许 Management 员通过定义shard key的范围并将其标记为一个或多个分片来控制分片群集中的数据分发。
本教程使用Zones以及多数据中心分片群集部署和应用程序侧逻辑来支持分布式本地写操作,并在选择副本集或数据中心出现故障时支持高写可用性。
Important
本教程中讨论的概念需要特定的部署体系结构以及应用程序级逻辑。
这些概念要求熟悉 MongoDB sharded clusters,replica 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
部署包含两个数据中心alfa
和bravo
。有两个分片shard0000
和shard0001
。每个分片都是具有三个成员的replica set。 shard0000
在alfa
上有两个成员,在bravo
上有一个优先级 0 成员。 shard0001
在bravo
上具有两个成员,在alfa
上具有一个优先级 0 成员。
Tags
此应用程序每个数据中心需要一个标签。每个分片都有一个标签,该标签基于包含大部分副本集成员的数据中心分配给它。有两个标签范围,每个数据中心一个。
-
alfa
数据中心- 在该数据中心上具有大多数成员的标记分片为
alfa
。
- 在该数据中心上具有大多数成员的标记分片为
使用以下方法创建标签范围:
-
下限
{ "datacenter" : "alfa", "userid" : MinKey }
, -
上限
{ "datacenter" : "alfa", "userid" : MaxKey }
,以及 -
标签
alfa
-
bravo
数据中心- 在该数据中心上具有大多数成员的标记分片为
bravo
。
- 在该数据中心上具有大多数成员的标记分片为
使用以下方法创建标签范围:
-
下限
{ "datacenter" : "bravo", "userid" : MinKey }
, -
上限
{ "datacenter" : "bravo", "userid" : MaxKey }
,以及 -
标签
bravo
Note
MinKey
和MaxKey
值是保留的特殊值,用于比较
Write Operations
如果插入或更新的文档与配置的标签范围匹配,则只能将其与相关标签一起写入分片。
MongoDB 可以将与配置的标签范围不匹配的文档写入群集中的任何分片。
Note
上述行为要求群集处于稳定状态,且没有块违反配置的标签范围。有关更多信息,请参见balancer的以下部分。
Balancer
将balancer migrates标记的块发送到适当的分片。在迁移之前,碎片可能包含违反已配置标签范围和标签的块。平衡完成后,分片应仅包含其范围不违反其分配的标签和标签范围的块。
添加或删除标签或标签范围可能导致块迁移。根据数据集的大小和标签范围影响的块数,这些迁移可能会影响群集性能。考虑在特定的 sched 时段内运行balancer。有关如何设置计划窗口的教程,请参见安排平衡窗口。
Application Behavior
默认情况下,应用程序将写入最近的数据中心。如果本地数据中心已关闭,或者在设定的时间段内未确认对该数据中心的写入,则应用程序将在尝试将文档写入数据库之前,通过更改datacenter
字段的值来切换到另一个可用的数据中心。
该应用程序支持写超时。该应用程序使用Write Concern为每个写操作设置timeout。
如果应用程序遇到写或超时错误,它将修改每个文档中的datacenter
字段并执行写操作。这会将文档路由到另一个数据中心。如果两个数据中心都关闭,则写入将无法成功。参见解决写入失败。
该应用程序会定期检查与标记为“关闭”的任何数据中心的连接。如果恢复了连接,则应用程序可以 continue 执行正常的写操作。
给定交换逻辑以及适当的负载平衡器或类似机制来处理数据中心之间的 Client 端流量,应用程序无法预测给定文档将写入两个数据中心中的哪个。为了确保在读取操作中不会遗漏任何文档,应用程序必须不能执行broadcast queries,而不能*将datacenter
字段作为任何查询的一部分。
该应用程序使用read preference或nearest执行读取,以减少延迟。
尽管报告了超时错误,写操作仍可能成功。应用程序通过尝试将文档重新写入另一个数据中心来响应该错误-这可能导致文档在两个数据中心之间重复。该应用程序将重复项解析为read逻辑的一部分。
Switching Logic
如果一个或多个写入失败,或者在设定的时间段内写入不是acknowledged,则应用程序具有切换数据中心的逻辑。应用程序根据目标数据中心的tag修改datacenter
字段,以将文档导向该数据中心。
例如,尝试写入alfa
数据中心的应用程序可以遵循以下一般过程:
-
尝试写文档,指定
datacenter : alfa
。 -
写入超时或错误时,暂时记录
alfa
。 -
尝试写相同的文档,修改
datacenter : bravo
。 -
写入超时或错误时,暂时记录
bravo
。 -
如果
alfa
和bravo
都关闭,则记录并报告错误。
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"
)
MinKey
和MaxKey
值是保留的特殊值,用于比较。 MinKey
总是比较小于其他所有可能的值,而MaxKey
总是比较大于其他所有可能的值。配置的范围捕获每个datacenter
的每个用户。
查看更改。
balancer下次运行时,它会在分片上splits和migrates块,遵守标记范围和标记。
平衡完成后,标记为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的一部分。
解决读取的重复文件
应用程序的切换逻辑允许潜在的文档重复。执行读取时,应用程序将解析应用程序层上的所有重复文档。
以下查询搜索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。
例如,在文档上将getTimestamp()与ObjectId("56f08c447fe58b2e96f595fa")
一起使用将返回:
ISODate("2016-03-22T00:05:24Z")
在文档上使用getTimestamp()并与ObjectId("56f08c457fe58b2e96f595fb")
一起返回:
ISODate("2016-03-22T00:05:25Z")