1. 基本概念

oplog使用固定大小集合记录了数据库中所有修改操作的操作日志(新增、修改和删除,无查询),mongodb收到修改请求后,先在主节点(Primary)执行请求,再把操作日志保存到oplog表中,其他从节点(Secondary)到主节点拉取oplog并在异步进程中应用这些操作,从而达到主从数据的一致性。复制组内的所有节点都会保存一份oplog(集合名local.oplog.rs),这让他们可以保持同样的数据库状态。

为了提高同步效率,所有复制组成员都会向其他成员发送保活报文(pings),任意从节点可以从其他成员节点同步oplog(即可以从主节点同步,也可以从从节点同步)。oplog中的操作都是幂等的,即oplog中的某个操作日志在目标数据库中应用一次或者多次,其结果都是一样的。

主从同步示意图如下(客户端写数据到主节点,从节点从主节点同步oplog并应用到本节点):

2. Oplog 的默认储存大小

当你首次启动复制组节点时,在你未指定oplog大小时,mongodb会使用默认大小来创建oplog。

对于Unix和Windows系统来说,默认大小和存储引擎的对应关系如下:

存储引擎类型 oplog大小 下限 上限
内存 物理内存的5% 50MB 50GB
WiredTiger 空闲磁盘的5% 990MB 50GB 

(注意,最新4.4版本的mongodb移除了MMAP类型存储引擎的支持。)

对于64位maxOS系统来说,参照使用的存储引擎类型,该默认大小是192MB(物理内存或者磁盘空间),如下:

存储引擎类型 oplog大小
内存 192MB物理内存
WiredTiger 192MB的磁盘空间

大部分情况下,oplog的默认大小是足够的。举个例子,如果5%的磁盘空间存储了最近24小时的操作日志,此时如果某个从节点的日志同步时间差超过24小时时,

从节点将停止同步oplog,并将自身的状态从“Secondery”切换到“STALE”。当然,在实际的运行环境中,大部分复制组成员的负载会低一些,他们的oplog中也会持有更长时间段的日志。

3. 可能需要更大oplog的工作负载

如果你预测到你的复制组的工作负载属于以下的模式,你需要创建比默认值更大一些的oplog。相反的,如果你的应用大部分情况下是读操作,只有小部分的写操作,那么更小一些的oplog也是满足需要的。

下面的工作负载可能需要更大一些的oplog

单次操作会更新多条记录

为了满足oplog的幂等性,单次操作更新多条记录时,mongodb会记录多条操作日志到oplog中,这种场景就需要使用大量的oplog的空间,虽然此时数据大小或者磁盘大小并没有相应的增加那么多。

删除操作和插入操作一样多时

如果你的删除操作请求量和插入操作的请求量大致相当时,数据库在磁盘空间消耗方面不会有明显增长,但是操作日志的大小会非常巨大。

显著数量的原文档更新

如果工作负载的大部分操作都是原文档更新,此时虽然不会增加数据库中文档的数量,但是数据库需要记录大量的操作日志。

4. Oplog状态

如果要查看oplog的状态,包含记录条数和时间范围,可以使用"rs.printReplicationInfo() "命令,如下:

MongoDB Enterprise repa:PRIMARY> rs.printReplicationInfo()
configured oplog size: 1024MB // oplog大小是1024MB
log length start to end: 867353secs (240.93hrs) // 第一条和最后一条日志的时间差是240.93小时
oplog first event time: Wed Jul 07 2021 20:24:57 GMT+0800
oplog last event time: Sat Jul 17 2021 21:20:50 GMT+0800
now: Sat Jul 17 2021 21:20:56 GMT+0800

5. Oplog格式

从前面知道oplog是存储在数据库local中,表名为“oplog.rs”,通过查询命令看一下oplog的数据格式:

