MONGO学习之旅(六、副本集)

副本集

在生产环境中,我们不建议使用单机版的MongoDB服务器,因为:

  • 单机版的MongoDB无法保证可靠性,一旦进程发生故障或者服务器宕机,业务将直接不可使用。
  • 一旦服务器上磁盘损坏,数据会直接丢失,而此时也没有任何副本可以用。

对于生产环境的数据库至少要保证一个或一个以上的可用副本。

Replication Set

对于MongoDB来说,数据高可用是通过副本集架构(Replication Set)实现的,由一个主节点(Primary)和若干个子节点(Secondary)组成,其架构图如下:

image.png

客户端通过数据库主节点写入数据,由备份节点进行复制同步,这样所有的节点都会同时拥有这些业务数据的副本。

早期版本中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角色。这个过程如下:

image.png

在投票中会伴随着一些冲突或者异常出现,为了保证达成最终的一致性,Raft协议还加入了以下的细节。

  • 在同一任期内,每个节点最多只能给一个Candidate投票(节点内部进行记录),任期内投票采用先到先得的原则。
  • 节点在收到Candidate的投票请求时,只有当对方的任期、操作日志时间至少与自己的一样新时,才会给它投票。
  • Candidate发起投票后,如果一直没有得到大多数票,则会一直保持这个状态直到超时,此后将继续发起新一轮任期的选举(Term自增),如果在投票期间检测到了Leader的心跳,则会判断Leader的任期是否至少和自己一样新,如果是则降级为Follower,并承认对方的Leader角色,否则不予理会。
  • 无论是Candidate节点还是Leader节点,一旦发现了其他节点有更新的任期(Term值),都会自动降级为Follower。

开始搭建

本次试验中我们采用docker容器搭建

拉取Mongo镜像

docker pull mongo:latest
image.png

我们用的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"
image.png

查看MongoDB实例IP地址

使用docker inspect 命令查看容器信息

docker inspect mongo_master
image.png

这个地址是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

进入之后大概是这个样子

image.png

初始化副本集配置

使用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: 服务器标识。

image.png

查看副本集配置

使用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

你可能感兴趣的:(MONGO学习之旅(六、副本集))