配置分片
1、何时分片
何时进行分片是需要权衡的。通常来说,不应该过早进行分片,因为这会增加部署操作的复杂性,并迫使你做出以后难以更改的设计决策。另外,也不应该等待太长时间再分片,因为在不停止运行的情况下很难对超载的系统进行分片。
通常情况下,分片用于:
- 增加可用 RAM;
- 增加可用磁盘空间;
- 减少服务器的负载;
- 处理单个 mongod 无法承受的吞吐量。
因此,良好的监控对于决定何时需要分片非常重要。应该仔细测量这些指标。通常情况下,我们会更快遇到其中某项资源的瓶颈,因此应弄清楚首先需要为哪个瓶颈做准备,并提前制订计划,以确定何时以及如何转换副本集。
2、启动服务器
创建集群的第一步是启动所需的所有进程。如第 14 章所述,需要创建 mongos 和分片。另外,还需创建第 3 个组件,即配置服务器,这是很重要的一个部分。配置服务器是存有集群配置的普通 mongod 服务器:哪个副本集上有分片,哪个集合被分片了,以及每个块位于哪个分片上。MongoDB 3.2 引入了副本集作为配置服务器。副本集取代了配置服务器使用的原始同步机制,MongoDB3.4 则彻底删除了使用该机制的能力。
2.1、配置服务器
配置服务器是集群的大脑,保存着关于每个服务器包含哪些数据的所有元数据,因此,必须首先创建配置服务器。由于配置服务器所保存的数据非常重要,因此必须确保它 在运行时启用了日志功能,并确保它的数据存储在非临时性的驱动器上。在生产环境中,配置服务器副本集应该至少包含 3 个成员。每个配置服务器应该位于单独的物理机器上,这些机器最好是跨地理位置分布的。
配置服务器必须在任何一个 mongos 进程之前启动,因为 mongos 需要从这些进程中提取配置信息。首先,在3 台不同的机器上运行以下命令来启动配置服务器:
bash
$ mongod --configsvr --replSet configRS --bind_ip localhost,198.51.100.51 mongod
--dbpath /var/lib/mongodb
$ mongod --configsvr --replSet configRS --bind_ip localhost,198.51.100.52 mongod
--dbpath /var/lib/mongodb
$ mongod --configsvr --replSet configRS --bind_ip localhost,198.51.100.53 mongod
--dbpath /var/lib/mongodb
然后,以副本集的方式启动配置服务器。使用 mongoshell 连接到一个副本集成员:
js
$ mongo --host <hostname> --port <port>
使用 rs.initiate() 辅助函数:
js
> rs.initiate(
{
_id: "configRS",
configsvr: true,
members: [
{ _id : 0, host : "cfg1.example.net:27019" },
{ _id : 1, host : "cfg2.example.net:27019" },
{ _id : 2, host : "cfg3.example.net:27019" }
]
}
)
这里使用 configRS 作为副本集的名称。注意,这个名称会同时出现在实例化每个配置服务器时的命令行和对rs.initiate() 的调用中。
--configsvr 选项向 mongod 表明准备将其用作配置服务器。在运行此选项的服务器上,除了 config 和 admin之外,客户端(比如其他集群组件)不能对任何其他数据库写入数据。
admin 数据库包含与身份验证和授权相关的集合,以及其他以 system.* 开头的集合以供内部使用。config 数据库包含保存分片集群元数据的集合。在元数据发生变化(比如数据块迁移、数据块拆分等)时,MongoDB 会将数据写入 config 数据库。
当对配置服务器进行写入时,MongoDB 会使用 "majority" 的 writeConcern 级别。类似地,当从配置服务器读取数据时,MongoDB 会使用 "majority" 的readConcern 级别。这确保了分片集群元数据在不发生回滚的情况下才会被提交到配置服务器副本集。它还确保了只有那些不受配置服务器故障影响的元数据才能被读取。这可以确保所有 mongos 路由节点对分片集群中的数据组织方式具有一致的看法。
在资源方面,应该为配置服务器配备充分的网络和 CPU资源。它们只保存了集群中数据的一个目录,因此只需要很少的存储资源。它们应该被部署在单独的硬件上,以避免机器资源的争用。
如果所有配置服务器都丢失了,那么你必须对分片上的数据进行分析,以确定数据的位置。这是可以做到的,但过程缓慢且令人厌烦。应该经常备份配置服务器的数据。在执行任何集群维护之前,应该总是对配置服务器 进行备份。
2.2、mongos进程
在 3 个配置服务器都运行后,启动一个 mongos 进程以供应用程序进行连接。mongos 进程需要知道配置服务器的地址,因此必须使用 --configdb 选项启动 mongos
bash
$ mongos --configdb \
configRS/cfg1.example.net:27019, \
cfg2.example.net:27019,cfg3.example.net:27019 \
--bind_ip localhost,198.51.100.100 --logpath /var/log/mongos.log
默认情况下,mongos 运行在 27017 端口上。注意,不需要指定数据目录(mongos 本身没有数据,它在启动时从配置服务器加载集群配置)。确保设置了 --logpath以将 mongos 日志保存到安全的地方。
应该启动一定数量的 mongos 进程,并尽可能将其放置在靠近所有分片的位置。这样可以提高需要访问多个分片或执行分散--收集操作时的查询性能。为了确保高可用性,至少应该创建两个 mongos 进程。可以运行数十个或数百个 mongos 进程,但这会导致配置服务器上的资源争用。推荐做法是提供一个小型的路由节点池。
2.3、将副本集转换为分片
最后,可以添加分片了。这时可能有两种情况:已经存在一个副本集,或者需要从头开始。接下来我们会假设已经存在一个副本集。如果是从头开始,则可以初始化一个空的副本集,然后按照这里列出的步骤进行操作。
如果已经有一个副本集为应用程序提供服务,那么这个副本集会成为第一个分片。为了将其转换为分片,需要对成员的配置进行一些小的修改,然后告诉 mongos 如何找到包含该分片的副本集。
如果在 svr1.example.net、svr2.example.net 和svr3.example.net 上有一个名为 rs0 的副本集,则需要首先使用 mongo shell 连接到其中一个成员:
js
$ mongo srv1.example.net
然后使用 rs.status() 来确定哪个成员是主节点,哪些是从节点:
js
> rs.status()
"set" : "rs0",
"date" : ISODate("2018-11-02T20:02:16.543Z"),
"myState" : 1,
"term" : NumberLong(1),
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1478116934, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1478116934, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1478116934, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1478116934, 1),
"t" : NumberLong(1)
}
},
"members" : [
{
"_id" : 0,
"name" : "svr1.example.net:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 269,
"optime" : {
"ts" : Timestamp(1478116934, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-11-02T20:02:14Z"),
"infoMessage" : "could not find member to sync from",
"electionTime" : Timestamp(1478116933, 1),
"electionDate" : ISODate("2018-11-02T20:02:13Z"),
"configVersion" : 1,
"self" : true
},
{
"_id" : 1,
"name" : "svr2.example.net:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 14,
"optime" : {
"ts" : Timestamp(1478116934, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1478116934, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-11-02T20:02:14Z"),
"optimeDurableDate" : ISODate("2018-11-02T20:02:14Z"),
"lastHeartbeat" : ISODate("2018-11-02T20:02:15.618Z"),
"lastHeartbeatRecv" : ISODate("2018-11-02T20:02:14.866Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "m1.example.net:27017",
"configVersion" : 1
},
{
"_id" : 2,
"name" : "svr3.example.net:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 14,
"optime" : {
"ts" : Timestamp(1478116934, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1478116934, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-11-02T20:02:14Z"),
"optimeDurableDate" : ISODate("2018-11-02T20:02:14Z"),
"lastHeartbeat" : ISODate("2018-11-02T20:02:15.619Z"),
"lastHeartbeatRecv" : ISODate("2018-11-02T20:02:14.787Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "m1.example.net:27017",
"configVersion" : 1
}
],
"ok" : 1
}
从 MongoDB 3.4 开始,对于分片集群,分片的mongod 实例必须配置 --shardsvr 选项,这可以通过配置文件设置 sharding.clusterRole,或者通过命令行选项--shardsvr 进行设置。
在将副本集转换为分片的过程中,需要对副本集的每个成员都重复以上动作。为此,首先使用 --shardsvr 选项依次重新启动每个从节点,然后让主节点退位,并使用 --shardsvr 选项重新启动。
关闭从节点后,按如下方式重新启动:
js
$ mongod --replSet "rs0" --shardsvr --port 27017
--bind_ip localhost,<ip address of member>
注意,对于 --bind_ip 参数,需要为每个从节点使用正确的 IP 地址。
现在将 mongo shell 连接到主节点:
js
$ mongo m1.example.net
然后让其退位:
js
> rs.stepDown()
接着用 --shardsvr 选项重新启动这个之前的主节点:
js
$ mongod --replSet "rs0" --shardsvr --port 27017
--bind_ip localhost,<ip address of the former primary>
现在可以将副本集作为分片添加到集群中了。在 mongoshell 中通过 mongos 连接到 admin 数据库:
js
$ mongo mongos1.example.net:27017/admin
同时使用 sh.addShard() 方法向集群中添加一个分片:
js
> sh.addShard(
"rs0/svr1.example.net:27017,svr2.example.net:27017,svr3.example.net:27017" )
可以在此指定集合中的所有成员,但并非一定要这样做。mongos 可以自动检测出任何不在种子列表中的成员。如果运行 sh.status(),可以看到 MongoDB 很快列出了所有分片:
js
rs0/svr1.example.net:27017,svr2.example.net:27017,svr3.example.net:27017
集合名 rs0 被用作了这个分片的标识符。如果想删除这个分片或将数据迁移到其中,可以使用 rs0 来对其进行标识。这比使用特定的服务器名称(如svr1.example.net)要好,因为副本集的成员关系和状态可能会随着时间而改变。
将副本集作为分片添加到集群中后,就可以将应用程序从连接到副本集改为连接到 mongos 了。在添加这个分片时,mongos 会将副本集中的所有数据库注册为分片所"拥有"的数据库,因此它会将所有的查询发送到新分片上。mongos 还会像客户端库一样自动处理应用程序的故障转移,也同样会将错误返回。
在开发环境中测试一下分片主节点的故障转移,以确保应用程序能够正确处理从 mongos 返回的错误(应该和直接与主节点对话接收到的错误相同)。
在添加分片之后,必须设置所有客户端将请求发送到mongos 而不是副本集。如果一些客户端仍然直接而不是通过 mongos 向副本集发送请求,那么分片将不能正常工作。在添加分片后,将所有客户端立即切换为与mongos 交互,并设置防火墙规则,以确保它们无法直接与分片相连。
在 MongoDB 3.6 之前,可以创建一个独立的 mongod进程作为一个分片。这在 MongoDB 3.6 之后的版本中不再可行。所有分片都必须是副本集。
2.4、增加集群容量
如果需要更多的容量,则可以添加更多的分片。要添加新的空分片,可以先创建一个副本集。确保副本集与任何其 他分片具有不同的名称。初始化并拥有一个主节点后,通过 mongos 运行 addShard 命令将其添加到集群中,并指定新副本集的名称及其主机名作为种子。
如果有几个现有的副本集不是分片,那么只要没有任何同名的数据库,就可以将它们全部作为新分片添加到集群中。如果有一个带有 blog 数据库的副本集,一个带有calendar 数据库的副本集,以及一个带有 mail、tel 和music 数据库的副本集,那么可以将每个副本集作为一个分片添加到集群中,最终得到一个拥有 3 个分片和 5 个数据库的集群。不过,如果还有第四个副本集,它也有一个名为 tel 的数据库,那么 mongos 将拒绝将其添加到集群中。
2.5、数据分片
只有在明确指定了规则之后,MongoDB 才会自动对数据进行拆分。在希望对数据进行拆分时,必须明确地告知数据库和集合。假设你有一个 music 数据库,现在希望在"name" 键上对 artists 集合进行分片。首先,为数据库启用分片:
js
> sh.enableSharding("music")
对数据库分片是对其中集合进行分片的先决条件。
在数据库级别上启用了分片后,就可以运行sh.shardCollection() 来对集合进行分片了:
js
> sh.shardCollection("music.artists", {"name" : 1})
现在 artists 集合会按照 "name" 键分片。如果是对一个已经存在的集合进行分片,则必须在 "name" 字段上有索引,否则,shardCollection 调用将返回错误。如果出现了错误,则需要先创建索引(mongos 会将它建议的索引作为错误消息的一部分返回),并重新运行shardCollection 命令。
如果要分片的集合还不存在,则 mongos 会自动在片键上创建索引。
shardCollection 命令会将集合拆分成多个数据块,这些块是 MongoDB 用来移动数据的单元。一旦命令成功返回,MongoDB 就会开始在集群中的分片间均匀地分散聚合中的数据。这个过程不是瞬间完成的。对于大型集合来说,完成这一初始平衡可能需要数小时。这段时间可以通过预拆分来缩短,即在加载数据之前,预先在分片上创建数据块。之后加载的数据就会直接插入当前分片,而不再需要额外的平衡。
3、MongoDB如何追踪集群数据
每个 mongos 都必须能够根据给定的片键来找到一个文档。理论上,MongoDB 可以跟踪每个文档的位置,但对于包含数百万或数十亿个文档的集合来说,这种方式会变得难以处理。因此,MongoDB 会将文档以数据块形式进行分组,这些数据块是片键指定范围内的文档。块总是存在于分片上,因此 MongoDB 可以用一个较小的表来维护数据块跟分片的映射。
如果一个用户集合的片键是 {"age" : 1},那么某个块可能是由所有 "age" 字段在 3 和 17 之间的文档组成的。如果mongos 收到一个 {"age" : 5} 的查询,那么它就可以将该查询路由到该块所在的分片上。
当写操作发生时,一个块中的文档数量和大小可能会改变。插入操作可以使块包含更多的文档,删除操作则会使其包含更少的文档。如果这是给儿童和青少年制作的一款游戏,那么 3 岁到 17 岁的数据块可能会越来越大。大部分的用户在这个数据块上,也就是在一个单独的分片上,这在某种程度上破坏了分发数据的意义。因此,一旦一个块增长到一定的大小,MongoDB 就会自动将它分成两个更小的块。在本例中,可以将原始块拆分为一个块包含年龄从 3 岁到 11 岁的文档,另一个块包含年龄从 12 岁到17 岁的文档。注意,这两个块仍然覆盖了原始块覆盖的整个年龄范围,即 3 岁到 17 岁。随着这些新块的增长,它们可能被分割成更小的块,直到每个年龄都有一个块。
块与块之间的范围不能重叠,比如不能有 3 到 15 和 12到 17 这样的块。如果可以重叠,那么当试图查找在重叠中的年龄时(如 14),MongoDB 就必须检查两个区块。只在一个地方查找会更高效,特别是当块已经分散在整个集群中时。
一个文档总是属于且仅属于一个块。这条规则意味着,不能使用数组字段作为片键,因为 MongoDB 会为数组创建多个索引项。如果一个文档的 "age" 字段为 5, 26,83,那么这个文档最多会出现在 3 个块中。
一个常见的误解是,同一个块的数据应保存在磁盘的同一片区域中。这是不正确的:块对 mongod 如何存储集合中的数据没有影响。
3.1、块范围
每个块都是由它所包含的文档范围来描述的。新分片的集合起初只有一个块,所有文档都位于这个块中。该块的边界从负无穷到正无穷,在 shell 中显示为 $minKey 和$maxKey。
随着块的增长,MongoDB 会自动将其拆分成两个块,范围分别是从负无穷到 <some value> 和 <some value>到无穷大,其中 <some value> 在两个块中是相同的:范围较小的块包含比 <some value> 小的所有值(但不包含<some value>),范围较大的块包含 <some value> 及比它大的所有值。
举个例子可能会更直观。假设我们按照前面提到的 "age"字段进行分片。所有 "age" 在 3 和 17 之间的文档都包含在一个块中,即 3 ≤ "age" < 17。当这个块被拆分时,最终会得到两个范围:3 ≤ "age" < 12 位于一个块中,12 ≤ "age" < 17 位于另一个块中。这里的 12 被称为拆分点(split point)。
块信息存储在 config.chunks 集合中。如果查看该集合中的内容,会看到下面的文档(为清晰起见,省略了一些字段)。
js
> db.chunks.find(criteria, {"min" : 1, "max" : 1})
{
"_id" : "test.users-age_-100.0",
"min" : {"age" : -100},
"max" : {"age" : 23}
}
{
"_id" : "test.users-age_23.0",
"min" : {"age" : 23},
"max" : {"age" : 100}
}
{
"_id" : "test.users-age_100.0",
"min" : {"age" : 100},
"max" : {"age" : 1000}
}
根据以上的 config.chunks 文档,下面是一些不同文档的示例。
{"_id" : 123, "age" : 50}:这个文档位于第二个块中,因为该块包含 "age" 在23 和 100 之间的所有文档。{"_id" : 456, "age" : 100}:这个文档位于第三个块中,因为块的下边界是包含在块中的。第二个块包含 "age" : 100 之前的所有文档,但不包含 "age" 等于 100 的文档。{"_id" : 789, "age" : -101}:这个文档不会出现在上面所示的任何块中。它位于某个范围小于第一个块的数据块中。
可使用复合片键,分片范围的工作方式与按两个键排序的工作方式相同。假设在 {"username" : 1, "age" : 1} 上有一个片键,那么可能会存在如下块范围:
js
{
"_id" : "test.users-username_MinKeyage_MinKey",
"min" : {
"username" : { "$minKey" : 1 },
"age" : { "$minKey" : 1 }
},
"max" : {
"username" : "user107487",
"age" : 73
}
}
{
"_id" : "test.users-username_\"user107487\"age_73.0",
"min" : {
"username" : "user107487",
"age" : 73
},
"max" : {
"username" : "user114978",
"age" : 119
}
}
{
"_id" : "test.users-username_\"user114978\"age_119.0",
"min" : {
"username" : "user114978",
"age" : 119
},
"max" : {
"username" : "user122468",
"age" : 68
}
}
因此,mongos 可以很容易地找到拥有给定用户名(或给定用户名和年龄)的文档位于哪个块。然而,如果仅仅给定年龄,mongos 就必须检查所有(或大部分)的块。如果想将年龄查询定位到正确的块,就必须使用"相反的"片键:{"age" : 1, "user name" : 1}。有一点需要注意:片键后半部分的范围会跨越多个块。
3.2、拆分块
各个分片的主节点 mongod 进程会跟踪它们当前的块,一旦达到某个阈值,就会检查该块是否需要拆分,如图1所示。如果块确实需要拆分,那么mongod 会从配置服务器请求全局块大小配置值,然后执行块拆分并更新配置服务器上的元数据。配置服务器会创建新的块文档,并修改旧块的范围("max")。如果该块位于分片顶部,则 mongod 会请求均衡器将其移动到其他分片上。这种方式是为了防止在片键单调递增的情况下,某个分片成为"热点"。


由于拆分数据块的方法有限,因此即使某个块已经很大了,分片可能仍然无法找到任何拆分点。因为任何具有相同片键的两个文档一定会处于相同的块中,所以块只能在片键值不同的文档之间进行拆分。如果以 "age" 作为片键,则下列块可以在片键发生变化的点进行拆分:
js
{"age" : 13, "username" : "ian"}
{"age" : 13, "username" : "randolph"}
------------ // 拆分点
{"age" : 14, "username" : "randolph"}
{"age" : 14, "username" : "eric"}
{"age" : 14, "username" : "hari"}
{"age" : 14, "username" : "mathias"}
------------ // 拆分点
{"age" : 15, "username" : "greg"}
{"age" : 15, "username" : "andrew"}
分片主节点的 mongod 只要求将分片的顶部块移动到均衡器。其他块会保留在分片上,除非手动执行移动。
然而,如果块包含以下文档,则不能被拆分(除非应用程序开始插入小数表示的年龄):
js
{"age" : 12, "username" : "kevin"}
{"age" : 12, "username" : "spencer"}
{"age" : 12, "username" : "alberto"}
{"age" : 12, "username" : "tad"}
因此,拥有不同的片键值是很重要的。
如果 mongod 试图进行拆分时其中一个配置服务器停止运行了,那么 mongod 将无法更新元数据,如图所示。

在进行拆分时,所有的配置服务器都必须启动并可以访问。如果 mongod 不断收到对一个块的写请求,则它会持续尝试拆分该块并失败。只要配置服务器没有处于健康状态,拆分就无法继续进行,而所有这些拆分尝试都会拖慢 mongod 和涉及的分片(对于每次收到的写请求,都会重复图 1 到图 3 所示的过程)。mongod 反复尝试分裂某个块却无法成功的过程被称为拆分风暴(split storm)。防止拆分风暴的唯一方法是确保配置服务器尽可能正常运行。
4、均衡器
均衡器负责数据的迁移。它会定期检查分片之间是否存在不均衡,如果存在,就会对块进行迁移。在 MongoDB3.4 以上的版本中,均衡器位于配置服务器副本集的主节点成员上,而在 MongoDB 3.4 及之前的版本中,每个mongos 会偶尔扮演"均衡器"的角色。
均衡器是配置服务器副本集主节点上的后台进程,它会监视每个分片上的块数量。只有当一个分片的块数量达到特定迁移阈值时,均衡器才会被激活。
在 MongoDB 3.4 以上的版本中,可以并发执行的迁移数量增加到了每个分片一个迁移,并发迁移的最大数量是分片总数的一半。而在早期版本中,只支持全局进行一个并发迁移。
假设一些集合已经达到了阈值,则均衡器会开始对块进行迁移。它会从负载较大的分片中选择一个块,并询问该分片是否应该在迁移之前对块进行拆分。在完成必要的拆分后,就会将块迁移到具有较少块的机器上。
使用集群的应用程序不需要感知数据的迁移:所有读写请求都会被路由到旧的块上,直到迁移完成。一旦元数据被更新,任何试图访问旧位置数据的 mongos 进程都会收到一个错误。这个错误对客户端是不可见的:mongos 会默默地处理这个错误并在新的分片上重试此操作。
有时可能会在 mongos 日志中看到"unable tosetShardVersion"的信息,这是一个常见的错误。当mongos 收到这种类型的错误时,它会从配置服务器查找数据的新位置,并更新块分布表,然后重新执行之前的请求。如果成功从新位置检索到数据,则会将数据返回给客户端,就像没有发生过任何错误一样(但会在日志中打印 一条错误发生的消息)。
如果 mongos 因配置服务器不可用而无法检索到新块的位置,则它会向客户端返回一个错误。这也是让配置服务器始终处于正常运行状态非常重要的另一个原因。
5、排序规则
MongoDB 中的排序规则允许指定特定于语言的字符串比较规则。这些规则的例子包括如何比较字母和重音符号。可以对默认排序规则的集合进行分片。这里有两个要求:集合必须有一个前缀为片键的索引,此索引还必须有 {locale: "simple" } 的排序规则。
6、变更流
变更流(change stream)允许应用程序跟踪数据库中数据的实时变更。在 MongoDB 3.6 之前,这只能通过跟踪oplog 实现,是一个复杂且容易出错的操作。变更流可以为单个集合、一组集合、一个数据库乃至整个集群的所有数据变更提供订阅机制。此特性使用了聚合框架。它允许应用程序过滤特定的变更或对接收到的变更通知进行转换。在分片集群中,所有的变更流操作都必须针对mongos 发出。
跨分片集群的变更使用全局逻辑时钟来保持有序性。这保证了变更的顺序,并且对变更流通知的解析可以根据接收的顺序安全地进行。mongos 需要在收到变更通知后检查每个分片,以确保没有分片看到更近的变更。集群的活跃级别和分片的地理分布都会影响此检查的响应时间。在这种情况下,使用通知过滤器可以改善响应时间。
在分片集群中使用变更流时,有一些事项需要注意。打开变更流可以通过发出打开变更流的操作来实现。在分片集群中,这个操作必须针对 mongos 发出。如果针对开启了变更流的分片集合运行带有 multi: true 的更新操作,那么可能会出现向孤儿文档发送通知的情况。如果一个分片被删除,那么这可能会导致打开的变更流游标关闭------而且,该游标可能无法恢复。