MongoDB分片:逐步实践指南

分片是将大规模数据集分成多个较小数据集的过程,分布在多个mongodb实例中的分布式环境中。

什么是分片?

mongodb分片为我们提供了一种可扩展的解决方案,将大量数据存储在多台服务器上,而不是存储在单台服务器上。

从实际角度来看,在单个机器上存储指数增长的数据是不可行的。查询存储在单个服务器上的大量数据可能导致资源利用率高和读写吞吐量不理想。

基本上,存在两种扩展方法来处理系统中不断增长的数据:

  • 垂直扩展
  • 水平扩展

垂直扩展通过添加更强大的处理器、升级ram或添加更多磁盘空间来提升单个服务器性能。但在实际使用案例中,应用垂直扩展可能存在技术和硬件配置方面的潜在影响。

水平扩展通过添加更多服务器并将负载分布在多个服务器上来实现。由于每台机器将处理整个数据集的子集,因此它提供了比部署高端硬件更高的效率和成本效益的解决方案。但它需要在大量服务器上进行复杂基础设施的额外维护。

mongodb分片使用水平扩展技术。

分片组件

为了在mongodb中实现分片,需要以下组件:

分片是处理原始数据子集的mongo实例。分片需要部署在副本集中。

mongos是一个mongo实例,充当客户端应用程序和分片集群之间的接口。它作为查询路由器到分片。

配置服务器是存储集群的元数据信息和配置细节的mongo实例。mongodb要求将配置服务器部署为副本集。

分片架构

mongodb集群由多个副本集组成。

每个副本集由至少3个或更多的mongo实例组成。分片集群可以由多个mongo分片实例组成,每个分片实例在一个分片副本集内工作。应用程序与mongos交互,后者再与分片通信。因此,在分片中,应用程序永远不会直接与分片节点交互。查询路由器根据分片键将数据子集分配给分片节点。

分片实现

按照以下步骤进行分片

步骤1

  • 在副本集上启动配置服务器并在它们之间启用复制。

mongod --configsvr --port 27019 --replset rs0 --dbpath c:datadata1 --bind_ip localhost

mongod --configsvr --port 27018 --replset rs0 --dbpath c:datadata2 --bind_ip localhost

mongod --configsvr --port 27017 --replset rs0 --dbpath c:datadata3 --bind_ip localhost

步骤2

  • 在其中一个配置服务器上初始化副本集。

rs.initiate( { _id : "rs0", configsvr: true, members: [ { _id: 0, host: "ip:27017" }, { _id: 1, host: "ip:27018" }, { _id: 2, host: "ip:27019" } ] })

rs.initiate( { _id : "rs0", configsvr: true, members: [ { _id: 0, host: "ip:27017" }, { _id: 1, host: "ip:27018" }, { _id: 2, host: "ip:27019" } ] })
{
        "ok" : 1,
        "$glestats" : {
                "lastoptime" : timestamp(1593569257, 1),
                "electionid" : objectid("000000000000000000000000")
        },
        "lastcommittedoptime" : timestamp(0, 0),
        "$clustertime" : {
                "clustertime" : timestamp(1593569257, 1),
                "signature" : {
                        "hash" : bindata(0,"aaaaaaaaaaaaaaaaaaaaaaaaaaa="),
                        "keyid" : numberlong(0)
                }
        },
        "operationtime" : timestamp(1593569257, 1)
}

第三步

  • 在副本集中启动分片服务器并在它们之间启用复制。

mongod --shardsvr --port 27020 --replset rs1 --dbpath c:datadata4 --bind_ip localhost

mongod --shardsvr --port 27021 --replset rs1 --dbpath c:datadata5 --bind_ip localhost

mongod --shardsvr --port 27022 --replset rs1 --dbpath c:datadata6 --bind_ip localhost

mongodb将第一个分片服务器初始化为主服务器,要移动主分片服务器,请使用moveprimary方法。