db.oplog.rs.find({"ns":"test.users"}).limit(1)   // ns字段指明查询对数据库test中users表的操作日志
{
"ts": Timestamp(1625660877, 2), // 日志的操作时间戳,第一个数字是时间戳,单位秒,第二个数字是当前秒的第2个操作
"t": NumberLong(2),
"h": NumberLong("5521980394145765083"),
"v": 2,
"op": "i", // i表示insert,u表示update,d表示delete,c 表示的是数据库的命令,比如建表,n表示noop,即空操作
"ns": "test.users", // 命名空间,即数据库和集合名称
"ui": UUID("edabbd93-76eb-42be-b54a-cdc29eb1f267"), // 连接到mongodb的客户端会话id
"wall": ISODate("2021-07-07T12:27:57.689Z"), // 操作执行时间,utc时间
"o": { // 操作的内容,对于不同的op类型,其格式不尽相同
"_id": ObjectId("60e59dcd46db1fb4605f8b18"),
"name": "1"
}
}

6. CUD操作和Oplog的对应关系

前面分析oplog日志格式的时候,查看了一条insert操作对应的日志,就不再赘述,下面再看下delete和update对应的日志格式(find不会产生oplog)。

delete操作

首先插入三条记录:

MongoDB Enterprise repa:PRIMARY> use test
switched to db test
MongoDB Enterprise repa:PRIMARY> db.users.insert({"name":"张三","age":NumberInt(10),"sex":"男"})
WriteResult({ "nInserted" : 1 })
MongoDB Enterprise repa:PRIMARY> db.users.insert({"name":"李四","age":NumberInt(11),"sex":"男"})
WriteResult({ "nInserted" : 1 })
MongoDB Enterprise repa:PRIMARY> db.users.insert({"name":"王五","age":NumberInt(12),"sex":"男"})
WriteResult({ "nInserted" : 1 })
MongoDB Enterprise repa:PRIMARY> db.users.find()
{ "_id" : ObjectId("60f2e11b0d98dc3b374199de"), "name" : "张三", "age" : 10, "sex" : "男" }
{ "_id" : ObjectId("60f2e11e0d98dc3b374199df"), "name" : "李四", "age" : 11, "sex" : "男" }
{ "_id" : ObjectId("60f2e11e0d98dc3b374199e0"), "name" : "王五", "age" : 12, "sex" : "男" }

执行delete操作,匹配条件是{"sex":"男"},即删除所有性别为男的记录:

MongoDB Enterprise repa:PRIMARY> db.users.remove({"sex":"男"})
WriteResult({ "nRemoved" : 3 })
MongoDB Enterprise repa:PRIMARY> db.users.find()
MongoDB Enterprise repa:PRIMARY>

可以看到,一条删除命令删除了三条记录,对应的oplog是什么呢,来,查一下:

MongoDB Enterprise repa:PRIMARY> use local
switched to db local
MongoDB Enterprise repa:PRIMARY> db.oplog.rs.find({"ns":"test.users","op":"d","wall":{"$gt":ISODate("2021-07-17T13:50:57.689Z")}})
{ "ts" : Timestamp(1626530154, 1), "t" : NumberLong(2), "h" : NumberLong("5834731856459959506"), "v" : 2, "op" : "d", "ns" : "test.users", "ui" : UUID("edabbd93-76eb-42be-b54a-cdc29eb1f267"), "wall" : ISODate("2021-07-17T13:55:54.424Z"), "o" : { "_id" : ObjectId("60f2e11b0d98dc3b374199de") } }
{ "ts" : Timestamp(1626530154, 2), "t" : NumberLong(2), "h" : NumberLong("-2164276082472824844"), "v" : 2, "op" : "d", "ns" : "test.users", "ui" : UUID("edabbd93-76eb-42be-b54a-cdc29eb1f267"), "wall" : ISODate("2021-07-17T13:55:54.424Z"), "o" : { "_id" : ObjectId("60f2e11e0d98dc3b374199df") } }
{ "ts" : Timestamp(1626530154, 3), "t" : NumberLong(2), "h" : NumberLong("3834858247238363179"), "v" : 2, "op" : "d", "ns" : "test.users", "ui" : UUID("edabbd93-76eb-42be-b54a-cdc29eb1f267"), "wall" : ISODate("2021-07-17T13:55:54.424Z"), "o" : { "_id" : ObjectId("60f2e11e0d98dc3b374199e0") } }
MongoDB Enterprise repa:PRIMARY>

