想从网络上查阅一些docker-compose搭建mongo分片的教程不少,可是发现完整的太少了,多机版的更是少之又少,所以出一片文章供大家参考。
MongoDB副本集和分片 各个服务
mongos层:请求的入口,是router的角色,相当于监听,负责将请求分发到对应的存储数据的shard上,多副本冗余
config server层:记录了mongos中使用到的元数据,自动向mongos同步最新的集群配置,多副本冗余
shard主节点层:将数据分片,数据库拆分,并将其分散在不同的机器上,原理是将整个数据集合切块块分散到各个shard中,每个shard只负责总数据的一部分,通过一个均衡器来对各个shard均衡,多副本冗余
shard副本层:是shard的备份,多副本冗余
shard仲裁层:用于仲裁,不存储数据,使用最小的资源,需要基数个仲裁角色,且不能放在同一设备上
开始准备
为了更清晰方便的操作,这里提前准备好三台机器
192.168.0.121 虚拟机、192.168.0.122 虚拟机、192.168.0.123 虚拟机
三台机器的操作目录均为:/usr/local/docker/mongosvr 下
在三台机器上分别创建目录文件夹
mkdir data mongos shard1 shard2 shard3
生成keyFile
- MongoDB使用keyfile认证,副本集中的每个mongod实例使用keyfile内容作为认证其他成员的共享密码。mongod实例只有拥有正确的keyfile才可以加入副本集。
- keyFile的内容必须是6到1024个字符的长度,且副本集所有成员的keyFile内容必须相同。
- 有一点要注意是的:在UNIX系统中,keyFile必须没有组权限或完全权限(也就是权限要设置成X00的形式)。Windows系统中,keyFile权限没有被检查。
- 可以使用任意方法生成keyFile。例如,如下操作使用openssl生成复杂的随机的1024个字符串。然后使用chmod修改文件权限,只给文件拥有者提供读权限。
# 400权限是要保证安全性,否则mongod启动会报错
# 存放目录随意,只要对应yaml文件中的地址即可。
openssl rand -base64 756 > key.file
chmod 400 key.file
编写docker-compose.yaml文件,如下
version: '3.9'
networks:
mongo-network:
external: false
services:
# 配置服务器configsvr
rs_config_server:
image: mongo:latest
networks:
- mongo-network
container_name: rs_config_server
restart: always
ports:
- 27019:27019
command: --configsvr --replSet "rs_config_server" --bind_ip_all
volumes:
- ${PWD}/data/db:/data/db
- ${PWD}/data/configdb:/data/configdb
- ${PWD}/key.file:/data/mongodb/key.file
# shard分片1
rs_shard_server_01:
image: mongo:latest
networks:
- mongo-network
container_name: rs_shard_server_01
restart: always
ports:
- 27118:27018
command: --shardsvr --replSet "rs_shard_server_01" --bind_ip_all
volumes:
- ${PWD}/shard1/db:/data/db
- ${PWD}/shard1/configdb:/data/configdb
- ${PWD}/key.file:/data/mongodb/key.file
# shard分片2
rs_shard_server_02:
image: mongo:latest
networks:
- mongo-network
container_name: rs_shard_server_02
restart: always
ports:
- 27218:27018
command: --shardsvr --replSet "rs_shard_server_02" --bind_ip_all
volumes:
- ${PWD}/shard2/db:/data/db
- ${PWD}/shard2/configdb:/data/configdb
- ${PWD}/key.file:/data/mongodb/key.file
# shard分片3
rs_shard_server_03:
image: mongo:latest
networks:
- mongo-network
container_name: rs_shard_server_03
restart: always
ports:
- 27318:27018
command: --shardsvr --replSet "rs_shard_server_03" --bind_ip_all
volumes:
- ${PWD}/shard3/db:/data/db
- ${PWD}/shard3/configdb:/data/configdb
- ${PWD}/key.file:/data/mongodb/key.file
# 配置路由 mongos
rs_mongos_server:
image: mongo:latest
networks:
- mongo-network
container_name: rs_mongos_server
restart: always
ports:
- 27017:27017
# 这里的 rs_config_server 是 配置的 --replSet "rs_config_server"
# 将三台机器的configsvr都写在这个位置
command: mongos --configdb rs-config-server/192.168.0.121:27019,192.168.0.122:27019,192.168.0.123:27019 --bind_ip_all
volumes:
- ${PWD}/mongos/db:/data/db
- ${PWD}/mongos/configdb:/data/configdb
- ${PWD}/key.file:/data/mongodb/key.file
目录目录如下图所示:
以上操作在三台机器上均操作一遍,保持目录结构一模一样,无需改动任何地方。
启动
三台机器均需要启动,启动方式如下:
# 目录结构依旧在/usr/local/docker/mongosvr目录下
cd /usr/local/docker/mongosvr
# 启动指令 docker-compose -f docker-compose.yaml up -d (这里yaml文件名定义成了docker-compose.yaml,所以启动时无需指定-f)
docker-compose up -d
# 停止指令 docker-compose -f docker-compose.yaml (这里yaml文件名定义成了docker-compose.yaml,所以启动时无需指定-f)
# docker-compose down
启动方式如下所示:
都启动成功后,我们现在来进行初始化配置,进行副本集、分片、路由的关联和初始化。
首先我们选择任意一台机器,我这里选择了192.168.0.121第一台机器进行操作(只在一台机器操作,不可三台机器都操作)
初始化配置服务器
# 进入rs_config_server容器
docker exec -it rs_config_server bash
# 登录mongo,这里的端口号不用解释了吧,docker-compose.yaml文件和使用docker ps 都能查看到
mongo --host 192.168.0.121 --port 27019
# 执行初始化语句,配置 mongo config
rs.initiate({
_id: "rs_config_server",
configsvr: true,
members: [
{ _id : 0, host : "192.168.0.121:27019" },
{ _id : 1, host : "192.168.0.122:27019" },
{ _id : 2, host : "192.168.0.123:27019" },
]
});
# 设置完成后,可以使用以下指令查看状态
rs.status()
执行方式如下图所示:
我们可以看到三台config已经出现了变化。
现在开始关联分片 或者 设置仲裁节点
以下标红的地方注意,别写错。执行 rs.initiate 后,可以观察一会,让系统自动选出primary节点。
初始化分片1
# 进入 rs_shard_server_01 容器
docker exec -it rs_shard_server_01 bash
# 登录mongo
mongo --host 192.168.0.121 --port 27118
# 执行初始化语句,配置 mongo 分片1
# 也是可以设置裁决节点的,只需要在任意一个节点后增加arbiterOnly:true即可。
# 比如:{ _id : 2, host : "192.168.0.123:27118", arbiterOnly:true }
rs.initiate({
_id: "rs_shard_server_01",
members: [
{ _id : 0, host : "192.168.0.121:27118" },
{ _id : 1, host : "192.168.0.122:27118" },
{ _id : 2, host : "192.168.0.123:27118" },
]
});
# 分片设置完成后,可以使用以下指令查看状态
rs.status()
初始化分片2
# 进入 rs_shard_server_01 容器
docker exec -it rs_shard_server_02 bash
# 登录mongo
mongo --host 192.168.0.121 --port 27218
# 执行初始化语句,配置 mongo 分片2
# 也是可以设置裁决节点的,只需要在任意一个节点后增加arbiterOnly:true即可。
# 比如:{ _id : 2, host : "192.168.0.123:27218", arbiterOnly:true }
rs.initiate({
_id: "rs_shard_server_02",
members: [
{ _id : 0, host : "192.168.0.121:27218" },
{ _id : 1, host : "192.168.0.122:27218" },
{ _id : 2, host : "192.168.0.123:27218" },
]
});
# 分片设置完成后,可以使用以下指令查看状态
rs.status()
初始化分片3
# 进入 rs_shard_server_01 容器
docker exec -it rs_shard_server_03 bash
# 登录mongo
mongo --host 192.168.0.121 --port 27318
# 执行初始化语句,配置 mongo 分片3
# 也是可以设置裁决节点的,只需要在任意一个节点后增加arbiterOnly:true即可。
# 比如:{ _id : 2, host : "192.168.0.123:27318", arbiterOnly:true }
# 指定设置主副节点,使用priority,设置优先级,值大的优先设置主节点
# 比如:{ _id : 0, host : "192.168.0.121:27318", priority:5 },
rs.initiate({
_id: "rs_shard_server_03",
members: [
{ _id : 0, host : "192.168.0.121:27318" },
{ _id : 1, host : "192.168.0.122:27318" },
{ _id : 2, host : "192.168.0.123:27318" },
]
});
# 分片设置完成后,可以使用以下指令查看状态
rs.status()
我们使用 rs.status()来查看一下 rs_shard_server_01 的状态吧,简单看一下。
{
"set" : "rs_shard_server_01",
"date" : ISODate("2021-11-03T05:20:57.821Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"votingMembersCount" : 3,
"writableVotingMembersCount" : 3,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1635916848, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2021-11-03T05:20:48.424Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1635916848, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1635916848, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1635916848, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2021-11-03T05:20:48.424Z"),
"lastDurableWallTime" : ISODate("2021-11-03T05:20:48.424Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1635916798, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2021-11-03T05:12:58.325Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1635916368, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 2,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"numCatchUpOps" : NumberLong(0),
"newTermStartDate" : ISODate("2021-11-03T05:12:58.338Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2021-11-03T05:12:59.766Z")
},
"members" : [
{
"_id" : 0,
"name" : "192.168.0.121:27118",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 1151,
"optime" : {
"ts" : Timestamp(1635916848, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-11-03T05:20:48Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1635916378, 1),
"electionDate" : ISODate("2021-11-03T05:12:58Z"),
"configVersion" : 1,
"configTerm" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "192.168.0.122:27118",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 489,
"optime" : {
"ts" : Timestamp(1635916848, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1635916848, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-11-03T05:20:48Z"),
"optimeDurableDate" : ISODate("2021-11-03T05:20:48Z"),
"lastHeartbeat" : ISODate("2021-11-03T05:20:56.963Z"),
"lastHeartbeatRecv" : ISODate("2021-11-03T05:20:56.228Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "192.168.0.121:27118",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
},
{
"_id" : 2,
"name" : "192.168.0.123:27118",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 489,
"optime" : {
"ts" : Timestamp(1635916848, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1635916848, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-11-03T05:20:48Z"),
"optimeDurableDate" : ISODate("2021-11-03T05:20:48Z"),
"lastHeartbeat" : ISODate("2021-11-03T05:20:56.963Z"),
"lastHeartbeatRecv" : ISODate("2021-11-03T05:20:56.228Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "192.168.0.121:27118",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1635916848, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1635916848, 1)
}
# 题外话
# 如果是SECONDARY节点,用slaveOk命令允许查询操作。
rs_shard_server_03:SECONDARY> rs.slaveOk()
rs_shard_server_03:SECONDARY> rs.status()
配置路由mongos
# 进入 rs_mongos_server 容器
docker exec -it rs_mongos_server bash
# 登录mongo
mongo --host 192.168.0.121 --port 27017
# 执行初始化语句,配置 mongo router 路由信息
sh.addShard("rs_shard_server_01/192.168.0.121:27118,192.168.0.122:27118,192.168.0.123:27118");
sh.addShard("rs_shard_server_02/192.168.0.121:27218,192.168.0.122:27218,192.168.0.123:27218");
sh.addShard("rs_shard_server_03/192.168.0.121:27318,192.168.0.122:27318,192.168.0.123:27318");
# 执行成功后会显示如下打印信息
mongos> sh.addShard("rs_shard_server_03/192.168.0.121:27318,192.168.0.122:27318,192.168.0.123:27318");
{
"shardAdded" : "rs_shard_server_03",
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1635917549, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1635917548, 3)
}
mongos>
# 路由设置完成后,可以使用以下指令查看状态
mongos> sh.status()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"minCompatibleVersion" : 5,
"currentVersion" : 6,
"clusterId" : ObjectId("61821955f12d632a3b31b26e")
}
shards:
{ "_id" : "rs_shard_server_01", "host" : "rs_shard_server_01/192.168.0.121:27118,192.168.0.122:27118,192.168.0.123:27118", "state" : 1, "topologyTime" : Timestamp(1635917525, 2) }
{ "_id" : "rs_shard_server_02", "host" : "rs_shard_server_02/192.168.0.121:27218,192.168.0.122:27218,192.168.0.123:27218", "state" : 1, "topologyTime" : Timestamp(1635917544, 1) }
{ "_id" : "rs_shard_server_03", "host" : "rs_shard_server_03/192.168.0.121:27318,192.168.0.122:27318,192.168.0.123:27318", "state" : 1, "topologyTime" : Timestamp(1635917548, 1) }
active mongoses:
"5.0.3" : 3
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 }
截止到目前为止,MongoDB的副本集和分片高可用已经配置完成。
测试使用
# 任意一节点操作
# 进入mongos 容器中
docker exec -it rs_mongos_server bash
# 连接mongos
mongo --host 192.168.0.121 --port 27017
use admin
# 使用testdb1库
# 循环插入数据到testdb1库的tab1集合中的键id中
# 该库对应的该集合对应的该键被设置成了分片
# 查看分片情况
# testdb1开启分片功能
db.runCommand( { enablesharding : "testdb1"});
db.runCommand( { shardcollection : "testdb1.tab1",key : {id: 1} } )
# 添加数据
use testdb1;
for(var i=1;i<=20000;i++) db.tab1.save({id:i,"test1":"testval1"});
db.tab1.stats();
exit
分别在三个主机上操作配置库、插入测试数据、查看测试数据
验证了副本同步,最后的显示结果看到 “sharded” : true 表示分片也是成功的
开启登录认证
# 在任意mongos节点操作
# 进入mongos 容器中
docker exec -it rs_mongos_server bash
# 连接mongos
mongo --host 192.168.0.121 --port 27017
mongos> use admin
switched to db admin
mongos> show tables
# 添加两个管理员账号,一个系统管理员:system 一个数据库管理员:administrator
# 先添加系统管理员账号,用来管理用户
db.createUser({user:"root",pwd:"root",roles:[{role:"root",db:"admin"}]})
# 添加数据库管理员,用来管理所有数据库
db.createUser({user:'administrator', pwd:'administrator', roles:[{role:"userAdminAnyDatabase",db:"admin"}]})
# 添加管理员用户认证,认证之后才能管理所有数据库
db.auth('administrator','administrator')
# 退出,用刚才创建的账号进行登录(两个都测试一下)
mongo 192.168.0.121:27017 -u root -proot --authenticationDatabase admin
mongo 192.168.0.121:27017 -u administrator -padministrator --authenticationDatabase admin
附带一些常用命令列表:
# 数据库 命令
db.serverStatus().connections; //连接数查看
show collections //查看表信息
db.test_shard.find().count() //查看table1数据长度
db.test_shard.remove({}) //删除数据表
db.stats() //查看所有的分片服务器状态
db.adminCommand( { listShards: 1 } ) //分片列表
db.test_shard.find({ age: 36 }).explain() //精确查询
db.test_shard.find({ age: { $gte : 36 ,$lt : 73 } }).explain() //范围查询
# 分片 操作命令
sh.enableSharding('testdb') //开启数据库testdb分片
sh.shardCollection('testdb.users',{uid:1}) //按testdb.users的uid字段分片
sh.shardCollection("testdb.test_shard",{"age": 1}) //按ranged分片
sh.shardCollection("testdb.test_shard2",{"age": "hashed"}) //按hash分片
sh.status() //查看分片节点
sh.addShard() //向集群中添加一个 shard
sh.getBalancerState() //查看平衡器
sh.disableBalancing() //禁用平衡器
sh.enableBalancing() //启用平衡器
db.runCommand( { removeShard: "mongodb0" } ) //删除分片mongodb0,迁移数据查看命令
db.runCommand( { movePrimary: "test", to: "mongodb1" }) //将数据库test未分片mongodb0的数据,迁移到mongodb1主分片。
db.adminCommand("flushRouterConfig") //处理分片后,刷新路由数据。
use config
db.databases.find() //查看所有数据库使用分片
db.settings.save({_id:"chunksize",value:1}) //将 chunk 的大小调整为 1MB
db.serverStatus().sharding
# 副本集 操作命令
rs.status() //查看成员的运行状态等信息
rs.config() //查看配置信息
rs.slaveOk() //允许在SECONDARY节点上进行查询操作,默认从节点不具有查询功能
rs.isMaster() //查询该节点是否是主节点
rs.add({}) //添加新的节点到该副本集中
rs.remove() //从副本集中删除节点
rs.stepDown //降级节点
db.printSlaveReplicationInfo() //查看同步情况
rs.addArb("172.20.0.16:27038") //添加仲裁节点
rs.add({_id : 2, host : "192.168.1.22:26003", arbiterOnly:true}) //添加仲裁节点
# 强制加入仲裁节点:
config=rs.conf()
config.members=[config.members[0],config.members[1],{_id:5,host:"127.0.0.1:27023",priority:5,arbiterOnly:"true"}]
rs.reconfig(config,{force:true})
# 强制主节点:
cfg = rs.conf()
cfg.members[0].priority = 0.5
cfg.members[1].priority = 0.5
cfg.members[2].priority = 1
rs.reconfig(cfg)
# 备份/恢复
mongodump -h 127.0.0.1:27017 -d test -o /data/backup/
mongorestore -h 127.0.0.1:27017 -d test --dir /data/db/test