之前的博文 MongoDB主从复制架构中 ,讲述了MongoDB用来实现数据冗余的一种方式,也提到了主从复制的缺点,当主节点出现故障时,数据库服务不可用,因此很多数据库也都选择了新的方式替代主从复制 , 比如MongoDB的复制集、redis的哨兵机制等,本文就是描述 MongoDB的复制集的相关特性。
1. MongoDB中副本集的特点
副本集(或者叫复制集,名字无所谓)是MongoDB用来实现数据冗余的另一个方式,基本架构和主从复制基本一致,如下图所示
不同之处是
- 主从复制中,需要显示指定主服务器和从服务器,并且主服务器挂掉后,需要用户手动选择一个从服务器作为新的主服务器
- 而 ”副本集“模式下,可以做到自动将其中一个成员升级为新的主服务器
总结一下,MongoDB中副本集,有如下的特点
N 个节点的集群
任何节点可作为主节点
所有写入操作都在主节点上
自动故障转移
自动恢复
当然为了实现自动讲一个成员升级为新的主服务器,副本集除了配置搭建过程和“主从复制”的过程不一样,还需要额外增加一些处理
- 正常运行中
各个主从节点的状态 ,如下图 ,彼此之间,利用心跳协议相互检测,判断对方是不是还活着(还能连接到); 当进行写操作时,主节点将数据同步到各个secondary
此外,还有一种节点,不保存数据,只负责在主节点挂掉之后,投票从“从节点”中选择新的主节点,结构如下
- 当节点发生了问题
这种情况下, 投票节点,选举产生新的主节点,并形成新的主从结构,过程如下图
2 . 搭建的基本步骤
在MongoDB中,非常灵活的实现了副本集的功能,可以通过配置项指定副本集, 后期也可以通过命令动态添加节点。我们在配置好了副本集中的主节点之后,可以灵活的选择,通过配置项指定,或者是通过添加节点命令添加,在下边的步骤中,我们先配置一个主节点, 两个从节点,再利用添加节点的命令,动态添加第三个节点,基本步骤如下
为了是程序建的条理,我们对每一个MongoDB实例,都会建立单独的目录
-
为每个实例建立单独的文件夹
mkdir mongo_repli_set_master mkdir mongo_repli_set_slave1 mkdir mongo_repli_set_slave2 mkdir mongo_repli_set_avater
bin 目录可以共用一个,也可以直接把一个实例的bin也copy出多份,每个实例的目录如下
cd mongo_repli_set_master mkdir bin # 保存包含一些mongod mongo命令等 mkdir data # 保存数据文件 mkdir log # 保存日志文件 sudo vi mongod.config # 配置文件,启动时需要带上
查看文件夹是否有权限,没有读、写、可执行权限的话,需要修改权限
ls -l data
-
编辑配置文件
配置文件中,只需要指定配置集的名字即可 (第五步时,会用到),如下replSet=myRepl #复制集的名称 oplogSize=1024 #Select the replication log size
简单的配置文件(完整的),如下
#一些基本的配置 dbpath = data/db # 用的相对bin目录的 “相对目录”,建议用绝对目录 logpath = log/mongodb/mongo.log logappend = true # 绑定id后,只可以利用ip访问 ,并且只可以利用这个ip访问 bind_ip = 192.168.0.126 port=27110 # 指定主服务器的端口 maxConns=100 #最大同时连接数 journal=true #每次写入会记录一条操作日志(通过journal可以重新构造出写入的数据) replSet=myRepl #复制集的名称 oplogSize=1024 #Select the replication log size
不同的实例,通过修改端口,模拟,其他选项可以不用修改 (因为本文中每个实例的基本目录都是一样的)
-
分别启动三个实例
通过shell命令启动,进入各自的目录中,直接mongodcd mongo_repli_set_master bin/mongod --config mongod.conf
查看日志文件,确定是否启动成功。
-
连接到一个用作主节点的MongoDB数据库,进行复制集节点配置
比如本文中,想把27110端口的MongoDB实例,作为primary (主节点),可以连接到27110, 进行配置,如下> mongo 192.168.0.126:27110 > cfg = {"_id" : "myRepl", "members" : [ {"_id" : 0,"host" : "192.168.0.126:27110"}, {"_id" : 1,"host" : "192.168.0.126:27111"} ]} > rs.initiate(cfg);
简单说一下,上边的initiate命令 。 通过配置项初始化复制集的节点信息,各个配置项的意思 (上边cfg中各个数据的意思)
cfg = {"_id" : "复制集的名称", "members" : [ {"_id" : 0,"host" : "主节点ip:主节点端口号"}]}
_id 就是复制集的名称,需要和配置文件中的名称一致。 members 是复制集中的成员,是一个数组,可以配置一到多个, 每个成员配置中,需要指定host ,_id是自增的整数。
如果执行成功,会有如下提示信息,并且进入primary的命令行,如下{ "ok" : 1, "operationTime" : Timestamp(1571392081, 1), "$clusterTime" : { "clusterTime" : Timestamp(1571392081, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } } myRepl:SECONDARY>
-
运行之后,一些动态修改节点命令,查看节点状态
上边指定了两个节点,如果需要继续添加节点,或者删除不想要的节点,可以用下边的命令rs.add("192.168.0.126:27112") rs.remove("192.168.100.201:27019")
添加投票节点:
rs.addArb("192.168.0.126:27113")
查看节点的状态,查看是否是主节点
rs.status() #查看复制集结果 rs.isMaster() # 查看是否是主节点
-
模拟测试
上边的复制集搭建成功之后,可以进行一些测试,比如
主节点插入数据,查看从节点是否用数据# 连接到主节点,新建数据库,插入数据 mongo 192.168.0.126:27110 use schdb db.teacher.insert({a:1}) #连接到从节点,查看是否存在数据 mongo 192.168.0.126:27110 rs.slaveOk() # 默认情况下,从节点无法读取数据,需要执行该命令 use schdb db.teacher.find()
主节点挂掉,是否会自动选举新的主节点,通过kill杀进程的方式,模拟测试, 这里有个小技巧,就是在启动每个MongoDB进程的时候,都记录一下其进程信息(包括进程Id等),这样可以在杀进程的时候,知道哪个是主节点 ,哪个是从节点,哪个是投票节点
kill 10604 #杀掉主节点
连接到剩余的从节点, 查看结果, 发现一个从节点变成了主节点
mongo 192.168.0.126 :27112 # 发现变成了主节点
3. 投票选举规则
-
投票的原则
在进行投票的时候, 如果 某一节点投票数占当前架构中多数,那么该节点就会成为新的主节点。 比如一个副本集架构中, 存在着四个节点, 分别是一个主节点,两个从节点, 一个投票节点,设有一个节点出现故障,那么只要有是三个节点投票选择一个节点的话,就能把对应节点切换为主节点,但是如果再有一台节点出现故障,那么只剩下两台,无论怎么选举,都不能选出一个(因为此时,最高才2/4),换句话说,四个节点的复制集架构,最多可以有一台出现故障
但是如果是五个节点呢,则可以保证坏两台的情况下,还可以选出主节点,这实际上是比上边的四台要健壮一些的,
所以呢,如果可能的话,尽可能在副本集中使用奇数个数据成员,不使用投票节点。
-
选举的误区
复制集架构中,可以不使用投票节点 。之前,我错误的认为,投票节点是必须的,有投票节点负责进行选举,但实际上是每个节点都会进行选举,它们每时每刻都会进行心跳检测检查是否连接,
在我杀掉当前的主节点(模拟主节点崩溃),其他节点(包括备份节点和投票节点)日志如下2019-10-21T11:48:44.098+0800 I ASIO [Replication] Connecting to 192.168.0.126:27110 2019-10-21T11:48:44.098+0800 I ASIO [Replication] Failed to connect to 192.168.0.126:27110 - HostUnreachable: Error connecting to 192.168.0.126:27110 :: caused by :: Connection refused 2019-10-21T11:48:44.098+0800 I CONNPOOL [Replication] Dropping all pooled connections to 192.168.0.126:27110 due to HostUnreachable: Error connecting to 192.168.0.126:27110 :: caused by :: Connection refused 2019-10-21T11:48:44.098+0800 I REPL_HB [replexec-1] Error in heartbeat (requestId: 3548) to 192.168.0.126:27110, response status: HostUnreachable: Error connecting to 192.168.0.126:27110 :: caused by :: Connection refused
-
节点的优先级
在上边测试的例子中,我们不确定剩下的健在节点中,哪一个会被选为主节点,但实际上可以设置一个成员节点的优先级,表示这个成员渴望成为主节点的程度,
默认的是1 ,比如备份节点的优先级是1,主节点的优先级是1 。优先级为0的成员永远不能成为主节点,这种节点称为被动节点。
拥有高优先级的成员会优先选举为主节点,比如在添加一个优先级高一点的节点,测试
rs.add({"_id":4,"host":"127.0.0.1:27114","priority":1.5})
发现 27114端口对应的节点,成为新的主节点
-
查看config中其他属性说明
当我们在利用辅助函数 rs查看复制集结构时,还有一些其他属性,如下"members" : [ { "_id" : 0, "host" : "192.168.0.126:27110", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : { }, "slaveDelay" : NumberLong(0), "votes" : 1 }, ... ]
_id | 成员的整数标识符。值必须介于0到255之间, 设置后无法更改。需要注意的是,在更新副本集对象时,可以用数组索引访问Members数组中副本集成员,从0开始, 但是和Members[n]._id没有任何关系,不要混了 |
host | 该节点的host地址,需要注意的是,不能用"localhost", 除非所有节点在一台机器上(但这样没有任何实际意义,说崩溃,就全崩溃了) |
arbiterOnly | 是否为投票节点, true表示是仲裁节点 |
buildIndexes | 建立索引,表示mongod是否可以在此成员上建立索引,只能在添加的时候设置该属性, 添加到集合之后,不能通过rs.add() 或者rs.reconfig()修改。 需要注意 , 如果将buildIndexes设为false, 也需要将该节点的priority设置为0 |
hidden | 是否为隐藏节点,true时isMaster()中看不到该节点 |
votes | 服务器将在副本集选择中投票的票数。每个成员的票数是1或0,而仲裁员始终只有1票。优先级大于0的不能此属性必须大于0。一个复制集最高可以有50个节点,但只有七个投票节点,其他节点均无表决权,请将其设置为0 |
priority | 优先级 , 仲裁节点优先级为0 , |
slaveDelay | 是否为延迟节点,延迟节点的数据会比主节点延迟指定的时间(单位是秒),目的是当主节点数据被删除之后,可以将数据从先前的备份中恢复。 该属性要求成员的优先级是0 。 如果在应用会将读请求路由到备份节点,应该将延迟备份节点隐藏掉,避免读请求被路由到延迟备份节点 |