从上可以看到,一条删除命令,在oplog中记录了三条日志,下面分析其中的一条:

{
"ts": Timestamp(1626530154, 1),
"t": NumberLong(2),
"h": NumberLong("5834731856459959506"),
"v": 2,
"op": "d", // 删除操作
"ns": "test.users", // 数据库是test,集合是users
"ui": UUID("edabbd93-76eb-42be-b54a-cdc29eb1f267"),
"wall": ISODate("2021-07-17T13:55:54.424Z"),
"o": { // 待删除记录的_id
"_id": ObjectId("60f2e11b0d98dc3b374199de")
}
}

从上面日志分析可以得到结论:

用户的一次删除请求,如果删除了N条记录,那么oplog中将记录N条日志,日志中会记录待删除记录的“_id”字段,与用户的删除请求的参数无关

update操作

下面再看下更新操作对应的oplog的日志数量和格式。

首先插入三条记录:

MongoDB Enterprise repa:PRIMARY> use test
switched to db test
MongoDB Enterprise repa:PRIMARY>
MongoDB Enterprise repa:PRIMARY> db.users.insert({"name":"张三","age":NumberInt(10),"sex":"男"})
WriteResult({ "nInserted" : 1 })
MongoDB Enterprise repa:PRIMARY> db.users.insert({"name":"李四","age":NumberInt(11),"sex":"男"})
WriteResult({ "nInserted" : 1 })
MongoDB Enterprise repa:PRIMARY> db.users.insert({"name":"王五","age":NumberInt(12),"sex":"男"})
WriteResult({ "nInserted" : 1 })
MongoDB Enterprise repa:PRIMARY> db.users.find()
{ "_id" : ObjectId("60f2e2db0d98dc3b374199e1"), "name" : "张三", "age" : 10, "sex" : "男" }
{ "_id" : ObjectId("60f2e2db0d98dc3b374199e2"), "name" : "李四", "age" : 11, "sex" : "男" }
{ "_id" : ObjectId("60f2e2dc0d98dc3b374199e3"), "name" : "王五", "age" : 12, "sex" : "男" }

再执行更新操作:

MongoDB Enterprise repa:PRIMARY> db.users.update({"sex":"男"},  {"$inc":{"age":NumberInt(1)}}, false, true)
WriteResult({ "nMatched" : 3, "nUpserted" : 0, "nModified" : 3 })
MongoDB Enterprise repa:PRIMARY> db.users.find()
{ "_id" : ObjectId("60f2e2db0d98dc3b374199e1"), "name" : "张三", "age" : 11, "sex" : "男" }
{ "_id" : ObjectId("60f2e2db0d98dc3b374199e2"), "name" : "李四", "age" : 12, "sex" : "男" }
{ "_id" : ObjectId("60f2e2dc0d98dc3b374199e3"), "name" : "王五", "age" : 13, "sex" : "男" }

从返回结果可以看到,更新操作执行成功,并更新了三条记录,下面看下oplog的日志:

