MongoDB 4.2——分片简介

发布时间:2026/7/28 2:36:56

MongoDB 4.2——分片简介 分片简介1、什么是分片2、理解集群组件3、在单机集群上进行分片1、什么是分片分片是指跨机器拆分数据的过程有时也会用术语分区partitioning来表示这个概念。通过在每台机器上放置数据的子集无须功能强大的机器只使用大量功能稍弱的机器就可以存储更多的数据并处理更多的负载。分片还可以用于其他目的包括将经常访问的数据放置在更高性能的硬件上或基于地理位置比如基于在一个特定语言环境下的用户来拆分集合中的文档以使它们接近最常对其进行访问的应用程序服务器。大部分数据库软件支持进行手动分片。使用这种方法应用程序会维护到多个不同数据库服务器端的连接每个服务器端都是完全独立的。应用程序不仅管理不同服务器上不同数据的存储还管理在适当的服务器上查询数据。这种方式可以很好地工作但当从集群中添加或删除节点或者面对数据分布或负载模式的变化时就非常难以维护了。MongoDB 支持自动分片这种方式试图将数据库架构从应用程序中抽象出来并简化系统管理。在某种程度上MongoDB 允许应用程序好像始终在和一台单机的MongoDB 服务器对话一样。在运维方面MongoDB 可以自动均衡分片上的数据使容量的添加和删除变得更容易。无论从开发还是运维的角度来看分片都是最复杂的MongoDB 配置方式。有许多组件需要配置和监控数据在集群中会自动转移。在尝试部署或使用分片集群之前应该首先熟悉单机服务器和副本集。此外与副本集一样配置和部署分片集群的推荐方式是通过 MongoDBOps Manager 或 MongoDB Atlas。如果需要保留对计算基础设施的控制建议使用 Ops Manager。如果可以 将基础设施管理留给 MongoDB可以选择在 Amazon AWS、Microsoft Azure 或 Google Compute Cloud 中运行​则推荐使用 MongoDB Atlas。2、理解集群组件MongoDB 的分片机制允许你创建一个由许多机器分片组成的集群并将集合中的数据分散在集群中在每个分片上放置数据的一个子集。这允许应用程序超出单机服务器或副本集的资源限制。许多人对复制和分片之间的区别感到困惑。记住复制在多台服务器上创建了数据的精确副本因此每台服务器都是其他服务器的镜像。相反每个分片包含了不同的数据子集。分片的目标之一是使由两个、3 个、10 个甚至数百个分片组成的集群对应用程序来说就像是一台单机服务器。为了对应用程序隐藏这些细节需要在分片前面运行一个或多个称为 mongos 的路由进程。mongos 维护着一个“目录”​指明了哪个分片包含哪些数据。如上图所示应用程序可以正常连接到此路由服务器并发出请求。路由服务器知道哪些数据在哪个分片上可以将请求转发到适当的分片。如果有对请求的响应路由服务器会收集它们并在必要时进行合并然后再发送回应用程序。对应用程序来说它只知道自己连接到了一个单独的mongod如下图所示。3、在单机集群上进行分片在单台机器上快速建立一个集群。首先使用 --nodb和 --norc 选项启动 mongo shell$ mongo--nodb--norc使用 ShardingTest 类创建集群。在刚启动的 mongoshell 中运行以下代码stShardingTest({name:one-min-shards,chunkSize:1,shards:2,rs:{nodes:3,oplogSize:10},other:{enableBalancer:true}});chunksize 选项会在后面行介绍。目前只需将其设置为 1。至于其他传递给 ShardingTest 的选项name仅仅是分片集群的一个标签shards 指定了集群将由两个分片组成在本例中这样做是为了保持较低的资源需求​rs 将每个分片定义为一组 3 个节点的副本集其oplogSize 为 10MiB同样保持较低的资源占用​。虽然可以为每个分片仅运行一个单独的 mongod 进程但是以副本集创建每个分片可以将典型的分片集群架构描绘得更加清晰。在最后一个选项中我们指示ShardingTest 在集群启动后启用均衡器。这可以确保数据均匀分布在两个分片上。ShardingTest 是 MongoDB 工程师为内部使用而设计的一个类因此没有外部文档。但是由于附带在了MongoDB 服务器端的程序中因此它提供了一个使用分 片集群的最直接方法。ShardingTest 最初是为支持服务器端测试套件而设计的现在仍然用于此目的。默认情况下它在保持尽可能低的资源占用以及建立体系结构相对复杂的分片集群方面提供了许多便利。它假设你的机器上存在 /data/db 目录如果 ShardingTest 运行失败则需要创建该目录然后重新运行命令。当运行这个命令时ShardingTest 会为你自动做很多事情。它会创建一个包含两个分片的集群每个分片都是一个副本集。同时会对副本集进行配置并使用必要的选项启动每个节点以建立复制协议。它会启动一个 mongos来管理跨分片的请求这样客户端就可以像与一个独立的mongod 通信一样与集群进行交互。最后它会为用于维护路由表信息的配置服务器启动一个额外的副本集以确保查询被定向到正确的分片。记住分片的主要使用场景是拆分数据集以解决硬件和成本的限制或为应用程序提 供更好的性能比如地理分区​。MongoDB 分片以一种与应用程序在许多方面无缝对接的方式提供了这些功能。当 ShardingTest 完成集群设置后将启动并运行 10 个进程你可以连接到这些进程两个副本集各有 3 个节点​、一个配置服务器副本集有 3 个节点​以及一个 mongos。默认情况下这些进程会从 20000 端口开始。mongos 会运行在 20009 端口上。在本地机器上运行的其他进程以及之前对 ShardingTest 的调用可能会影响 ShardingTest 使用的端口但是确定集群进程运行在哪些端口上应该不会有什么困难。接下来会连接到 mongos 来使用集群。整个集群会将日志转储到当前 shell 中因此打开第二个终端窗口并启动另一个 mongo shell$ mongo--nodb使用这个 shell 连接到集群的 mongos。再次说明你的mongos 应该运行在 20009 端口上db(newMongo(localhost:20009)).getDB(accounts)注意mongo shell 中的提示符应该发生变化以反映出已经连接到一个 mongos。shell 作为客户端连接到了 mongos。可以开始向mongos 发送请求了它会将请求路由到相应的分片。你不需要知道任何关于分片的信息比如有多少个分片或者它们的地址是什么。只要有分片存在就可以将请求发送给 mongos并允许其转发到合适的分片上。首先插入一些数据for(vari0;i100000;i){db.users.insert({username:useri,created_at:newDate()});}db.users.count()100000可以看到与 mongos 的交互与使用单机服务器是一样的。可以运行 sh.status() 来获得集群的总体视图。此命令会提供一个分片、数据库以及集合的摘要。sh.status()---Sharding Status---sharding version:{_id:1,minCompatibleVersion:5,currentVersion:6,clusterId:ObjectId(5a4f93d6bcde690005986071)}shards:{_id:one-min-shards-rs0,host:one-min-shards-rs0/MBP:20000,MBP:20001,MBP:20002,state:1}{_id:one-min-shards-rs1,host:one-min-shards-rs1/MBP:20003,MBP:20004,MBP:20005,state:1}active mongoses:3.6.1:1autosplit:Currently enabled:nobalancer:Currently enabled:no Currently running:no Failed balancer roundsinlast5attempts:0Migration Resultsforthe last24hours:No recent migrationsdatabases:{_id:accounts,primary:one-min-shards-rs1,partitioned:false}{_id:config,primary:config,partitioned:true}config.system.sessions shard key:{_id:1}unique:falsebalancing:truechunks:one-min-shards-rs01{_id:{$minKey:1}}--{_id:{$maxKey:1}}on:one-min-shards-rs0Timestamp(1,0)sh 类似于 rs但它是用于分片的这是一个全局变量定义了许多关于分片的辅助函数可以通过运行sh.help() 进行查看。正如 sh.status() 的输出所示当前有两个分片和两个数据库config 数据库是自动创建的​。你的 accounts 数据库的主分片primary shard可能与这里显示的不同。主分片是为每个数据库随机选择的一个“主基地”​。所有的数据都会在这个主分片上。MongoDB 现在还不能自动分发数据因为它不知道你希望如何或者是否进行分发。你必须明确指定在每个集合中应该如何分布数据。主分片与副本集的主节点不同。主分片是指组成某个分片的整个副本集。副本集中的主节点是集合中可以接收写操作的单台服务器。要对一个特定的集合进行分片首先需要在集合的数据库上启用分片。如下所示运行 enableSharding 命令sh.enableSharding(accounts)现在可以对 accounts 数据库中的集合进行分片了。在对集合进行分片时需要选择一个片键shardkey​。片键是 MongoDB 用来拆分数据的一个或几个字段。如果选择在 “username” 字段上分片MongoDB 就 会根据用户名的范围对数据进行拆分“a1-steak-sauce” 到 “defcon”、“defcon1” 到 “howie1998”等等。可以将选择一个片键看作为集合中的数据选择一个排列顺序。这与索引的概念类似也十分合理随着集合的增大片键会成为集合中最重要的索引。只有创建了索引的字段才能够作为片键。因此在启用分片之前必须在想要分片的键上创建一个索引db.users.createIndex({username:1})现在可以通过 “username” 来对集合进行分片了sh.shardCollection(accounts.users,{username:1})尽管这里在选择片键时没有做太多考虑但在实际系统中这是一个需要仔细斟酌的重要决定。等待几分钟并再次运行 sh.status()可以看到比之前显示出了更多的信息sh.status()---Sharding Status---sharding version:{_id:1,minCompatibleVersion:5,currentVersion:6,clusterId:ObjectId(5a4f93d6bcde690005986071)}shards:{_id:one-min-shards-rs0,host:one-min-shards-rs0/MBP:20000,MBP:20001,MBP:20002,state:1}{_id:one-min-shards-rs1,host:one-min-shards-rs1/MBP:20003,MBP:20004,MBP:20005,state:1}active mongoses:3.6.1:1autosplit:Currently enabled:nobalancer:Currently enabled:yes Currently running:no Failed balancer roundsinlast5attempts:0Migration Resultsforthe last24hours:6:Successdatabases:{_id:accounts,primary:one-min-shards-rs1,partitioned:true}accounts.users shard key:{username:1}unique:falsebalancing:truechunks:one-min-shards-rs06one-min-shards-rs17{username:{$minKey:1}}--{username:user17256}on:one-min-shards-rs0Timestamp(2,0){username:user17256}--{username:user24515}on:one-min-shards-rs0Timestamp(3,0){username:user24515}--{username:user31775}on:one-min-shards-rs0Timestamp(4,0){username:user31775}--{username:user39034}on:one-min-shards-rs0Timestamp(5,0){username:user39034}--{username:user46294}on:one-min-shards-rs0Timestamp(6,0){username:user46294}--{username:user53553}on:one-min-shards-rs0Timestamp(7,0){username:user53553}--{username:user60812}on:one-min-shards-rs1Timestamp(7,1){username:user60812}--{username:user68072}on:one-min-shards-rs1Timestamp(1,7){username:user68072}--{username:user75331}on:one-min-shards-rs1Timestamp(1,8){username:user75331}--{username:user82591}on:one-min-shards-rs1Timestamp(1,9){username:user82591}--{username:user89851}on:one-min-shards-rs1Timestamp(1,10){username:user89851}--{username:user9711}on:one-min-shards-rs1Timestamp(1,11){username:user9711}--{username:{$maxKey:1}}on:one-min-shards-rs1Timestamp(1,12){_id:config,primary:config,partitioned:true}config.system.sessions shard key:{_id:1}unique:falsebalancing:truechunks:one-min-shards-rs01{_id:{$minKey:1}}--{_id:{$maxKey:1}}on:one-min-shards-rs0Timestamp(1,0)这个集合被分成了 13 个块每个块是数据的一个子集。这些是按照片键范围排列的{“username” : minValue}– {“username” : maxValue} 表示每个数据块的范围​。查看输出信息的 “on” : shard 部分可以看到这些块均匀地分布在各个分片之间。将集合拆分成数据块的过程如图所示。在分片之前集合实际上是一个单独的块。分片根据片键将其拆分成更小的块。之后这些数据块可能会分布到集群中。注意块列表开始和结束处的键即$minKey和$maxKey。$minKey可以被认为是“负无穷”​。这个值比MongoDB 中的其他值都要小。类似地$maxKey相当于“正无穷”​。它比任何其他值都要大。因此总是会在块范围中看到这两个“极值”​。片键的值始终位于$minKey和$maxKey之间。这两个值实际上是 BSON 类型不应该用在应用程序中它们主要是供内部使用的。如果希 望在 shell 中引用它们可以使用 MinKey 和 MaxKey常量。现在数据已经分布在多个分片上了让我们尝试执行一些查询。首先查询一个特定的用户名db.users.find({username:user12345}){_id:ObjectId(5a4fb11dbb9ce6070f377880),username:user12345,created_at:ISODate(2018-01-05T17:08:45.657Z)}可以看到查询语句工作正常。现在执行一下 explain 来看看 MongoDB 在幕后是如何处理的db.users.find({username:user12345}}).explain(){queryPlanner:{mongosPlannerVersion:1,winningPlan:{stage:SINGLE_SHARD,shards:[{shardName:one-min-shards-rs0,connectionString:one-min-shards-rs0/MBP:20000,MBP:20001,MBP:20002,serverInfo:{host:MBP,port:20000,version:3.6.1,gitVersion:025d4f4fe61efd1fb6f0005be20cb45a004093d1},plannerVersion:1,namespace:accounts.users,indexFilterSet:false,parsedQuery:{username:{$eq:user12345}},winningPlan:{stage:FETCH,inputStage:{stage:SHARDING_FILTER,inputStage:{stage:IXSCAN,keyPattern:{username:1},indexName:username_1,isMultiKey:false,multiKeyPaths:{username:[]},isUnique:false,isSparse:false,isPartial:false,indexVersion:2,direction:forward,indexBounds:{username:[[\user12345\, \user12345\]]}}}},rejectedPlans:[]}]}},ok:1,$clusterTime:{clusterTime:Timestamp(1515174248,1),signature:{hash:BinData(0,AAAAAAAAAAAAAAAAAAAAAAAAAAA),keyId:NumberLong(0)}},operationTime:Timestamp(1515173700,201)}从 explain 输出的 “winningPlan” 字段中可以看到集群使用单个分片 (one-min-shards-rs0完成了这个查询。根据之前展示出来的 sh.status() 的输出可以看到user12345 确实属于集群中为该分片列出的第一个块的键范围。由于 “username” 是片键因此 mongos 能够将查询直接路由到正确的分片上。作为对比下面来看看查询所有用户的结果db.users.find().explain(){queryPlanner:{mongosPlannerVersion:1,winningPlan:{stage:SHARD_MERGE,shards:[{shardName:one-min-shards-rs0,connectionString:one-min-shards-rs0/MBP:20000,MBP:20001,MBP:20002,serverInfo:{host:MBP.fios-router.home,port:20000,version:3.6.1,gitVersion:025d4f4fe61efd1fb6f0005be20cb45a004093d1},plannerVersion:1,namespace:accounts.users,indexFilterSet:false,parsedQuery:{},winningPlan:{stage:SHARDING_FILTER,inputStage:{stage:COLLSCAN,direction:forward}},rejectedPlans:[]},{shardName:one-min-shards-rs1,connectionString:one-min-shards-rs1/MBP:20003,MBP:20004,MBP:20005,serverInfo:{host:MBP.fios-router.home,port:20003,version:3.6.1,gitVersion:025d4f4fe61efd1fb6f0005be20cb45a004093d1},plannerVersion:1,namespace:accounts.users,indexFilterSet:false,parsedQuery:{},winningPlan:{stage:SHARDING_FILTER,inputStage:{stage:COLLSCAN,direction:forward}},rejectedPlans:[]}]}},ok:1,$clusterTime:{clusterTime:Timestamp(1515174893,1),signature:{hash:BinData(0,AAAAAAAAAAAAAAAAAAAAAAAAAAA),keyId:NumberLong(0)}},operationTime:Timestamp(1515173709,514)}从这个 explain 中可以看到该查询必须访问两个分片才能找到所有数据。通常来说如果在查询中没有使用片键mongos 就不得不将查询发送到每个分片上。包含片键并可以发送到单个分片或分片子集的查询称为定向查询targeted query​。必须发送到所有分片的查询称为分散–收集查询scatter-gather query​也称为广播查询mongos 会将查询分散到所有分片然后再从各个分片收集结果。完成这个实验后就可以关闭副本集了。切换回原来的shell并按几次 Enter 键返回到命令行然后运行st.stop() 干净地关闭所有服务器st.stop()如果不确定某个操作的作用那么使用 ShardingTest 快速创建一个本地集群并尝试一下会很有帮助。

相关新闻