Sharding 分片
分片机制的概念
Sharding is a method for distributing data across multiple machines. MongoDB uses sharding to support deployments with very large data sets and high throughput operations.
- 分片(Shard)
- 每个分片(Shard)是 MongoDB 的一个节点或副本集,存储部分数据。所有分片组合在一起形成一个逻辑数据库。
- 数据分片的好处是可以分布存储,减轻单个节点的存储压力,同时通过分布式并行处理提高查询性能。
- 分片键(Shard Key)
- 分片键是指定数据如何分配到各分片的依据。
- 分片键可以是一个字段或多个字段的组合,要求能有效地划分数据,保持分片间负载均衡。(分片集1:2w,分片集2:2w)
- 常见分片策略:
- 范围分片(Range Sharding):基于分片键值的范围进行划分。
- 哈希分片(Hash Sharding):基于分片键值的哈希结果进行划分。
- 数据块(Chunk)
- 数据块是分片机制中的基本存储单元,一个 Chunk 包含一定范围的数据记录。64MB
- MongoDB 会动态监控 Chunk 的大小并在必要时进行 分裂 或 迁移,以保持分片数据的均衡分布。
- 配置服务器(Config Server)
- 配置服务器存储分片集的元数据,包括每个分片的 Chunk 分布信息。
- 路由服务器(Mongos)依赖配置服务器提供的元数据来确定客户端请求应该路由到哪个分片。
- 路由服务器(Mongos)
- 路由服务器是客户端与分片集交互的入口。它将客户端的查询请求路由到正确的分片。
- 客户端只与 Mongos 交互,因此分片的分布对客户端透明。
MongoDB 中 Sharding 的优势
线性扩展性
-
通过增加分片节点,可以轻松扩展存储容量和查询吞吐量。
-
数据分布到不同的机器
注意与Replication的不同,复制是生成副本,每一份都是完整的;分片是将一份数据分解成多个部分。
-
拆分大数据集(横向容量扩展)
-
分散负载压力(横向性能扩展)
负载均衡
- 数据和查询请求可以分散到不同分片上,避免单点压力,提高系统的整体性能。
大数据支持
- 单个 MongoDB 实例的存储受限于磁盘和内存,通过分片,MongoDB 可以轻松存储 PB 级数据。
高可用性
每个分片通常是一个副本集,具有数据冗余能力,在某个分片故障时仍能提供服务。
Sharding 的常见使用场景
- 数据量极大,单节点存储不足:如社交媒体、电子商务中用户或商品数据的存储。
- 高并发读写请求:如实时在线服务、日志分析系统等。
- 需要动态扩展的业务场景:如数据快速增长的企业。
通过 Sharding,MongoDB 实现了大规模分布式存储和高效处理能力,是 NoSQL 数据库架构中的重要组成部分。
为什么要采用分片集群?
- 水平扩展性:通过分片,可以将数据分散存储到多个节点上,从而提高系统容量。随着数据量的增加,可以简单地通过增加新的分片来扩展集群,而不需要对现有数据进行大规模迁移。
- 性能提升:分片集群可以将读写请求分担到多个节点上,减少单个节点的负载。因此,当有大量并发访问时,整体性能会得到显著提高。
- 高可用性:在分片集群中,通常每个分片可以有多个副本,这样即使某个节点出现故障,系统仍然可以正常工作,保证数据的高可用性。
- 数据管理灵活性:分片允许开发者根据不同的业务需求和数据结构选择合适的分片策略,例如按范围分片、哈希分片或地理位置分片,这样可以优化数据分布和查询性能。
- 降低单点故障风险:通过将数据分散在多个节点上,分片集群有效降低了单点故障的风险,提高了系统的可靠性。
- 更好的资源利用:分片集群可以根据负载动态调整资源,确保计算和存储资源得到更高效的利用。
分片集群的架构
-
Shard (分片)= Sharded ReplcaSet (分片集)
由副本集构成的一组存放分片数据的节点
-
Config Server (配置集)
由副本集构成的一组存放分片元数据的节点,即存放数据是如何分片的元数据的节点
-
Router (Mongos) 路由节点
访问一个分片集集群的路由服务器,作为客户端访问的接口。Router从Config Server中读取数据分片的信息来代理客户访问后台Shard上的数据。
1.从零搭建分片集群
- 规划图
2.规划表(由于一个分片集群包含的节点较多,创建之前我们可以进行规划)
3.配置环境变量,创建9台服务器实例(server1~server9)
REM 0.配置环境变量(根据实际环境配置)
SET MONGOD_CMD=C:\mongodb4\bin\mongod.exe
SET MONGOS_CMD=C:\mongodb4\bin\mongos.exe
SET MONGO_CMD=C:\mongodb4\bin\mongo.exeREM 1.创建9台服务器实例(server1~server9)
mkdir server1 server2 server3 server4 server5 server6 server7 server8 server9
4.编写9台服务器的启动配置文件:手动完成server1.ini~server9.ini
#server1.ini
dbpath=.\server1
bind_ip=127.0.0.1
port=27011
logpath=.\server1\mongod.log
logappend=true
replSet=rs0
shardsvr=true
#server2.ini
#server3.ini#server4.ini
dbpath=.\server4
bind_ip=127.0.0.1
port=27014
logpath=.\server4\mongod.log
logappend=true
replSet=rs1
shardsvr=true
#server5.ini
#server6.ini#server7.ini
bind_ip=0.0.0.0
port=27017
logpath=.\server7\mongos.log
logappend=true
configdb=cfg0/127.0.0.1:27018,127.0.0.1:27019
#configdb参数可以通过mongo --help查看#server8.ini
dbpath=.\server8
bind_ip=127.0.0.1
port=27018
logpath=.\server8\mongod.log
logappend=true
replSet=cfg0
configsvr=true
#server9.ini
5.启动配置集、分片集的副本集实例
REM 配置集 cfg0
start "cfg0:server8:27018" %MONGOD_CMD% --config=.\server8.ini
start "cfg0:server9:27019" %MONGOD_CMD% --config=.\server9.ini
REM 分片集1 rs0
start "rs0:server1:27011" %MONGOD_CMD% --config=.\server1.ini
start "rs0:server2:27012" %MONGOD_CMD% --config=.\server2.ini
start "rs0:server3:27013" %MONGOD_CMD% --config=.\server3.ini
REM 分片集2 rs1
start "rs1:server4:27014" %MONGOD_CMD% --config=.\server4.ini
start "rs1:server5:27015" %MONGOD_CMD% --config=.\server5.ini
start "rs1:server6:27016" %MONGOD_CMD% --config=.\server6.ini
如果启动脚本中有中文,需要将utf-8编码修改为中文GBK-232
6.编写副本集初始化脚本for分片集和配置集
REM 手动完成rs0conf.js,rs1conf.js,cfg0conf.js
//rs0conf.js
var rsconf={_id:"rs0","members":[{_id:0,host:"127.0.0.1:27011",priority:2},{_id:1,host:"127.0.0.1:27012",priority:1},{_id:2,host:"127.0.0.1:27013",priority:0,arbiterOnly:true}]};
rs.initiate(rsconf)
//rs1conf.js
var rsconf={_id:"rs1","members":[{_id:0,host:"127.0.0.1:27014",priority:2},//P{_id:1,host:"127.0.0.1:27015",priority:1},//S{_id:2,host:"127.0.0.1:27016",priority:0,arbiterOnly:true}//仲裁节点Arbiter]};
rs.initiate(rsconf)
//cfg0conf.js
var rsconf={_id:"cfg0","members":[{_id:0,host:"127.0.0.1:27018",priority:2},{_id:1,host:"127.0.0.1:27019",priority:1}]};
rs.initiate(rsconf)
7.初始化配置集、分片集(Sharded ReplicaSet)的副本集实例
start "连接到分片1" %MONGO_CMD% --port=27011 --shell rs0conf.js
start "连接到分片2" %MONGO_CMD% --port=27014 --shell rs1conf.js
start "连接到配置集" %MONGO_CMD% --port=27018 --shell cfg0conf.js
8.启动路由节点/分片服务器,然后连接分片服务器 mongos
REM 等待11秒确保副本集完成选举
TIMEOUT /T 11
start "router0:server7:27017" %MONGOS_CMD% --config=.\server7.ini
REM 等待2秒确保路由节点完成启动
TIMEOUT /T 2
start "连接到分片服务器" %MONGOS_CMD% --shell addShards.js
启动脚本程序,如果mongos连接失败,需要打开一个新终端,并输入mongo进入mongos,启动脚本汇总如下:
REM 0.配置环境变量(根据实际环境配置)
SET MONGOD_CMD=C:\mongodb4\bin\mongod.exe
SET MONGOS_CMD=C:\mongodb4\bin\mongos.exe
SET MONGO_CMD=C:\mongodb4\bin\mongo.exeREM 1.创建9台服务器实例(server1~server9)
mkdir server1 server2 server3 server4 server5 server6 server7 server8 server9REM 配置集 cfg0
start "cfg0:server8:27018" %MONGOD_CMD% --config=.\server8.ini
start "cfg0:server9:27019" %MONGOD_CMD% --config=.\server9.ini
REM 分片集1 rs0
start "rs0:server1:27011" %MONGOD_CMD% --config=.\server1.ini
start "rs0:server2:27012" %MONGOD_CMD% --config=.\server2.ini
start "rs0:server3:27013" %MONGOD_CMD% --config=.\server3.ini
REM 分片集2 rs1
start "rs1:server4:27014" %MONGOD_CMD% --config=.\server4.ini
start "rs1:server5:27015" %MONGOD_CMD% --config=.\server5.ini
start "rs1:server6:27016" %MONGOD_CMD% --config=.\server6.inistart "连接到分片1" %MONGO_CMD% --port=27011 --shell rs0conf.js
start "连接到分片2" %MONGO_CMD% --port=27014 --shell rs1conf.js
start "连接到配置集" %MONGO_CMD% --port=27018 --shell cfg0conf.jsREM 等待11秒确保副本集完成选举
TIMEOUT /T 11
start "router0:server7:27017" %MONGOS_CMD% --config=.\server7.ini
REM 等待2秒确保路由节点完成启动
TIMEOUT /T 2
start "连接到分片服务器" %MONGO_CMD% --shell addShards.js
9.添加分片到分片集群
REM 手动编写脚本
REM // 添加rs0
sh.addShard("rs0/127.0.0.1:27011,127.0.0.1:27012,127.0.0.1:27013");
REM // 添加rs1
sh.addShard("rs1/127.0.0.1:27014,127.0.0.1:27015,127.0.0.1:27016");
- 插入数据后查看
use zrdb
db.myc.insert({"name":"zhangsan"})
- 查看添加分片信息
问题1:mongos连不上,需要在终端进行连接,输入mongo进入mongos【不是输入mongos】
2.分片集群操作
- 在mongos上创建一个数据库,默认存在主分片服务器上
- partitioned:false表示非分片数据库,只能在一台服务器上。可以更换数据库的主分片,将"zr"数据库更换到分片集rs1上
db.adminCommand({movePrimary:"zr",to:"rs1"});
- admin数据库是管理数据库,config是存放配置集的数据库
- 查看分片信息,state表示状态,1表示可用,13和16是仲裁节点,不参与存储
db.shards.find()
- 查看collections集合
db.collections.find()
_id
: 集合的唯一标识。
lastmodEpoch
: 记录分片配置变更的唯一标识符。
lastmod
: 最近一次修改时间。
dropped
: 如果为 false
,表示该集合当前处于活动状态。
key
: 分片键,当前为 { "_id": 1 }
。
unique
: 分片键是否唯一,此处为 false
。
uuid
: 集合的全局唯一标识符。
distributionMode
: 表示该集合的分布模式,此处为 sharded
,说明此集合已分片。
2.创建分片数据库(都在mongos中执行)
- 对cqust数据库支持分片
sh.enableSharding("cqust")
-
分片的对象是集合,不是数据库,所以接下来还需要手动对集合分片存储,物理上切块,集合默认情况下是存储在主服务器上
-
使用范围索引片键值
- 缺点:值比较集中在同一个chunk中,往往集中在一台服务器上,但每次只有一台服务器在工作。负载不均衡。
-
使用哈希索引片键值
- 哈希索引片键是一种将数据分布更均匀的分片策略。它基于字段的哈希值来确定数据分片的位置,而不是直接使用字段的原始值。
- 哈希位数固定,值不固定,使用哈希索引能够让值平均分布
-
优点:
均匀分布数据: 哈希值会将数据随机地映射到不同的范围,这样即使数据本身分布不均匀(例如顺序插入的 ID 值),经过哈希后也能实现较为均匀的分布。
避免热点问题: 当使用顺序值(如时间戳或自增 ID)作为片键时,插入的数据可能会集中在一个分片上,造成单点性能瓶颈。哈希片键可以有效避免这种问题。
-
创建集合时分片
新建集合时就设置分片,那么此时集合分片均匀,只需要修改GenerateStudents.js中的一条语句。
- 通过命令可以看到分配比较均匀
db.students.getShardDistribution()
-
先导入数据,后分片
- 如果直接加载,不会分片,数据全部在rs1中,rs0中无数据
load("GenerateStudents.js")
db.students.getShardDistribution()
通过命令看到,该集合不是分片集
-
对students集合中的sno创建哈希索引,如果是范围索引:{sno:1},哈希索引:{sno:“hashed”}
创建索引的字段,最好是经常使用的字段,如此处的sno学号
db.students.createIndex({sno:"hashed"})
- 创建分片
sh.shardCollection("cqust.students",{sno:"hashed"})
- db.stats()查看状态,目前仍然没有分片,以为一块是64MB,目前是15MB左右,还未达到对应的阈值。
- 执行加载students集合多次,就会有两个分片,每个分片上有多个chunk
表示有两个分片rs0和rs1,每个分片上有2个chunks,实现负载均衡。
-
不建议中途,将非分片集修改成分片集。
导入8万条学生记录后,发现有一半的数据在分片集1中,有一半的数据在分片集2中。