MongoDB Enterprise repa:PRIMARY> use local
switched to db local
MongoDB Enterprise repa:PRIMARY> db.oplog.rs.find({"ns":"test.users","op":"u","wall":{"$gt":ISODate("2021-07-17T13:50:57.689Z")}})
{ "ts" : Timestamp(1626530575, 1), "t" : NumberLong(2), "h" : NumberLong("-6359278368726841648"), "v" : 2, "op" : "u", "ns" : "test.users", "ui" : UUID("edabbd93-76eb-42be-b54a-cdc29eb1f267"), "o2" : { "_id" : ObjectId("60f2e2db0d98dc3b374199e1") }, "wall" : ISODate("2021-07-17T14:02:55.319Z"), "o" : { "$v" : 1, "$set" : { "age" : 11 } } }
{ "ts" : Timestamp(1626530575, 2), "t" : NumberLong(2), "h" : NumberLong("-4351658862590633053"), "v" : 2, "op" : "u", "ns" : "test.users", "ui" : UUID("edabbd93-76eb-42be-b54a-cdc29eb1f267"), "o2" : { "_id" : ObjectId("60f2e2db0d98dc3b374199e2") }, "wall" : ISODate("2021-07-17T14:02:55.319Z"), "o" : { "$v" : 1, "$set" : { "age" : 12 } } }
{ "ts" : Timestamp(1626530575, 3), "t" : NumberLong(2), "h" : NumberLong("5911110003695351597"), "v" : 2, "op" : "u", "ns" : "test.users", "ui" : UUID("edabbd93-76eb-42be-b54a-cdc29eb1f267"), "o2" : { "_id" : ObjectId("60f2e2dc0d98dc3b374199e3") }, "wall" : ISODate("2021-07-17T14:02:55.319Z"), "o" : { "$v" : 1, "$set" : { "age" : 13 } } }

和delete类似,update操作也是产生了三条日志,选第一条分析:

{
"ts": Timestamp(1626530575, 1),
"t": NumberLong(2),
"h": NumberLong("-6359278368726841648"),
"v": 2,
"op": "u", // 更新操作
"ns": "test.users", // 数据库test,集合是users
"ui": UUID("edabbd93-76eb-42be-b54a-cdc29eb1f267"),
"o2": { // 更新操作的查询条件,使用的记录的_id
"_id": ObjectId("60f2e2db0d98dc3b374199e1")
},
"wall": ISODate("2021-07-17T14:02:55.319Z"),
"o": { // 更新操作的更新内容,原始的inc操作符转变为set操作符,可以满足幂等性
"$v": 1,
"$set": {
"age": 11
}
}
}

从上面日志分析可以得到结论:

用户的一次更新请求,如果更新了N条记录,那么oplog中将记录N条日志,日志中记录待更新记录的“_id”字段为查询条件,更新操作使用的是set操作符,并不是用户的更新操作符

小结

从上面的delete和update操作对应的oplog日志分析可以看出,oplog记录的不是用户的原始命令,而是对应的逻辑命令,通过这种方式可以满足oplog的幂等性,但是也会衍生出可能产生大量oplog记录的问题,需要用户根据业务模型的需要,来选择合适的oplog大小。

mongodb oplog详解和格式分析的更多相关文章

  1. MongoDB副本集配置系列七:MongoDB oplog详解

    1:oplog简介 oplog是local库下的一个固定集合,Secondary就是通过查看Primary 的oplog这个集合来进行复制的.每个节点都有oplog,记录这从主节点复制过来的信息,这样 ...

  2. MongoDB oplog 详解

    oplog 简介 oplog 是local库下的一个固定集合,Secondary就是通过查看Primary的oplog这个集合来进行复制的.每个节点都有oplog,记录从主节点复制过来的信息,这样每个 ...

  3. Java性能分析之线程栈详解与性能分析

    Java性能分析之线程栈详解 Java性能分析迈不过去的一个关键点是线程栈,新的性能班级也讲到了JVM这一块,所以本篇文章对线程栈进行基础知识普及以及如何对线程栈进行性能分析. 基本概念 线程堆栈也称 ...

  4. 关于syslog日志功能详解 事件日志分析、EventLog Analyzer

    关于syslog日志功能详解 事件日志分析.EventLog Analyzer 一.日志管理 保障网络安全 Windows系统日志分析 Syslog日志分析 应用程序日志分析 Windows终端服务器 ...

  5. Block详解二(底层分析)

    Block专辑: Block讲解一 MRC-block与ARC-block Block详解一(底层分析) 今天讲述Block的最后一篇,后两篇仅仅是加深1,2篇的理解,废话少说,开始讲解! __blo ...

  6. ArrayList详解-源码分析

    ArrayList详解-源码分析 1. 概述 在平时的开发中,用到最多的集合应该就是ArrayList了,本篇文章将结合源代码来学习ArrayList. ArrayList是基于数组实现的集合列表 支 ...

  7. LinkedList详解-源码分析

    LinkedList详解-源码分析 LinkedList是List接口的第二个具体的实现类,第一个是ArrayList,前面一篇文章已经总结过了,下面我们来结合源码,学习LinkedList. 基于双 ...

  8. UEFI启动视频详解:启动分析+N项操作实例

    ============================================================= ※※※※最给力的视频解说※※※※ 2011hiboy全部共享资料:立刻去   ...

  9. MongoDB数据库详解

    第1章 数据库管理系统 1.1 前言 01.数据的定义:文字.图像.地理位置信息(坐标.经纬度)等 02.数据库管理系统的定义:建立.存取和管理数据,保证数据安全和完整性的软件 03.常见的数据库管理 ...