第四步

  • 在其中一个分片服务器上初始化副本集。

rs.initiate( { _id : "rs0", members: [ { _id: 0, host: "ip:27020" }, { _id: 1, host: "ip:27021" }, { _id: 2, host: "ip:27022" } ] })

rs.initiate( { _id : "rs0", members: [ { _id: 0, host: "ip:27020" }, { _id: 1, host: "ip:27021" }, { _id: 2, host: "ip:27022" } ] })
{
        "ok" : 1,
        "$clustertime" : {
                "clustertime" : timestamp(1593569748, 1),
                "signature" : {
                        "hash" : bindata(0,"aaaaaaaaaaaaaaaaaaaaaaaaaaa="),
                        "keyid" : numberlong(0)
                }
        },
        "operationtime" : timestamp(1593569748, 1)
}

第五步

  • 启动分片集群的mangos。

mongos --port 40000 --configdb rs0/localhost:27019,localhost:27018, localhost:27017

第六步

  • 连接mongo路由服务器

mongo --port 40000

  • 现在,添加分片服务器。

sh.addshard( "rs1/localhost:27020,localhost:27021,localhost:27022")

sh.addshard( "rs1/localhost:27020,localhost:27021,localhost:27022")
{
        "shardadded" : "rs1",
        "ok" : 1,
        "operationtime" : timestamp(1593570212, 2),
        "$clustertime" : {
                "clustertime" : timestamp(1593570212, 2),
                "signature" : {
                        "hash" : bindata(0,"aaaaaaaaaaaaaaaaaaaaaaaaaaa="),
                        "keyid" : numberlong(0)
                }
        }
}

第七步

  • 在mongo shell上启用数据库和集合的分片。
  • 在数据库上启用分片

sh.enablesharding("yaoweibindb")

sh.enablesharding("yaoweibindb")
{
        "ok" : 1,
        "operationtime" : timestamp(1591630612, 1),
        "$clustertime" : {
                "clustertime" : timestamp(1591630612, 1),
                "signature" : {
                        "hash" : bindata(0,"aaaaaaaaaaaaaaaaaaaaaaaaaaa="),
                        "keyid" : numberlong(0)
                }
        }
}

步骤 8

  • 要对集合进行分片操作,需要指定分片键(稍后在本文中描述)。

语法sh.shardcollection("dbname.collectionname", { "key" : 1 } )

sh.shardcollection("yaoweibindb.yaoweibincollection", { "key" : 1 } )
{
        "collectionsharded" : "yaoweibindb.yaoweibincollection",
        "collectionuuid" : uuid("0d024925-e46c-472a-bf1a-13a8967e97c1"),
        "ok" : 1,
        "operationtime" : timestamp(1593570389, 3),
        "$clustertime" : {
                "clustertime" : timestamp(1593570389, 3),
                "signature" : {
                        "hash" : bindata(0,"aaaaaaaaaaaaaaaaaaaaaaaaaaa="),
                        "keyid" : numberlong(0)
                }
        }
}

注意:如果集合不存在,可以按以下方式创建。

db.createcollection("yaoweibincollection")
{
        "ok" : 1,
        "operationtime" : timestamp(1593570344, 4),
        "$clustertime" : {
                "clustertime" : timestamp(1593570344, 5),
                "signature" : {
                        "hash" : bindata(0,"aaaaaaaaaaaaaaaaaaaaaaaaaaa="),
                        "keyid" : numberlong(0)
                }
        }
}

步骤 9

将数据插入集合中。mongo日志将开始增长,并指示均衡器正在运行并尝试在分片之间平衡数据。

步骤 10

最后一步是检查分片的状态。可以通过在mongos路由节点上运行以下命令来检查状态。

分片状态

通过在mongo路由节点上运行以下命令来检查分片状态。

sh.status()

数据分布

mongos路由器根据分片键分发负载到各个分片,并为了均匀分布数据使用平衡器。

