# 七、MongoDB 数据分片
# 1. 分片目标
- 缓解单机压力,解决数据量过大引起的 IO 性能下降问题;
- 增加可扩展性;
# 2. 分区方式
数据分区是一种将数据分割到多个独立数据存储中的机制,以此来减少每个索引的大小和更新记录所需的 IO 访问量。
# 2.1 垂直分区
在数据库的传统视图中,数据以行和列的方式存储。垂直分区的实现为:拆分列边界上的记录,并将各个部分存储在不同的表或集合中。可以认为,关系数据库通过按照一对一关系使用连接表构成的是本地垂直数据分区。
不过,MongoDB 并未使用这种形式的分区,因为它的记录结构(文档)并不适合整洁的行列模型。因此,几乎很难根据列边界将行清楚地分开。MongoDB 还采用了嵌入式文档,它并未直接提供在服务器上连接关联集合的能力。
# 2.2 水平分区
使用 MongoDB 时,水平分区是唯一可采用的方式,而分片就是各种流行水平分区的通用术语。通过分片,可将集合分割到多个服务器,从而改善包含大量文档的集合的性能。
MongoDB 使用一种唯一的方法用于分片,由 MongoS 路径进程管理数据的分割,并将请求路由到必需的分片服务器。如果查询需要访问多个分片中的数据,MongoS 将管理从多个分片获取数据并将数据合并成单个游标的过程。
# 3. 分片需求
- 数据均匀分散;
- 具有容错性,某个分片出问题了没关系;
- 可以在系统运行时添加或删除分片;
# 4. 分片架构
# 4.1 主分片
- 集群中的每个数据库都会选择一个分片作为主分片;
- 主分片存储所有不需要分片的集合;
- 创建数据库时,数据最少的分片需要被选为主分片;
# 4.2 mongos 分片控制器
- 负责管理应用发送到 MongoDB 服务器的所有命令;
- 聚合多个分片的查询结果;
# 4.3 mongod 配置服务器
- 存储分片服务器的配置及信息;
- 存储各分片数据段列表和数据段范围;
- 存储集群的认证和授权配置;
- 不同的集群不要共用配置服务器;
- 主节点故障时,配置服务器进入只读模式,该模式下,数据段分裂和集群平衡都不可执行;
- 整个复制集故障时,分片集群不可用;
# 5. 分片片键
- 区间划分
- 哈希片键
- 复合片键
- 标签分片
# 6. 分片搭建
Docker
# 6.1 服务器实例
服务 | 守护进程 | 镜像 | 端口 | 数据库路径 |
---|---|---|---|---|
分片控制器 | mongos | mongo:4 | 27021 | |
配置服务器 | mongod | mongo:4 | 27022 | $HOME/mymongo/data27022:/data/db |
分片 0 | mongod | mongo:4 | 27023 | $HOME/mymongo/data27023:/data/db |
分片 1 | mongod | mongo:4 | 27024 | $HOME/mymongo/data27024:/data/db |
# 6.2 创建 docker network
docker network create sharding-network
# 6.3 创建配置服务器
启动一个 mongod 进程,用作配置服务器
docker run --net sharding-network --name mongo27022 -v $HOME/mymongo/data27022:/data/db -p 27022:27017 -d mongo:4 --configsvr --replSet configSet --port 27017
--net
: 指定 Docker 网络,这样组成分片集群的不同容器才可以进行连接--name
: 指定容器名称--v
: 数据挂载-p
: 端口映射-d
: 后台运行--configsvr
: 指定作为配置服务器--replSet
: 指定复制集,这里配置服务器我们用复制集--port
: 指定 mongod 启动的端口
因为这是一个复制集,所以我们需要初始化它,先进入 mongo27022
docker exec -it mongo27022 mongo
初始化复制集,运行
rs.initiate()
输出:
{ "info2" : "no configuration specified. Using a default configuration for the set", "me" : "0644ee04a016:27017", "ok" : 1, "$gleStats" : { "lastOpTime" : Timestamp(1638934296, 1), "electionId" : ObjectId("000000000000000000000000") }, "lastCommittedOpTime" : Timestamp(0, 0) }
# 6.4 创建分片控制器
这里我们再运行一个 MongoDB 进程,我们需要这里需要加两个端口映射,因为我们需要在该容器里面运行 mongos 进程,它也需要监听一个端口 27021
docker run --net sharding-network --name mongo27021 -v $HOME/mymongo/data27021:/data/db -p 27017:27017 -p 27021:27021 -d mongo:4 --port 27017
进入该容器
docker exec -it mongo27021 /bin/bash
启动 mongos
mongos --configdb configSet/mongo27022:27017 --port 27021 --bind_ip_all
--config
: 指定配置服务器:<config replset name>/<host1:port>,<host2:port>,[...]
--bind_ip_all
: 允许所有 IP 访问,这样后面才能在主机上连接该 mongos 进程
# 6.5 创建两个分片
创建两个分片,启动新的 mongodb
docker run --net sharding-network --name mongo27023 -v $HOME/mymongo/data27023:/data/db -p 27023:27017 -d mongo:4 --port 27017 --shardsvr
docker run --net sharding-network --name mongo27024 -v $HOME/mymongo/data27024:/data/db -p 27024:27017 -d mongo:4 --port 27017 --shardsvr
--shardsvr
: 指明这是一个分片
在本机连接 mongo27021 中的 mongos 进程,这里本机需要先安装好 MongoDB:
mongo localhost:27021
添加两个分片
sh.addShard("mongo27023:27017")
输出:
{ "shardAdded" : "shard0000", "ok" : 1, "operationTime" : Timestamp(1638934720, 4), "$clusterTime" : { "clusterTime" : Timestamp(1638934720, 4), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
再加
sh.addShard("mongo27024:27017")
输出:
{ "shardAdded" : "shard0001", "ok" : 1, "operationTime" : Timestamp(1638934745, 4), "$clusterTime" : { "clusterTime" : Timestamp(1638934745, 4), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
查看集群状态
sh.status()
输出:
--- Sharding Status --- sharding version: { "_id" : 1, "minCompatibleVersion" : 5, "currentVersion" : 6, "clusterId" : ObjectId("61b02718995d33fc87f70c68") } shards: { "_id" : "shard0000", "host" : "mongo27023:27017", "state" : 1 } { "_id" : "shard0001", "host" : "mongo27024:27017", "state" : 1 } active mongoses: "4.4.10" : 1 autosplit: Currently enabled: yes balancer: Currently enabled: yes Currently running: no Failed balancer rounds in last 5 attempts: 0 Migration Results for the last 24 hours: No recent migrations databases: { "_id" : "config", "primary" : "config", "partitioned" : true }
# 6.6 创建数据库和集合
现在分片环境已经可以正常运行,但没有分片数据:接下来窗户就一个名为 testdb 的数据库,然后在该数据库中激活一个名为 testcollection 的集合,对该集合进行分配,赋予它一个名为 testkey 的参数,用作分片函数。
连接 mongos
mongo localhost:27021
给 testdb 开启分片
sh.enableSharding("testdb")
输出:
{ "ok" : 1, "operationTime" : Timestamp(1638935020, 5), "$clusterTime" : { "clusterTime" : Timestamp(1638935020, 5), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
激活分片集合,添加分片函数
sh.shardCollection("testdb.testcollection", {testkey: 1})
输出:
{ "collectionsharded" : "testdb.testcollection", "collectionUUID" : UUID("88e5b51e-ca77-4f38-a4ab-cc3a3a0f1b42"), "ok" : 1, "operationTime" : Timestamp(1638935102, 8), "$clusterTime" : { "clusterTime" : Timestamp(1638935102, 8), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
# 6.7 修改 chunksize
MongoDB 默认的 chunksize 是 64M,为了使后面的分片效果显著,这里我们改成 1M。
进入 mongos
mongo localhost:27021
使用 config 数据库
use config
设置 chunksize 为 1M
db.settings.save( { _id:"chunksize", value: 1 } )
# 6.8 客户端插入数据
这里使用 Go 程序来插入数据。
创建测试程序目录
mkdir -p $HOME/mongo-sharding-go
进入该目录
cd $HOME/mongo-sharding-go
初始化 go 程序
go mod init mongo-sharding-go
创建 main.go 文件
touch main.go
拉取 mongo-driver 驱动库
go get go.mongodb.org/mongo-driver/mongo
编写 main.go
vim main.go
内容如下:
package main import ( "context" "fmt" "math/rand" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) type Test struct { TestKey int `bson: testkey` TestText string `bson: testtext` } func main() { // 注意,要连接的是 mongos 所监听的端口 const ( MONGODB_URL = "mongodb://localhost:27021" DATABASE = "testdb" COLLECTION = "testcollection" ) // 1. 建立连接 client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(MONGODB_URL).SetRetryWrites(false)) if err != nil { panic(err) } // 2. 选择数据库 testdb db := client.Database(DATABASE) // 3. 选择表 testcollection collection := db.Collection(COLLECTION) // 4. 插入数据 for i := 0; i < 100000; i++ { test := &Test{ TestKey: rand.Intn(100000), TestText: "Hello everyone, this is the test created by hedon while learning mongodb shrading cluster.", } _, _ = collection.InsertOne(context.TODO(), test) } fmt.Println("插入完成...") }
执行 go 程序
go run main.go
# 6.9 查看分片效果
先看 mongos
进入 mongos
mongo localhost:27021
切换数据库
use testdb
查看集合文档条数
mongos> db.testcollection.count() 100000
可以看到 10W 条文档已经插进去了,分片集群访问是没有问题的。
再看分片 0
进入分片 0 —— mongo27023
docker exec -it mongo27023 mongo
切换数据库
use testdb
查看文档条数
> db.testcollection.count() 43242
再看分片 1
进入分片 1 —— mongo27024
docker exec -it mongo27024 mongo
切换数据库
use testdb
查看文档条数
> db.testcollection.count() 56758
至此,MongoDB 分片集群就搭建完毕了。
# 7. 分片平衡
MongoDB 中会有一个均衡器,如果数据在分片中分配不平衡的话,它会在后台自己转移数据,使其平衡。