MongoDB之oplog
<p>1:oplog简介</p><p>oplog是local库下的一个<strong>固定集合</strong>,Secondary就是通过查看Primary 的oplog这个集合来进行复制的。每个节点都有oplog,记录这从主节点复制过来的信息,这样每个成员都可以作为同步源给其他节点。</p>
<p> Oplog 可以说是Mongodb Replication的纽带了。</p>
<p>2:副本集数据同步的过程</p>
<p>副本集中数据同步的详细过程:Primary节点写入数据,Secondary通过读取Primary的oplog得到复制信息,开始复制数据并且将复制信息写入到自己的oplog。如果某个操作失败(只有当同步源的数据损坏或者数据与主节点不一致时才可能发生),则备份节点停止从当前数据源复制数据。如果某个备份节点由于某些原因挂掉了,当重新启动后,就会自动从oplog的最后一个操作开始同步,同步完成后,将信息写入自己的oplog,由于复制操作是先复制数据,复制完成后再写入oplog,有可能相同的操作会同步两份,不过MongoDB在设计之初就考虑到这个问题,将oplog的同一个操作执行多次,与执行一次的效果是一样的。</p>
<ul>
<li>
<h4>作用:</h4>
</li>
</ul>
<p> 当Primary进行写操作的时候,会将这些写操作记录写入Primary的Oplog 中,而后Secondary会将Oplog 复制到本机并应用这些操作,从而实现Replication的功能。<br> 同时由于其记录了Primary上的写操作,故还能将其用作数据恢复。<br> 可以简单的将其视作Mysql中的binlog。</p>
<p>3:oplog的增长速度</p>
<p>oplog是固定大小,他只能保存特定数量的操作日志,通常oplog使用空间的增长速度跟系统处理写请求的速度相当,如果主节点上每分钟处理1KB的写入数据,那么oplog每分钟大约也写入1KB数据。如果单次操作影响到了多个文档(比如删除了多个文档或者更新了多个文档)则oplog可能就会有多条操作日志。db.testcoll.remove() 删除了1000000个文档,那么oplog中就会有1000000条操作日志。如果存在大批量的操作,oplog有可能很快就会被写满了。</p>
<ul>
<li>
<h4>大小:</h4>
</li>
</ul>
<p> Oplog 是一个capped collection。<br> 在64位的Linux, Solaris, FreeBSD, and Windows 系统中,<strong>Mongodb默认将其大小设置为可用disk空间的5%(默认最小为1G,最大为50G)</strong>,<strong>或也可以在mongodb复制集实例初始化之前将mongo.conf中oplogSize设置为我们需要的值</strong>。</p>
<p> local.oplog.rs 一个capped collection集合.<strong>可在命令行下使用--oplogSize 选项设置该集合大小尺寸.</strong><br> 但是由于Oplog 其保证了复制的正常进行,以及数据的安全性和容灾能力。</p>
<p>4:oplog注意事项:</p>
<p><tt class="xref mongodb mongodb-data docutils literal">local.oplog.rs特殊的集合。<strong>用来记录Primary节点的操作</strong>。</tt></p>
<p><span class="pre">为了提高复制的效率,复制集中的所有节点之间会相互的心跳检测(ping)。每个节点都可以从其他节点上获取oplog。</span></p>
<p><strong>oplog中的一条操作。不管执行多少次效果是一样的</strong></p>
<p><span class="pre">5:oplog的大小</span></p>
<p><span class="pre">第一次启动复制集中的节点时,MongoDB会建立Oplog,会有一个默认的大小,这个大小取决于机器的操作系统</span></p>
<p><span class="pre">rs.printReplicationInfo() 查看 oplog 的状态,输出信息包括 oplog 日志大小,操作日志记录的起始时间。</span></p>
<p><span class="pre">db.getReplicationInfo() 可以用来查看oplog的状态、大小、存储的时间范围。</span></p>
<p><img src="https://images2017.cnblogs.com/blog/136834/201710/136834-20171024145343254-334142106.png" alt="" class="medium-zoom-image"></p>
<h3 id="_nav_2">oplog的大小</h3>
<p>capped collection是MongoDB中一种提供高性能插入、读取和删除操作的固定大小集合,当集合被填满的时候,新的插入的文档会覆盖老的文档。</p>
<p>所以,oplog表使用capped collection是合理的,因为不可能无限制的增长oplog。MongoDB在初始化副本集的时候都会有一个默认的oplog大小:</p>
<ul>
<li>在64位的Linux,Solaris,FreeBSD以及Windows系统上,MongoDB会分配磁盘剩余空间的5%作为oplog的大小,如果这部分小于1GB则分配1GB的空间</li>
<li>在64的OS X系统上会分配183MB</li>
<li>在32位的系统上则只分配48MB</li>
</ul>
<p><strong>oplog的大小设置是值得考虑的一个问题,如果oplog size过大,会浪费存储空间;如果oplog size过小,老的oplog记录很快就会被覆盖,那么宕机的节点就很容易出现无法同步数据的现象。</strong></p>
<p>比如,基于上面的例子,我们停掉一个备份节点(port=33333),然后通过主节点插入以下记录,然后查看oplog,发现以前的oplog已经被覆盖了。</p>
<p><strong>通过MongoDB shell连接上这个节点,会发现这个节点一直处于RECOVERING状态</strong>。</p>
<p>解决方法:</p>
<h4 id="_nav_3">数据同步</h4>
<p>在副本集中,有两种数据同步方式:</p>
<ul>
<li>
<div>initial sync(初始化):这个过程发生在当副本集中创建一个新的数据库或其中某个节点刚从宕机中恢复,或者向副本集中添加新的成员的时候,默认的,副本集中的节点会从离它最近的节点复制oplog来同步数据,这个最近的节点可以是primary也可以是拥有最新oplog副本的secondary节点。</div>
<ul>
<li>该操作一般会重新初始化备份节点,开销较大</li>
</ul>
</li>
<li>replication(复制):在初始化后这个操作会一直持续的进行着,以保持各个secondary节点之间的数据同步。</li>
</ul>
<h4 id="_nav_4">initial sync</h4>
<p>当遇到上面例子中无法同步的问题时,只能使用以下两种方式进行initial sync了</p>
<ul>
<li>
<div><strong>第一种方式</strong>就是停止该节点,然后删除目录中的文件,重新启动该节点。这样,这个节点就会执行initial sync</div>
<ul>
<li>注意:通过这种方式,sync的时间是根据数据量大小的,如果数据量过大,sync时间就会很长</li>
<li>同时会有很多网络传输,可能会影响其他节点的工作</li>
</ul>
</li>
<li><strong>第二种方式</strong>,停止该节点,然后删除目录中的文件,找一个比较新的节点,然后把该节点目录中的文件拷贝到要sync的节点目录中</li>
</ul>
<p>通过上面两种方式中的一种,都可以重新恢复"port=33333"的节点。改变<strong>一直处于RECOVERING状态的错误。</strong></p>
<p> </p>
<p><span class="pre">6:oplog数据结构</span></p>
<p>下面来分析一下oplog中字段的含义,通过下面的命令取出一条oplog:</p>
<pre>db.oplog.rs.find().skip(1).limit(1).toArray()</pre>
<ul>
<li>ts: 8字节的时间戳,由4字节unix timestamp + 4字节自增计数表示。这个值很重要,在选举(如master宕机时)新primary时,会选择ts最大的那个secondary作为新primary</li>
<li>
<div>op:1字节的操作类型</div>
<ul>
<li>"i": insert</li>
<li>"u": update</li>
<li>"d": delete</li>
<li>"c": db cmd</li>
<li>"db":声明当前数据库 (其中ns 被设置成为=>数据库名称+ '.')</li>
<li>"n": no op,即空操作,其会定期执行以确保时效性</li>
</ul>
</li>
<li>ns:操作所在的namespace</li>
<li>o:操作所对应的document,即当前操作的内容(比如更新操作时要更新的的字段和值)</li>
<li>o2: 在执行更新操作时的where条件,仅限于update时才有该属性</li>
</ul>
<h3 id="_nav_6">查看oplog的信息</h3>
<p>通过"db.printReplicationInfo()"命令可以查看oplog的信息</p>
<p><em><img src="https://images2017.cnblogs.com/blog/136834/201710/136834-20171024145054660-1523225984.png" alt="" class="medium-zoom-image"></em></p>
<p><em id="__mceDel">字段说明:</em></p>
<ul>
<li>configured oplog size: oplog文件大小</li>
<li>log length start to end: oplog日志的启用时间段</li>
<li>oplog first event time: 第一个事务日志的产生时间</li>
<li>oplog last event time: 最后一个事务日志的产生时间</li>
<li>now: 现在的时间</li>
</ul>
<h3 id="_nav_7">查看slave状态</h3>
<p>通过"db.printSlaveReplicationInfo()"可以查看slave的同步状态</p>
<p>副本节点中执行db.printSlaveReplicationInfo()命令可以查看同步状态信息</p>
<ul>
<li>
<div>source——从库的IP及端口</div>
</li>
<li>
<div>syncedTo——当前的同步情况,延迟了多久等信息</div>
</li>
</ul>
<p><img src="https://images2017.cnblogs.com/blog/136834/201710/136834-20171024145157707-852884216.png" alt="" class="medium-zoom-image"></p>
<p>当我们插入一条新的数据,然后重新检查slave状态时,就会发现sync时间更新了</p>
<p> </p>
<p> </p>
<p>常见操作:</p>
<p>1.创建新用户</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 一般要切换到某个数据库中,创建用户
use admin
指定用户的角色和数据库:
(注意此时添加的用户都只用于admin数据库,而非你存储业务数据的数据库)
db.createUser(
{
user: </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">admin</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
customData: {description:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">superuser</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">},
pwd: </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">admin</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
roles: [{role:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">userAdminAnyDatabase</span><span style="color: rgba(128, 0, 0, 1)">"</span>, db:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">admin</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">}]
}
)</span></pre>
</div>
<p>user字段,为新用户的名字;<br>pwd字段,用户的密码;<br>cusomData字段,为任意内容,例如可以为用户全名介绍;<br>roles字段,指定用户的角色,可以用一个空数组给新用户设定空角色。在roles字段,可以指定内置角色和用户定义的角色。<br>超级用户的role有两种,userAdmin或者userAdminAnyDatabase(比前一种多加了对所有数据库的访问,仅仅是访问而已)。<br>db是指定数据库的名字,admin是管理数据库。<br>不能用admin数据库中的用户登录其他数据库。注:只能查看当前数据库中的用户,哪怕当前数据库admin数据库,也只能查看admin数据库中创建的用户。<br><br></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 创建不受限制的超级用户
db.createUser(
{
user:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">root</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
pwd:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">pwd</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
roles:[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">root</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">]
}</span></pre>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 创建一个业务数据库管理员用户
</span>><span style="color: rgba(0, 0, 0, 1)"> db.createUser({
user:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">user001</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
pwd:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">123456</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
customData:{
name:</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">jim</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
email:</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">jim@qq.com</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
age:</span><span style="color: rgba(128, 0, 128, 1)">18</span><span style="color: rgba(0, 0, 0, 1)">,
},
roles:[
{role:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">readWrite</span><span style="color: rgba(128, 0, 0, 1)">"</span>,db:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">db001</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">},
{role:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">readWrite</span><span style="color: rgba(128, 0, 0, 1)">"</span>,db:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">db002</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">},
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">read</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 对其他数据库有只读权限,对db001、db002是读写权限</span>
<span style="color: rgba(0, 0, 0, 1)"> ]
})</span></pre>
</div>
<p>数据库用户角色:read、readWrite;<br>数据库管理角色:dbAdmin、dbOwner、userAdmin;<br>集群管理角色:clusterAdmin、clusterManager、4. clusterMonitor、hostManage;<br>备份恢复角色:backup、restore;<br>所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase<br>超级用户角色:root<br>内部角色:__system<br><br></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 查看创建的用户
show users
db.system.users.find()
db.runCommand({usersInfo:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">userName</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">})
# 修改密码
use admin
db.changeUserPassword(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">username</span><span style="color: rgba(128, 0, 0, 1)">"</span>,<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">password</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
# 修改密码和用户信息
db.runCommand(
{
updateUser:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">username</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
pwd:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">xxx</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
customData:{title:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">xxx</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">}
}
)
# 删除数据库用户
use admin
db.dropUser(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">databaseName</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
# 授权其他数据管理员
use admin
db.auth(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">admin</span><span style="color: rgba(128, 0, 0, 1)">'</span>,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">admin</span><span style="color: rgba(128, 0, 0, 1)">'</span>)</pre>
</div>
<p> </p>
<p> </p>
<p> </p>
<p>回收物理空间</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 切换到集合所在的数据库
use database_name
# 查看预估回收空间
db.</span><collection_name>.stats().wiredTiger[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">block-manager</span><span style="color: rgba(128, 0, 0, 1)">"</span>][<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file bytes available for reuse</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">]
# 对某个集合进行碎片整理
db.runCommand({compact:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"><collection_name></span><span style="color: rgba(128, 0, 0, 1)">"</span>}, force:<span style="color: rgba(0, 0, 255, 1)">true</span>) # 等待执行,返回<code>{ "ok" : 1 }</code>代表执行完成。<br><br></pre>
<ol class="linenums"><ol class="linenums">
<li class="L0"><span class="tag"><database_name><span class="pln">:数据库名。<br></span></span></li>
<li class="L1"><span class="tag"><collection_name><span class="pln">:集合名。<br></span></span></li>
<li class="L2"><span class="pln">force为可选项,如您需要在副本集实例的Primary节点执行该命令,需要设置force的值为true。<br></span></li>
<li class="L3"><span class="pln">compact操作不会传递给Secondary节点,当实例为副本集实例时,请重复上述步骤通过mongo shell连接至Secondary节点,执行碎片整理命令。</span></li>
</ol></ol></div>
<p> </p>
<p> </p>
<p> </p>
<p>常见问题:</p>
<h1 id="articleContentId" class="title-article">1.“not authorized on admin to execute command“</h1>
<p>一般是由于当前的用户没有该执行的权限</p>
<p> </p>
<p> </p>
<p>注意点:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">和用户管理相关的操作基本都要在admin数据库下运行,要先use admin;
如果在某个单一的数据库下,那只能对当前数据库的权限进行操作;
db.addUser是老版本的操作,现在版本也还能继续使用,创建出来的user是带有root role的超级管理员。
</pre>
</div>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p>权限:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">数据库用户角色:read、readWrite;
数据库管理角色:dbAdmin、dbOwner、userAdmin;
集群管理角色:clusterAdmin、clusterManager、4. clusterMonitor、hostManage;
备份恢复角色:backup、restore;
所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
超级用户角色:root
内部角色:__system//一般执行内部系统相关命令的时候, 需要使用该用户角色
</pre>
</div>
<p> </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">Read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库
dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
userAdmin:允许用户向system.users集合写入,可以在指定数据库里创建、删除和管理用户
clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
root:只在admin数据库中可用。超级账号,超级权限</span></pre>
</div>
<p> </p><br><br>
来源:https://www.cnblogs.com/xingxia/p/mongodb_oplog.html
頁:
[1]