分布数据到各个分片的关键组件是:

  • 平衡器在平衡被分片节点之间的数据子集时起到关键作用。平衡器在mongos服务器开始分发负载给分片时运行。一旦启动,平衡器会更均匀地分布数据。要检查平衡器的状态,运行 sh.status()sh.getbalancerstate() sh.isbalancerrunning().
mongos> sh.isbalancerrunning()
true
mongos>

或者

mongos> sh.getbalancerstate()
true
mongos>

在插入数据之后,我们可以注意到mongos守护进程中有一些活动,它正在移动特定分片的一些数据块,等等,即平衡器将尝试平衡数据在分片之间的分布。运行平衡器可能会导致性能问题,因此建议在一定的平衡器窗口内运行平衡器。

mongos> sh.status()
--- 分片状态 ---
  分片版本: {
        "_id" : 1,
        "mincompatibleversion" : 5,
        "currentversion" : 6,
        "clusterid" : objectid("5efbeff98a8bbb2d27231674")
  }
  分片:
        {  "_id" : "rs1",  "host" : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022",  "state" : 1 }
        {  "_id" : "rs2",  "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",  "state" : 1 }
  活跃的mongoses:
        "4.2.7" : 1
  自动分片:
        当前启用: 是
  平衡器:
        当前启用: 是
        当前正在运行: 是
        最近5次平衡尝试失败的次数: 5
        最近报告的错误: 无法找到与读取首选项{ mode: "primary" }匹配的主机,集合rs2
        报告错误的时间: wed jul 01 2020 14:39:59 gmt+0530 (india standard time)
        过去24小时的迁移结果:
                1024 : 成功
  数据库:
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        分片键: { "_id" : 1 }
                        唯一: false
                        平衡: true
                        数据块:
                                rs2     1024
                        数据块过多无法打印,请使用详细模式强制打印
        {  "_id" : "yaoweibindb",  "primary" : "rs2",  "partitioned" : true,  "version" : {  "uuid" : uuid("a8b8dc5c-85b0-4481-bda1-00e53f6f35cd"),  "lastmod" : 1 } }
                yaoweibindb.yaoweibincollection
                        分片键: { "key" : 1 }
                        唯一: false
                        平衡: true
                        数据块:
                                rs2     1
                        { "key" : { "$minkey" : 1 } } -->> { "key" : { "$maxkey" : 1 } } on : rs2 timestamp(1, 0)
        {  "_id" : "test",  "primary" : "rs2",  "partitioned" : false,  "version" : {  "uuid" : uuid("a28d7504-1596-460e-9e09-0bdc6450028f"),  "lastmod" : 1 } }

mongos>
  • 分片键确定分片集合中的文档在分片之间的分布逻辑。分片键可以是索引字段或索引复合字段,在要插入的集合中的所有文档中必须存在。数据将被分区为数据块,每个数据块将与基于范围的分片键相关联。基于范围查询路由器将决定哪个分片存储数据块。

分片键可以通过考虑五个属性来选择:

  • 基数
  • 写分布
  • 读分布
  • 读取目标
  • 读取位置

理想的分片键可以使mongodb在所有分片之间均匀分配负载。选择一个好的分片键非常重要。

图像:mongodb

删除分片节点

在从集群中移除分片之前,用户需要确保将数据安全迁移到剩余的分片上。mongodb会在移除所需分片节点之前,将数据安全地转移至其他分片节点。

运行以下命令以移除所需分片。

步骤 1

首先,我们需要确定要移除的分片的主机名。以下命令将列出集群中所有存在的分片以及分片的状态。

db.admincommand( { listshards: 1 } )

mongos> db.admincommand( { listshards: 1 } )
{
        "shards" : [
                {
                        "_id" : "rs1",
                        "host" : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022",
                        "state" : 1
                },
                {
                        "_id" : "rs2",
                        "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",
                        "state" : 1
                }
        ],
        "ok" : 1,
        "operationtime" : timestamp(1593572866, 15),
        "$clustertime" : {
                "clustertime" : timestamp(1593572866, 15),
                "signature" : {
                        "hash" : bindata(0,"aaaaaaaaaaaaaaaaaaaaaaaaaaa="),
                        "keyid" : numberlong(0)
                }
        }
}

步骤 2

发出以下命令以从集群中移除所需的分片。一旦发出命令,负载均衡器会负责从正在排出的分片节点中删除块,然后在其余分片节点之间平衡剩余块的分布。

db.admincommand( { removeshard: "shardedreplicanodes" } )

mongos> db.admincommand( { removeshard: "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022" } )
{
        "msg" : "draining started successfully",
        "state" : "started",
        "shard" : "rs1",
        "note" : "you need to drop or moveprimary these databases",
        "dbstomove" : [ ],
        "ok" : 1,
        "operationtime" : timestamp(1593572385, 2),
        "$clustertime" : {
                "clustertime" : timestamp(1593572385, 2),
                "signature" : {
                        "hash" : bindata(0,"aaaaaaaaaaaaaaaaaaaaaaaaaaa="),
                        "keyid" : numberlong(0)
                }
        }
}

步骤 3

要检查正在排出的分片的状态,请再次发出相同的命令。

db.admincommand( { removeshard: "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022" } )

我们需要等待直到数据排出完成。msgstate字段将显示数据是否已经排出完成,如下所示

"msg" : "draining ongoing",
"state" : "ongoing",

我们也可以使用命令sh.status()来检查状态。一旦移除的分片节点将不会在输出中反映出来。但是如果排出仍在进行中,分片节点将以排出状态为true显示。

步骤 4

使用上述相同的命令不断检查排出状态,直到完全移除所需的分片。
完成后,命令的输出将反映消息和状态已完成。

"msg" : "removeshard 完成成功",
"state" : "完成",
"shard" : "rs1",
"ok" : 1,

第5步

最后,我们需要检查集群中剩余的分片。要检查状态,请输入 sh.status() db.admincommand( { listshards: 1 } )

mongos> db.admincommand( { listshards: 1 } )
{
        "shards" : [
                {
                        "_id" : "rs2",
                        "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",
                        "state" : 1
                }
        ],
        "ok" : 1,
        "operationtime" : timestamp(1593575215, 3),
        "$clustertime" : {
                "clustertime" : timestamp(1593575215, 3),
                "signature" : {
                        "hash" : bindata(0,"aaaaaaaaaaaaaaaaaaaaaaaaaaa="),
                        "keyid" : numberlong(0)
                }
        }
}

在这里,我们可以看到已移除的分片不再出现在分片列表中。

分片相对于复制的优势

  • 在复制中,主节点处理所有写操作,而次要服务器需要维护备份副本或提供只读操作。但是在分片中,负载分布在多个服务器之间。
  • 单个复制集限于12个节点,但对分片数量没有限制。
  • 复制需要高端硬件或垂直扩展来处理大型数据集,与分片中添加额外服务器相比,成本太高。
  • 在复制中,通过添加更多的从服务器可以提高读取性能,而在分片中,通过添加更多的分片节点可以提高读写性能。

分片限制

  • 分片群集不支持跨分片的唯一索引,除非唯一索引以完整的分片键为前缀。
  • 所有对分片集合的更新操作,无论是在一个文档上还是在多个文档上,都必须在查询中包含分片键或_id字段。
  • 只有当集合的大小不超过指定阈值时,才能进行分片。可以根据所有分片键的平均大小和配置的块大小估算此阈值。
  • 分片对最大集合大小或分裂数设置了操作限制。
  • 选择错误的分片键会导致性能问题。

结论

mongodb提供了内置的分片功能,以实现大型数据库的部署而不影响性能。希望以上内容能帮助您设置mongodb分片。接下来,您可能希望熟悉一些常用的mongodb命令

类似文章