天天看点

MongoDB 如何保证 oplog 顺序?

mongodb 复制集里,主备节点间通过 oplog 来同步数据,priamry 上写入数据时,会记录一条oplog,secondary 从 primary 节点拉取 oplog并重放,以保证最终存储相同的数据集。

oplog 主要特性

幂等性,每一条oplog,重放一次或多次,得到的结果是一样的;为实现幂等 mongodb 对很多操作进行来转换,比如将 insert 转换为 upsert、$inc 操作转换为 $set等等。

固定大小(capped collection),oplog 使用固定空间存储,当空间满了时,会自动删除最旧的文档。

oplog 按时间戳排序,并且在所有节点上顺序保持一致

本文主要介绍mongodbd 如何保证 oplog 有序存储并读取,关于 oplog 扩展阅读

<a href="https://yq.aliyun.com/articles/54392?spm=5176.8091938.0.0.soobc6">阿里云mongodb数据库的自适应oplog管理</a>

<a href="https://yq.aliyun.com/articles/50138?spm=5176.8091938.0.0.rzfb5e">mongodb 3.2删除优化策略</a>

<a href="https://yq.aliyun.com/articles/47336?spm=5176.8091938.0.0.rm8ccj">mongodb secondary同步慢问题分析</a>

<a href="https://yq.aliyun.com/articles/52404?spm=5176.8091938.0.0.niko9c">mongodb secondary同步慢问题分析(续)</a>

<a href="https://yq.aliyun.com/articles/57755?spm=5176.8091938.0.0.8uuhtx">mongodb同步原理解析</a>

write1

write2

基于上述并发策略,在多个写并发的情况下,如何保证 oplog 顺序?

oplog是一个特殊的 capped collection,文档没有_id字段,但包含一个 ts(时间戳字段),所有 oplog 的文档按照 ts 顺序存储。如下是几条 oplog 的例子。

以 wiredtiger 为例,在写入 oplog 文档时,会以 oplog 的 ts 字段作为 key、文档内容作为 value,写入一条 kv 记录,wiredtiger 会保证存储(btree 或 lsm 的方式都能保证)的文档按 key 来排序,这样就解决『文档按 ts 字段顺序存储』的问题。但仍然存在并发乱序的问题,例如:

并发写入多条 oplog时,时间戳分别是ts1、ts2、ts3 (ts1 &lt; ts2 &lt; ts3 ),ts1、ts3先成功了,这时secondary 拉取到这2条 oplog,然后 ts2才写成功,然后 secondary 再拉取到ts2,也就是说 secondary 看到的 oplog 顺序为ts1、ts3、ts2,就会出现 oplog 乱序的问题。

mongodb(wiredtiger 引擎)的解决方案是通过在读取时进行限制,保证secondary 节点看到一定是顺序的,具体实现机制如下:

写入 oplog前,会先加锁给 oplog 分配时间戳,并注册到未提交列表里

lock();

ts = getnextoptime(); // 根据当前时间戳 + 计数器生成

_uncommittedrecordids.insert(ts);

unlock();

正式写入 oplog,在写完后,将对应的 oplog 从未提交列表里移除

writeoplog(ts, oplogdocument);

_uncommittedrecordids.erase(ts);

在拉取 oplog 时

if (_uncommittedrecordids.empty()) {

} else {

}

通过上述规则,最终保证primary 上 oplog 按 ts 字段存储,并且 secondary能按序读取所有 oplog。

secondary 把 oplog 拉取到本地后,会多线程重放,最后在一个线程里将拉取到的 oplog原样写入本地的 local.oplog.rs集合,这样就保证 secondary oplog 最终与 primary 上完全相同。