分片

Chunk

条目 说明

修改 Chunk 默认大小

Chunk 默认大小为 64 MB,允许 Chunk 大小的范围是 1MB - 1024MB。

连接到 mongos,修改 Chunk 大小为 100 MB

use config
db.settings.save( { _id:"chunksize", value: 100 } )

配置集合分片

执行如下命令,如果集合不为空,需要基于片键创建索引。

sh.enableSharding("DB")
sh.shardCollection("DB.COLLECTION", {X: 1})

通过 admin command 也可以执行集合分片

db.adminCommand({shardCollection: "DB.COLLECTION", key: {X: 1}})

jumbo chunk

当片键是一个不变的值时,chunk 是不会分裂的,运行 jumbo.js,它会查人 1秒条文档,约 1GB,其中 accountNo 为一不变的值,运行如下命令会产生一个 jumbo chunk

sh.enableSharding("testJumbo")
sh.shardCollection("testJumbo.account", {accountNo: 1})
use testJumbo
load("jumbo.js")

Chunk 分裂

当 Chunk 的大小大于 Chunk Size 或 大于一个 Chunk 允许的最大文档数目时,Chunk 就会分裂。

执行 splits.js,它会查人 200k 条文档,约 200 MB,其中 accountNo 为片键,单调递增,构建输出提示查看 chunk 分裂情况:

sh.enableSharding("testSplits")
sh.shardCollection("testSplits.account", {accountNo: 1})
use testSplits
load("splits.js")

手动 Chunk 分裂

本部分手动进行 Chunk 分裂

创建分片集合:

sh.enableSharding("testSplitsManually")
sh.shardCollection("testSplitsManually.account", {accountNo: 1})

停止 Balancer 的运行

sh.disableBalancing("testSplitsManually.account")

执行 splitsManually.js,进行手动 Chunk 分裂

load("splitsManually.js")

查看 Chunk 分布情况

db.chunks.find({ns: "testSplitsManually.account"}, {_id: 0, min: 1, max: 1, shard: 1})
{ "min" : { "accountNo" : { "$minKey" : 1 } }, "max" : { "accountNo" : 10000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 10000 }, "max" : { "accountNo" : 20000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 20000 }, "max" : { "accountNo" : 30000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 30000 }, "max" : { "accountNo" : 40000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 40000 }, "max" : { "accountNo" : 50000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 50000 }, "max" : { "accountNo" : 60000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 60000 }, "max" : { "accountNo" : 70000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 70000 }, "max" : { "accountNo" : 80000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 80000 }, "max" : { "accountNo" : 90000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 90000 }, "max" : { "accountNo" : 100000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 100000 }, "max" : { "accountNo" : 110000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 110000 }, "max" : { "accountNo" : 120000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 120000 }, "max" : { "accountNo" : 130000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 130000 }, "max" : { "accountNo" : 140000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 140000 }, "max" : { "accountNo" : 150000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 150000 }, "max" : { "accountNo" : 160000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 160000 }, "max" : { "accountNo" : 170000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 170000 }, "max" : { "accountNo" : 180000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 180000 }, "max" : { "accountNo" : 190000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 190000 }, "max" : { "accountNo" : 200000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 200000 }, "max" : { "accountNo" : 210000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 210000 }, "max" : { "accountNo" : 220000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 220000 }, "max" : { "accountNo" : 230000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 230000 }, "max" : { "accountNo" : 240000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 240000 }, "max" : { "accountNo" : 250000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 250000 }, "max" : { "accountNo" : 260000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 260000 }, "max" : { "accountNo" : 270000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 270000 }, "max" : { "accountNo" : 280000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 280000 }, "max" : { "accountNo" : 290000 }, "shard" : "myShard_1" }
{ "min" : { "accountNo" : 290000 }, "max" : { "accountNo" : { "$maxKey" : 1 } }, "shard" : "myShard_1" }

执行 splitsManuallyLoad.js 导入数据

use testSplitsManually
load("splitsManuallyLoad.js")

启用 Balancer

sh.enableBalancing("testSplitsManually.account")

查看最终数据的分布

db.chunks.aggregate([{$match: {"ns" : "testSplitsManually.account"}}, { $group: { _id: { shard: "$shard" }, count: { $sum: 1 } } }, { $sort : { "_id.shard" : 1 } } ])
{ "_id" : { "shard" : "myShard_0" }, "count" : 15 }
{ "_id" : { "shard" : "myShard_1" }, "count" : 15 }

Chunk 常见 Troubleshooting 步骤

1. 收集 config dump
mongodump --host HOST:PORT -d config -o configdump
2. 查看所有分片的数据库
use config
db.databases.find({},{primary: 1, partitioned: 1})
{ "_id" : "testSharding", "primary" : "repl-b", "partitioned" : true }
{ "_id" : "test", "primary" : "repl-c", "partitioned" : true }
3. 查看各分片 chunk 总数
// 各分片 chunk 总数
use config
db.chunks.aggregate([{ $group: { _id: { shard: "$shard" }, count: { $sum: 1 } } }, { $sort : { "_id.shard" : 1 } } ])
{ "_id" : { "shard" : "repl-a" }, "count" : 26 }
{ "_id" : { "shard" : "repl-b" }, "count" : 25 }
{ "_id" : { "shard" : "repl-c" }, "count" : 26 }

