副本集
在生产环境中,我们不建议使用单机版的MongoDB服务器,因为:
- 单机版的MongoDB无法保证可靠性,一旦进程发生故障或者服务器宕机,业务将直接不可使用。
- 一旦服务器上磁盘损坏,数据会直接丢失,而此时也没有任何副本可以用。
对于生产环境的数据库至少要保证一个或一个以上的可用副本。
Replication Set
对于MongoDB来说,数据高可用是通过副本集架构(Replication Set)实现的,由一个主节点(Primary)和若干个子节点(Secondary)组成,其架构图如下:
客户端通过数据库主节点写入数据,由备份节点进行复制同步,这样所有的节点都会同时拥有这些业务数据的副本。
早期版本中MongoDB使用了一种Master-Slave的架构,该做法在MongoDB 3.4版本之后已废弃。
集群选举
Raft选举算法
MongoDB的副本选举是通过Raft算法来实现的。
Raft是一种强一致性、去中心化、高可用的分布式共识算法。所谓共识,就是多个节点对某个事情达成一致的看法,即使实在部分节点故障、网络延时、网络分割的情况下。
协议中的角色
- Leader:领导者,Leader会向其他节点发送心跳,同时负责处理客户端的读写操作,包括将数据同步到其他节点。
- Follower:追随者,响应来自Leader和Candidate的投票请求,如果在一定时间内没有收到Leader的心跳,则会传唤为Candidate。
- Candidate:候选者,Follower主动选举转换成Candidate,获得投票后会成为Leader。
选举的流程
在开始的时候,所有的节点都是Follower,此时大家都没有办法收到Leader的心跳。接下来,A节点先是给自己投一票,然后接着向其他节点发送投票请求,一旦A节点获得了集群中大多数节点的投票,那么A节点将成为Leader节点,同时向其他节点广播心跳,此时来申明自己的Leader角色。这个过程如下:
在投票中会伴随着一些冲突或者异常出现,为了保证达成最终的一致性,Raft协议还加入了以下的细节。
- 在同一任期内,每个节点最多只能给一个Candidate投票(节点内部进行记录),任期内投票采用先到先得的原则。
- 节点在收到Candidate的投票请求时,只有当对方的任期、操作日志时间至少与自己的一样新时,才会给它投票。
- Candidate发起投票后,如果一直没有得到大多数票,则会一直保持这个状态直到超时,此后将继续发起新一轮任期的选举(Term自增),如果在投票期间检测到了Leader的心跳,则会判断Leader的任期是否至少和自己一样新,如果是则降级为Follower,并承认对方的Leader角色,否则不予理会。
- 无论是Candidate节点还是Leader节点,一旦发现了其他节点有更新的任期(Term值),都会自动降级为Follower。
开始搭建
本次试验中我们采用docker容器搭建
拉取Mongo镜像
docker pull mongo:latest
我们用的MongoDB版本号是5.0
启动MongoDB容器
启动三个MongoDB实例,其中mongo_master 为主节点, mongo_node1、mongo_node2为从节点。
主节点负责读写操作,从节点可读但不可写。
docker run -itd --name mongo_master -p 27017:27017 mongo --replSet "re0"
docker run -itd --name mongo_node1 -p 27018:27017 mongo --replSet "re0"
docker run -itd --name mongo_node2 -p 27019:27017 mongo --replSet "re0"
查看MongoDB实例IP地址
使用docker inspect 命令查看容器信息
docker inspect mongo_master
这个地址是docker内部虚拟网络地址,容器之间可以访问,外部是访问不到的,在配置副本集的时候需要这个地址。
本次安装的地址为:
172.17.0.2
172.17.0.3
172.17.0.4
172.17.0.2地址为Master地址,另外两个为node节点。
副本集配置
进入master容器,进行集群配置
docker exec -it mongo_master mongo
进入之后大概是这个样子
初始化副本集配置
使用rs.initiate()方法初始化副本集配置
var config = {
_id:"re0",
members:[
{_id:0,host:"172.17.0.2:27017"},
{_id:1,host:"172.17.0.3:27017"},
{_id:2,host:"172.17.0.4:27017"}
]
}
rs.initiate(config)
config._id: 这个是docker启动时 replSet 设置的值。
members_id: 服务器标识。
查看副本集配置
使用rs.conf()方法查看配置信息
rs.conf()
执行结果如下
re0:PRIMARY> rs.conf()
{
"_id" : "re0",
"version" : 1,
"protocolVersion" : NumberLong(1),
"members" : [
{
"_id" : 0,
"host" : "172.17.0.2:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "172.17.0.3:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "172.17.0.4:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"catchUpTimeoutMillis" : 60000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("6106384c55f1745e07001366")
}
}
到这里,副本集基本上就配置好了,现在让我们插入一条数据试试
测试
- 插入数据
use order
db.user.insert({
username:"zhouhc",
nickname:"呢称",
password: "123123"
})
执行结果如下:
re0:PRIMARY> use order
switched to db order
re0:PRIMARY> db.user.insert({
... username:"zhouhc",
... nickname:"呢称",
... password: "123123"
... })
WriteResult({ "nInserted" : 1 })
re0:PRIMARY> db.user.find()
{ "_id" : ObjectId("61064400ca2a42d1044bd372"), "username" : "zhouhc", "nickname" : "呢称", "password" : "123123" }
连接node节点查看以下
re0:SECONDARY> exit
bye
root@6b5043311fe9:/# mongo --host 172.17.0.3:27017
MongoDB shell version v3.4.9
connecting to: mongodb://172.17.0.3:27017/
MongoDB server version: 3.4.9
Server has startup warnings:
2021-08-01T05:41:32.227+0000 I STORAGE [initandlisten]
2021-08-01T05:41:32.227+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2021-08-01T05:41:32.227+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten]
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten]
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten]
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten] ** We suggest setting it to 'never'
2021-08-01T05:41:32.259+0000 I CONTROL [initandlisten]
re0:SECONDARY> use order
switched to db order
re0:SECONDARY> db.user.find()
Error: error: {
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk"
}
re0:SECONDARY>
上述查询出现“not master and slaveOk = false "错误, 这很正常,默认情况下SECONDARY是不允许读写的,需要设置slaveOk
例如
re0:SECONDARY> db.getMongo().setSlaveOk()
re0:SECONDARY> db.user.find()
{ "_id" : ObjectId("61064400ca2a42d1044bd372"), "username" : "zhouhc", "nickname" : "呢称", "password" : "123123" }
db.getMongo().setSlaveOk()只是临时的,一旦退出在进入Mongo也会报错。如果需要永久性设置可以参考:MONGODB副本集的从库永久性设置SETSLAVEOK
宕机恢复
假设业务场景Master节点宕机了,那么将从Node1和Node2中选举一个新的PRIMARY节点
如
C:\Users\zhc12>docker stop mongo_master
mongo_master
C:\Users\zhc12>docker exec -it mongo_node2 mongo
MongoDB shell version v3.4.9
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.9
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
http://docs.mongodb.org/
Questions? Try the support group
http://groups.google.com/group/mongodb-user
Server has startup warnings:
2021-08-01T05:42:06.068+0000 I STORAGE [initandlisten]
2021-08-01T05:42:06.068+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2021-08-01T05:42:06.068+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten]
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten]
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten]
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten] ** We suggest setting it to 'never'
2021-08-01T05:42:06.099+0000 I CONTROL [initandlisten]
re0:PRIMARY>
这个时候NODE2就是副本集的PRIMARY节点。
这个时候重新启动Mongo_master节点
C:\Users\zhc12>docker start mongo_master
mongo_master
进入Mongo_master节点查看以下
C:\Users\zhc12>docker exec -it mongo_master mongo
MongoDB shell version v3.4.9
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.9
Server has startup warnings:
2021-08-01T09:15:04.910+0000 I STORAGE [initandlisten]
2021-08-01T09:15:04.910+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2021-08-01T09:15:04.910+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2021-08-01T09:15:05.256+0000 I CONTROL [initandlisten]
2021-08-01T09:15:05.256+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2021-08-01T09:15:05.256+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2021-08-01T09:15:05.256+0000 I CONTROL [initandlisten]
2021-08-01T09:15:05.257+0000 I CONTROL [initandlisten]
2021-08-01T09:15:05.257+0000 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2021-08-01T09:15:05.257+0000 I CONTROL [initandlisten] ** We suggest setting it to 'never'
2021-08-01T09:15:05.257+0000 I CONTROL [initandlisten]
re0:SECONDARY>
MongoBD已经成为了SECONDARY节点。到这里Mongo副本集已经基本布置好了,更多Mongo操作会在之后的博客中补充。
参考
MongoDB进阶与实战:微服务整合、性能优化、架构管理
MONGODB副本集的从库永久性设置SETSLAVEOK