1. 序言
- 本博客是上一博客的进阶版:mac M2安装单机版 MongoDB 7.x,上一博客可以看做是单机、单节点部署MongoDB
- 本博客将介绍单机、多服务部署MongoDB,实际就是伪分布式部署
2. 副本集(Replica Set)方式部署
2.1 什么是副本集?
- 多节点部署、主从部署等其实都是为了 MongoDB 的高可用,支持数据多副本、读写分离等
- 较新版本的MongoDB Master/slave 被废弃,通过副本集提供高可用和数据冗余
"msg":"Master/slave replication is no longer supported"
- 一个副本集由多个节点组成,其中一个节点是主节点(Primary),其余是从节点(Secondary)
- 主节点处理所有的写操作,从节点复制主节点的数据并可以处理读操作(如果启用了读偏好)
- 副本集的特点:
- 高可用性: 如果主节点发生故障,从节点会自动选举一个新的主节点。
- 数据冗余:数据在多个节点上复制,提供数据冗余。
- 读扩展:可以配置从节点处理读操作,以减轻主节点的负载。
2.2 副本集部署
2.2.1 启动3个MongoDB服务
-
在本机以前台进程的方式启动3个 MongoDB 服务,且指定副本集为
rs0
mongod --replSet rs0 --dbpath /Users/bytedance/mongodb11/data --port 27031 --bind_ip_all mongod --replSet rs0 --dbpath /Users/bytedance/mongodb12/data --port 27032 --bind_ip_all mongod --replSet rs0 --dbpath /Users/bytedance/mongodb13/data --port 27033 --bind_ip_all
-
如果想通过配置文件部署,服务1的配置文件如下
# 数据存储相关配置 storage: dbPath: /Users/bytedance/mongodb2/data journal: enabled: true # 网络相关配置 net: port: 27030 bindIp: 0.0.0.0 # 允许从所有IP地址访问 # 副本集相关配置 replication: replSetName: "rs0" # 日志相关配置 systemLog: destination: file logAppend: true logRotate: rename path: /Users/xxx/mongodb11/log/mongo.log # 进程管理相关配置 processManagement: fork: true # 以守护进程方式运行(后台运行) # 安全相关配置 security: authorization: disabled # 关闭用户认证
2.2.2 初始化副本集
-
通过mongosh登录访问其中一个服务:
mongosh --port 27032
-
执行如下命令初始化副本集合:
rs.initiate({ _id: "rs0", members: [ { _id: 0, host: "localhost:27031" }, { _id: 1, host: "localhost:27032" }, { _id: 2, host: "localhost:27033" } ] })
-
执行
rs.status()
验证副本集状态rs0 [direct: primary] test> rs.status() { set: 'rs0', ... # 其他信息省略 members: [ { _id: 0, # initiate 时指定的id name: 'localhost:27031', health: 1, state: 2, stateStr: 'SECONDARY', # 从节点 ... # 其他信息省略 }, { _id: 1, name: 'localhost:27032', health: 1, state: 1, stateStr: 'PRIMARY', # 主节点 ... # 其他信息省略 }, { _id: 2, name: 'localhost:27033', health: 1, state: 2, stateStr: 'SECONDARY', # 从节点 ... # 其他信息省略 } ], ok: 1, ... # 其他信息省略 }
-
还可以通过
rs.isMaster()
查看当前访问的服务是不是primary节点rs0 [direct: primary] test> rs.isMaster() { ... # 其他信息省略 hosts: [ 'localhost:27031', 'localhost:27032', 'localhost:27033' ], setName: 'rs0', setVersion: 1, ismaster: true, # 当前访问的是primary节点(服务) secondary: false, primary: 'localhost:27032', me: 'localhost:27032', .. # 其他信息省略 readOnly: false, ... # 其他信息省略 isWritablePrimary: true # 只能通过primary节点写入 }
2.3 副本集中数据的读写
-
从上面的信息就可以看出, 副本集的可以支持设置读写偏好、支持读写分离
-
例如,
isWritablePrimary: true
只能在primary节点写入,若在非primary节点插入数据将报错Uncaught: MongoBulkWriteError[NotWritablePrimary]: not primary
-
在primary节点插入数据
db.test_data.insertMany([ { name: "Bob", age: 25, sex: "male", city: "Los Angeles" }, { name: "Carol", age: 28, sex: "female", city: "Chicago" }, { name: "Dave", age: 35, sex: "male", city: "San Francisco" } ])
-
在任意节点查询数据
db.test_data.find({'name':"Bob"})
3. 部署分片(Sharding)集群
3.1 什么是分片?
- 在数据存储系统中,除了支持副本集(多副本存储全量数据),还支持分片(
Sharding
),MongoDB也不例外 - 分片是 MongoDB 提供的一种水平扩展机制,用于将数据分布在多个服务器上
- 分片具备以下特性:
- 水平扩展:通过将数据分布在多个分片上,解决单个服务器的存储和性能限制。
- 负载均衡:数据和请求可以在多个分片之间均匀分布。
- 高可用性:结合副本集使用,每个分片可以是一个副本集,从而提供高可用性。
3.2 副本集 vs 分片
- 相对副本集,分片会按照某种规则将数据拆分成多个split,每个split存储到相应的分片
- 以销售数据为例,形象化比喻:
- 副本集就是一个机房有多台机器,每台机器都将存储中国市场的销售数据
- 分片就是存在多个机房,每个机房存储只存储所在区域的销售数据。例如,华北机房只存储华北地区的销售数据,西南机房只存储西南地区的销售数据
- 同时,为了提供高可用和数据冗余,分片存储时,每个机房需要有多台机器,支持多副本储存该区域的销售数据
- 也就是说,一个分片就是一个副本集,分片内部通过副本集实现高可用和数据冗余机制
3.3 部署分片集群(副本集与分片的结合使用)
- 分片集群由配置服务器(Config Servers)、分片服务器(Shards)、和路由服务器(mongos)组成
- 配置服务器作为副本集:配置服务器存储集群的元数据,为了确保配置服务器的高可用性,配置服务器也通常设置为一个副本集
- 每个分片作为一个副本集:在分片集群中,每个分片通常是一个副本集。这意味着每个分片不仅能存储数据的一部分,还能提供高可用性和数据冗余。
- 路由服务器(mongos):mongos不存储数据,而是作为路由器将客户端请求路由到适当的分片,可以部署多个mongos实例以提供高可用性和负载均衡
3.3.1 部署配置服务器
-
使用如下命令,在本地启动三个配置服务器,注意提前创建好data目录
-
--configsvr
:表示这是一个配置服务器 -
--replSet rs0
:设置副本集,配置服务器的本质是一个副本集mongod --configsvr --replSet rs0 --dbpath /Users/xxx/mongodb1/data/configdb --port 27019 --bind_ip_all mongod --configsvr --replSet rs0 --dbpath /Users/xxx/mongodb2/data/configdb --port 27020 --bind_ip_all mongod --configsvr --replSet rs0 --dbpath /Users/xxx/mongodb3/data/configdb --port 27021 --bind_ip_all
-
-
访问某个配置服务,初始化副本集
mongosh --port 27019 # 访问配置服务 rs.initiate({ _id: "rs0", configsvr: true, members: [ { _id: 0, host: "localhost:27019" }, { _id: 1, host: "localhost:27020" }, { _id: 2, host: "localhost:27021" } ] })
-
PS: 若以后台服务方式启动,上述启动方式对应的配置文件如下
storage: dbPath: /Users/xxx/mongodb1/data/configdb journal: enabled: true # 网络相关配置 net: port: 27010 bindIp: 0.0.0.0 # 允许从所有IP地址访问 # 复制集相关配置 replication: replSetName: "rs0" # 设置role:分片集群中的配置服务器 sharding: clusterRole: "configsvr" # 日志相关配置 systemLog: destination: file logAppend: true logRotate: rename path: /Users/xxx/mongodb1/log/configd/mongo.log # 进程管理相关配置 processManagement: fork: true # 以守护进程方式运行(后台运行) # 安全相关配置 security: authorization: disabled # 关闭用户认证
3.3.2 部署分片服务器
shard1
-
使用如下命令,在本地启动三个分片服务器,注意提前创建好data目录
mongod --shardsvr --replSet shard1 --dbpath /Users/bytedance/mongodb1/data/shard --port 27022 --bind_ip_all mongod --shardsvr --replSet shard1 --dbpath /Users/bytedance/mongodb2/data/shard --port 27023 --bind_ip_all mongod --shardsvr --replSet shard1 --dbpath /Users/bytedance/mongodb3/data/shard --port 27024 --bind_ip_all
-
访问其中某个分片服务器,初始化分片信息
mongosh --port 27022 rs.initiate({ _id: "shard1", members: [ { _id: 0, host: "localhost:27022" }, { _id: 1, host: "localhost:27023" }, { _id: 2, host: "localhost:27024" } ] })
shard2
-
使用如下命令,在本地启动三个分片服务器,注意提前创建好data目录
mongod --shardsvr --replSet shard2 --dbpath /Users/bytedance/mongodb1/data/shard2 --port 27025 --bind_ip_all mongod --shardsvr --replSet shard2 --dbpath /Users/bytedance/mongodb2/data/shard2 --port 27026 --bind_ip_all mongod --shardsvr --replSet shard2 --dbpath /Users/bytedance/mongodb3/data/shard2 --port 27027 --bind_ip_all
-
访问其中某个分片服务器,初始化分片信息
mongosh --port 27025 rs.initiate({ _id: "shard2", members: [ { _id: 0, host: "localhost:27025" }, { _id: 1, host: "localhost:27026" }, { _id: 2, host: "localhost:27027" } ] })
3.3.4 部署mongos
-
先创建好mongos所需的相关目录
mkdir /Users/xxx/mongos cd /Users/xxx/mongos mkdir data log etc
-
在etc目录下创建mongos的配置文件,
mongos-config.yaml
net: bindIp: 0.0.0.0 port: 27017 sharding: configDB: rs0/localhost:27019,localhost:27020,localhost:27021
-
以前台方式启动mongos
mongos --config /Users/xxx/mongos/etc/mongos-config.yaml
-
访问mongos,添加分片(之前部署好的分片服务器)
mongosh --port 27017 sh.addShard("shard1/localhost:27022,localhost:27023,localhost:27024") sh.addShard("shard2/localhost:27025,localhost:27026,localhost:27027")
-
通过
sh.status()
查看shard信息# 关键信息 shards [ { _id: 'shard1', host: 'shard1/localhost:27022,localhost:27023,localhost:27024', state: 1, topologyTime: Timestamp({ t: 1727872634, i: 3 }) }, { _id: 'shard2', host: 'shard2/localhost:27025,localhost:27026,localhost:27027', state: 1, topologyTime: Timestamp({ t: 1727939236, i: 4 }) } ]
3.5 启用分片
3.5.1 启用数据库分片和集合分片
-
启用数据库分片和集合分片
use admin # 一定要先切换到admin, sh.enableSharding("test") use test sh.shardCollection("test.test_data", {user_id: 1}) # user_id作为分片的key
-
验证是否成功开启集合分片
use test db.test_data.getShardDistribution()
-
若有如下输出,说明开启成功
3.5.2 插入数据并查看
-
插入100条数据
# 数据示例 -- user_id: id000001; name: name1 for (let i = 1; i <= 2000; i++) { let user_id = `id${String(i).padStart(6, '0')}`; let name = `name${i}`; db.test_data.insertOne({ user_id: user_id, name: name }); }
-
执行
sh.status()
查看分片信息,但数据只分布到了shard2,要么是集群部署存在问题,要么跟公司的 “chatGPT” 回答一样- 分片键选择不当:如果分片键的值分布不均匀,可能会导致数据集中在一个分片上。例如,如果您的分片键是一个递增的字段(如时间戳或自增 ID),那么所有新插入的数据都会集中在同一个分片上
- 数据量不足:在数据量较小的情况下,MongoDB 可能不会立即将数据分布到多个分片上。MongoDB 需要一定的数据量来决定何时进行分片 (笔者倾向于该原因)
... # 其他信息省略,可以看到 'test.test_data' 集合成功开启了分片 collections: { 'test.test_data': { shardKey: { user_id: 1 }, unique: false, balancing: true, chunkMetadata: [ { shard: 'shard2', nChunks: 1 } ], chunks: [ { min: { user_id: MinKey() }, max: { user_id: MaxKey() }, 'on shard': 'shard2', 'last modified': Timestamp({ t: 1, i: 0 }) } ], tags: [] } }
-
PS: 公司的 “chatGPT” 还给出了其他的查看分片信息的方法,但无任何输出
use config db.chunks.find({ ns: "testDB.test_data" }).sort({ min: 1 }).pretty()
3.5.3 在shard2的各服务器查询数据
-
由于上面的集合只分布在shard2,现在连接shard2的任意服务器,执行如下查询命令
use test db.test_data.find({'name':"name1"})
-
均能从shard2的每个服务器查到数据,这也说明了,shard2分片自身就是一个副本集
4. 后记
- 网上查了很多MongoDB多副本部署,大部分的版本都比较低,还有
master/slave
之类的配置- MongoDB主从复制(master–>slave)环境搭建
- mongodb的主从同步配置:实战详细版(建议收藏)
- 笔者依靠公司的chartGPT完成部署后,发现这篇文章是最靠谱的:MongoDB数据库之主从复制配置实战
- 其他一些感悟
- AI真强大: 此次部署完全依靠公司的chartGPT,不懂就问、出问题也问,最后磕磕绊绊完成了部署
- 磨刀不误砍柴工: 部署分片集群时,一开始没有为认真了解分片集群的架构,不知道副本集是存储元数据的,导致部署失败