mongodb副本集杂谈

1.什么是mongodb副本集

mongodb副本集是一种在多台机器同步数据的进程,副本提供了数据冗余,扩展了数据可用性。在多台服务器保存数据以避免一台服务器出现问题导致数据的丢失。
在某些场景下,也可以通过使用副本集来扩展读取性能,因为客户端可以将读操作发送到不同的服务器上。在不同的数据中心维护数据副本可以提高分布式应用程序的数据本地化和可用性。您还可以维护额外的副本以实现特殊用途,比如灾难恢复、报告或备份。

2.mongodb副本集的结构和原理

2.1结构

mongodb副本集和之前的主从模式并不相同,副本集是一组mongod维护相同数据集的实例,一个副本集中可以包含多个数据承载节点和一个仲裁节点(可选)。在数据承载节点中,只有一个成员会被视为主节点,而其他节点则被认为是从节点或者是仲裁节点。
image

2.2原理

主节点:在副本集中只有主节点可以进行读写操作,主节点会记录所有改变数据库状态的操作,这些记录保存在oplog中。
从节点:只有读操作,从节点会通过oplog来复制数据进而保证和主机点的数据一致性,oplog本身具有幂等性,因此无论执行几次其结果都是一样的。
仲裁节点:只有选举作用,不会从主节点进行复制操作,因此仲裁节点是没有数据访问的压力,相对来说也不容易出现故障。
心跳检测:在整个副本集中需要时刻保持一定的通信才可以知道哪一个节点活着,哪一个几点挂掉了,mongodb的节点会相互之间每两秒发送一个pings包,如果对方节点在10秒钟内没有返回那么就会被标识为不能访问。
数据同步:副本集同步有初始化同步和keep同步。初始化同步也就是全量从节点数据同步;keep复制是指初始化同步之后,节点之间的增量同步。

当集群中主节点出现异常,副本集节点就会选举出一个新的主服务器,以保证整个集群的正常运行。原来的主节点会自动降级为从节点,以保证写入数据的一致性。
有一点需要注意,就是在副本集出现异常的时候,存活的节点数一定要大于副本集节点总数的一半,否则就无法选举出主节点,甚至有可能会出现主节点自动降级为从节点,导致整个副本集变为只读。因此,考虑增加不容易出现故障的仲裁节点,可以增加有效的选票,降低整个副本集不可用的风险,因为仲裁节点只参与投票,不接收复制的数据,也不会成为活跃节点。当然如果最后真的出现了副本集只读的情况,当主节点仍可以正常工作的时候,也是可以快速恢复副本集的读写状态的,这里我会在下面进行详解。
这里官方推荐的mongodb副本集节点最少是3台,并且建议副本集成员的数量为奇数,最多12个副本集节点,最多7个节点参与选举。限制副本集节点的数量主要是因为副本集节点参与选举,也会增加选举的时间增加复制的成本,最后可能会拖累整个集群。

3.实际生产中的选择

网络上有不少副本集的部署流程,这里就不在详细进行说明了。主要说一下在实际的生产环境下是怎么使用的。
实际环境中,由于之前我们使用的是mongodb3.X版本的,还可以使用主从模式来保证数据的安全,但是为了与时俱进,开始有项目使用mongodb4.X版本的,而这个版本官方是不推荐使用主从模式了的,为了能继续保证数据的安全性,我们需要另外一种模式来替代之前的主从模式。当查阅官方的文档之后,目前是比较推荐使用副本集模式或者是分片模式的。上面已经简单了解了副本集模式,那么分片模式是怎么样的呢?这里我也做一个简单的说明:
分片模式:是指将数据库进行拆分,将其分散在不同的机器上的过程。简单理解就是我把数据分散到不同的机器上,那么我不需要功能特别强大的服务器就可以存储更多的数据以及处理更大的负载。

结合到我们目前的一个使用场景,游戏数据库的使用,考虑开服型,服的数量会比较多,为了保证数据的一致性,因此不考虑分片这种模式。那目前来看只有副本集模式是比较符合我们目前的需求的。接下来就是需要对副本集进行系统性的测试了。如果测试没有什么问题,就可以灰度到线上先进行测试使用,当一段测试一段时间没有什么问题后,继而就可以正式投入到线上环境使用了。

