复制集数据同步
使用复制集的过程中,当主节点有数据变更的时候,备份节点都会进行数据同步操作,需要注意的是,在Mongo的复制集中,备份节点进行数据同步是依赖主节点的oplog
,oplog
数据存放在主节点local数据库里的一个固定集合中,每个备份节点自身也会维护一份自身的oplog
,记录着每一次从主节点同步过来的复制数据的操作。这样,每个备份节点也方便提供给其他备份节点比较和复制使用。备份节点从当前使用的数据集上执行这些操作, 然后再将这些操作写入自己的oplog
,如果遇到故障或者同步操作失败的情况(正常情况不会出现,只有当前节点数据丢失或者部分数据损坏才会出现这样),这个时候备份节点就会停止从主节点进行复制数据操作。
如果说备份节点因为一些原因导致奔溃或者故障了,我们重新启动当前节点以后,会从自身维护的oplog里面的最后一条数据开始重新进行同步,但是由于复制数据的过程,是先把数据复制过来,写入到本地以后再去写入oplog,那么就有可能出现数据同步了,oplog还没写入的情况,为了避免出现重复写入导致出现脏数据,mongo官方做了处理,保证了多次执行oplog数据操作和一次执行的结果是一样的。需要注意的一点是,oplog的内存大小是固定的,也就是说保存操作的数量是固定的,一般我们使用比较稳定的话,oplog的增长速率也会比较稳定,但是如果我们执行了类似db.collection.remove()
操作,这种情况下,如果清理了1亿条文档数据,那么就会在oplog中存入1亿条操作记录,直至填满。
初始化启动后同步流程
当我们启动复制集以后,首先会检查当前状态是否良好,并且确认从哪一台复制集节点进行数据同步操作,当发现无法从该节点进行数据同步的时候,会选择其他的节点尝试进行数据同步操作。当确认以后就开始了数据同步的过程:
- 确定了从哪一个节点进行同步数据源,会在当前的
local.me
中创建一个标识符,并且将自身的所有用户数据库删除,开始以一个全新的状态来开始数据同步。 - 接着会从复制源上将所有的数据复制到本地,这个过程称为克隆数据,也是最耗时的操作
- 当从复制源获取到数据以后,就开始操作oplog了,克隆过程中所有复制来的数据的操作都会记录到oplog中,因此需要注意,在克隆过程中,由于较为耗时,可能会把复制源的数据进行变动或者修改,导致部分数据无法克隆,这种情况只能等待下一次初始化同步才可以同步回来,因此我们在做初始化同步操作的时候需要格外注意
- 在oplog同步的过程中,会将oplog的每一步操作都记录下来,直到初始化同步数据源操作完成。这个时候,理论上当前mongo副本集的数据应该和同步数据源的某个时间节点的数据完全一致,这个时候就可以创建数据索引了。如果说集合数据比较多,切索引数量较多,这个创建索引的过程就会较为耗时。
- 如果初始化同步完成后,发现当前节点的数据依然远远落后于同步的数据源的数据,那么这个时候oplog同步过程中的最后一步就是将创建索引期间的操作记录也同步过来,防止这个节点成为备份节点
旧数据的处理
前面我们也提到过,备份节点的数据有可能落后于同步源,当落后的较多的时候,那么这个备份节点就是陈旧节点,陈旧的节点很难跟上同步源数据,如果需要继续同步,那么势必要跳过一部分数据操作,当然导致备份节点陈旧的原因有很多,例如此节点曾经出现过停机一段时间,或者是写入已经超过了自身处理的能力上限,或者读取数据的请求太多,就会导致备份节点陈旧。当然如果某个节点变为陈旧节点以后,会去查看当前副本集中的其他节点成员,如果发现某个成员的oplog日志比较准确,可以用于同步当前落下的操作,就会改为从这个节点进行同步,如果发现所有的成员节点都不满足,就会停止当前的复制操作,当前节点的数据需要再次重新完全同步(或者从备份里面恢复)才可以。因此为了避免陈旧节点的出现,我们往往可以考虑,优先让主节点使用比较大的oplog保存足够多的操作日志是很好的选择,因为硬盘发展到现在已经是成本很低的产品了,同时也便于其他节点的数据同步。
心跳
mongo复制集中为了保证成员之间彼此知道其他成员的状态,比如哪个是主节点,哪个可以是同步源,哪个节点已经不可用了?这个过程也是利用了心跳机制来维护的,默认情况下,mongo节点之间每两秒就会向其他成员节点发送一个心跳请求,用于通知其他节点当前的状态,同时也是主节点是否能明确知道当前是否满足大多数节点状态的重要依据
成员状态
每个成员节点都会将当前的状态作为心跳的信号发送给其他成员节点,常见的成员节点状态如主节点、备份节点,是我们见到最多的,但是除了这两个以外,还有其他成员状态:
STARTUP
当前状态代表节点刚启动的时候的状态,处于这个状态的时候,mongo会尝试加载副本集相关的配置,当加载配置完成以后,就会进入STARTUP2
状态
STARTUP2
当进入到此状态的时候,代表当前节点正在进行初始化同步操作。如果是普通的节点,这个过程一般不会太久,几秒钟就能完成,这个时候mongo会创建几个线程,用于完成复制和选举等操作,完成以后状态就会变为RECOVERING
状态
RECOVERING
当节点处于当前状态的时候,代表此节点运行正常,只是暂时还没完成,暂时无法处理请求。此状态下,节点处于轻微过载,需要做一些检查,确保当前处于有效状态,之后就会切换到正常状态,当然如果是陈旧节点,也会处于这个状态,会去查找副本集中oplog较为准确的成员,进行同步oplog,完成以后也会切换到正常状态。
ARBITER
在正常的操作中,如果节点属于仲裁节点,就应该处于当前状态
DOWN
如果节点无法被访问了,变为不可达节点,那么就会变更为此状态。当然不可达节点的原因有很多,比如有可能是网络问题导致的访问不到此节点
UNKNOWN
如果一个成员节点无法到达其他成员,这个时候对于其他成员来说,此节点的状态就是未知的,这个时候就会将其标记为UNKNOWN
状态,标记这个节点为不可达节点
REMOVED
当成员退出副本集的时候,就会处于这个状态。当然如果重新加入副本集,此节点的状态又会慢慢变为正常状态
ROLLBACK
如果当前节点正在执行数据回滚操作,就会将状态改为此状态,完成回滚操作以后会恢复为正常状态
FATAL
如果节点发生了严重的错误,导致服务不可用,此时应该处理尝试正常恢复服务,就会将状态标记为FATAL
,我们可以在日志中通过grep replSet FATAL
关键字应该可以查看到发生错误的时间等信息
复制集选举
当一个成员节点加入到复制集以后,发现无法到达主节点,这个时候此节点会认为当前复制集没有主节点,就会发起申请成为主节点操作,然后将请求发送给复制集中所有当前节点可达的其他成员节点。其他成员节点在接受到这个请求以后,会检查此节点是否满足作为主节点的条件,比如先检查在当前复制集中是否有可达的主节点存在,如果已经有主节点存在了,那么会直接拒绝,如果找不到已经存在的主节点,就会去检查发起申请的节点的数据是否比当前节点的数据更新,如果比当前数据更新,说明对于当前节点来说,是比较合适作为主节点的,否则也会拒绝申请。
假如整个复制集中超过大半的节点都投了同意票,那么发出申请的节点就会选举成功,成为master,切换自身的状态到主节点状态,如果收到的结果是大部分反对,那么则仍然保持为备份节点的状态。
当然主节点被选举出来以后,正常情况下会一直保持着主节点的状态,除非是心跳检测以后,不满足大多数可达的要求,强制退位,或者服务挂了导致退位,除此之外,我们修改了此节点对应的副本集的配置,要求重新加载副本集,也会导致主节点退位。
正常网络情况良好,副本集内进行选举的过程是很快的,因为默认情况下心跳是两秒一次,也就是说网络没有异常的情况下,两秒内就会有成员节点发现主节点状态出现问题,或者出现了错误、不可达等,这个时候就会重新进行选举,选举过程很快,但是如果遇到了网络响应比较慢,甚至可能会导致心跳超时,默认时间为20秒,而且发起选举可能会导致最终的投票平票,在平票的时候,需要等待30秒以后才能发起第二轮投票,这个时候就会等待很久,甚至好几分钟以上。
多机房环境下选举回滚问题
除了正常情况下的复制集以外,有时候当我们复制集的节点较多的时候,有可能出现在多个机房或者多个网络内组建复制集的情况,假设我们现在是五个节点组成的复制集,其中A机房是两个节点,B机房是三个节点,大体如下:
此时A机房是主节点,由于网络问题,突然A机房和B机房之间无法访问,此时对于B机房来说是主节点不可达,而由于B机房内还有三个节点存在,满足大多数特性,这个时候,就会触发新的主节点选举操作,但是如果在网络断开的一瞬间,原本A机房的主节点正在处理新的请求,如图所示,此时A机房的oplog是超过了B机房的oplog的,即在断开之前还有部分数据没有复制进B机房的备份节点中,这个时候B机房重新选举出了一个master节点,继续对外提供服务,当操作了一段时间以后,AB机房此时网络又可以访问了,这个时候A机房的节点加入到复制集以后,发现当前节点有部分oplog数据对不上,即断开的时候多处理的那部分数据,此时就会进入回滚操作(状态为回滚状态),A机房的节点会去B机房中找oplog共同操作的最后一条日志数据,即125的oplog,这个时候会选择回滚到125以后,在进行同步操作。
不过mongo会去查看那些没有被复制的操作,将这部分受影响的文档写入.bson文件,保存在目录文件下的rollback目录中,如果这个多出来的126是更新文档的操作,会将这部分操作写入collectionname下的bson文件中,然后再去主节点中复制这部分文档数据。
如果要将被回滚的操作应用到当前主节点,首先使用mongorestore
命令将它们加载到一个临时集合中,然后在在shell中将这些文档与同步后的集合进行比较,然后在将确定的数据加载到主集合中去。