随机推荐

  1. 出现 关于UTF-8 序列的字节 2 无效的异常

    学习mybatis中碰到了 Caused by: org.apache.ibatis.builder.BuilderException: Error creating document instanc ...

  2. 特斯拉ADAS

    特斯拉ADAS Tesla 目前在Model S和Model X上面采用的自动辅助驾驶系统集成了12个超声波传感器,用来识别周围环境: 一个前置摄像头,用来辨识前方物体: 一个前置雷达,用来辨识前方物 ...

  3. 蓝牙mesh网络技术的亮点

    蓝牙mesh网络技术的亮点 The highlights of Bluetooth mesh networking technology 导言 蓝牙是当今最主要的低功耗无线技术之一,对无线设备用户和开 ...

  4. Kaggle上的犬种识别(ImageNet Dogs)

    Kaggle上的犬种识别(ImageNet Dogs) Dog Breed Identification (ImageNet Dogs) on Kaggle 在本节中,将解决在Kaggle竞赛中的犬种 ...

  5. SQL进阶总结(一)

    学而不思则罔,趁着假期好好总结下SQL的编程知识. 掌握SQL,首先有两个知识点要明确,要贯穿在我们整个学习SQL的过程中. SQL 不同于之前学习的面向过程.面向编程语言,SQL是一门面向集合的编程 ...

  6. python_selenium_键盘事件

    引言 ----在实际的web测试工作中,需要配合键盘按键来操作,webdriver的  keys()类提供键盘上所有按键的操作,还可以模拟组合键Ctrl+a,Ctrl+v等. 举例: #cording ...

  7. 【读书笔记】《C语言 从入门到精通》(第三版)笔记

    C语言,上学的时候都没学好,没想到现在却靠它吃饭.因为对C语言还是比较熟悉,所以买这本书是用来当"字典"用的.所以下面的笔记不会有很基础的内容. 1.书籍介绍 2.结构体 3.[C ...

  8. apache jmeter下载与安装

    JMeter是Apache软件基金会的产品,用于对静态的和动态的资源性能进行测试.jmeter可以运行在多个平台上,如Windows和Linux,本文讲的是在Windows安装jmeter. 工具/原 ...

  9. 合宙Luat | 电源设计——模块应用必看的2个要点

    在模块应用设计中,电源设计是很重要的一部分. 由于射频发射时,会在短时间有一个较大电流的突发脉冲.在突发脉冲阶段内,电源必须能够提供高的峰值电流,不然有可能会引起供电电压的跌落.   而很多初学的朋友 ...

  10. OpenCV随笔

    创建一个窗口#zeros(shape,dtype=float,order='C')#shape:形状,dtype:数据类型,可选参数,默认numpy.float64img = np.zeros((50 ...