1)数据复制原理shell
开启复制集后,主节点会在 local 库下生成一个集合叫 oplog.rs,这是一个有限集合,也就是大小是固定的。其中记录的是整个mongod实例一段时间内数据库的全部变动(插入/更新/删除)操做,当空间用完时新记录自动覆盖最老的记录。数据库
复制集中的从节点就是经过读取主节点上面的 oplog 来实现数据同步的,MongoDB的oplog(操做日志)是一种特殊的封顶集合,滚动覆盖写入,固定大小。另外oplog的滚动覆盖写入方式有两种:一种是达到设定大小就开始覆盖写入;二是设定文档数,达到文档数就开始覆盖写入(不推荐使用)。服务器
复制集工做方式以下图:网络
主节点跟应用程序之间的交互是经过Mongodb驱动进行的,Mongodb复制集有自动故障转移功能,那么应用程序是如何找到主节点呢?Mongodb提供了一个rs.isMaster()函数,这个函数能够识别主节点。默认应用程序读写都是在主节点上,默认状况下,读和写都只能在主节点上进行。可是主压力过大时就能够把读操做分离到从节点上从而提升读性能。下面是MongoDB的驱动支持5种复制集读选项:数据结构
primary:默认模式,全部的读操做都在复制集的主节点进行的。多线程
primaryPreferred:在大多数状况时,读操做在主节点上进行,可是若是主节点不可用了,读操做就会转移到从节点上执行。并发
secondary:全部的读操做都在复制集的从节点上执行。异步
secondaryPreferred:在大多数状况下,读操做都是在从节点上进行的,可是当从节点不可用了,读操做会转移到主节点上进行。ide
nearest:读操做会在复制集中网络延时最小的节点上进行,与节点类型无关。函数
可是除了primary 模式之外的复制集读选项都有可能返回非最新的数据,由于复制过程是异步的,从节点上应用操做可能会比主节点有所延后。若是咱们不使用primary模式,请确保业务容许数据存在可能的不一致。
举个例子:
用客户端向主节点添加了 100 条记录,那么 oplog 中也会有这 100 条的 insert 记录。从节点经过获取主节点的 oplog,也执行这 100 条 oplog 记录。这样,从节点也就复制了主节点的数据,实现了同步。
须要说明的是:并非从节点只能获取主节点的 oplog。为了提升复制的效率,复制集中全部节点之间会互相进行心跳检测(经过ping)。每一个节点均可以从任何其余节点上获取oplog。还有,用一条语句批量删除 50 条记录,并非在 oplog 中只记录一条数据,而是记录 50 条单条删除的记录。oplog中的每一条操做,不管是执行一次仍是屡次执行,对数据集的影响结果是同样的,i.e 每条oplog中的操做都是幂等的。
2)复制集写操做
若是启用复制集的话,在内存中会多一个OPLOG区域,是在节点之间进行同步的一个手段,它会把操做日志放到OPLOG中来,而后OPLOG会复制到从节点上。从节点接收并执行OPLOG中的操做日志来达到数据的同步操做。
1) 客户端的数据进来;
2) 数据操做写入到日志缓冲;
3) 数据写入到数据缓冲;
4) 把日志缓冲中的操做日志放到OPLOG中来;
5) 返回操做结果到客户端(异步);
6) 后台线程进行OPLOG复制到从节点,这个频率是很是高的,比日志刷盘频率还要高,从节点会一直监听主节点,OPLOG一有变化就会进行复制操做;
7) 后台线程进行日志缓冲中的数据刷盘,很是频繁(默认100)毫秒,也可自行设置(30-60);
后台线程进行数据缓冲中的数据刷盘,默认是60秒;
3)Oplog的数据结构
1
2
3
4
5
6
7
8
9
10
11
|
ywnds:PRIMARY> db.oplog.rs.findOne()
{
"ts" : Timestamp(1453059127, 1),
"h" : NumberLong("-6090285017139205124"),
"v" : 2,
"op" : "n",
"ns" : "",
"o" : {
"msg" : "initiating set"
}
}
|
ts:操做发生时的时间戳,这个时间戳包含两部份内容t和i,t是标准的时间戳(自1970年1月1日 00:00:00 GMT 以来的毫秒数)。而i是一个序号,目的是为了保证t与i组合出的Mongo时间戳ts能够惟一的肯定一条操做记录。
h:此操做的独一无二的ID。
v:oplog的版本。
op:操做类型(insert、update、delete、db cmd、null),牢牢表明一个消息信息。
ns:操做所处的命名空间(db_name.coll_name)。
o:操做对应的文档,文档在更新前的状态(“msg” 表示信息)。
o2:仅update操做时有,更新操做的变动条件(只记录更改数据)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
{
"ts" : Timestamp(1481094522, 1),
"t" : NumberLong(1),
"h" : NumberLong("-7286476219427328778"),
"v" : 2,
"op" : "u",
"ns" : "test.foo",
"o2" : {
"_id" : ObjectId("58466e872780d8f6f65951ad")
},
"o" : {
"_id" : ObjectId("58466e872780d8f6f65951ad"),
"a" : 1000
}
}
|
PS:当你想查询最后一次数据库操做的oplog记录时,可使用此语句db.oplog.rs.find().sort({$natural:-1}).limit(1).pretty()。
须要重点强调的是oplog只记录改变数据库状态的操做。好比,查询就不存储在oplog中。这是由于oplog只是做为从节点与主节点保持数据同步的机制。存储在oplog中的操做也不是彻底和主节点的操做如出一辙的,这些操做在存储以前先要作等幂变换,也就是说,这些操做能够在从服务器端屡次执行,只要顺序是对的,就不会有问题。例如,使用“$inc”执行的增长更新操做,会被转换为“$set”操做。
4)Oplog大小及意义
当你第一次启动复制集中的节点时,MongoDB会用默认大小创建Oplog。这个默认大小取决于你的机器的操做系统。大多数状况下,默认的oplog大小是足够的。在 mongod 创建oplog以前,咱们能够经过设置 oplogSizeMB 选项来设定其大小。可是,若是已经初始化过复制集,已经创建了Oplog了,咱们须要经过修改Oplog大小中的方式来修改其大小。
OpLog的默认大小:
4.1 复制时间窗口
既然Oplog是一个封顶集合,那么Oplog的大小就会有一个复制时间窗口的问题。举个例子,若是Oplog是大小是可用空间的5%,且能够存储24小时内的操做,那么从节点就能够在中止复制24小时后仍能追遇上主节点,而不须要从新获取所有数据。若是说从节点在24小时后开始追赶数据,那么很差意思主节点的oplog已经滚动覆盖了,把从节点没有执行的那条语句给覆盖了。这个时候为了保证数据一致性就会终止复制。然而,大多数复制集中的操做没有那么频繁,oplog能够存放远不止上述的时间的操做记录。可是,再生产环境中尽量把oplog设置大一些也不碍事。使用rs.printReplicationInfo()能够查看oplog大小以及预计窗口覆盖时间。
1
2
3
4
5
6
|
test:PRIMARY> rs.printReplicationInfo()
configured oplog size: 1024MB <--集合大小
log length start to end: 423849secs (117.74hrs) <--预计窗口覆盖时间
oplog first event time: Wed Sep 09 2015 17:39:50 GMT+0800 (CST)
oplog last event time: Mon Sep 14 2015 15:23:59 GMT+0800 (CST)
now: Mon Sep 14 2015 16:37:30 GMT+0800 (CST)
|
4.2 Oplog大小应随着实际使用压力而增长
若是我可以对我复制集的工做状况有一个很好地预估,若是可能会出现如下的状况,那么咱们就可能须要建立一个比默认大小更大的oplog。相反的,若是咱们的应用主要是读,而写操做不多,那么一个小一点的oplog就足够了。
下列状况咱们可能须要更大的oplog。
4.2.1 同时更新大量的文档。
Oplog为了保证 幂等性 会将多项更新(multi-updates)转换为一条条单条的操做记录。这就会在数据没有那么多变更的状况下大量的占用oplog空间。
4.2.2 删除了与插入时相同大小的数据
若是咱们删除了与咱们插入时一样多的数据,数据库将不会在硬盘使用状况上有显著提高,可是oplog的增加状况会显著提高。
4.2.3 大量In-Place更新
若是咱们会有大量的in-place更新,数据库会记录下大量的操做记录,但此时硬盘中数据量不会有所变化。
4.3 Oplog幂等性
Oplog有一个很是重要的特性——幂等性(idempotent)。即对一个数据集合,使用oplog中记录的操做重放时,不管被重放多少次,其结果会是同样的。举例来讲,若是oplog中记录的是一个插入操做,并不会由于你重放了两次,数据库中就获得两条相同的记录。
5)Oplog的状态信息
咱们能够经过 rs.printReplicationInfo() 来查看oplog的状态,包括大小、存储的操做的时间范围。关于oplog的更多信息能够参考Check the Size of the Oplog。
1
2
3
4
5
6
|
test:PRIMARY> rs.printReplicationInfo()
configured oplog size: 1024MB <--集合大小
log length start to end: 423849secs (117.74hrs) <--预计窗口覆盖时间
oplog first event time: Wed Sep 09 2015 17:39:50 GMT+0800 (CST)
oplog last event time: Mon Sep 14 2015 15:23:59 GMT+0800 (CST)
now: Mon Sep 14 2015 16:37:30 GMT+0800 (CST)
|
在各种异常状况下,从节点oplog的更新可能落后于主节点一些时间。在从节点上经过 db.getReplicationInfo() 和 db.getReplicationInfo能够得到如今复制集的状态与,也能够知道是否有意外的复制延时。
1
2
3
4
5
6
7
8
9
10
|
test:SECONDARY> db.getReplicationInfo()
{
"logSizeMB" : 1024,
"usedMB" : 168.06,
"timeDiff" : 7623151,
"timeDiffHours" : 2117.54,
"tFirst" : "Fri Aug 19 2016 12:27:04 GMT+0800 (CST)",
"tLast" : "Tue Nov 15 2016 17:59:35 GMT+0800 (CST)",
"now" : "Tue Dec 06 2016 11:27:36 GMT+0800 (CST)"
}
|
6)复制集数据同步过程
Mongodb复制集里的Secondary会从Primary上同步数据,以保持副本集全部节点的数据保持一致,数据同步主要包含2个过程
6.1 initial sync
6.2 replication(oplog sync)
先经过initial sync同步全量数据,再经过replication不断重放Primary上的oplog同步增量数据。
initial sync
初始同步会将完整的数据集复制到各个节点上,Secondary启动后,若是知足如下条件之一,会先进行initial sync。
1. Secondary上oplog为空,好比新加入的空节点。
2. local.replset.minvalid集合里_initialSyncFlag标记被设置。当initial sync开始时,同步线程会设置该标记,当initial sync结束时清除该标记,故若是initial sync过程当中途失败,节点重启后发现该标记被设置,就知道应该从新进行initial sync。
3. BackgroundSync::_initialSyncRequestedFlag被设置。当向节点发送resync命令时,该标记会被设置,此时会强制从新initial sync。
initial sync同步流程
1. minValid集合设置_initialSyncFlag(db.replset.minvalid.find())。
2. 获取同步源当前最新的oplog时间戳t0。
3. 从同步源克隆全部的集合数据。
4. 获取同步源最新的oplog时间戳t1。
5. 同步t0~t1全部的oplog。
6. 获取同步源最新的oplog时间戳t2。
7. 同步t1~t2全部的oplog。
8. 从同步源读取index信息,并创建索引(除了_id ,这个以前已经创建完成)。
9. 获取同步源最新的oplog时间戳t3。
10. 同步t2~t3全部的oplog。
11. minValid集合清除_initialSyncFlag,initial sync结束。
当完成了全部操做后,该节点将会变为正常的状态secondary。
replication (sync oplog)
initial sync结束后,Secondary会创建到Primary上local.oplog.rs的tailable cursor,不断从Primary上获取新写入的oplog,并应用到自身。Tailable cursor每次会获取到一批oplog,Secondary采用多线程重放oplog以提升效率,经过将oplog按照所属的namespace进行分组,划分到多个线程里,保证同一个namespace的全部操做都由一个线程来replay,以保证统一namespace的操做时序跟primary上保持一致(若是引擎支持文档锁,只需保证同一个文档的操做时序与primary一致便可)。
同步场景分析
1. 副本集初始化
初始化选出Primary后,此时Secondary上无有效数据,oplog是空的,会先进行initial sync,而后不断的应用新的oplog。
2. 新成员加入
因新成员上无有效数据,oplog是空的,会先进行initial sync,而后不断的应用新的oplog。
3. 有数据的节点加入
有数据的节点加入有以下状况:
A.该节点与副本集其余节点断开链接,一段时间后恢复
B.该节点从副本集移除(处于REMOVED)状态,经过replSetReconfig命令将其从新加入
C.其余? 因同一个副本集的成员replSetName配置必须相同,除非有误配置,应该不会有其余场景
此时,若是该节点最新的oplog时间戳,比全部节点最旧的oplog时间戳还要小,该节点将找不到同步源,会一直处于RECOVERING而不能服务;反之,若是能找到同步源,则直接进入replication阶段,不断的应用新的oplog。
因oplog太旧而处于RECOVERING的节点目前没法自动恢复,需人工介入处理(故设置合理的oplog大小很是重要),最简单的方式是发送resync命令,让该节点从新进行initial sync。
7)多线程复制
MongoDB容许经过多线程进行批量写操做来提升并发能力,MongoDB将批操做经过命名空间来分组。