首先是考虑的是副本集的节点数,按照官方提供的建议是最好使用奇数个数量的节点数,以保证当有节点出现问题的时候,剩下的节点数和故障节点数不会出现相同数量而导致无法选出新的主节点,而出现"脑死亡"。

官方建议是不少于3个,太多的节点也会影响整个集群的性能,综合考虑觉得3个是比较合适的,即一个主节点,一个从节点(仅做数据备份使用)和一个仲裁节点。
image

3.1部署

点击查看代码
测试环境:
master:192.168.130.130
slave:192.168.130.131
仲裁节点:192.168.130.31
mongodb版本:4.0.4
点击查看代码
初始化环境配置
config = { _id:"test", members:[{_id:0,host:"xxxxxx:27017",priority:2}, {_id:1,host:"xxxxxx:27017",priority:2}, {_id:2,host:"xxxxxx:27018",arbiterOnly:true}]}
rs.initiate(config) 
点击查看代码
查看配置是否生效
rs.status()
{
        "_id" : "test",
        "version" : 1,
        "protocolVersion" : NumberLong(1),
        "writeConcernMajorityJournalDefault" : true,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "192.168.130.130:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 2,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                },
                {
                        "_id" : 1,
                        "host" : "192.168.130.131:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 0,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                },
                {
                        "_id" : 2,
                        "host" : "192.168.130.131:27018",
                        "arbiterOnly" : true,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 0,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                }
        ],
        "settings" : {
                "chainingAllowed" : true,
                "heartbeatIntervalMillis" : 2000,
                "heartbeatTimeoutSecs" : 10,
                "electionTimeoutMillis" : 10000,
                "catchUpTimeoutMillis" : -1,
                "catchUpTakeoverDelayMillis" : 30000,
                "getLastErrorModes" : {

                },
                "getLastErrorDefaults" : {
                        "w" : 1,
                        "wtimeout" : 0
                },
                "replicaSetId" : ObjectId("6324500235476054dd19b2d7")
        }
}
这里配置从节点的priority为0,意思就是该从节点不会被选择为主节点,votes设置为1,意思是该从节点具有投票权。这样从节点就只是作为数据备份来使用,而不会在主节点出现故障的时候被选举为主节点,符合我们当前的需求。

4.故障处理

4.1 主节点故障无法启动

模拟主节点故障无法启动,拉去从节点直接启动,观察是否启动正常,副本集是否正常。
1.停掉主节点,并把主节点数据目录修改为mongo_bak
2.拉去从节点到主节点机器上,直接启动
image
image
image
image
可以发现整个状态正常,过程中也不需要修改什么东西,拉取直接启动即可使用。

4.2 集群整个状态只读

如果出现集群节点只剩下主节点正常,或者不足以维持选举出主节点的时候,整个集群会变成只读状态,这种情况出现的概率虽然不大,但是也是可能会在实际生产环境中出现的,如果一旦出现,如果不能快速解决的话,对线上的业务也是会产生比较大的影响的。
经过研究之后发现一个比较好的方式就是强制删除故障的节点,保留集群中剩下的节点可以维持选举的状态。因为我们目前的集群是3个节点,如果要保证主节点重新恢复读写状态,就需要把从节点和仲裁节点全部剔除掉。具体的一个处理方式如下:

点击查看代码
cfg = rs.conf()
cfg.members = [cfg.members[0]]   ##保留主节点
rs.reconfig(cfg, {force : true})  ##强制重新生成配置

image
上图已经显示了只剩下主节点,主节点已变成只读状态。
image
按照上面步骤操作之后查看,主节点已恢复正常,之后就可以等从节点和总裁节点修复好了之后再重新添加回去即可。

点击查看代码
rs.add( { host: "192.168.130.131:27018", arbiterOnly:true} )  #仲裁节点
rs.add( { host: "192.168.130.131:27017", priority:0} )   #从节点

5.参考

《MongoDB中文手册》

posted @ 2022-12-01 11:45  coffee_kai  阅读(344)  评论(0编辑  收藏  举报