// 某一个数据库 chunk 在各分片的分布
db.chunks.aggregate([{$match: {"ns" : "testSharding.people"}}, { $group: { _id: { shard: "$shard" }, count: { $sum: 1 } } }, { $sort : { "_id.shard" : 1 } } ])
{ "_id" : { "shard" : "repl-a" }, "count" : 25 }
{ "_id" : { "shard" : "repl-b" }, "count" : 25 }
{ "_id" : { "shard" : "repl-c" }, "count" : 25 }
4. 查看集合总数,以及分片集合的总数
db.collections.count({dropped: false})
3

db.collections.count({key: {x: 1}, dropped: false})
2

手动迁移 chunk

chunk 的移动有两种方式:Balancer 均衡器,手动,本部分说明如何手动迁移 chunk。

TODO

将新数据引导到新的分片上

通过分片集标签可以在新集合上将数据引导到新分片上。由于每天一个集合,数量较大,使用脚本完成分配工作。

// disable balancing for all namespaces
use config
db.collections.find({dropped: false}).forEach(function(coll) {
  sh.disableBalancing(coll._id);
  print(coll._id + " balancing disabled");
});

sh.addTagRange("test.people", {x: MinKey}, {x: MaxKey}, "new_shards")
sh.moveChunk("test.people", {x: 1}, "repl-b")
sh.addShardTag("repl-b", "new_shards");

//re-enable balancer for all namespaces
db.collections.find({dropped: false}).forEach(function(coll) {
  sh.enableBalancing(coll._id);
  print(coll._id + " balancing enabled");
});

导入 100 万条数据

java -jar target/bulkLoad-jar-with-dependencies.jar -u "mongodb://localhost:27017" -d test -c people -f people.json -s -n 1000000 -k x --start 0 -m

查看chunk 分布

db.chunks.aggregate([{$match: {"ns" : "test.people"}}, { $group: { _id: { shard: "$shard" }, count: { $sum: 1 } } }, { $sort : { "_id.shard" : 1 } } ])
{ "_id" : { "shard" : "repl-a" }, "count" : 1 }
{ "_id" : { "shard" : "repl-b" }, "count" : 14 }

片键选择

片键选择三原则

Shard Key 决定如何将一个集合中的文档分发到不同的分片,Shard Key 需要是一个索引的字段或索引的复合字段。将一个集合进行分片语法如下:

sh.shardCollection( <database>.<collection>, <key> )
Note
如果集合不为空,那么进行分区操作前,首先必须对 Shard Key 对应的字段创建索引,如果集合为空,那么进行分区操作时会自动创建对 Shard Key 对应的字段创建索引。

到目前可以看出,Shard Key 在集合分区中起到决定性的作用,如何确保集合分区合理高效,就必须选择最合适的 Shard Key。那么如何选择 Shard Key?选择 Shard Key 有三个指标:

  • 合理的基数(Cardinality) - 基数不能太小,如果太小,例如 Shard Key 就有三个可能的值,那么如果有超过 3 个分区,则是无意义的,基数太大也不行,虽然可以保证水平扩展,但无法高效的通过 chunk 分发

  • 合理的频率(Frequency) - 如果数据集中在某一个 Shard Key 对应的值上,那么数据无法很好的分发

  • 合理的变化(Monotonically) - 如果 Shard Key 对应的值在不停的增加或减小,那么靠近 maxKey 或 minKey 的 分区数据变得越来越多,不利于集群高效运行.

单调递增/递减片键易造成数据分配不均

单调递增或递减的片键(以下简称单调片键)因为新值总是位于整个值域的上限(递增)或下限(递减)处,造成无论哪个片拥有上限或下限处的数据块,新文档始终会被分配到那个分片上。一方面这样会造成该分片成为写入热点,无法分散写入压力。另外一方面持续对该片的写入会使数据分布极不均匀,需要依赖均衡器在后期将数据移动到其他分片达到均衡的目的。从另一个角度讲,如果数据一开始就能够分散写入到不同的分片上,则不需要通过重新搬迁来均衡数据,即省去了均衡对资源带来的额外消耗。因此,单调片键无论对性能或水平扩展都会造成不利影响,选择片键时应当避免。

常见的单调片键包括:

  • 数字序列

  • 时间

  • ObjectId

  • UUID

读写负载影响片键选择

选择分片键时,需要考虑最主要的因素是什么?明白集群的读写负载。

如果工作负载主要是写入,则不能使用单调增加/减少的片键并分发写入是至关重要的。

如果工作负载主要是读取,则需要识别最常见的查询,并确保这些查询得到分发和本地化。不使用片键的查询将被发送到所有分片。那些非目标查询不能很好地扩展,这意味着添加新的分片没有帮助,所以我们希望最小化这些。

TODO

results matching ""

    No results matching ""