作者:coffeekai
在游戏后端数据库的选择中,MySQL可以说占据了很大一部分,Mongodb的使用就相对少了一些。我们项目中也只是有个别的项目是在使用着Mongodb数据库作为后端的数据库,但也不是全部都是Mongodb数据库,只是在某个场景需要使用到,因此我们对于Mongodb数据库的版本要求和性能要求并不是很高。
对于滚服模式的小型生态的项目来说,一台机器上的区服都共用一个Mongodb数据库,而且机器上区服迭代的速度相对也比较快,并且本身它们也不是一种集群,而是一个个独立的后端程序,因此不适合使用比较复杂的集群模式,而比较适合主从这种简单的模式,能保证主库异常的时候从库可以快速拿来进行恢复。
但随着项目的发展,有项目在某个场景下实现了登录的集群,也使用了比较高版本的Mongodb数据库,并且这个版本的数据库又没办法使用主从模式,因此目前需要考虑使用另外两种模式中的一个来替代主从模式实现数据的备份,同时也是为了未来如果有大世界的项目需要使用到Mongodb集群而做一些技术的储备和相关的探究。
mongodb可以支持的集群方式有三种,主从模式,副本集模式以及分片模式。只不过4.X之后主从模式已经没有了,因此目前的选择只能有副本集和分片两种。那么这两种模式哪种会更符合我们的需求呢?那么这面先简单介绍下这两种模式。
副本集模式:mongodb副本集是一种在多台机器同步数据的进程,副本提供了数据冗余,扩展了数据的可用性,在多台服务器保存数据以避免一台服务器出现问题导致数据的丢失。在某些场景下,也可也通过使用副本集来扩展读性能,因为客户端可以将读操作发送到不同的服务器上。在不同的数据中心维护数据副本可以提高分布式应用程序的数据本地化和可用性。您还可以维护额外的副本以实现特殊用途,比如灾难恢复、报告或备份。
分片模式:分片是指将数据库进行拆分,将其分散在不同的机器上的过程。简单理解就是我把数据分散到不同的机器上,那么我不需要功能特别强大的服务器就可以存储更多的数据以及处理更大的负载。其基本思想就是将集合切成小块,然后把这些小块分散到若干的分片中,每个分片只负责总数据的一部分,然后通过mongos路由进行操作。
通过这两种模式的简单介绍,我们可以看出,副本集模式相比较分片模式更为适合做数据的备份、故障转移,而分片模式则更为适合磁盘空间不足、写数据性能不够等。因此相比较而言副本集模式更为符合我们的需求。
mongodb副本集和之前的主从模式架构不同,副本集中包含主节点、从节点,在需要的时候还可以加入仲裁节点。在副本集中有一个主节点,其余的节点称为从节点。当主节点出现异常的时候会从其他正常的从节点中选择出一个节点作为新的主节点,即故障转移。
主节点: 在副本集中只有主节点可以进行读写操作,主节点会记录所有改变数据库状态的操作,这些记录保存在oplog中。
从节点: 只有读操作,从节点会通过oplog来复制数据进而保证和主机点的数据一致性,oplog本身具有幂等性,因此无论执行几次其结果都是一样的。
仲裁节点: 只有选举作用,不会从主节点进行复制操作,因此仲裁节点是没有数据访问的压力,相对来说也不容易出现故障。
在副本集架构上,官方给出的说法是最少3个,最多12个,然后最多要有7个节点参与选举,其限制副本集节点的数量主要是因为副本集节点参与选举,也会增加选举的时间增加复制的成本,最后可能会拖累整个集群。而且官方还建议副本集的成员数量最好是奇数,避免出现当集群某些节点故障,导致正常节点和故障节点数一致或者正常节点数少于故障节点数,从而无法正常选举出主节点。结合当前我们的使用场景,是替代之前的主从模式,也就是数据的备份,因此在架构选择上要满足下面几个条件:
初始化代码:
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)
结果片段截取:
"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 } ],
副本集状态说明:
|
状态字符串 | 说明 |
0 | STARTUP | 表示节点正在通过ping与其他节点沟通,分享配置数据 |
1 | PRIMARY | 主节点,副本集有且仅有一个主节点 |
2 | SECONDARY | 从节点,在故障转移的时候可能会成为主节点 |
3 | RECOVERING | 该节点不能用于读写操作,通常会在故障转移或者添加新节点后看到这个状态,在恢复时,数据文件通常正在同步中,可以查看正在恢复的节点的日志进行验证。 |
4 | FATAL | 网络连接仍然建立着,但节点对ping没有响应,通常说明托管该节点的机器发生了致命错误 |
5 | STARTUP2 | 初始数据文件正在同步中 |
6 | UNKNOWN | 还在等待建立网络连接 |
7 | ARBITER | 仲裁节点 |
8 | DOWN | “心跳”检测没应答 |
9 | ROLLBACK | 正在进行回滚 |
当所有节点的状态都是1,2或者7,并且至少有一个节点是主节点的时候,可以认为副本集是稳定并且在线的。这里把从节点的priority为0,意思就是该从节点不会被选择为主节点,votes设置为1,意思是该从节点具有投票权。这样从节点就只是作为数据备份来使用,而不会在主节点出现故障的时候被选举为主节点,符合我们当前的需求。
为之后应用于线上环境,避免在出现问题的时候手忙脚乱,要先思考可能会遇到的问题,并给出比较合适的处理方案。这里就只针对3个节点可能会出现的除了主节点另外两个节点都故障的情况进行下讨论。
情景: 主节点正常,从节点和仲裁节点故障。
结果: 导致整个副本集集群只读,即主节点虽然正常但是会降级为只读状态。线上数据无法写入到数据库中,如果长时间无法解决的话,会对线上业务产生比较大的影响。
由上图可以看到除了主节点,另外两个节点的状态都变成了异常状态,并且主节点的状态变成了只读的状态。
处理思路: 解决的思路大方向就是如何把主节点恢复回来。首先先了解其会出现这种问题的原理,无非就是故障的节点数大于了正常的节点数,导致整个集群无法选出主节点。知道了这些,那解决的思路也就是看是否可以增加仲裁节点来平衡整个集群,或者删除这些故障的节点。考虑到从节点是没办法用正常的操作去重新添加或者删除,这里官方给出的一种比较切合的一种方案是使用rs.reconfig(cfg, {force : true})去强制处理某个节点。这里推荐使用删除的方式,因为添加需要额外去再去配置节点,时间相比较会稍微长些,而删除的话速度更快,具体的操作方式如下:
cfg = rs.conf() cfg.members = [cfg.members[0]] ##保留主节点 rs.reconfig(cfg, {force : true}) ##强制重新生成配置
按照上面的操作处理之后我们可以看到下面的结果:
整个集群剩下一个主节点,并且主节点的状态也转变为了之前的状态。这个操作如果使用脚本进行操作,速度是非常快的,据测试大概1~2分钟就可以使原本只读状态的主节点恢复为正常状态。对线上的影响也是会大大的降低。
echo -e "cfg = rs.conf() \ncfg.members = [cfg.members[0]] \nrs.reconfig(cfg,force : true})"${MONGO_BINadmin &> /dev/null
最后可以在从节点和仲裁节点都恢复之后再重新添加进来即可。重新添加之后整个集群仍和之前是一样的。
rs.add( { host: "192.168.130.131:27018", arbiterOnly:true} ) #仲裁节点 rs.add( { host: "192.168.130.131:27017", priority:0} ) #从节点
在不考虑项目的特殊场景的使用的话,其实主从模式就已经满足了我们当前的使用了,但是如果长远来看,并不能排除是否有项目会有大世界,然后刚好又需要使用到Mongodb集群,那这样的话,副本集集群模式就可以说相当适配了,既可以实现故障转移,又可以实现数据备份。
《MongoDB中文手册》
《MongoDB实战》