Ivanzz
2024-03-09T15:58:10+00:00
ivan.L
万字长文总结分布式事务,总有一款适合你(转)
2022-03-22T00:00:00+00:00
/post/distribute-systems/2022/03/22/transaction-summary
<p>导语:本文参考网络相关文章,主要总结了XA, 2PC, 3PC, 本地事务状态表, 可靠消息队列, 最大努力通知, TCC, SAGA等分布式事务的特点和适用场景,为大家选择分布式事务提供一些参考。</p>
<!-- more -->
<h2 id="1-概述">1. 概述</h2>
<p>分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,相对的,传统事务也称之为单机事务。在单机事务时代,我们通常可以使用数据库的事务操作来解决数据的一致性问题;那么在微服务越来越流行的当下,我们应该如何保证不同服务器上数据的一致性?本文先从CAP理论和BASE理论说起,之后从一致性强弱的角度梳理当前主流的强一致性方案、最终一致性方案和弱一致性方案,最后总结一下各个方案的特点和适用场景,希望对你有所帮助。</p>
<h2 id="2-预备知识">2. 预备知识</h2>
<h3 id="21-cap理论">2.1 CAP理论</h3>
<p>CAP理论可以说是分布式系统的基石,它说的是一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项,而不能同时满足。</p>
<blockquote>
<p>2000年7月,加州大学伯克利分校的Eric Brewer教授在98年提出CAP猜想,99年发表(Harvest, Yield and Scalable Tolerant Systems),2000年在ACM PODC主题演讲(CAP keynote)。2年后,麻省理工学院的Seth Gilbert和Nancy Lynch从理论上证明了CAP。之后,CAP理论正式成为分布式计算领域的公认定理。</p>
</blockquote>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-1.jpg" alt="cap" /></p>
<ul>
<li>
<p>C:一致性,所有客户端看到都是同一份数据,即使在数据更新和删除之后</p>
</li>
<li>
<p>A:可用性,即使部分节点发生故障,所有客户端也能找到可用的数据备份</p>
</li>
<li>
<p>P:分区容忍性,即使发生网络分区故障,系统仍然能够按照预期正常工作</p>
</li>
</ul>
<p>CAP定理在分布式领域至关重要,在构建大型分布式系统的时候我们必须根据自己业务的独特性在三者之间进行权衡。</p>
<p>由于网络的各种不确定因素,在构建分布式应用的时候我们往往不得不考虑分区容忍性,这个时候我们通常只能在一致性和可用性之间进行选择。</p>
<h3 id="22">2.2</h3>
<p>根据CAP定理,如果要完整的实现事务的ACID特性,只能放弃可用性选择一致性,即CP模型。然而如今大多数的互联网应用中,可用性也同样至关重要。于是eBay架构师根据CAP定理进行妥协提出一种ACID替代性方案,即BASE,从而来达到可用性和一致性之间的某种微妙的平衡,选择AP模型的同时最大限度的满足一致性。</p>
<blockquote>
<p>由 eBay 架构师 Dan Pritchett 于 2008 年在《BASE: An Acid Alternative》论文中首次提出</p>
</blockquote>
<p>BASE是下面三部分的英文缩写简称:</p>
<ul>
<li>BA:Basically Available ,基本可用性</li>
<li>S:Soft State,软状态</li>
<li>E:Eventually Consistency,最终一致性</li>
</ul>
<p><code class="language-plaintext highlighter-rouge">基本可用</code>是相对CAP的“完全可用”而言的,即在部分节点出现故障的时候不要求整个系统完全可用,允许系统出现部分功能和性能上的损失:比如增加响应时间,引导用户到一个降级提示页面等等。</p>
<p><code class="language-plaintext highlighter-rouge">软状态</code>则是相对CAP定理强一致性的“硬状态”而言,CAP定理的一致性要求数据变化要立即反映到所有的节点副本上去,是一种强一致性。“软状态”不要求数据变化立即反映到所有的服务器节点上,允许存在一个中间状态进行过渡,比如允许放大延时等。</p>
<p><code class="language-plaintext highlighter-rouge">最终一致性</code>则是相对强一致性而言,它不要求系统数据始终保持一致的状态,只要求系统经过一段时间后最终会达到一致状态即可。</p>
<p>Base 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于 CAP 定理逐步演化而来的。其核心思想是:强一致性(Strong consistency)无法得到保障时,我们可以根据业务自身的特点,采用适当的方式来达到最终一致性(Eventual consistency)</p>
<h2 id="3-强一致性方案">3. 强一致性方案</h2>
<p>强一致性的方案便是前面提到的舍A保C的CP模型,即通过牺牲可用性来保证一致性,这种方案适用于对一致性要求很高的场景,比如金融交易等。</p>
<h3 id="31-2pc-二阶段提交">3.1 2PC-二阶段提交</h3>
<p>二阶段提交(Two-phaseCommit)是指,在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。通常,二阶段提交也被称为是一种协议(Protocol))。</p>
<blockquote>
<p>第一个一致性问题实例应该是Lamport的“Time, Clocks and the Ordering of Events in a Distributed System” (1978),大概在这篇论文发表的同一时间,JimGray在“Notes on Database Operating Systems” (1979)中描述了两阶段提交(2PC)。</p>
</blockquote>
<p>当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。</p>
<p>因此,二阶段提交的算法思路可以概括为: 参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。</p>
<p>所谓的两阶段是指:</p>
<p><strong>第一阶段:voting phase 投票阶段</strong></p>
<p>事务协调者给每个参与者发送Prepare消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志,但不提交,到达一种“万事俱备,只欠东风”的状态。</p>
<p><strong>第二阶段:commit phase 提交阶段</strong></p>
<p>如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)</p>
<p>二阶段提交的操作时序图如下:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-2.jpg" alt="2pc" /></p>
<p>以上这两个过程被称为“两段式提交”(2 Phase Commit,2PC)协议,而它能够成功保证一致性还需要一些其他前提条件。</p>
<ul>
<li>
<p>必须假设网络在提交阶段的短时间内是可靠的,即提交阶段不会丢失消息。两段式提交中投票阶段失败了可以补救(回滚),而提交阶段失败了无法补救(不再改变提交或回滚的结果,只能等崩溃的节点重新恢复),因而此阶段耗时应尽可能短,这也是为了尽量控制网络风险的考虑。</p>
</li>
<li>
<p>必须假设因为网络分区、机器崩溃或者其他原因而导致失联的节点最终能够恢复,不会永久性地处于失联状态。由于在准备阶段已经写入了完整的重做日志,所以当失联机器一旦恢复,就能够从日志中找出已准备妥当但并未提交的事务数据,并向协调者查询该事务的状态,确定下一步应该进行提交还是回滚操作。</p>
</li>
</ul>
<p>两段式提交的原理很简单,也不难实现,但有几个非常明显的缺点:</p>
<p>1) <strong>单点故障问题</strong></p>
<p>协调者在两段提交中具有举足轻重的作用,协调者等待参与者回复时可以有超时机制,允许参与者宕机,但参与者等待协调者指令时无法做超时处理。一旦协调者宕机,所有参与者都会受到影响。如果协调者一直没有恢复,没有正常发送 Commit 或者 Rollback 的指令,那所有参与者都必须一直等待。</p>
<p>2) <strong>同步阻塞问题</strong></p>
<p>执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。也就是说从投票阶段到提交阶段完成这段时间,资源是被锁住的。</p>
<p>3) <strong>数据一致性问题</strong></p>
<p>前面已经提到,两段式提交的成立是有前提条件的,当<code class="language-plaintext highlighter-rouge">网络稳定性</code>和<code class="language-plaintext highlighter-rouge">宕机恢复能力</code>的假设不成立时,两阶段提交可能会出现一致性问题。</p>
<blockquote>
<p>对于宕机恢复能力这一点无需多说。1985 年 Fischer、Lynch、Paterson 用定理(被称为FLP 不可能原理,在分布式中与 CAP 定理齐名)证明了如果宕机最后不能恢复,那就不存在任何一种分布式协议可以正确地达成一致性结果。</p>
</blockquote>
<p>对于<code class="language-plaintext highlighter-rouge">网络稳定性</code>来说,尽管提交阶段时间很短,但仍是明确存在的危险期。如果协调者在发出准备指令后,根据各个参与者发回的信息确定事务状态是可以提交的,协调者就会先持久化事务状态,并提交自己的事务。如果这时候网络忽然断开了,无法再通过网络向所有参与者发出 Commit 指令的话,就会导致部分数据(协调者的)已提交,但部分数据(参与者的)既未提交也没办法回滚,导致数据不一致。</p>
<h3 id="32-3pc-三阶段提交">3.2 3PC-三阶段提交</h3>
<p>为了解决两段式提交的单点故障问题、同步阻塞问题和数据一致性问题,“三段式提交”(3 Phase Commit,3PC)协议出现了</p>
<blockquote>
<p>Dale Skeen在“NonBlocking Commit Protocols” (1981)中指出,对于一个分布式系统,需要3阶段的提交算法来避免2PC中的阻塞问题</p>
</blockquote>
<p>与两阶段提交不同的是,三阶段提交有两个改动点:</p>
<ul>
<li>
<p>引入超时机制: 同时在协调者和参与者中都引入超时机制。</p>
</li>
<li>
<p>把原本的2PC的准备阶段再细分为两个阶段: 将准备阶段一分为二的理由是,这个阶段是重负载的操作,一旦协调者发出开始准备的消息,每个参与者都将马上开始写重做日志,这时候涉及的数据资源都会被锁住。如果此时某一个参与者无法完成提交,相当于所有的参与者都做了一轮无用功。所以,增加一轮询问阶段,如果都得到了正面的响应,那事务能够成功提交的把握就比较大了,也意味着因某个参与者提交时发生崩溃而导致全部回滚的风险相对变小了。</p>
</li>
</ul>
<p>三个阶段分别为:</p>
<p><strong>第一阶段:CanCommit阶段</strong></p>
<p>3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。</p>
<p><strong>第二阶段:PreCommit阶段</strong></p>
<p>本阶段协调者会根据第一阶段的询盘结果采取相应操作,询盘结果主要有两种:</p>
<ul>
<li>
<p>情况1-假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行:</p>
</li>
<li>
<p>情况2-假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。</p>
</li>
</ul>
<p><strong>第三阶段:doCommit阶段</strong></p>
<p>该阶段进行真正的事务提交,也可以分为以下两种情况。</p>
<ul>
<li>
<p>情况1-执行提交:针对第一种情况,协调者向各个参与者发起事务提交请求</p>
</li>
<li>
<p>情况2-中断事务:协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。</p>
</li>
</ul>
<p>三段式提交的操作时序如下图所示:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-3.jpg" alt="3pc" /></p>
<p>可以看出,3PC可以解决单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit,而不会一直持有事务资源并处于阻塞状态。</p>
<p>但是3PC对于数据一致性问题并未有任何改进,比如在进入PreCommit阶段后,如果协调者发送的是abort指令,而此时由于网络问题,有部分参与者在等待超时后仍未收到Abort指令的话,那这些参与者就会执行commit,这样就产生了不同参与者之间数据不一致的问题。</p>
<p>由于3PC非常难实现,目前市面上主流的分布式事务解决方案都是2PC协议。</p>
<h3 id="33-xa协议">3.3 XA协议</h3>
<p>2PC 的传统方案是在数据库层面实现的,如 Oracle、MySQL 都支持 2PC 协议,为了统一标准减少行业内不必要的对接成本,需要制定标准化的处理模型及接口标准,国际开放标准组织Open Group 在1994年定义了<em>分布式事务处理模型 DTP(Distributed Transaction Processing Reference Model)</em>。</p>
<p>DTP 规范中主要包含了 AP、RM、TM 三个部分,如下图所示:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-4.webp" alt="xa" /></p>
<p>它定义了三大组件:</p>
<ul>
<li>
<p>AP(Application Program):应用程序,一般指事务的发起者(比如数据库客户端或者访问数据库的程序),定义事务对应的操作(比如更新操作 UPDATE table SET xxx WHERE xxx)</p>
</li>
<li>
<p>RMs(Resource Managers):资源管理器,是分布式事务的参与者,管理共享资源,并提供访问接口,供外部程序来访问共享资源,比如数据库、打印服务等,另外 RM 还应该具有事务提交或回滚的能力。</p>
</li>
<li>
<p>TM(Transaction Manager):事务管理器,是分布式事务的协调者,管理全局事务,与每个RM进行通信,协调事务的提交和回滚,并协助进行故障恢复。</p>
</li>
</ul>
<p>其中XA约定了TM和RM之间双向通讯的接口规范,并实现了二阶段提交协议,从而在多个数据库资源下保证 ACID 四个特性。所以,DTP模型可以理解为:应用程序访问、使用RM的资源,并通过TM的事务接口(TX interface)定义需要执行的事务操作,然后TM和RM会基于 XA 规范,执行二阶段提交协议进行事务的提交/回滚:</p>
<ul>
<li>
<p>准备阶段:TM 向所有与当前事务相关的 RM 发起准备请求,每个 RM 评估自身是否能正常进行提交,并在响应中告知 TM。这类似于一个 TM 发起投票,各个 RM 进行投票的过程。</p>
</li>
<li>
<p>提交阶段:在第一阶段中,如果所有 RM 能在一定时间内表示 OK,则第二阶段由 TM 向所有 RM 发起提交请求。有任何一个 RM 没有准备好,TM 则向所有 RM 发起回滚请求。</p>
</li>
</ul>
<p>目前大多数实现XA的都是一些关系型数据库(包括PostgreSQL,MySQL,DB2,SQL Server和Oracle)和消息中间件(包括ActiveMQ,HornetQ,MSMQ和IBM MQ),所以提起XA往往指基于资源层的底层分布式事务解决方案。</p>
<blockquote>
<p>MySQL 从5.0.3开始支持XA分布式事务(只有InnoDB引擎才支持XA事务),业务开发人员在编写代码时,不应该直接操作这些XA事务操作的接口。因为在DTP模型中,RM上的事务分支的开启、结束、准备、提交、回滚等操作,都应该是由事务管理器TM来统一管理。</p>
</blockquote>
<p>XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。基于两阶段提交的分布式事务在提交事务时需要在多个节点之间进行协调,最大限度延后了提交事务的时间点,客观上延长了事务的执行时间,这会导致事务在访问共享资源时发生冲突和死锁的概率增高。因此,XA并发性能不理想(使用 XA 协议的 MySQL 集群,操作延时是单机的 10 倍),无法满足高并发场景,在互联网中使用较少。</p>
<h2 id="4-最终一致性方案">4. 最终一致性方案</h2>
<p>上面所述的强一致性方案在性能上都不理想,在CAP定理中属于CP范畴;在互联网应用中,为了提升性能和可用性,基于BASE理论使用最终一致性来替代强一致性,也就是通过牺牲部分一致性来换取性能和可用性的提升。</p>
<h3 id="41-本地事务状态表">4.1 本地事务状态表</h3>
<p>本地事务状态表方案的大概处理流程是:</p>
<p>1) 在调用方请求外部系统前将待执行的事务流程及其状态信息存储到数据库中,依赖数据库本地事务的原子特性保证本地事务和调用外部系统事务的一致性,这个存储事务执行状态信息的表称为本地事务状态表。</p>
<p>2) 在将事务状态信息存储到DB后,调用方才会开始继续后面流程,同步调用外部系统,并且每次调用成功后会更新相应的子事务状态,某一步失败时则中止执行。</p>
<p>3) 同时在后台运行一个定时任务,定期扫描事务状态表中未完成的子事务,并重新发起调用,或者执行回滚,或者在失败重试指定次数后触发告警让人工介入进行修复。</p>
<p>如下图所示:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-5.webp" alt="本地事务状态表" /></p>
<p>其中本地事务表的设计由业务方自己来定,可以是如上图中所示拆分为多个子事务来管理,简单点也可以只有一条记录,然后通过状态的流转来控制程序调用不同的外部系统。</p>
<h3 id="42-可靠消息队列">4.2 可靠消息队列</h3>
<p>可靠消息队列方案是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收到消息并处理事务成功,此方案强调的是只要消息发给事务参与方,则最终事务要达到一致。</p>
<p>此方案是利用消息中间件完成,如下图:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-6.png" alt="可靠消息队列" /></p>
<p>事务发起方(消息生产方)将消息发给消息中间件,事务参与方从消息中间件接收消息,事务发起方和消息中间件之间,事务参与方(消息消费方)和消息中间件之间都是通过网络通信,由于网络通信的不确定性会导致分布式事务问题。</p>
<p>因此可靠消息最终一致性方案要解决以下几个问题:</p>
<p>1) 本地事务与消息发送的原子性问题:要求事务发起方在本地事务执行成功后消息必须发出去,否则就丢弃消息</p>
<p>2) 事务参与方接收消息的可靠性:要求事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息</p>
<p>3) 消息重复消费的问题:要解决消息重复消费的问题就要实现事务参与方的方法幂等性。</p>
<p>目前主要的解决方案有2种,一种是<code class="language-plaintext highlighter-rouge">本地消息表方案</code>,一种是<code class="language-plaintext highlighter-rouge">事务消息方案</code>。</p>
<h4 id="421-本地消息表">4.2.1 本地消息表</h4>
<p>如果是使用 Kafka(< 0.11.0) 这类不支持事务消息的消息中间件,参与事务的系统需要在给消息中间件发送消息之前,把消息的信息和状态存储到本地的消息表中,如下图所示:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-7.jpg" alt="本地消息表" /></p>
<p>主要流程如下:</p>
<p>1) 参与分布式事务的系统A接收到请求后,在执行本地事务的同时把将待发送的消息记录到事务消息表中,将业务表和消息表放在一个数据库事务里,保证两者的原子性;</p>
<p>2) 执行完后系统A不直接给消息中间件发消息,而是通过后台定时任务扫描消息表将消息push到消息中间件,对于push失败的消息则会不断重试,直到消息中间件成功返回 ack 消息,并更细消息表中投递状态,从而保证消息的不丢失。</p>
<p>3) 消息中间件收到消息后,会将消息投递给订阅消息的外部系统1/2/3,外部系统1/2/3收到消息后执行本地事务,只有成功才应答 Ack 消息,消息中间件只有在收到Ack消息后才将该条消息丢弃,否则会不断的重复发送直到成功,所以事务的所有参与者需要自行保证事务执行的幂等性。</p>
<h4 id="422-事务消息方案">4.2.2 事务消息方案</h4>
<p>如果是基于RocketMQ或Kafka(>=0.11.0)这类的支持事务操作的消息中间件,上述的方案则可以简化,此时上面的的定时任务的工作将交给消息中间件来提供,如下图所示:
<img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-8.webp" alt="事务消息方案" /></p>
<p>流程比较简单不再赘述,需要说明的是,消息中间件如果收到Comfirm消息,则会将消息转为对消费者可见,并开始投递;如果收到Rollback消息,则会删除之前的事务消息;如果未收到确认消息,则会通过事务回查机制定时检查本地事务的状态,决定是否可以提交投递。</p>
<h3 id="43-最大努力通知">4.3 最大努力通知</h3>
<p>最大努力通知方案(Best-effort delivery)是最简单的一种柔性事务,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果不影响主动方的处理结果。典型的使用场景:如银行通知、商户通知等。最大努力通知型的实现方案,一般符合以下特点:</p>
<ul>
<li>
<p>不可靠消息:业务活动主动方,在完成业务处理之后,向业务活动的被动方发送消息,直到通知N次后不再通知,允许消息丢失(不可靠消息)。</p>
</li>
<li>
<p>定期校对:业务活动的被动方,根据定时策略,向业务活动主动方查询(主动方提供查询接口),恢复丢失的业务消息。</p>
</li>
</ul>
<p>比如充值的一个例子:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-9.webp" alt="充值例子" /></p>
<p><strong>与可靠消息队列方案区别:</strong></p>
<ul>
<li>
<p>解决方案思想不同: 可靠消息一致性,发起通知方需要保证将消息发出去,并且将消息发到接收通知方,消息的可靠性关键由发起通知方来保证。最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接 收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方。</p>
</li>
<li>
<p>两者的业务应用场景不同: 可靠消息一致性关注的是交易过程的事务一致,以异步的方式完成交易。最大努力通知关注的是交易后的通知事务,即将交易结果可靠的通知出去。</p>
</li>
<li>
<p>技术解决方向不同: 可靠消息一致性要解决消息从发出到接收的一致性,即消息发出并且被接收到。最大努力通知无法保证消息从发出到接收的一致性,只提供消息接收的可靠性机制。可靠机制是,最大努力的将消息通知给接收方,当消息无法被接收方接收时,由接收方主动查询消息(业务处理结果)。</p>
</li>
</ul>
<h3 id="44-tcc">4.4 TCC</h3>
<p>前面介绍的可靠消息队列方案能保证最终的结果是相对可靠的,过程也足够简单,但是可靠消息队列的整个实现过程完全没有任何隔离性可言。虽然在有些业务中,有没有隔离性不是很重要,比如说搜索系统。</p>
<p>但在有些业务中,一旦缺乏了隔离性,就会带来许多麻烦,比如下面一个简化版的订销存交易流程:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-10.png" alt="订销存交易流程" /></p>
<p>用户在电商网站下订单后通知库存服务扣减库存,最后通过积分服务给用户增加积分。整个交易操作应该具有原子性,这些交易步骤要么一起成功,要么一起失败,必须是一个整体性的事务。</p>
<p>假设用户下完订单通知库存服务扣减库存失败时,比如原本是10件商品卖了1件剩余9件,但由于库存DB操作失败,导致库存还是10件,这时就出现了数据不一致的情况,此时如果有其它用户也进行了购买操作,则可能出现超卖的问题。</p>
<p>如果采用2PC的解决方案,在整个交易成功完成或者失败回滚之前,其它用户的操作将会处于阻塞等待的状态,这会大大的降低系统的性能和用户体验。</p>
<p>果业务需要隔离,通常就应该重点考虑 TCC(Try-Confirm-Cancel)方案,TCC天生适用于需要强隔离性的分布式事务中,它是由数据库专家帕特 · 赫兰德(Pat Helland)在 2007 年撰写的论文《Life beyond Distributed Transactions: An Apostate’s Opinion》中提出的。</p>
<p>在具体实现上,TCC 的操作其实有点儿麻烦和复杂,它是一种业务侵入性较强的事务方案,要求业务处理过程必须拆分为“预留业务资源”和“确认 / 释放消费资源”两个子过程。另外,你看名字也能看出来,TCC 的实现过程分为了三个阶段:</p>
<ul>
<li>
<p>Try:尝试执行阶段,完成所有业务可执行性的检查(保障一致性),并且预留好事务需要用到的所有业务资源(保障隔离性)。</p>
</li>
<li>
<p>Confirm:确认执行阶段,不进行任何业务检查,直接使用 Try 阶段准备的资源来完成业务处理。注意,Confirm 阶段可能会重复执行,因此需要满足幂等性。</p>
</li>
<li>
<p>Cancel:取消执行阶段,释放 Try 阶段预留的业务资源。注意,Cancel 阶段也可能会重复执行,因此也需要满足幂等性。</p>
</li>
</ul>
<p>TCC是基于BASE理论的类2PC方案,根据业务的特性对2PC的流程进行了优化,与2PC的区别在一些步骤的细节上,如下图:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-11.webp" alt="TCC与2PC的区别" /></p>
<p>可以看出,不同于2PC第一阶段的Prepare,TCC在Try阶段主要是对资源的预留操作这类的轻量级操作,比如冻结部分库存数量,它不需要像2PC在第二阶段完成之后才释放整个资源,也就是它不需要等待整个事务完成后才进行提交,这时其它用户的购买操作可以继续正常进行,因此它的阻塞范围小时间短暂,性能上比2PC方案要有很大的提升。</p>
<p>另外,TCC是位于用户代码层面,而不是在基础设施层面,这为它的实现带来了较高的灵活性,可以根据需要设计资源锁定的粒度。TCC 在业务执行时只操作预留资源,几乎不会涉及锁和资源的争用,具有很高的性能潜力。</p>
<p>但是 TCC要求所有的事务参与方都必须要提供三个操作接口:Try/Confirm/Cancel,带来了更高的开发成本和业务侵入性,意味着有更高的开发成本和更换事务实现方案的替换成本,特别是对一些难以改动的老旧系统来说甚至是不可行的。</p>
<h3 id="45-saga事务">4.5 SAGA事务</h3>
<p>SAGA 事务模式的历史十分悠久,比分布式事务的概念提出还要更早。SAGA 的意思是“长篇故事、长篇记叙、一长串事件”,它起源于 1987 年普林斯顿大学的赫克托 · 加西亚 · 莫利纳(Hector Garcia Molina)和肯尼斯 · 麦克米伦(Kenneth Salem)在 ACM 发表的一篇论文《SAGAS》。</p>
<p>文中提出了一种如何提升“长时间事务”(Long Lived Transaction)运作效率的方法,大致思路是把一个大事务分解为可以交错运行的一系列子事务的集合。原本提出 SAGA 的目的,是为了避免大事务长时间锁定数据库的资源,后来才逐渐发展成将一个分布式环境中的大事务,分解为一系列本地事务的设计模式。</p>
<ul>
<li>
<p>每个 Saga 事务由一系列幂等的有序子事务(sub-transaction) T1,T2,…,Ti,…,Tn组成。</p>
</li>
<li>
<p>每个 Ti 都有对应的幂等补偿动作C1,C2,…,Ci,…,Cn,补偿动作用于撤销 T1,T2,…,Ti,…,Tn造成的结果。</p>
</li>
</ul>
<p>如果 T1 到 Tn 均成功提交,那么事务就可以顺利完成。否则,就要采取恢复策略,恢复策略分为向前恢复和向后恢复两种。</p>
<h4 id="451-向前恢复forward-recovery">4.5.1 向前恢复(Forward Recovery)</h4>
<p>如果 Ti 事务提交失败,则一直对 Ti 进行重试,直至成功为止(最大努力交付)。这种恢复方式不需要补偿,适用于事务最终都要成功的场景,比如在别人的银行账号中扣了款,就一定要给别人发货。正向恢复的执行模式为:T1,T2,…,Ti(失败),Ti(重试)…,Ti+1,…,Tn,该情况下不需要Ci。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-12.webp" alt="SAGA向前恢复" /></p>
<h4 id="452-向后恢复backward-recovery">4.5.2 向后恢复(Backward Recovery)</h4>
<p>如果 Ti 事务提交失败,则一直执行 Ci 对 Ti 进行补偿,直至成功为止(最大努力交付)。这里要求 Ci 必须(在持续重试后)执行成功。向后恢复的执行模式为:T1,T2,…,Ti(失败),Ci(补偿),…,C2,C1。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-13.webp" alt="SAGA向后恢复" /></p>
<p>Saga 事务常见的有两种不同的实现方式。</p>
<h4 id="453-命令协调模式">4.5.3 命令协调模式</h4>
<p>这种模式由中央协调器(Orchestrator,简称 OSO)集中处理事件的决策和业务逻辑排序,以命令/回复的方式与每项服务进行通信,全权负责告诉每个参与者该做什么以及什么时候该做什么。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-14.webp" alt="命令协调模式" /></p>
<p>以电商订单的例子为例:</p>
<p>1) 事务发起方的主业务逻辑请求 OSO 服务开启订单事务。</p>
<p>2) OSO 向库存服务请求扣减库存,库存服务回复处理结果。</p>
<p>3) OSO 向订单服务请求创建订单,订单服务回复创建结果。</p>
<p>4) OSO 向支付服务请求支付,支付服务回复处理结果。</p>
<p>5) 主业务逻辑接收并处理 OSO 事务处理结果回复。</p>
<p>中央协调器必须事先知道执行整个订单事务所需的流程(例如通过读取配置)。如果有任何失败,它还负责通过向每个参与者发送命令来撤销之前的操作来协调分布式的回滚。</p>
<p>基于中央协调器协调一切时,回滚要容易得多,因为协调器默认是执行正向流程,回滚时只要执行反向流程即可。</p>
<h4 id="454-事件编排模式">4.5.4 事件编排模式</h4>
<p>这种模式没有中央协调器(没有单点风险),由每个服务产生并观察其他服务的事件,并决定是否应采取行动。</p>
<p>在事件编排方法中,第一个服务执行一个事务,然后发布一个事件。该事件被一个或多个服务进行监听,这些服务再执行本地事务并发布(或不发布)新的事件。</p>
<p>当最后一个服务执行本地事务并且不发布任何事件时,意味着分布式事务结束,或者它发布的事件没有被任何 Saga 参与者听到都意味着事务结束。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-15.webp" alt="事件编排模式" /></p>
<p>电商订单的例子为例:</p>
<p>1) 事务发起方的主业务逻辑发布开始订单事件。</p>
<p>2) 库存服务监听开始订单事件,扣减库存,并发布库存已扣减事件。</p>
<p>3) 订单服务监听库存已扣减事件,创建订单,并发布订单已创建事件。</p>
<p>4) 支付服务监听订单已创建事件,进行支付,并发布订单已支付事件。</p>
<p>5) 主业务逻辑监听订单已支付事件并处理。</p>
<p>事件/编排是实现 Saga 模式的自然方式,它很简单,容易理解,不需要太多的代码来构建。如果事务涉及 2 至 4 个步骤,则可能是非常合适的。</p>
<h4 id="455-saga适用场景">4.5.5 SAGA适用场景</h4>
<p>SAGA的适用场景主要是以下几种:</p>
<ul>
<li>
<p>业务流程长、业务流程多</p>
</li>
<li>
<p>参与者包含第三方或遗留系统服务,无法提供TCC模式要求的三个接口</p>
</li>
<li>
<p>典型业务系统:如金融网络(与外部金融机构对接)、互联网微贷、渠道整合、分布式架构服务集成等业务系统</p>
</li>
<li>
<p>银行业金融机构使用广泛</p>
</li>
</ul>
<h4 id="456-saga优势">4.5.6 SAGA优势</h4>
<ul>
<li>
<p>一阶段提交本地数据库事务,无锁,高性能;</p>
</li>
<li>
<p>参与者可以采用事务驱动异步执行,高吞吐;</p>
</li>
<li>
<p>补偿服务即正向服务的“反向”,易于理解,易于实现;</p>
</li>
</ul>
<p>但是Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。</p>
<h2 id="5-弱一致性方案">5. 弱一致性方案</h2>
<p>前面最终一致性方案基本能满足大多数的场景,但在一些场景下,我们对系统的性能和可用性有更高的要求。比如海量请求的高并发秒杀场景中,如何保证服务的高可用是个很大的挑战,除了要对秒杀的非核心功能进行降级、增加响应时间外,根据CAP定理,还需要对对一致性的再进行妥协,从最终一致性弱化到弱一致性。</p>
<p>弱一致性是指数据更新后,容忍后续只能访问到部分或者全部访问不到(也不承诺多久可以访问到),并且不会对业务产生重大影响。下面介绍的几个方案都是根据自身业务特点做的妥协,不是严格意义上完备的技术方案,而是一种解决思路,是适合业务自身特点、满足性能要求、满足成本要求或技术架构要求下的一种解决思路,仅供参考。</p>
<h3 id="51-基于状态的补偿">5.1 基于状态的补偿</h3>
<p>这是一个根据业务特性进行妥协的一种方案,根据实际的业务场景对数据重要性进行划分,放弃传统的全局数据一致,允许部分不重要的数据出现不一致,但不会对业务产生重大影响。</p>
<p>比如在电商网站购物场景中,其中两个主要的步骤是创建订单和扣库存,这分别由两个服务进行处理:订单服务和库存服务。</p>
<ul>
<li>
<p>如果采用前面可靠消息队列方案,创建订单的消息通知库存服务扣除库存,由于异步消息的延迟则会导致超卖;</p>
</li>
<li>
<p>如果采用TCC的方案,每次请求操作都需要Try、Confirm两次请求调用,性能又不能达标;</p>
</li>
<li>
<p>如果采用本地事务状态表,则需要对海量的事务进行状态更新操作,性能和延迟也同样会是个问题。</p>
</li>
</ul>
<p>但是我们可以依据实际的电商购物场景进行取舍:<code class="language-plaintext highlighter-rouge">允许少卖,但不能超卖</code>。于是我们可以先扣库存,库存扣减成功后才创建订单并关联库存,若扣库存失败则不创建订单。有以下几种情况:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-16.png" alt="基于状态的补偿" /></p>
<p>对于第2种情况,会出现多扣库存的情况,这时可以基于状态进行补偿,就不会出现超卖的问题了:根据库存流水记录查找那些一段时间内未关联订单的库存记录进行撤销操作。这个和我们在12306上的买车票,如果30分钟内未支付的话车票会被释放,是一个道理。</p>
<p>这是一种事后处理机制,即使补偿失败,也不会有严重后果,对业务来说也是可接受的,大不了手工重新上架。</p>
<h3 id="55-事后对账">5.5 事后对账</h3>
<p>对于那些业务流程复杂,涉及外部服务比较多,并且需要维护的状态也很复杂的场景,就很难根据状态进行自动补偿,这时可以进一步简化操作:不做自动的状态补偿。</p>
<p>还是拿上面那个订单和库存的例子进行说明,比如先扣库存,然后创建订单,如果订单创建失败则重试,重试还是失败则回滚,回滚失败则触发告警,然后由脚本对业务数据自动进行对账,并对异常数据进行修复。</p>
<p>对账的关键是找出数据的特征,有些好找,有些难,但是它的基本要求是数据记录是“完备”的,然后研发人员根据经验,对不同特征的数据执行不同的修复,对于常见的问题可能会有一些修复脚本来辅助处理。</p>
<p>事后对账一般会根据业务特点设计自动对账脚本,实现对业务数据的自动检查,发现业务中可能存在的问题(比如异常、假账)等,然后触发执行对应的动作,至少是要有告警,通知研发同学介入,如果做的更好一点的话,可以对特定类型的异常数据自动进行修复,减少人工成本。</p>
<h2 id="6-总结">6. 总结</h2>
<p>强一致性方案主要用于对数据一致性要求比较高的场景,比如金融银行等,且大多是在数据库层面实现,然后业务方直接使用;</p>
<p>在常规的互联网应用中,对性能和可用性要求更高,可以采用基于BASE理论的最终一致性方案;</p>
<p>弱一致性方案需要容忍数据的部分不一致,主要用于一些极端的场景中,比如高并发秒杀场景。</p>
<p>各个方案的特点总结如下:</p>
<ul>
<li>
<p>2PC/3PC:依赖于数据库,能够很好的提供强一致性和强事务性,但相对来说延迟比较高,比较适合传统的单体应用,不适合高并发和高性能要求的场景。</p>
</li>
<li>
<p>XA协议:基于XA协议的强一致事务使用起来相对简单,但是无法很好地应对互联网的短事务和高并发场景。</p>
</li>
<li>
<p>本地事务状态表:方案轻量,容易实现,但与具体的业务场景耦合较高,不可公用。</p>
</li>
<li>
<p>可靠消息队列:适合执行周期长且实时性要求不高的场景。引入消息机制后,同步的事务操作变为基于消息执行的异步操作, 避免了分布式事务中的同步阻塞操作的影响,并实现了两个服务的解耦。典型的使用场景:注册送积分,登录送优惠券等。</p>
</li>
<li>
<p>最大努力通知:是分布式事务中要求最低的一种,适用于一些最终一致性时间敏感度低的业务;允许发起通知方处理业务失败,在接收通知方收到通知后积极进行失败处理,无论发起通知方如何处理结果都会不影响到接收通知方的后续处理;发起通知方需提供查询执行情况接口,用于接收通知方校对结果。典型的使用场景:银行通知、支付结果通知等。</p>
</li>
<li>
<p>TCC:适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。但是对于业务的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。</p>
</li>
<li>
<p>SAGA:适合于“业务流程长、业务流程多”的场景。特别是针对参与事务的服务是遗留系统服务。但由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。另外, Saga 相比缺少预提交动作,导致补偿动作的实现比较麻烦,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。</p>
</li>
<li>
<p>弱一致性方案:上面给出的几种弱一致性方案是在高并发等场景下,为了提高系统的性能和可用性而在一致性方面做的妥协,一般需要结合具体的业务特点、实现成本等各方因素对一些最终一致性方案做改造</p>
</li>
</ul>
<p>总之,分布式事务没有能一揽子包治百病的解决方案,只有因地制宜地选用适合自己的,才是唯一有效的做法。</p>
<p>以下是在网上看到的一些技术大佬的经验之谈,共勉之:</p>
<ul>
<li>
<p>实际运用理论时进行架构设计时,许多人容易犯“手里有了锤子,看什么都觉得像钉子”的错误,设计方案时考虑的问题场景过多,各种重试,各种补偿机制引入系统,导致系统过于复杂,落地遥遥无期。世界上解决一个计算机问题最简单的方法:“恰好”不需要解决它!</p>
</li>
<li>
<p>有些问题,看起来很重要,但实际上我们可以通过合理的设计或者将问题分解来规避。</p>
</li>
<li>
<p>设计分布式事务系统也不是需要考虑所有异常情况,不必过度设计各种回滚,补偿机制。</p>
</li>
<li>
<p>如果硬要把时间花在解决问题本身,实际上不仅效率低下,而且也是一种浪费。</p>
</li>
<li>
<p>如果系统要实现回滚流程的话,有可能系统复杂度将大大提升,且很容易出现 Bug,估计出现 Bug 的概率会比需要事务回滚的概率大很多。</p>
</li>
<li>
<p>在设计系统时,我们需要衡量是否值得花这么大的代价来解决这样一个出现概率非常小的问题,可以考虑当出现这个概率很小的问题,能否采用人工解决的方式,这也是大家在解决疑难问题时需要多多思考的地方。</p>
</li>
</ul>
<h2 id="7-开源框架">7. 开源框架</h2>
<p>目前我所了解的分布式事务解决方案的开源框架主要是Seata和Dtm,但都未实际使用过,所以没有发言权,在此只是列出来各自框架的主要特点,仅供参考。</p>
<h3 id="71-seata">7.1 seata</h3>
<p>Seata(Simple Extensible Autonomous Transaction Architecture,一站式分布式事务解决方案)是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案(https://github.com/seata/seata)。目前start数有21k。</p>
<p>如下图所示,Seata 中有三大模块,分别是 TM、RM 和 TC。 其中 TM 和 RM 是作为 Seata 的客户端与业务系统集成在一起,TC 作为 Seata 的服务端独立部署。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-17.webp" alt="seata" /></p>
<p>在 Seata 中,分布式事务的执行流程:</p>
<ul>
<li>
<p>TM 开启分布式事务(TM 向 TC 注册全局事务记录);</p>
</li>
<li>
<p>按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );</p>
</li>
<li>
<p>TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);</p>
</li>
<li>
<p>TC 汇总事务信息,决定分布式事务是提交还是回滚;</p>
</li>
<li>
<p>TC 通知所有 RM 提交/回滚 资源,事务二阶段结束;</p>
</li>
</ul>
<p>Seata 会有 4 种分布式事务解决方案,分别是 <code class="language-plaintext highlighter-rouge">AT 模式</code>、<code class="language-plaintext highlighter-rouge">TCC 模式</code>、<code class="language-plaintext highlighter-rouge">Saga 模式</code>和 <code class="language-plaintext highlighter-rouge">XA 模式</code>。</p>
<h3 id="72-dtm">7.2 DTM</h3>
<p>DTM是一款golang开发的分布式事务管理器(https://github.com/dtm-labs/dtm),目前star数4.6k,它解决了跨数据库、跨服务、跨语言栈更新数据的一致性问题。他优雅的解决了幂等、空补偿、悬挂等分布式事务难题,提供了简单易用、高性能、易水平扩展的解决方案。</p>
<p><strong>亮点如下:</strong></p>
<ul>
<li>
<p>极易接入:零配置启动服务,提供非常简单的HTTP接口,极大降低上手分布式事务的难度,新手也能快速接入</p>
</li>
<li>
<p>跨语言:可适合多语言栈的公司使用。方便go、python、php、nodejs、ruby、c# 各类语言使用。</p>
</li>
<li>
<p>使用简单:开发者不再担心悬挂、空补偿、幂等各类问题,首创子事务屏障技术代为处理</p>
</li>
<li>
<table>
<tbody>
<tr>
<td>易部署、易扩展:依赖mysql</td>
<td>redis,部署简单,易集群化,易水平扩展</td>
</tr>
</tbody>
</table>
</li>
<li>多种分布式事务协议支持:TCC、SAGA、XA、二阶段消息,一站式解决所有分布式事务问题</li>
</ul>
<p>与其他框架对比(非Java语言类的,暂未看到除dtm之外的成熟框架,因此这里将DTM和Java中最成熟的Seata对比):</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/distribute/distri-summary-18.webp" alt="dtm与其他框架对比" /></p>
<p><br />
<br /></p>
<p><strong>[参看]:</strong></p>
<ol>
<li><a href="https://zhuanlan.zhihu.com/p/461294722">万字长文总结分布式事务,总有一款适合你</a></li>
</ol>
<p><br />
<br />
<br /></p>
Pulsar单机版安装
2022-01-21T00:00:00+00:00
/post/mq/2022/01/21/mq-pulsar-part2
<p>针对本地开发(local development)及测试(testing),我们可以在主机上以单机模式运行Pulsar。单机模式(standalone mode)包括Pulsar broker,必要的Zookeeper以及BookKeeper组件,并运行在同一个JVM进程中。</p>
<blockquote>
<p>Pulsar in production?</p>
<p>If you’re looking to run a full production Pulsar installation, see the <a href="https://pulsar.apache.org/docs/en/deploy-bare-metal">Deploying a Pulsar instance guide</a>.</p>
</blockquote>
<!-- more -->
<h2 id="1-install-pulsar-standalone">1. Install Pulsar standalone</h2>
<p>本文介绍单机版Pulsar安装的每一个步骤。</p>
<h3 id="11-system-requirements">1.1 System requirements</h3>
<p>目前Pulsar可以运行在64位<code class="language-plaintext highlighter-rouge">macOS</code>、<code class="language-plaintext highlighter-rouge">Linux</code>以及<code class="language-plaintext highlighter-rouge">Windows</code>上。要使用Pulsar,必须安装64-bit JRE/JDK 8或更高版本。</p>
<p>关于JDK的安装,请参看相关文档,这里不做介绍。当前我们的系统环境及Java版本如下:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string"># uname -a
Linux localhost.localdomain 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
# cat /etc/redhat-release
CentOS Linux release 7.3.1611 (Core)
# java -version
java version "1.8.0_301"
Java(TM) SE Runtime Environment (build 1.8.0_301-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode)</code></pre></figure>
<blockquote>
<p>Tip</p>
<p>By default, Pulsar allocates 2G JVM heap memory to start. It can be changed in <code class="language-plaintext highlighter-rouge">conf/pulsar_env.sh</code> file under <code class="language-plaintext highlighter-rouge">PULSAR_MEM</code>. This is extra options passed into <code class="language-plaintext highlighter-rouge">JVM</code></p>
</blockquote>
<blockquote>
<p>Note</p>
<p>Broker is only supported on 64-bit JVM.</p>
</blockquote>
<h3 id="12-install-pulsar-using-binary-release">1.2 Install Pulsar using binary release</h3>
<p>我们可以通过如下方式来下载Pulsar:</p>
<ul>
<li>
<p>download from the Apache mirror (<a href="https://archive.apache.org/dist/pulsar/pulsar-2.9.1/apache-pulsar-2.9.1-bin.tar.gz">Pulsar 2.9.1 binary release</a>)</p>
</li>
<li>
<p>download from the Pulsar <a href="https://pulsar.apache.org/download">downloads page</a></p>
</li>
<li>
<p>download from the Pulsar <a href="https://github.com/apache/pulsar/releases/latest">releases page</a></p>
</li>
<li>
<p>use wget:</p>
</li>
</ul>
<pre>
# $ wget https://archive.apache.org/dist/pulsar/pulsar-2.9.1/apache-pulsar-2.9.1-bin.tar.gz
</pre>
<p>当前最新稳定版为<code class="language-plaintext highlighter-rouge">2.9.1</code>,我们使用该版本。</p>
<pre>
# mkdir pulsar-inst
# cd pulsar-inst
# wget https://dlcdn.apache.org/pulsar/pulsar-2.9.1/apache-pulsar-2.9.1-bin.tar.gz --no-check-certificate
# ls -alh
total 322M
drwxr-xr-x. 2 root root 44 Feb 7 01:37 .
dr-xr-x---. 16 root root 4.0K Feb 7 01:34 ..
-rw-r--r--. 1 root root 322M Dec 16 04:31 apache-pulsar-2.9.1-bin.tar.gz
</pre>
<p>下载完成后解压<code class="language-plaintext highlighter-rouge">apache-pulsar-2.9.1-bin.tar.gz</code>如下:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string"># tar -zxvf apache-pulsar-2.9.1-bin.tar.gz
# cd apache-pulsar-2.9.1
# ls
bin conf examples instances lib LICENSE licenses NOTICE README</code></pre></figure>
<p>1) <strong>What your package contains</strong></p>
<p>在Pulsar二进制包中初始含有如下目录:</p>
<ul>
<li>
<p>bin: Pulsar命令行工具,比如<a href="https://pulsar.apache.org/docs/en/reference-cli-tools#pulsar">pulsar</a>、<a href="https://pulsar.apache.org/tools/pulsar-admin/">pulsar-admin</a></p>
</li>
<li>
<p>conf: Pulsar配置文件,包含<a href="https://pulsar.apache.org/docs/en/reference-configuration#broker">broker configuration</a>、<a href="https://pulsar.apache.org/docs/en/reference-configuration#zookeeper">ZooKeeper configuration</a>等</p>
</li>
<li>
<p>examples: 一个包含有<a href="https://pulsar.apache.org/docs/en/functions-overview">Pulsar Functions</a>示例的Java <code class="language-plaintext highlighter-rouge">JAR</code>文件。</p>
</li>
<li>
<p>lib: Pulsar所用到的一些<code class="language-plaintext highlighter-rouge">JAR</code>文件</p>
</li>
<li>
<p>licenses: <code class="language-plaintext highlighter-rouge">.txt</code>形式的license文件</p>
</li>
</ul>
<p>如下的目录会在首次运行Pulsar时创建:</p>
<ul>
<li>
<p>data: ZooKeeper及BookKeeper的数据存储目录</p>
</li>
<li>
<p>instances: 为<a href="https://pulsar.apache.org/docs/en/functions-overview">Pulsar Functions</a>所创建的Artifacts</p>
</li>
<li>
<p>logs: Logs created by the installation.</p>
</li>
</ul>
<blockquote>
<p>Tip</p>
<p>假如你想要使用内置的connectors以及tiered storage offloaders,你可以根据如下的指令来进行安装</p>
<ul>
<li>
<p><a href="https://pulsar.apache.org/docs/en/standalone/#install-builtin-connectors-optional">Install builtin connectors (optional)</a></p>
</li>
<li>
<p><a href="https://pulsar.apache.org/docs/en/standalone/#install-tiered-storage-offloaders-optional">Install tiered storage offloaders (optional)</a></p>
</li>
</ul>
<p>否则,你可以跳过相关的步骤,然后直接执行<a href="https://pulsar.apache.org/docs/en/standalone/#start-pulsar-standalone">Start Pulsar standalone</a>步骤。在不安装内置connectors以及tiered storaged offloaders的情况下,Pulsar也可以成功的安装。</p>
</blockquote>
<p><br />
<br /></p>
<p><strong>[参考]</strong></p>
<ol>
<li>
<p><a href="https://pulsar.apache.org/">pulsar 官网</a></p>
</li>
<li>
<p><a href="https://pulsar.apache.org/docs/zh-CN/next/concepts-architecture-overview/">pulsar 中文网</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/weixin_33775572/article/details/92127055">pulsar 集群搭建</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
pulsar架构
2022-01-21T00:00:00+00:00
/post/mq/2022/01/21/mq-pulsar-part1
<p>本文从整体上介绍一下Pulsar的架构,在此做个记录。</p>
<!-- more -->
<h2 id="1-architecture-overview">1. Architecture Overview</h2>
<p>从更高层级来看,一个Pulsar实例(instance)是由一个或多个Pulsar cluster所组成的。一个instance中的clusters可以相互之间自我复制数据(replicate data)。</p>
<p>在一个Pulsar集群中:</p>
<ul>
<li>
<p>One or more brokers handles and load balances incoming messages from producers, dispatches messages to consumers, communicates with the Pulsar configuration store to handle various coordination tasks, stores messages in BookKeeper instances (aka bookies), relies on a cluster-specific ZooKeeper cluster for certain tasks, and more.</p>
</li>
<li>
<p>A BookKeeper cluster consisting of one or more bookies handles persistent storage of messages.</p>
</li>
<li>
<p>A ZooKeeper cluster specific to that cluster handles coordination tasks between Pulsar clusters.</p>
</li>
</ul>
<p>下图是一个Pulsar集群的整体架构:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/mq/pulsar-system-architecture.png" alt="pulsar" /></p>
<p>在Pulsar实例的层级,有一个实例级别(instance-wide) Zookeeper集群,被称为configuration store,用于处理涉及多集群之间的任务协调。</p>
<h2 id="2-brokers">2. Brokers</h2>
<p>Pulsar消息broker是一个是一个无状态的组件,主要负责运行如下两个其他的组件:</p>
<ul>
<li>
<p>HTTP Server,向producers及consumers暴露任务管理及<a href="https://pulsar.apache.org/docs/en/concepts-clients#client-setup-phase">topic lookup</a>相关的RESTful接口。生产者连接到broker来发布消息,消费者连接到broker来消费消息。</p>
</li>
<li>
<p>消息分发器(dispatcher),是一个采用私有二进制协议(<a href="https://pulsar.apache.org/docs/en/develop-binary-protocol">binary protocol</a>)所实现的异步TCP服务器,主要用于数据的传输。</p>
</li>
</ul>
<p>基于性能方面的考虑,消息(messages)通常是从<a href="https://pulsar.apache.org/docs/en/concepts-architecture-overview/#managed-ledgers">managerd ledger</a>所分发出去,除非backlog超出了缓存大小。假如backlog增长到超出cache大小,broker将会从BookKeeper中读取消息条目。</p>
<p>最后,针对全局topic(global topic),为了支持geo-replication,broker会使用复制器(replicator)将本区域的消息条目重新发布到远程区域。</p>
<blockquote>
<p>For a guide to managing Pulsar brokers, see the brokers <a href="https://pulsar.apache.org/docs/en/admin-api-brokers">guide</a> guide.</p>
</blockquote>
<h2 id="3-clusters">3. Clusters</h2>
<p>一个Pulsar instance是由一个或多个pulsar集群所组成的。而clusters又是由如下组成:</p>
<ul>
<li>
<p>一个或多个pulsar brokers</p>
</li>
<li>
<p>Zookeeper所实现的quorum,用于cluster级别的配置和协调</p>
</li>
<li>
<p>一个bookies集群来持久化存储消息(message)</p>
</li>
</ul>
<p>clusters之间可以基于<a href="https://pulsar.apache.org/docs/en/concepts-replication">geo-replication</a>来自我复制数据。</p>
<blockquote>
<p>For a guide to managing Pulsar clusters, see the <a href="https://pulsar.apache.org/docs/en/admin-api-clusters">clusters</a> guide.</p>
</blockquote>
<h2 id="4-metadata-store">4. Metadata store</h2>
<p>Pulsar metadata store维持着<code class="language-plaintext highlighter-rouge">一个</code>pulsar集群的所有元数据信息,比如topic元数据、schema、broker负载信息等等。Pulsar使用<a href="https://zookeeper.apache.org/">Apache Zookeeper</a>来做元数据存储、配置集群信息以及协调操作。Pulsar metadata store可以部署到一个单独的Zookeeper集群上,也可以部署到一个已存在的Zookeeper集群上。你可以使用一个Zookeeper集群来同时存放Pulsar metadata store以及<a href="https://bookkeeper.apache.org/docs/latest/getting-started/concepts/#metadata-storage">BookKeeper metadata sotre</a>。假如想要部署Pulsar brokers连接到已存在的BookKeeper集群,你需要各自单独的为Pulsar metadata store及BookKeeper metadata store分别部署一个Zookeeper集群。</p>
<p>在一个Pulsar instance中:</p>
<ul>
<li>
<p>configuration store quorum存放tenants、namespaces和其他一些需要全局一致性的配置信息;</p>
</li>
<li>
<p>每一个cluster都有其自己的本地Zookeeper,用于存储特定集群(cluster-specific)的配置和协调信息,比如哪些brokers负责某个topic、报告broker load等。</p>
</li>
</ul>
<h2 id="5-configuration-store">5. Configuration store</h2>
<p>configuration store维护着一个pulsar实例(instance)的所有配置信息,比如clusters、tenants、namespaces、partitioned topic相关的配置信息等等。一个Pulsar实例可以是单独一个local cluster,也可以是多个local clusters,还可以是多个跨区域clusters。因此,在一个Pulsar实例中,configuration store可以跨clusters共享配置信息。configuration store可以被部署到一个单独的Zookeeper集群,也可以被部署到一个已存在的Zookeeper集群。</p>
<h2 id="6-persistent-storage">6. Persistent storage</h2>
<p><br />
<br /></p>
<p><strong>[参考]</strong></p>
<ol>
<li>
<p><a href="https://pulsar.apache.org/">pulsar 官网</a></p>
</li>
<li>
<p><a href="https://pulsar.apache.org/docs/zh-CN/next/concepts-architecture-overview/">pulsar 中文网</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/weixin_33775572/article/details/92127055">pulsar 集群搭建</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
C/C++标准版本及不同版本的新特性(转)
2022-01-01T00:00:00+00:00
/post/cplusplus/2022/01/01/cplusplus_new_feature
<p>本文记录一下C/C++标准版本,以及不同版本的新特性。其中:</p>
<ul>
<li>
<p>C语言: C89/90、C95、C99、C11、C17</p>
</li>
<li>
<p>C++语言: C++98、C++03、C++11、C++14、C++17、C++20</p>
</li>
</ul>
<!-- more -->
<h2 id="1-发行标准">1. 发行标准</h2>
<h3 id="11-c语言标准">1.1 C语言标准</h3>
<ul>
<li>
<p>C89 (ANSI X3.159-1989): 由美国国家标准协会(ANSI)于1989年发布的C语言标准,也被称作ANSI C。</p>
</li>
<li>
<p>C90 (ISO/IEC 9899:1990): 与C89基本相同,是C89在国际化上的延伸,由国际标准化组织(ISO)和国际电工委员会(IEC)于1990年采纳的C语言标准。</p>
</li>
<li>
<p>C95 (ISO/IEC 9899:1995): 是对ISO C90标准的一次修订,增加了一些新特性,例如支持多字节字符等。</p>
</li>
<li>
<p>C99 (ISO/IEC 9899:1999): 由ISO/IEC在1999年采纳的新版C语言标准,新增了一些特性,如内联函数、边长数组、严格类型别名、具名结构初始化等。</p>
</li>
<li>
<p>C11 (ISO/IEC 9899:2011): 由ISO/IEC于2011年发布的C语言标准,增加了更多的新特性,例如静态断言、通用结构初始器、匿名结构和联合等。</p>
</li>
<li>
<p>C17 (ISO/IEC 9899:2018): 于2018年发布的当前最新的C语言标准,主要修复了C11标准中的一些问题,没有引入新特性。</p>
</li>
</ul>
<h3 id="12-c语言标准">1.2 C++语言标准</h3>
<ul>
<li>
<p>C++98: 于1998年发布的C标准,是最早的国际标准化版本,包含了面向对象编程、模板等基本特性。</p>
</li>
<li>
<p>C++03: 于2003年发布,对C++98进行一些小修小补,主要是修复C++98的一些bug和漏洞。</p>
</li>
<li>
<p>C++11: 于2011年发布,被视为现代C++的开始,引入了多个重要特性,如自动类型推导、基于范围的 for 循环、Lambda 表达式、智能指针等。</p>
</li>
<li>
<p>C++14: 于2014年发布的C++标准,以更大的灵活性和性能优化为目标,引入了多个新特性,进行了增量式改进,例如泛型Lambda表达式、返回类型后置等。</p>
</li>
<li>
<p>C++17: 于2017年发布,进一步完善C++特性,如结构化绑定、并行算法库、内联变量等。</p>
</li>
<li>
<p>C++20: 最新的C标准,已经获得批准,编译器也已经开始支持其中的新特性。它引入了模块、概念、协程等重要特性 ,对C++语言进行了较大的扩展。</p>
</li>
</ul>
<p>这些标准制定了C语言和C++的基本规范,各个编译器需要支持这些标准以确保代码的正确执行和相互兼容。在实际编程过程中,需要根据所使用编译器的支持情况选择合适的标准。</p>
<h2 id="2-版本新特性">2. 版本新特性</h2>
<h3 id="21-c语言版本新特性">2.1 C语言版本新特性</h3>
<p>1) <strong>C89/90</strong></p>
<ul>
<li>
<p>函数原型</p>
</li>
<li>
<p>const 限定符</p>
</li>
<li>
<p>volatile 限定符</p>
</li>
<li>
<p>enum 枚举类型</p>
</li>
<li>
<p>void 指针类型</p>
</li>
<li>
<p>单行注释,使用 <code class="language-plaintext highlighter-rouge">//</code></p>
</li>
</ul>
<p>2) <strong>C99</strong></p>
<ul>
<li>
<p>可变长度数组(VLA)</p>
</li>
<li>
<p>行内函数 (inline)</p>
</li>
<li>
<p>类型宽度宏,如 <code class="language-plaintext highlighter-rouge">UINT32_MAX</code></p>
</li>
<li>
<p>严格的类型别名规则(strict aliasing)</p>
</li>
<li>
<p>灵活的数组成员 (Flexible Array Member)</p>
</li>
<li>
<p>复合文字 (Compound Literals)</p>
</li>
<li>
<p>布尔数据类型 <code class="language-plaintext highlighter-rouge">_Bool</code></p>
</li>
<li>
<p>复数和虚数数据类型</p>
</li>
</ul>
<p>3) <strong>C11</strong></p>
<ul>
<li>
<p>多线程支持</p>
</li>
<li>
<p>原子操作</p>
</li>
<li>
<p>静态断言(Static assertions)</p>
</li>
<li>
<p>无类型泛型宏(Generic selection)</p>
</li>
<li>
<p>匿名结构和匿名联合</p>
</li>
<li>
<p>外部变量的对齐声明</p>
</li>
<li>
<p>类型泛化表达式</p>
</li>
</ul>
<h3 id="22-c语言特性">2.2 C++语言特性</h3>
<p>1) <strong>C++98</strong></p>
<ul>
<li>
<p>命名空间(namespaces)</p>
</li>
<li>
<p>类模板(class templates)</p>
</li>
<li>
<p>异常处理(exceptions)</p>
</li>
<li>
<p>运行时类型识别(RTTI)</p>
</li>
<li>
<p>标准模板库(STL)</p>
</li>
<li>
<p>bool 类型</p>
</li>
<li>
<p>类型转换操作符</p>
</li>
</ul>
<p>2) <strong>C++03</strong></p>
<p>在C++98基础上修复了一些bug和漏洞</p>
<p>3) <strong>C++11</strong></p>
<ul>
<li>
<p>自动类型推导(auto)</p>
</li>
<li>
<p>基于范围的 for 循环(Range-based for loops)</p>
</li>
<li>
<p>Lambda 表达式</p>
</li>
<li>
<p>右值引用和移动语义(Rvalue references and move semantics)</p>
</li>
<li>
<p>初始化列表(Initializer lists)</p>
</li>
<li>
<p>类型推断 decltype</p>
</li>
<li>
<p>constexpr 编译时计算</p>
</li>
<li>
<p>强类型枚举(Scoped enumerations)</p>
</li>
<li>
<p>nullptr为NULL的替代品</p>
</li>
<li>
<p>智能指针(shared_ptr, unique_ptr, weak_ptr)</p>
</li>
<li>
<p>并发编程(包括多线程的支持)</p>
</li>
</ul>
<p>4) <strong>C++14</strong></p>
<ul>
<li>
<p>泛型Lambda表达式</p>
</li>
<li>
<p>返回类型后置(函数返回类型推导)</p>
</li>
<li>
<p>二进制字面值</p>
</li>
<li>
<p>编译时整数序列(整数常量模板)</p>
</li>
<li>
<p>引入传递引用类型的函数</p>
</li>
<li>
<p>引入类型deprecated属性 (废弃声明)</p>
</li>
</ul>
<p>5) <strong>C++17</strong></p>
<ul>
<li>
<p>结构化绑定(Structured bindings)</p>
</li>
<li>
<p>并行算法库(Parallel algorithms)</p>
</li>
<li>
<p>内联变量(Inline variables)</p>
</li>
<li>
<p>文件系统库(Filesystem library)</p>
</li>
<li>
<p>变体类型(std::variant)</p>
</li>
<li>
<p>可选类型(std::optional)</p>
</li>
<li>
<p>任务型未来(std::future)</p>
</li>
</ul>
<p>6) <strong>C++20</strong></p>
<ul>
<li>
<p>概念(Concepts)</p>
</li>
<li>
<p>范围(Ranges)</p>
</li>
<li>
<p>协程(Coroutines)</p>
</li>
<li>
<p>模块(Modules)</p>
</li>
<li>
<p>连续表述(constexpr features)</p>
</li>
<li>
<p>Lambda 表达式的优化</p>
</li>
<li>
<p>std::span 视图经常使用的一段连续内存</p>
</li>
<li>
<p>std::format 格式库</p>
</li>
</ul>
<p><br />
<br /></p>
<p><strong>[参看]:</strong></p>
<ol>
<li>
<p><a href="https://cplusplus.com/reference/algorithm/sort/">cpluscpus reference</a></p>
</li>
<li>
<p><a href="https://en.cppreference.com">cppreference</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/crr411422/article/details/125592160">C/C++标准版本及不同版本的新特性</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/qq_65139309/article/details/130540716">深入剖析C++ 11新特性</a></p>
</li>
<li>
<p><a href="https://github.com/0voice/cpp_new_features">cpp_new_feature</a></p>
</li>
<li>
<p><a href="https://cpp.sh">c++ shell</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
Erasure-Code(纠删码) 最佳实践(转)
2020-05-04T00:00:00+00:00
/post/ceph/2020/05/04/ceph-erasure-code
<p>本文主要介绍一下纠删码的基本原理,以便更好的理解ceph中相关代码的实现。</p>
<blockquote>
<p>ps: 文章转载自https://zhuanlan.zhihu.com/p/106096265, 主要目的是为了后续方便的找到相关文章,以防原文丢失。</p>
</blockquote>
<!-- more -->
<h2 id="1-纠删码原理">1. 纠删码原理</h2>
<p>这个星球产生的数据越来越庞大,差不多2010年开始各大互联网公司大都上线了系统以应对数据膨胀带来的成本增长。Erasure-Code(纠删码)技术应用其中。典型如Google 新一代分布式存储系统colossus系统的Reed-solomon算法、Window Azure Storage 的LRC算法等等。</p>
<p>EC(Erasure-Code)算法的最底层的基本的数学原理:</p>
<pre>
行列矩阵中一种特殊矩阵的性质:即任意MxN(M行N列{M<N})的行列式,其任意MxM的子矩阵都是可逆,以实现数据恢复运算。
</pre>
<p>如下图所示,以一个典型的例子进行说明。</p>
<p>D1~D5通过<code class="language-plaintext highlighter-rouge">矩阵A(8*5)</code>相乘得到D1~D5的原始数据和C1~C3的校验数据块(Figure-1)。假设此时原始数据块D1、D4和校验数据块C1发生损坏。那要如何才能读取D1、D4等数据块、还原C1校验数据块?这个时候就依赖矩阵运算的特性。首先可知从A获取子矩阵B‘ (5*5)与原始数据相乘可以得到D2、D3、D5、C2、C3(即现有还未损坏的数据)(Figure-1),那反过来说,当前的问题就是:如何通过已有的B‘和D2、D3、D5、C2、C3还原得到D1、D4数据块和C1校验块。此时利用矩阵运算,假设B可逆,在等式2两边分别乘上B’的可逆矩阵B’-1,这样就可以通过B’-1 和已有的D2、D3、D5、C2、C3 进行矩阵运算还原得到D1和D4数据块。C1可以通过(B11~B15)与已经恢复的数据(D1~D5)相乘获得。该过程可行的核心保障就是需要确保矩阵A的任意<code class="language-plaintext highlighter-rouge">5*5</code>的子矩阵的可逆矩阵都是存在的,这样才能确保丢失8块数据中的任意3块数据都可以进行数据还原。核心的重点就是需要找到这样的矩阵A,其中黄色部分就是范德蒙矩阵(这里对此不多做展开,自行google或者参看任何矩阵论的教材都有清晰的说明)。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/erasure/erasure-code.jpg" alt="erasure-code" /></p>
<h2 id="2-ec与数据放置">2. EC与数据放置</h2>
<p>首先看如何对数据进行数据放置。比如HDFS、colossus、Ceph 将数据条带化的放置在不同的chunk(Stripe placement)。而Windows Azure Storage 则使用连续数据块放置方式(Contiguous placement)。各自有各自的特点。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/erasure/erasure-code.jpg" alt="stripe-placement" /></p>
<p>如上图所示,假设<em>abcdfghigklmnopqrstuvwxyz</em>为原始的一段数据内容,在EC场景下可以有2种截然不同的数据划分方式。</p>
<ul>
<li>
<p>Stripe Placement: 条带的数据放置方式,即将数据顺序进行拆散,逻辑放置在不同的数据块中,打破了数据原先的物理相邻顺序。</p>
</li>
<li>
<p>Contiguous PlaceMent: 连续的数据放置方式,即保留数据原来的顺序,除了数据分块的边界(如上图D1、D2)的边界,核心上来说数据逻辑上还是保持了相邻的顺序。</p>
</li>
</ul>
<p>这2种方式各有各的特点,如上图所示,在工程上D1~D5数据块 、C1~C3校验块一般都按照故障域原则放置在不同可用区的不同的磁盘上。</p>
<h3 id="21-stripe-placement-的特点">2.1 Stripe Placement 的特点</h3>
<p>1) 一份数据的读取可以同时利用多个磁盘的吞吐能力,但是对于IOPS来说是放大(换句话说对大块数据读取比较友好),缺点就是失去了数据的locality(这在Hadoop大数据体系中将计算放置在数据附近来说是很关键的一点);</p>
<p>2) 及时EC,即不用等凑足整一份大的数据才进行EC写入,基本在凑足EC的条带大小即可进行写入,也就是说在线数据写入可以直接以EC的体系。</p>
<h3 id="22-contiguous-placement的特点">2.2 Contiguous Placement的特点</h3>
<p>Contiguous Placement的特点则相对来说相反:</p>
<p>1) 数据都是临近放置,所以一般情况下的数据的读取就跟副本形式一样,在一个数据节点是就可以获得,对于小IO来说比较友好,对于大IO没有明显的缺陷。</p>
<p>2) 不能进行及时EC。需要进行凑足一定的数据才能够形成D1到D5的数据块进行EC,所以一般来说比较适合做后台的EC。比如Window Azure Storage 是先写三副本的Extent,在Extent seal(关闭掉)之后后台异步得将数据EC。</p>
<h2 id="3-ec条带大小与小io">3. EC条带大小与小IO</h2>
<p>在大规模的存储系统中,小文件往往会结合索引机制,将小文件合并成一整个大文件。详见对象存储架构设计<a href="https://zhuanlan.zhihu.com/p/103700905">A Bite of S3 Storage Arch</a></p>
<p>对于小文件一般是指小于128KB的文件,在Contiguous PlaceMent 条件下小文件在常规情况下的读取方式与传统的多副本类似。但是在高负载情况下和节点故障情况下需要backup request 机制保障latency,在如上5+3的模式下,一个IO,需要其他5个节点的IO进行恢复。</p>
<p>为了避免在5倍的IO造成对用户latency的显著放大(负载情况下慢节点拖慢整个数据读取的速度)。一般来说可以通过Backup Request的方式减少对用户即时访问的影响。window-azure 给出了RS(12+3 )、 LRC(12+2+2)等 纠删码算法情况下EC重建4KB数据的响应时间情况。从<code class="language-plaintext highlighter-rouge">图(a)</code>可以看出在低压力情况下通过RS方式重建(读取12块数据)相比直接读取的时间差不多是2.5倍(23ms VS 51ms vs),在通过Backup Requst的方式发送15个请求选最快的12个的策略恢复数据情况下可以获得与单副本差不多的响应时间(29ms)。但是在高压力情况下,读取12块数据的响应时间相比于直接读取的响应时间是(305ms VS 91ms ),同样可以通过backup Request(12+2)的方式来使得响应时间降低到差不多与直接读取差不多的响应时间97ms。也就是说在整体IOPS能力并非瓶颈情况下,可以通过BackUp Request的机制显著降低采用EC技术方案在坏盘等故障情况下对用户IO延迟的直接影响。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/erasure/small-io-reconstruction.webp" alt="small-io-reconstruction" /></p>
<p>而对于Stripe PlaceMent 情况下。如果Stripe Unit 过小,比如4KB,那么可能会导致128KB的小文件读取需要跨很多节点才能够读取完整的数据,相对来说比较费IOPS。这个时候可以适当调整条带大小,使得在正常情况下,小IO的绝大多数情况下的读取可以在单个节点读取,跨越边界情况下读取2个节点。</p>
<p>但是这会导致小文件需要很大的IO填充才能够进行一次写入(满条带写),空间利用率会有比较大的降低。上层直接写入的文件不适合太小,<a href="https://zhuanlan.zhihu.com/p/103700905">A Bite of S3 Storage Arch</a>中说明的小文件一般来说先可以不通过3副本WAL的方式保障持久性的情况下,通过Merge成更大的MobFile EC的方式来避免文件太小。如下图所示EC 4+2组合,可以使得EC Stripe Unit大小比如为1.5MB。每个分片数据256KB的方式来使得小IO在正常情况下可以在一个节点上读取。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/erasure/small-io-stripe.webp" alt="small-io-stripe" /></p>
<h2 id="4-ec与大io">4. EC与大IO</h2>
<p>在大块数据读写情况下,Contiguous PlaceMent 方案,在一般场景下跟传统的多副本策略几乎是一样的。因为数据一般来说都是临近放置,直接按照分块的放置进行直接数据读取即可。但是在异常情况下,按照Window Azure Storage 场景的测试,由于磁盘和网络带宽容易相对容易达到瓶颈,所以采用BackUp Request的并没有啥改善。</p>
<p>如下图所示,RS(read k) 、RS(readk +1)、RS(readk +2)、RS(readk +3) 均没有太大的改善。其发表paper的时候大概是2010年当初其网络(NIC) 大概是1Gbps。而现在其实网络越来越多的是10Gbps、50Gbps、100Gbps。所以今日不同往时,最核心的原因还是看当前系统带宽层面的带宽是否已经饱和。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/erasure/large-io-stripe.webp" alt="large-io-stripe" /></p>
<p>一般情况下可以认为上层业务的大块连续IO读取都是满条带的读取,在Stripe Placement 情况下,满条带的读取在正常情况下和异常情况下从底层读取的数据量可以认为是一致的(如下图左侧图所示),而且当前一般来说EC 解码有硬件加速,即计算层面不太容易成为瓶颈,所以Stripe Placement 在正常度和异常情况下的开销基本可以认为差不多。在极端情况下,数据跨越stripe unit边界的情况下,会带来2倍的IO放大。但是在Contiguous PlaceMent 策略下,则需要更大的范围内的放大,如下EC 4+2 的策略下,可能会导致4倍的放大,在比如12+3等情况下,会有更大的放大。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/erasure/stripe-compare.webp" alt="stripe-compare" /></p>
<h2 id="5-总结">5. 总结</h2>
<p>上述分析了EC 结合不同的放置策略Stripe PlaceMent、Congiguous PlaceMent情况下各自的优势和缺点,在这些固有的约束条件下,需要通过合理的架构选择来充分利用EC的优点,屏蔽EC缺点以最大化EC的价值。</p>
<p>在当前机房网络能力越来越强的情况下(如(Flat DataCenter Storage说明),数据的locality总体来说在大多数大数据场景下越来越不重要了,存储计算分离是大趋势。比如S3(对象存储)、EBS等,可以考虑使用 RS + Stripe PlaceMent 结合合理的 Stripe Unit的方案作为底层的纠删码方案,在架构选择层面可以参考之前的2篇博文:</p>
<ul>
<li>
<p><a href="https://zhuanlan.zhihu.com/p/103700905">对象存储架构设计</a></p>
</li>
<li>
<p><a href="https://zhuanlan.zhihu.com/p/104726520">随机IO存储系统EBS架构设计</a></p>
</li>
</ul>
<h2 id="5-附录ceph纠删码插件介绍">5. 附录:ceph纠删码插件介绍</h2>
<p>ceph是目前最为流行的分布式存储系统。支持副本备份模式和纠删码备份模式。使用纠删码模式进行备份能达到更高的磁盘利用率。</p>
<p>基于纠删码冗余策略,Ceph添加了几个开源的纠删码库,提供不同的纠删码算法,用户可根据需要选择纠删码算法类型,并创建相应的纠删码池。</p>
<p>OSD:object storage daemon 对象存储设备。当存储设备中有故障,可以从其他健康的设备上获取数据</p>
<p>chunk:数据段。可以用来重新生成原始数据。</p>
<p>k:chunk的数量,把原始数据分成k份</p>
<p>m:编码出来的chunk数量,如果m=2,表示即使丢失了2个OSD数据,也可以恢复出数据。</p>
<p>ceph中ec插件介绍:</p>
<p>1) jerasure是最广泛使用和灵活的插件。使用库代码地址:http://jerasure.org/ 参数格式如下。</p>
<pre>
# ceph osd erasure-code-profile set {name} \
plugin=jerasure \
k={data-chunks} \
m={coding-chunks} \
technique={reed_sol_van|reed_sol_r6_op|cauchy_orig|cauchy_good|liberation|blaum_roth|liber8tion} \
[crush-root={root}] \
[crush-failure-domain={bucket-type}] \
[crush-device-class={device-class}] \
[directory={directory}] \
[--force]
</pre>
<p>2) isa插件, 库代码地址:https://www.intel.com/content/www/us/en/developer/topic-technology/open/overview.html</p>
<p>这个是Intel公司开发的一套纠删码数据存储库:</p>
<pre>
# ceph osd erasure-code-profile set {name} \
plugin=isa \
technique={reed_sol_van|cauchy} \
[k={data-chunks}] \
[m={coding-chunks}] \
[crush-root={root}] \
[crush-failure-domain={bucket-type}] \
[crush-device-class={device-class}] \
[directory={directory}] \
[--force]
</pre>
<p>3) LRC插件,通过创建一些冗余的chunk,能使用更少的OSD来恢复丢失的数据</p>
<p>例如LRC(12,2,2)编码,将12个数据块为一组编码,并进一步将这12个数据块平均分为2个本地组, 每个本地组包括6个数据块,并分别计算出一个local parity,之后把所有12个数据块计算出2个global parities。</p>
<p>当发生任何一个数据块错误时,只需用本地组内的数据和校验块用于计算,即可恢复出原始数据。而恢复代价(通过网络传输的数据块数量)就由传统RS(12,4)编码的12,变为6,恢复过程的网络I/O开销减半,同时空间冗余率保持不变,仍为(12+2+2)/12 = 1.33</p>
<pre>
ceph osd erasure-code-profile set {name} \
plugin=lrc \
k={data-chunks} \
m={coding-chunks} \
l={locality} \
[crush-root={root}] \
[crush-locality={bucket-type}] \
[crush-failure-domain={bucket-type}] \
[crush-device-class={device-class}] \
[directory={directory}] \
[--force]
</pre>
<p>4) shec插件。库代码地址:http://tracker.ceph.com/projects/ceph/wiki/Shingled_Erasure_Code_(SHEC)</p>
<p>比Reed Solomon 编码更高效的一种方法。参数c表示真正可以允许丢失的chunk数。参数m表示有m个冗余chunk数,不保证能完全恢复。</p>
<pre>
# ceph osd erasure-code-profile set {name} \
plugin=shec \
[k={data-chunks}] \
[m={coding-chunks}] \
[c={durability-estimator}] \
[crush-root={root}] \
[crush-failure-domain={bucket-type}] \
[crush-device-class={device-class}] \
[directory={directory}] \
[--force]
</pre>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/erasure/erasure-shec.webp" alt="erasure-shec" /></p>
<p><br />
<br /></p>
<p><strong>[参看]</strong></p>
<ol>
<li>
<p><a href="https://zhuanlan.zhihu.com/p/106096265">Erasure-Code(纠删码) 最佳实践</a></p>
</li>
<li>
<p><a href="https://www.bilibili.com/video/BV1hD4y1R776/?p=10&vd_source=2699f104de8828a576fed54818f8cd79">线性代数:行列式</a></p>
</li>
<li>
<p><a href="https://link.zhihu.com/?target=https%3A//blog.openacid.com/storage/ec-1/">Erasure Code 原理和工程化介绍</a></p>
</li>
<li>
<p><a href="https://zhuanlan.zhihu.com/p/554262696">纠删码(erasure code)介绍</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
文件系统比较
2020-05-03T00:00:00+00:00
/post/ceph/2020/05/03/ceph-dfs-compare
<p>本文简要介绍各分布式文件系统(Distribute Filesystem),并做一个对比。</p>
<blockquote>
<p>Note: This article is from the monthly share of Juicedata founder & CEO Davies in the Shanghai Linux User Group (SHLUG) (2018/6/23).</p>
</blockquote>
<!-- more -->
<h2 id="1-what-is-a-file-system">1. What is a file system?</h2>
<p>A file system is an essential component of a computer that provides consistent access and management for storage devices. There are some differences in the file system between different operating systems, but there are some commonalities that have not changed for decades:</p>
<p>1) The data exists in the form of files and provides APIs such as Open, Read, Write, Seek, and Close for access;</p>
<p>2) Files are organized in a tree-style directory that provides atomic rename (Rename) operations to relocate files or directories.</p>
<p>The access and management methods provided by the file system support most of the computer applications. The philosophy of Unix’s “everything is a file” highlights the prominent position of files. However, the complexity of the file system makes its scalability fail to follow with the rapid development of the internet, and the greatly simplified object storage fills the gap in time to develop rapidly. Since the object storage lacks a tree structure and does not support atomic rename operations, it is quite different from the file system. It’s a different topic, and we are not planning to discuss it here.</p>
<h2 id="2-challenges-of-the-stand-alone-file-system">2. Challenges of the stand-alone file system</h2>
<p>Most file systems are stand-alone, providing access and management for one or more storage devices within an operating system. With the rapid development of the internet, stand-alone file systems are facing many challenges:</p>
<p>1) Sharing: It is not possible to provide access to applications distributed across multiple machines at the same time. Therefore the NFS protocol born. With the help of NFS, a single file system can be accessed to multiple machines simultaneously via the network.</p>
<p>2) Capacity: The data has to be split across multiple isolated stand-alone file systems because of unable to provide enough space to store data.</p>
<p>3) Performance: The application has to do a logical splitting and read and write from multiple file systems because of unable to fulfill very high read and write performance requirements of specific applications.</p>
<p>4) Reliability: Limited by the reliability of a single machine, machine failure can result in data loss.</p>
<p>5) Availability: Limited by the availability of a single operating system, failures or operations such as reboots can result in unavailability.</p>
<p>6) With the rapid development of the internet, these problems have become increasingly prominent, and some distributed file systems have emerged to meet these challenges.</p>
<p>Here we introduce some fundamental architectures of distributed file systems I learned and compare the advantages and limitations of different architectures.</p>
<h3 id="21-glusterfs">2.1 GlusterFS</h3>
<p>GlusterFS is a POSIX distributed file system developed by Gluster Inc. of the United States (open source as GPL). The first public release was released in 2007 and was acquired by RedHat in 2011.</p>
<p>Its principle is to provide users with a unified namespace by combining multiple stand-alone file system through a stateless middleware. This middleware is implemented by a series of superimposable translators. Each translator solves a particular problem, such as data distribution, copying, splitting, caching, locks, etc., and users can flexibly configure according to specific application scenarios. For example, a typical distributed volume is shown below:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/dfs/glusterfs.png" alt="GlusterFS" /></p>
<blockquote>
<p>Image Source:https://docs.gluster.org/en/latest/Quick-Start-Guide/Architecture/</p>
</blockquote>
<p>Server1 and Server2 form a Volume0 with 2 copies, and Server3 and Server4 form Volume1, which are then merged into a distributed volumes with more space.</p>
<p>Pros:</p>
<ol>
<li>Data files are eventually stored on a stand-alone file system in the same directory structure, so you don’t have to worry about data loss due to unavailability of GlusterFS.</li>
<li>No obvious single point of failure problem and it can be extended linearly.</li>
<li>Decent support for a large number of small files.</li>
</ol>
<p>Cons:</p>
<ol>
<li>This structure is relatively static, not easy to modify, and requires each storage node to have the same configuration. When data or access is unbalanced, space or load adjustment is unable to perform. The fault recovery capability is relatively weak. For instance, when Server1 fails, the files on Server2 cannot be used to increase the reliability of copy data on the healthy Server3 or Server4.</li>
<li>Because of the lack of independent metadata services, all storage nodes are required to have a complete data directory structure. When traversing directories or making directory structure adjustments, all nodes need to be accessed to get correct results, resulting in limited scalability of the entire system. It is acceptable to manage several nodes but challenging to manage hundreds of nodes efficiently.</li>
</ol>
<h3 id="22-cephfs">2.2 CephFS</h3>
<p>CephFS began with a doctoral thesis study by Sage Weil to implement distributed metadata management to support EB-level data scale. In 2012, Sage Weil established InkTank to continue supporting the development of CephFS, which was acquired by RedHat in 2014. Until 2016, CephFS released a stable version available for production environments (the metadata portion of CephFS is still stand-alone). However, the distributed metadata of CephFS is still immature.</p>
<p>Ceph is a layered architecture. The bottom layer is a CRUSH-based (hash) distributed object storage. The upper layer provides three APIs: object storage (RADOSGW), block storage (RDB), and file system (CephFS), as shown in the following figure:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/dfs/Ceph_stack.png" alt="ceph" /></p>
<blockquote>
<p>Image Source:https://en.wikipedia.org/wiki/Ceph_(software)</p>
</blockquote>
<p>Using a single storage system to meet the storage needs of multiple scenarios (virtual machine images, massive small files, and general file storage) is still very attractive. But because of the complexity of the system, it requires strong operation and maintenance capabilities to support. At present, only block storage is relatively mature and has wide application, the object storage and file system are not ideal in that case. I have heard some use cases that they gave up after a period of use.</p>
<p>The architecture of CephFS is shown below:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/dfs/ceph_fs.png" alt="ceph" /></p>
<blockquote>
<p>Image Source:https://en.wikipedia.org/wiki/Ceph_(software</p>
</blockquote>
<p>CephFS is implemented by MDS (Metadata Daemon), which is one or more stateless metadata services that load the meta information of the file system from the bottom layer OSD and cache it in memory to improve access speed. Because MDS is stateless, it is relatively easy to configure multiple spare nodes to implement HA. However, the backup node has no cache and needs to be warmed up again. It is possible that the recovery time will be notable longer.</p>
<p>Because loading or writing data from the storage tier is relatively slow, MDS must use multiple-threads to increase throughput, and various concurrent file system operations result in increased complexity, likely to occur deadlocks or performance sharply decline due to slow I/O. In order to achieve better performance, MDS often needs to have enough memory to cache most of the metadata, which also limits its actual support capabilities.</p>
<p>When there are multiple active MDSs, a part of the directory structure (subtree) can be dynamically assigned to an MDS and completely processed by it to achieve horizontal expansion. Before having multiple MDSs activities, it is inevitable to need a self-lock mechanism to negotiate the ownership of the subtree, and the atomic renaming of the cross subtree through distributed transactions, these mechanisms are very complicated to implement. The current official documentation still does not recommend using multiple MDSs (it’s OK to use as a backup).</p>
<h3 id="23-gfs">2.3 GFS</h3>
<p>Google’s GFS is a pioneer and a typical representative of distributed file systems, developed by early BigFiles. Its design concept and details were elaborated in the paper published in 2003, which has a great impact on the industry. Many later distributed file systems inspired by its design.</p>
<p>As the name implies, BigFiles/GFS is optimized for large files and is not suitable for scenarios with an average file size of less than 1MB. The architecture of GFS is shown in the figure below.</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/dfs/gfs.png" alt="gfs" /></p>
<blockquote>
<p>Image Source:https://en.wikipedia.org/wiki/Google_File_System</p>
</blockquote>
<p>GFS has a Master node to manage metadata (loaded into memory, write snapshots and update logs to disk), divided files into 64MB Chunk and store to several ChunkServers (using a stand-alone file system directly). Files can only be appended write, no need to worry about Chunk’s version and consistency issues (you can use length as a form of version). The use of completely different techniques to solve the metadata and data design greatly simplifies the system complexity and has sufficient scalability (if the average file size is larger than 256MB, the Master node can support about 1PB of data per GB of memory). Dropping support some of the features of the POSIX file system (such as random writes, extended attributes, hard links, etc.) further simplifies system complexity in exchange for better system performance, robustness, and scalability.</p>
<p>Because of the mature and stable of GFS, it makes Google easier to build upper-layer applications (MapReduce, BigTable, etc.). Later, Google developed Colossus, a next-generation storage system with greater scalability, separate metadata and data storage completely, implement distributed (automatic sharding) metadata, and use Reed Solomon encoding to reduce storage space to achieve costs cutting.</p>
<h3 id="24-hdfs">2.4 HDFS</h3>
<p>Hadoop from Yahoo is an open source Java implementation of Google’s GFS, MapReduce, etc. HDFS is also a copy of GFS design, so we omitted discussion here. The following figure is an HDFS architecture diagram:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/dfs/HDFS-architecture-768x569.png" alt="hdfs" /></p>
<p>The reliability and scalability of HDFS are very excellent. There are many thousands of nodes and 100PB level deployment cases. The performance of supporting big data applications is also very gorgeous. Only rare cases about losing data (except for no trash policy configured and removed data by mistake).</p>
<p>HDFS’s HA solution was added later and implemented complexly. It was so complicated that Facebook, the first to do this HA solution, manually do failover switch (does not trust automatic failover) for a long time (at least 3 years).</p>
<p>Because the NameNode is implemented in Java, depending on the pre-allocated heap memory size. Insufficient allocation can trigger Full GC and affect the performance of the entire system. Some teams tried to rewrite it in C++, but still no mature open source solution.</p>
<p>HDFS also lacks mature non-Java clients, making it challenging to use in scenarios (such as deep learning) other than big data (such as Hadoop).</p>
<h3 id="25-moosefs">2.5 MooseFS</h3>
<p>MooseFS is an open source distributed POSIX file system from Poland. It also inspired by the architecture of GFS. It implements most of the POSIX semantics and APIs. It can be accessed like a local file system after being mounted by a very mature FUSE client. The architecture of MooseFS is shown below:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/dfs/moosefs-architecture-768x542.png" alt="MooseFs" /></p>
<p>MooseFS supports snapshots, and it is convenient to use for data backup or backup recovery scenarios.</p>
<p>MooseFS is implemented by C. The Master is a standalone thread of asynchronous event-driven, similar to Redis. However, the network part uses poll instead of the more efficient epoll, which results in consume tremendous CPU resources when concurrent reach about 1000.</p>
<p>The open source community version does not have HA, it is implemented asynchronous backup by metalogger. The close source commercial version support HA.</p>
<p>In order to support random write operations, the chunks in MooseFS can be modified. A series of version management mechanism is used to ensure data consistency. This mechanism is more complicated and likely to occur different problems (for example, there may be a few chunk actual copies below expected after the cluster restart).</p>
<h3 id="26-juicefs">2.6 JuiceFS</h3>
<p>The above GFS, HDFS and MooseFS are designed for the environments of self-built datacenter. The reliability of the data and the node availability are combined to solved by multi-machine and multiple copies. However, in a public cloud or private cloud virtual machine, the block device is already a virtual block device with three copies of reliability design. If implemented by multiple machines and multiple copies way, the cost of data will be extremely high (actually it is 9 copies).</p>
<p>Therefore we designed JuiceFS for the public cloud, improved HDFS and MooseFS architecture. The architecture is shown below:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/dfs/juicefs-architecture-768x565.png" alt="JuiceFS" /></p>
<p>JuiceFS replaces DataNode and ChunkServer with the object storage which is already existed in the public cloud, implemented a fully flexible serverless storage system. Public cloud object storage has already well solved the security and efficiency problem of large-scale data store. JuiceFS only needs to focus on the management of metadata, and dramatically reduces the complexity of metadata services (master of GFS and MooseFS need simultaneously solve metadata storage and health management of data blocks). We’ve also made many improvements to the metadata part, and implemented Raft-based high availability from the beginning. To provide a service with high availability and high performance, metadata management and operation are still very challenging. Metadata is provided to users as a service. Because the POSIX File system API is the most widely used API, we implement a highly POSIX compatible client based on FUSE, which allows users to mount JuiceFS to Linux or macOS with a command line tool, access as fast as a local file system.</p>
<p>The dotted line on the right in the figure above is the part responsible for data storage and access. For the sake of the user’s data privacy, they are entirely running in the customer’s own account and network environment, and unable to communicate with the metadata service. We (Juicedata) have no way to access the customer’s content (except for metadata, please do not put sensitive content in the file name).</p>
<h2 id="3-conclusion">3. Conclusion</h2>
<p>The above briefly introduces the architecture of several distributed file systems that I have learned. The following diagram put them together in the order of time (arrows indicate that the latter refers to the former or is the new generation version):</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/ceph/dfs/distributed-filesystem-timeline.png" alt="dfs-timeline" /></p>
<p>The blue ones in the upper part of the above figure are mainly used for big data scenarios, which implements a subset of POSIX, while the green ones below are POSIX-compatible file systems.</p>
<p>The system design of separate metadata and data represented by GFS can effectively balance the complexity of the system, effectively solve the storage problem of large-scale data (usually large files), and has better scalability. Colossus and WarmStorage, which support distributed storage of metadata under this architecture, are even more scalable.</p>
<p>As a successor, JuiceFS learned how MooseFS implements a distributed POSIX file system. Also learned the idea of completely separating metadata and data from Facebook’s WarmStorage, and desires to provide the best experience of distributed storage for public or private cloud scenarios. By storing data in object storage, JuiceFS effectively avoids the cost problem of double-layer redundancy (block storage redundancy and distributed system multi-machine redundancy) when using the above distributed file system. JuiceFS also supports all public clouds, without worrying about a cloud service lock, and smoothly migrating data between public clouds or regions.</p>
<p>Conclusively, if you have a public cloud account on hand, Sign up with JuiceFS, you can mount a file system with a PB capacity in your virtual machine (or even your macOS) in 5 minutes.</p>
<p><br />
<br /></p>
<p><strong>[参看]</strong></p>
<ol>
<li><a href="https://juicefs.com/blog/en/posts/distributed-filesystem-comparison/">Distributed filesystem comparison</a></li>
</ol>
<p><br />
<br />
<br /></p>
MongoDB CRUD操作
2020-05-02T00:00:00+00:00
/post/database/2020/05/02/mongodb_part7
<p>MongoDB CRUD操作即创建(create)、读取(read)、更新(update)、删除(delete) documents。本文我们介绍一下这些操作,在此做个记录,以便后续查阅。</p>
<!-- more -->
<h2 id="1-crud操作">1. CRUD操作</h2>
<h3 id="11-create操作">1.1 Create操作</h3>
<p>创建(create)或插入(insert)操作可以添加新<a href="https://docs.mongodb.com/manual/core/document/#std-label-bson-document-format">documents</a>到一个<a href="https://docs.mongodb.com/manual/core/databases-and-collections/#std-label-collections">collection</a>。假如collection当前不存在,那么插入操作会自动的创建该collection。</p>
<p>MongoDB提供了如下的方法来插入documents到collection:</p>
<ul>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertOne/#mongodb-method-db.collection.insertOne">db.collection.insertOne()</a><em>New in version 3.2</em></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#mongodb-method-db.collection.insertMany">db.collection.insertMany()</a><em>New in version 3.2</em></p>
</li>
</ul>
<p>在MongoDB中,插入操作的目标(target)只能是一个collection。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/db/mongodb/crud-annotated-mongodb-insertOne.bakedsvg.svg" alt="mongodb-atlas" />MongoDB针对<code class="language-plaintext highlighter-rouge">单个</code>(single)document的写操作均为原子性(<a href="https://docs.mongodb.com/manual/core/write-operations-atomicity/">atomic</a>)的。</p>
<p>更多示例,请参看<a href="https://docs.mongodb.com/manual/tutorial/insert-documents/">Insert Documents</a>。</p>
<h3 id="12-读操作">1.2 读操作</h3>
<p>读(Read)操作可以从collection中获取documents,比如从collection中查询documents。MongoDB提供如下的方法来从collection中读取documents:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a></li>
</ul>
<p>可以指定<a href="https://docs.mongodb.com/manual/tutorial/query-documents/#std-label-read-operations-query-argument">query filters or criteria</a>来标识需要返回哪些documents:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/db/mongodb/crud-annotated-mongodb-find.bakedsvg.svg" alt="mongodb-atlas" /></p>
<p>更多示例,请参看:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-documents/">Query Documents</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-embedded-documents/">Query on Embedded/Nested Documents</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-arrays/">Query an Array</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-array-of-documents/">Query an Array of Embedded Documents</a></li>
</ul>
<h3 id="13-更新操作">1.3 更新操作</h3>
<p>更新(update)操作用于修改collection中已存在的documents。MongoDB提供了如下的方法来更新collection中documents:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/#mongodb-method-db.collection.updateOne">db.collection.updateOne()</a> <em>New in version 3.2</em></li>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/#mongodb-method-db.collection.updateMany">db.collection.updateMany()</a> <em>New in version 3.2</em></li>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/#mongodb-method-db.collection.replaceOne">db.collection.replaceOne()</a> <em>New in version 3.2</em></li>
</ul>
<p>MongoDB中,更新操作的目标(target)只能是一个collection。MongoDB针对<code class="language-plaintext highlighter-rouge">单个</code>(single)document的所有写操作均为原子性(<a href="https://docs.mongodb.com/manual/core/write-operations-atomicity/">atomic</a>)的。</p>
<p>在执行更新操作时可以指定criterial, filters来更新特定的documents。这些<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter">filters</a>与读操作的filters具有完全一样的语法。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/db/mongodb/crud-annotated-mongodb-updateMany.bakedsvg.svg" alt="mongodb-atlas" /></p>
<p>更多示例,请参看<a href="https://docs.mongodb.com/manual/tutorial/update-documents/">Update Documents</a></p>
<h3 id="14-删除操作">1.4 删除操作</h3>
<p>删除(delete)操作可以从collection中移除documents。MongoDB提供了如下的方法来从一个collection中删除documents:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.deleteOne/#mongodb-method-db.collection.deleteOne">db.collection.deleteOne()</a> <em>New in version 3.2</em></li>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.deleteMany/#mongodb-method-db.collection.deleteMany">db.collection.deleteMany()</a> <em>New in version 3.2</em></li>
</ul>
<p>MongoDB中,删除操作的目标(target)只能是一个collection。MongoDB针对<code class="language-plaintext highlighter-rouge">单个</code>(single)document的所有写操作均为原子性(<a href="https://docs.mongodb.com/manual/core/write-operations-atomicity/">atomic</a>)的。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/db/mongodb/crud-annotated-mongodb-deleteMany.bakedsvg.svg" alt="mongodb-atlas" /></p>
<p>更多示例,请参看<a href="https://docs.mongodb.com/manual/tutorial/remove-documents/">Delete Documents</a></p>
<h3 id="15-bulk-write">1.5 Bulk Write</h3>
<p>MongoDB提供了批量写操作。更多详细细节,请参看<a href="https://docs.mongodb.com/manual/core/bulk-write-operations/">Bulk Write Operations</a></p>
<h2 id="2-insert-documents">2. Insert Documents</h2>
<blockquote>
<p>Note: 假如collection当前不存在,insert操作会创建对应的collection</p>
</blockquote>
<h6 id="insert-a-single-document">Insert a Single Document</h6>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertOne/#mongodb-method-db.collection.insertOne">db.collection.insertOne()</a>可以将单个document插入到collection。</p>
<p>如下的示例插入一个新的document到<code class="language-plaintext highlighter-rouge">inventory</code>这个collection中。假如document未指定<code class="language-plaintext highlighter-rouge">_id</code>字段的话,MongoDB会添加一个<code class="language-plaintext highlighter-rouge">_id</code>字段,并赋予一个ObjectId值。参看<a href="https://docs.mongodb.com/manual/tutorial/insert-documents/#std-label-write-op-insert-behavior">Insert Behavior</a>。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.insertOne(
{ item: "canvas", qty: 100, tags: ["cotton"], size: { h: 28, w: 35.5, uom: "cm" } }
)</code></pre></figure>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertOne/#mongodb-method-db.collection.insertOne">insertOne()</a>方法会返回新插入的document的<code class="language-plaintext highlighter-rouge">_id</code>字段值。要查看返回的document示例,请参看<a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertOne/#std-label-insertOne-examples"> db.collection.insertOne() reference</a></p>
<p>如果要查询刚刚插入的document,执行<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter">query the collection</a></p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { item: "canvas" } )</code></pre></figure>
<h6 id="insert-multiple-documents">Insert Multiple Documents</h6>
<blockquote>
<p>New in version 3.2.</p>
</blockquote>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#mongodb-method-db.collection.insertMany">db.collection.insertMany()</a>可以插入多个document到collection中。可以传递一个document数组作为参数。</p>
<p>如下的示例插入3个documents到<code class="language-plaintext highlighter-rouge">inventory</code>集合中。假如document并未指定<code class="language-plaintext highlighter-rouge">_id</code>字段的话,那么MongoDB会帮助为每个document生成一个<code class="language-plaintext highlighter-rouge">_id</code>字段,值为ObjectId类型。参看<a href="https://docs.mongodb.com/manual/tutorial/insert-documents/#std-label-write-op-insert-behavior">Insert Behavior</a>。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.insertMany([
{ item: "journal", qty: 25, tags: ["blank", "red"], size: { h: 14, w: 21, uom: "cm" } },
{ item: "mat", qty: 85, tags: ["gray"], size: { h: 27.9, w: 35.5, uom: "cm" } },
{ item: "mousepad", qty: 25, tags: ["gel", "blue"], size: { h: 19, w: 22.85, uom: "cm" } }
])</code></pre></figure>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#mongodb-method-db.collection.insertMany">insertMany()</a>会返回一个document,里面包含<code class="language-plaintext highlighter-rouge">新插入</code>的documents的<code class="language-plaintext highlighter-rouge">_id</code>。参看<a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#std-label-insertMany-examples">reference</a>以了解相关示例。</p>
<p>要查询所插入的documents的话,执行<a href="https://docs.mongodb.com/manual/tutorial/query-documents/#std-label-read-operations-query-document">query the collection</a>。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( {} )</code></pre></figure>
<h6 id="insert-behavior">Insert Behavior</h6>
<p>1) <strong>Collection Creation</strong></p>
<p>假如对应的collection不存在的话,insert操作会创建对应的collection。</p>
<p>2) <strong>_id字段</strong></p>
<p>在MongoDB中,collection中的每一个document都需要一个唯一的<code class="language-plaintext highlighter-rouge">_id</code>字段来作为primary key。假如所插入的document未指定<code class="language-plaintext highlighter-rouge">_id</code>字段的话,MongoDB driver会自动为<code class="language-plaintext highlighter-rouge">_id</code>字段生成一个<a href="https://docs.mongodb.com/manual/reference/bson-types/#std-label-objectid">ObjectId</a>。</p>
<p>当执行update操作时,如果<a href="https://docs.mongodb.com/manual/reference/method/db.collection.update/#std-label-upsert-parameter">upsert: true</a>被设置的话,当执行的结果是插入document的话,也同样会生成<code class="language-plaintext highlighter-rouge">_id</code>字段。</p>
<p>3) <strong>Atomicity</strong></p>
<p>在MongoDB中,对于<code class="language-plaintext highlighter-rouge">单个</code>document来说所有的写操作都是原子性。更多关于MongoDB及原子操作,请参看<a href="https://docs.mongodb.com/manual/core/write-operations-atomicity/">Atomicity and Transactions</a>。</p>
<p>4) <strong>Write Acknowledgement</strong></p>
<p>对于数据库的写操作,我们可以指定MongoDB的ack响应级别。参看<a href="https://docs.mongodb.com/manual/reference/write-concern/">Write Concern</a></p>
<h3 id="21-insert-methods">2.1 Insert Methods</h3>
<p>MongoDB提供了如下的方法来向collection中插入documents:</p>
<ul>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertOne/#mongodb-method-db.collection.insertOne">db.collection.insertOne()</a> 插入一个document到collection</p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#mongodb-method-db.collection.insertMany">db.collection.insertMany()</a> 插入多个document到collection</p>
</li>
</ul>
<h6 id="221-additional-methods-for-inserts">2.2.1 Additional Methods for Inserts</h6>
<p>如下的方法也可以插入documents到collection:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/#mongodb-method-db.collection.updateOne">db.collection.updateOne()</a> when used with the upsert: true option.</li>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/#mongodb-method-db.collection.updateMany">db.collection.updateMany()</a> when used with the upsert: true option.</li>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/#mongodb-method-db.collection.findAndModify">db.collection.findAndModify()</a> when used with the upsert: true option.</li>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndUpdate/#mongodb-method-db.collection.findOneAndUpdate">db.collection.findOneAndUpdate()</a> when used with the upsert: true option.</li>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/#mongodb-method-db.collection.findOneAndReplace">db.collection.findOneAndReplace()</a> when used with the upsert: true option.</li>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/#mongodb-method-db.collection.bulkWrite">db.collection.bulkWrite()</a>.</li>
</ul>
<h2 id="3-query-documents">3. Query Documents</h2>
<p>本节我们提供一些使用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a>方法的示例来演示查询操作。例子使用的collection为<code class="language-plaintext highlighter-rouge">inventory</code>。首先我们向集合中插入一些数据,执行如下:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.insertMany([
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }
]);</code></pre></figure>
<h6 id="select-all-documents-in-a-collection">Select All Documents in a Collection</h6>
<p>要查询collection中所有的documents,可以向find()方法传递一个empty document作为查询过滤器。查询过滤器(query filter)决定了查询准则:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( {} )</code></pre></figure>
<p>上面的操作对应于如下SQL语句:</p>
<pre>
SELECT * FROM inventory
</pre>
<p>更多关于find()方法的语法信息,请参看<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">find()</a></p>
<h6 id="specify-equality-condition">Specify Equality Condition</h6>
<p>要指定<code class="language-plaintext highlighter-rouge">相等条件</code>(equality conditions),在<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter">query filter document</a>中使用<code class="language-plaintext highlighter-rouge"><field>:<value></code>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{ <field1>: <value1>, ... }</code></pre></figure>
<p>下面的例子从<code class="language-plaintext highlighter-rouge">inventory</code>这个collection中查询<code class="language-plaintext highlighter-rouge">status</code>等于<code class="language-plaintext highlighter-rouge">D</code>的记录:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { status: "D" } )</code></pre></figure>
<p>该操作对应于如下的SQL语句:</p>
<pre>
SELECT * FROM inventory WHERE status = "D"
</pre>
<h6 id="specify-conditions-using-query-operators">Specify Conditions Using Query Operators</h6>
<p>一个<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter">query filter document</a>可以通过如下形式来使用<a href="https://docs.mongodb.com/manual/reference/operator/query/#std-label-query-selectors">query operators</a>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{ <field1>: { <operator1>: <value1> }, ... }</code></pre></figure>
<p>下面的列子获取<code class="language-plaintext highlighter-rouge">inventory</code>这个collection中所有<code class="language-plaintext highlighter-rouge">status</code>等于<code class="language-plaintext highlighter-rouge">"A"</code>或<code class="language-plaintext highlighter-rouge">D</code>的记录:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { status: { $in: [ "A", "D" ] } } )</code></pre></figure>
<blockquote>
<p>Note: 上面的例子中尽管可以使用<a href="https://docs.mongodb.com/manual/reference/operator/query/or/#mongodb-query-op.-or">$or</a> operator,但是对于同一字段进行<code class="language-plaintext highlighter-rouge">相等</code>比较时,建议采用<a href="https://docs.mongodb.com/manual/reference/operator/query/in/#mongodb-query-op.-in">$in</a>。</p>
</blockquote>
<p>该操作对应于如下SQL语句:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">SELECT * FROM inventory WHERE status in ("A", "D")</code></pre></figure>
<p>参看<a href="https://docs.mongodb.com/manual/reference/operator/query/">Query and Projection Operators</a>以了解MongoDB query operators的完整列表。</p>
<h6 id="specify-and-conditionsicons">Specify <code class="language-plaintext highlighter-rouge">AND</code> Conditionsicons</h6>
<p>一个复合查询可以指定collection documents中的多个字段作为查询条件。我们可以使用<code class="language-plaintext highlighter-rouge">AND</code>来连接所有的查询条件。</p>
<p>如下的示例查询<code class="language-plaintext highlighter-rouge">inventory</code>这个collection中所有<code class="language-plaintext highlighter-rouge">status</code>为<code class="language-plaintext highlighter-rouge">"A"</code>且<code class="language-plaintext highlighter-rouge">qty</code>小于等于30的记录:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { status: "A", qty: { $lt: 30 } } )</code></pre></figure>
<p>上述语句等价于如下的SQL语句:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">SELECT * FROM inventory WHERE status = "A" AND qty < 30</code></pre></figure>
<p>更多比较操作符,请参看<a href="https://docs.mongodb.com/manual/reference/operator/query-comparison/#std-label-query-selectors-comparison">comparison operators</a>。</p>
<h6 id="specify-or-conditions">Specify OR Conditions</h6>
<p>在进行一个符合查询时,我们可以使用<a href="https://docs.mongodb.com/manual/reference/operator/query/or/#mongodb-query-op.-or">$or</a>操作符来多个查询条件,只要collection中的documents满足其中一个条件,即匹配成功。</p>
<p>如下的示例查询<code class="language-plaintext highlighter-rouge">inventory</code>这个collection中<code class="language-plaintext highlighter-rouge">status</code>为<code class="language-plaintext highlighter-rouge">"A"</code>或<code class="language-plaintext highlighter-rouge">qty</code>小于等于30的数据记录:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { $or: [ { status: "A" }, { qty: { $lt: 30 } } ] } )</code></pre></figure>
<p>上面的查询语句等价于如下SQL语句:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">SELECT * FROM inventory WHERE status = "A" OR qty < 30</code></pre></figure>
<blockquote>
<p>Note: Queries which use <a href="https://docs.mongodb.com/manual/reference/operator/query-comparison/#std-label-query-selectors-comparison">comparison operators</a> are subject to <a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#std-label-type-bracketing">Type Bracketing</a>.</p>
</blockquote>
<h6 id="specify-and-as-well-as-or-conditions">Specify AND as well as OR Conditions</h6>
<p>如下的例子中,查询<code class="language-plaintext highlighter-rouge">inventroy</code>这个collection中<code class="language-plaintext highlighter-rouge">status</code>为<code class="language-plaintext highlighter-rouge">"A"</code>,且<code class="language-plaintext highlighter-rouge">qty</code>小于等于30或<code class="language-plaintext highlighter-rouge">item</code>以字符<code class="language-plaintext highlighter-rouge">p</code>开头的数据记录:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( {
status: "A",
$or: [ { qty: { $lt: 30 } }, { item: /^p/ } ]
} )</code></pre></figure>
<p>上面的语句对应于如下SQL查询语句:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">SELECT * FROM inventory WHERE status = "A" AND ( qty < 30 OR item LIKE "p%")</code></pre></figure>
<blockquote>
<p>Note: MongoDB支持正则表达式<a href="https://docs.mongodb.com/manual/reference/operator/query/regex/#mongodb-query-op.-regex">$regex</a>查询,查询某字段匹配某个pattern</p>
</blockquote>
<h6 id="additional-query-tutorials">Additional Query Tutorials</h6>
<p>对于其他的查询示例,请参看:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-embedded-documents/">Query on Embedded/Nested Documents</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-arrays/">Query an Array</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-array-of-documents/">Query an Array of Embedded Documents</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/">Project Fields to Return from Query</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-for-null-fields/">Query for Null or Missing Fields</a></li>
</ul>
<h6 id="behavior">Behavior</h6>
<p>1) <strong>Cursor</strong></p>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a>会为所匹配的documents返回一个<a href="https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/">cursor</a>。</p>
<p>2) <strong>Read Isolation</strong></p>
<blockquote>
<p>New in version 3.2</p>
</blockquote>
<p>如果从<a href="https://docs.mongodb.com/manual/replication/">replica set</a>或<a href="https://docs.mongodb.com/manual/sharding/">replica set shards</a>中执行查询的话,允许客户端选择读取的隔离级别。更详细信息,请参看<a href="https://docs.mongodb.com/manual/reference/read-concern/">Read Concern</a>。</p>
<h3 id="31-query-on-embeddednested-documents">3.1 Query on Embedded/Nested Documents</h3>
<p>如下提供一个示例,在mongosh中使用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a>方法查询内嵌documents。我们使用<code class="language-plaintext highlighter-rouge">inventory</code>这个collection。首先向其中填充如下数据:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.insertMany( [
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }
]);</code></pre></figure>
<h6 id="match-an-embeddednested-document">Match an Embedded/Nested Document</h6>
<p>要查询内嵌document中某个字段,使用<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter">query filter document</a> <code class="language-plaintext highlighter-rouge">{ <field>:<value>}</code>,其中<code class="language-plaintext highlighter-rouge"><value></code>为所要匹配的document.</p>
<p>下面的例子查询<code class="language-plaintext highlighter-rouge">size</code>字段等于<code class="language-plaintext highlighter-rouge">{ h: 14, w: 21, uom: "cm" }</code>的document:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { size: { h: 14, w: 21, uom: "cm" } } )</code></pre></figure>
<p>对一个内嵌document做<code class="language-plaintext highlighter-rouge">Equality</code>匹配的话,要求<code class="language-plaintext highlighter-rouge">严格</code>(exact)的完全匹配,包括其中的字段顺序。例如,如下的查询将匹配不上<code class="language-plaintext highlighter-rouge">inventory</code>这个collection中的任何document:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { size: { w: 21, h: 14, uom: "cm" } } )</code></pre></figure>
<h6 id="query-on-nested-field">Query on Nested Field</h6>
<p>如果要使用内嵌document中某个字段作为查询条件来查询的话,使用<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-dot-notation">dot notation</a>(<code class="language-plaintext highlighter-rouge">"field.nestedField"</code>)。</p>
<blockquote>
<p>Note: 当使用dot notation来执行查询的话,查询字段必须在双引号内</p>
</blockquote>
<p>1) <strong>Specify Equality Match on a Nested Field</strong></p>
<p>下面的例子查询<code class="language-plaintext highlighter-rouge">size</code>中的内嵌字段<code class="language-plaintext highlighter-rouge">uom</code>等于<code class="language-plaintext highlighter-rouge">in</code>的所有document:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { "size.uom": "in" } )</code></pre></figure>
<p>2) <strong>Specify Match using Query Operator</strong></p>
<p>一个<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter">query filter document</a>可以使用<a href="https://docs.mongodb.com/manual/reference/operator/query/#std-label-query-selectors">query operators</a>来指定查询条件,形式如下:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{ <field1>: { <operator1>: <value1> }, ... }</code></pre></figure>
<p>下面的示例使用less than operator (<a href="https://docs.mongodb.com/manual/reference/operator/query/lt/#mongodb-query-op.-lt">$lt</a>)在<code class="language-plaintext highlighter-rouge">size</code>的内嵌字段<code class="language-plaintext highlighter-rouge">h</code>上做查询:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { "size.h": { $lt: 15 } } )</code></pre></figure>
<p>3) <strong>Specify AND Condition</strong></p>
<p>如下查询内嵌字段<code class="language-plaintext highlighter-rouge">h</code>小于15,内嵌字段<code class="language-plaintext highlighter-rouge">uom</code>等于<code class="language-plaintext highlighter-rouge">in</code>,且<code class="language-plaintext highlighter-rouge">status</code>等于<code class="language-plaintext highlighter-rouge">D</code>的documents:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { "size.h": { $lt: 15 }, "size.uom": "in", status: "D" } )</code></pre></figure>
<h6 id="additional-query-tutorials-1">Additional Query Tutorials</h6>
<p>其他查询示例,请参看:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-documents/">Query Documents</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-arrays/">Query an Array</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-array-of-documents/">Query an Array of Embedded Documents</a></li>
</ul>
<h3 id="32-query-an-array">3.2 Query an Array</h3>
<p>本节我们提供一些示例,使用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a>方法在数组字段上做查询。例子使用<code class="language-plaintext highlighter-rouge">inventory</code>这个collection。下面我们先向该collection填充一些数据:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.insertMany([
{ item: "journal", qty: 25, tags: ["blank", "red"], dim_cm: [ 14, 21 ] },
{ item: "notebook", qty: 50, tags: ["red", "blank"], dim_cm: [ 14, 21 ] },
{ item: "paper", qty: 100, tags: ["red", "blank", "plain"], dim_cm: [ 14, 21 ] },
{ item: "planner", qty: 75, tags: ["blank", "red"], dim_cm: [ 22.85, 30 ] },
{ item: "postcard", qty: 45, tags: ["blue"], dim_cm: [ 10, 15.25 ] }
]);</code></pre></figure>
<h6 id="match-an-array">Match an Array</h6>
<p>要在一个数组上指定equality condition,使用query document <code class="language-plaintext highlighter-rouge">{ <field>: <value>}</code>,其中<code class="language-plaintext highlighter-rouge"><value></code>与数组严格匹配,包含数组顺序也要匹配。</p>
<p>如下的例子查询的字段<code class="language-plaintext highlighter-rouge">tags</code>的值为数组,且按顺序值为<code class="language-plaintext highlighter-rouge">red</code>和<code class="language-plaintext highlighter-rouge">blank</code>的document:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { tags: ["red", "blank"] } )</code></pre></figure>
<p>另外,假如你仅仅只想查询一个数组中含有<code class="language-plaintext highlighter-rouge">red</code>及<code class="language-plaintext highlighter-rouge">blank</code>元素(不关心顺序,也不关心是否有其他元素),请使用<a href="https://docs.mongodb.com/manual/reference/operator/query/all/#mongodb-query-op.-all">$all</a> operator。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { tags: { $all: ["red", "blank"] } } )</code></pre></figure>
<h6 id="query-an-array-for-an-element">Query an Array for an Element</h6>
<p>如果要查询数组中<code class="language-plaintext highlighter-rouge">至少</code>包含某一指定值,使用filter <code class="language-plaintext highlighter-rouge">{<field>: <value>}</code>,其中<code class="language-plaintext highlighter-rouge"><value></code>为数组元素的值。</p>
<p>如下的例子查询<code class="language-plaintext highlighter-rouge">tags</code>字段至少含有<code class="language-plaintext highlighter-rouge">red</code>元素的documents:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { tags: "red" } )</code></pre></figure>
<p>如果要在数组字段的elements上指定查询条件的话,可以在<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter">query filter document</a>上使用<a href="https://docs.mongodb.com/manual/reference/operator/query/#std-label-query-selectors">query operators</a>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{ <array field>: { <operator1>: <value1>, ... } }</code></pre></figure>
<p>例如,下面的操作查询<code class="language-plaintext highlighter-rouge">dim_cm</code>数组中至少包含一个元素,该元素的值大于25:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { dim_cm: { $gt: 25 } } )</code></pre></figure>
<h6 id="specify-multiple-conditions-for-array-elements">Specify Multiple Conditions for Array Elements</h6>
<p>当需要在数组元素上指定复合条件的话,你可以为<code class="language-plaintext highlighter-rouge">单个</code>数组元素指定查询条件,也可以为<code class="language-plaintext highlighter-rouge">多个</code>数组元素指定查询条件.</p>
<p>1) <strong>Query an Array with Compound Filter Conditions on the Array Elements</strong></p>
<p>下面的例子查询<code class="language-plaintext highlighter-rouge">dim_cm</code>数组中元素满足匹配条件的documents。例如,数组中有一个元素大于15且有另一个元素小于20, 或者某一个元素同时满足这两个条件的documents:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { dim_cm: { $gt: 15, $lt: 20 } } )</code></pre></figure>
<p>2) <strong>Query for an Array Element that Meets Multiple Criteria</strong></p>
<p>使用<a href="https://docs.mongodb.com/manual/reference/operator/query/elemMatch/#mongodb-query-op.-elemMatch">$elemMatch</a>操作符来为数组中的元素指定多个准则(criteria),要求数组中至少有一个元素匹配所有准则。</p>
<p>如下的示例查询<code class="language-plaintext highlighter-rouge">dim_cm</code>数组中至少有一个元素同时满足:大于(<a href="https://docs.mongodb.com/manual/reference/operator/query/gt/#mongodb-query-op.-gt">$gt</a>)22,且小于(<a href="https://docs.mongodb.com/manual/reference/operator/query/lt/#mongodb-query-op.-lt">$lt</a>)30</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { dim_cm: { $elemMatch: { $gt: 22, $lt: 30 } } } )</code></pre></figure>
<p>3) <strong>Query for an Element by the Array Index Position</strong></p>
<p>使用<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-dot-notation">dot notation</a>,你可以为某一特定index上的数组元素指定查询条件。数组下表从0开始索引。</p>
<blockquote>
<p>Note: 当使用dot notation执行查询时,所查询的field(包括内嵌field)必须在双引号中</p>
</blockquote>
<p>下面的示例查询<code class="language-plaintext highlighter-rouge">dim_cm</code>数组中第二个元素大于25的所有documents:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { "dim_cm.1": { $gt: 25 } } )</code></pre></figure>
<p>4) <strong>Query an Array by Array Length</strong></p>
<p>使用<a href="https://docs.mongodb.com/manual/reference/operator/query/size/#mongodb-query-op.-size">$size</a>操作符来查询一个数组中数组元素的个数。例如,下面查询<code class="language-plaintext highlighter-rouge">tags</code>数组有3个元素的documents:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { "tags": { $size: 3 } } )</code></pre></figure>
<h6 id="additional-query-tutorials-2">Additional Query Tutorials</h6>
<p>更多查询示例,请参看:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-documents/">Query Documents</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-embedded-documents/">Query on Embedded/Nested Documents</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-array-of-documents/">Query an Array of Embedded Documents</a></li>
</ul>
<h3 id="33-query-an-array-of-embedded-documents">3.3 Query an Array of Embedded Documents</h3>
<p>本节提供一些示例,使用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a>方法查询数组元素为<code class="language-plaintext highlighter-rouge">内嵌</code>(nested) documents的记录。例子基于<code class="language-plaintext highlighter-rouge">inventory</code>这个collection,这里我们首先向该collection填充一些数据:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.insertMany( [
{ item: "journal", instock: [ { warehouse: "A", qty: 5 }, { warehouse: "C", qty: 15 } ] },
{ item: "notebook", instock: [ { warehouse: "C", qty: 5 } ] },
{ item: "paper", instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 15 } ] },
{ item: "planner", instock: [ { warehouse: "A", qty: 40 }, { warehouse: "B", qty: 5 } ] },
{ item: "postcard", instock: [ { warehouse: "B", qty: 15 }, { warehouse: "C", qty: 35 } ] }
]);</code></pre></figure>
<h6 id="query-for-a-document-nested-in-an-array">Query for a Document Nested in an Array</h6>
<p>下面的例子查询<code class="language-plaintext highlighter-rouge">instock</code>数组中匹配指定条件的documents:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { "instock": { warehouse: "A", qty: 5 } } )</code></pre></figure>
<p>对整个内嵌(embeded/nested)document的Equality匹配要求对指定的document严格匹配,包括字段的顺序。例如,如下的查询将匹配不上<code class="language-plaintext highlighter-rouge">inventory</code>这个collection中的任何document:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { "instock": { qty: 5, warehouse: "A" } } )</code></pre></figure>
<h6 id="specify-a-query-condition-on-a-field-in-an-array-of-documents">Specify a Query Condition on a Field in an Array of Documents</h6>
<p>1) <strong>Specify a Query Condition on a Field Embedded in an Array of Documents</strong></p>
<p>假如你不知道某一document在内嵌数组中的索引位置,那么可以使用<code class="language-plaintext highlighter-rouge">.</code>(dot)将将数组字段的名称(array-field-name)与内嵌document字段名称(nested-document-field-name)连接起来。</p>
<p>如下的示例查询<code class="language-plaintext highlighter-rouge">instock</code>数组至少有一个内嵌元素含有<code class="language-plaintext highlighter-rouge">qty</code>字段,且该字段的值小于等于20:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { 'instock.qty': { $lte: 20 } } )</code></pre></figure>
<p>2) <strong>Use the Array Index to Query for a Field in the Embedded Document</strong></p>
<p>使用<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-dot-notation">dot notation</a>,你可以为数组中某个索引(position)位置上的内嵌document字段指定查询条件。数组的索引从0开始。</p>
<blockquote>
<p>Note: 当使用dot notation时,字段(field)与索引(index)必须在双引号中</p>
</blockquote>
<p>下面的例子查询<code class="language-plaintext highlighter-rouge">instock</code>数组中第一个document元素含有内嵌<code class="language-plaintext highlighter-rouge">qtr</code>字段,并且该字段的值小于等于20:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { 'instock.0.qty': { $lte: 20 } } )</code></pre></figure>
<h6 id="specify-multiple-conditions-for-array-of-documents">Specify Multiple Conditions for Array of Documents</h6>
<p>当需要对一个数组内嵌document的多个field指定查询条件时,你可以为内嵌的<code class="language-plaintext highlighter-rouge">单个</code>document指定多个匹配条件,也可以为<code class="language-plaintext highlighter-rouge">多个</code>document指定多个匹配条件。</p>
<p>1) <strong>A Single Nested Document Meets Multiple Query Conditions on Nested Fields</strong></p>
<p>使用<a href="https://docs.mongodb.com/manual/reference/operator/query/elemMatch/#mongodb-query-op.-elemMatch">$elemMatch</a> operator为数组中的内嵌document指定多个<code class="language-plaintext highlighter-rouge">准则</code>(criteria),要求数组中至少有一个内嵌document匹配所有准则。</p>
<p>下面的示例查询<code class="language-plaintext highlighter-rouge">instock</code>数组中至少有一个内嵌document同时含有字段<code class="language-plaintext highlighter-rouge">qty</code>等于5,且含有字段<code class="language-plaintext highlighter-rouge">warehouse</code>等于<code class="language-plaintext highlighter-rouge">A</code>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { "instock": { $elemMatch: { qty: 5, warehouse: "A" } } } )</code></pre></figure>
<p>2) <strong>Combination of Elements Satisfies the Criteria</strong></p>
<p>假如对于数组字段(field)执行复合条件查询时<code class="language-plaintext highlighter-rouge">不使用</code><a href="https://docs.mongodb.com/manual/reference/operator/query/elemMatch/#mongodb-query-op.-elemMatch">$elemMatch</a>的话,则只要数组中的内嵌document的任何组合(可以多个内嵌documents组合在一起)满足查询条件即匹配成功。</p>
<p>例如,下面查询<code class="language-plaintext highlighter-rouge">instock</code>数组中任何内嵌document含有<code class="language-plaintext highlighter-rouge">qty</code>字段大于10,以及数组中任何内嵌document(没必要是同一个内嵌document)含有<code class="language-plaintext highlighter-rouge">qty</code>字段小于等于20:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { "instock.qty": { $gt: 10, $lte: 20 } } )</code></pre></figure>
<p>下面的例子查询<code class="language-plaintext highlighter-rouge">instock</code>数组中至少一个内嵌document含有字段<code class="language-plaintext highlighter-rouge">qty</code>等于5,且至少一个内嵌document(没必要是同一内嵌document)含有字段<code class="language-plaintext highlighter-rouge">warehouse</code>等于<code class="language-plaintext highlighter-rouge">A</code>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { "instock.qty": 5, "instock.warehouse": "A" } )</code></pre></figure>
<h6 id="additional-query-tutorials-3">Additional Query Tutorials</h6>
<p>更多查询示例,请参看:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-arrays/">Query an Array</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-documents/">Query Documents</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-embedded-documents/">Query on Embedded/Nested Documents</a></li>
</ul>
<h3 id="34-project-fields-to-return-from-query">3.4 Project Fields to Return from Query</h3>
<blockquote>
<p>Note: projection这里可以翻译为“字段筛选”</p>
</blockquote>
<p>默认情况下,在MongoDB中执行查询会返回所匹配的documents中的所有字段。为了限制返回给应用程序的数据量,我们可以使用一个<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-projection">projection</a> document来指定(specify)或限制(restrict)返回的字段(field)。</p>
<p>本节提供一些查询示例,使用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a>方法执行查询,并返回指定的字段。例子基于<code class="language-plaintext highlighter-rouge">inventory</code>这个collection,这里我们首先向该collection填充一些数据:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.insertMany( [
{ item: "journal", status: "A", size: { h: 14, w: 21, uom: "cm" }, instock: [ { warehouse: "A", qty: 5 } ] },
{ item: "notebook", status: "A", size: { h: 8.5, w: 11, uom: "in" }, instock: [ { warehouse: "C", qty: 5 } ] },
{ item: "paper", status: "D", size: { h: 8.5, w: 11, uom: "in" }, instock: [ { warehouse: "A", qty: 60 } ] },
{ item: "planner", status: "D", size: { h: 22.85, w: 30, uom: "cm" }, instock: [ { warehouse: "A", qty: 40 } ] },
{ item: "postcard", status: "A", size: { h: 10, w: 15.25, uom: "cm" }, instock: [ { warehouse: "B", qty: 15 }, { warehouse: "C", qty: 35 } ] }
]);</code></pre></figure>
<h6 id="return-all-fields-in-matching-documents">Return All Fields in Matching Documents</h6>
<p>假如不指定一个<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-projection">projection</a> document的话,则<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a>会返回匹配字段的所有元素。</p>
<p>下面的示例返回<code class="language-plaintext highlighter-rouge">inventory</code>这个collection中<code class="language-plaintext highlighter-rouge">status</code>等于<code class="language-plaintext highlighter-rouge">"A"</code>的document的所有字段:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { status: "A" } )</code></pre></figure>
<p>上面的查询语句等价于如下SQL语句:</p>
<pre>
SELECT * from inventory WHERE status = "A"
</pre>
<h6 id="return-the-specified-fields-and-the-_id-field-only">Return the Specified Fields and the <code class="language-plaintext highlighter-rouge">_id</code> Field Only</h6>
<p>可以通过projection显式的指定返回哪些字段,方法是将返回的<code class="language-plaintext highlighter-rouge"><field></code>设置为1。如下的操作返回匹配查询条件的所有documents。在返回的结果集中,只包含<code class="language-plaintext highlighter-rouge">item</code>及<code class="language-plaintext highlighter-rouge">status</code>字段,以及默认情况下的<code class="language-plaintext highlighter-rouge">_id</code>字段。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { status: "A" }, { item: 1, status: 1 } )</code></pre></figure>
<p>上述查询语句对应于如下SQL语句:</p>
<pre>
SELECT _id, item, status from inventory WHERE status = "A"
</pre>
<h6 id="suppress-_id-field">Suppress <code class="language-plaintext highlighter-rouge">_id</code> Field</h6>
<p>我们可以在projection中将<code class="language-plaintext highlighter-rouge">_id</code>字段设置为0,从而返回结果中不含有<code class="language-plaintext highlighter-rouge">_id</code>字段:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { status: "A" }, { item: 1, status: 1, _id: 0 } )</code></pre></figure>
<p>例子中查询语句对应于如下SQL语句:</p>
<pre>
SELECT item, status from inventory WHERE status = "A"
</pre>
<blockquote>
<p>Note: 除了<code class="language-plaintext highlighter-rouge">_id</code>字段外,你不能在projection document中同时使用inclusion与exclusion</p>
</blockquote>
<h6 id="return-all-but-the-excluded-fields">Return All But the Excluded Fields</h6>
<p>我们可以使用projection来排除返回某些字段。下面的示例会返回除<code class="language-plaintext highlighter-rouge">status</code>及<code class="language-plaintext highlighter-rouge">instock</code>字段外的所有其他字段:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { status: "A" }, { status: 0, instock: 0 } )</code></pre></figure>
<blockquote>
<p>Note: 除了<code class="language-plaintext highlighter-rouge">_id</code>字段外,你不能在projection document中同时使用inclusion与exclusion</p>
</blockquote>
<h6 id="return-specific-fields-in-embedded-documents">Return Specific Fields in Embedded Documents</h6>
<p>我们可以返回内嵌document的指定字段。可以在projection document中使用<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-dot-notation">dot noation</a>来引用指定的内嵌字段。</p>
<p>如下的示例返回:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">_id</code>字段(默认会返回)</li>
<li><code class="language-plaintext highlighter-rouge">item</code>字段</li>
<li><code class="language-plaintext highlighter-rouge">status</code>字段</li>
<li><code class="language-plaintext highlighter-rouge">size</code>中的<code class="language-plaintext highlighter-rouge">uom</code>字段</li>
</ul>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find(
{ status: "A" },
{ item: 1, status: 1, "size.uom": 1 }
)</code></pre></figure>
<p>从MongoDB 4.4版本开始,针对内嵌字段我们也可以使用内嵌形式,例如:</p>
<pre>
{ item: 1, status: 1, size: { uom: 1 } }
</pre>
<h6 id="suppress-specific-fields-in-embedded-documents">Suppress Specific Fields in Embedded Documents</h6>
<p>我们可以抑制内嵌document的指定字段。使用<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-dot-notation">dot notation</a>来引用对应的字段,并将对应的字段值设置为0.</p>
<p>下面的例子指定一个projection,排除返回<code class="language-plaintext highlighter-rouge">size</code>document中的<code class="language-plaintext highlighter-rouge">uom</code>字段,而其他的字段均返回:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find(
{ status: "A" },
{ "size.uom": 0 }
)</code></pre></figure>
<p>从MongoDB 4.4版本开始,针对内嵌字段我们也可以使用内嵌形式,例如:</p>
<pre>
{ item: 1, status: 1, size: { uom: 0 } }
</pre>
<h6 id="projection-on-embedded-documents-in-an-array">Projection on Embedded Documents in an Array</h6>
<p>使用<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-dot-notation">dot notation</a>数组中内嵌document的指定字段。</p>
<p>如下的例子返回指定字段:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">_id</code>字段(默认会返回)</li>
<li><code class="language-plaintext highlighter-rouge">item</code>字段</li>
<li><code class="language-plaintext highlighter-rouge">status</code>字段</li>
<li><code class="language-plaintext highlighter-rouge">instock</code>数组中内嵌document的<code class="language-plaintext highlighter-rouge">qty</code>字段</li>
</ul>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { status: "A" }, { item: 1, status: 1, "instock.qty": 1 } )</code></pre></figure>
<h6 id="project-specific-array-elements-in-the-returned-array">Project Specific Array Elements in the Returned Array</h6>
<p>对于包含数组的字段,MongoDB提供了如下的projection operator来操作数组:</p>
<ul>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/operator/projection/elemMatch/#mongodb-projection-proj.-elemMatch">$elemMatch</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/operator/projection/slice/#mongodb-projection-proj.-slice">$slice</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/operator/projection/positional/#mongodb-projection-proj.-">$</a></p>
</li>
</ul>
<p>如下的例子使用<a href="https://docs.mongodb.com/manual/reference/operator/projection/slice/#mongodb-projection-proj.-slice">$slice</a>这一projection operator来返回<code class="language-plaintext highlighter-rouge">instock</code>数组中最后一个元素。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { status: "A" }, { item: 1, status: 1, instock: { $slice: -1 } } )</code></pre></figure>
<p><a href="https://docs.mongodb.com/manual/reference/operator/projection/elemMatch/#mongodb-projection-proj.-elemMatch">$elemMatch</a>、<a href="https://docs.mongodb.com/manual/reference/operator/projection/slice/#mongodb-projection-proj.-slice">$slice</a>、<a href="https://docs.mongodb.com/manual/reference/operator/projection/positional/#mongodb-projection-proj.-">$</a>是仅有的三种可以筛选特定数组元素的方法。比如,你不能使用数组索引来筛选特定元素,eg: <code class="language-plaintext highlighter-rouge">{"instock.0": 1}</code>这一projection<code class="language-plaintext highlighter-rouge">不能</code>筛选数组的第一个元素。</p>
<h6 id="additional-considerations">Additional Considerations</h6>
<p>从MongoDB 4.4版本开始,针对projection MongoDB会强制一些额外的限制,请参看<a href="https://docs.mongodb.com/manual/reference/limits/#mongodb-limit-Projection-Restrictions">Projection Restrictions</a>。</p>
<h3 id="35-query-for-null-or-missing-fields">3.5 Query for Null or Missing Fields</h3>
<p>在MongoDB中不同的query operators对待<code class="language-plaintext highlighter-rouge">null</code>值的方式也不同。</p>
<p>本文提供一些使用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a>方法查询<code class="language-plaintext highlighter-rouge">null</code>值的示例。例子使用<code class="language-plaintext highlighter-rouge">inventory</code>这个collection,我们向该collection填充一些数据:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.insertMany([
{ _id: 1, item: null },
{ _id: 2 }
])</code></pre></figure>
<h6 id="equality-filter">Equality Filter</h6>
<p><code class="language-plaintext highlighter-rouge">{item: null}</code>查询匹配<code class="language-plaintext highlighter-rouge">item</code>字段的值为<code class="language-plaintext highlighter-rouge">null</code>或者不含<code class="language-plaintext highlighter-rouge">item</code>的document。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { item: null } )</code></pre></figure>
<p>上述查询会返回<code class="language-plaintext highlighter-rouge">inventory</code>集合中的两个元素。</p>
<h6 id="type-check">Type Check</h6>
<p><code class="language-plaintext highlighter-rouge">{ item : { $type: 10 } }</code>查询匹配含有<code class="language-plaintext highlighter-rouge">item</code>字段且该字段值为<code class="language-plaintext highlighter-rouge">null</code>的documents。比如,<code class="language-plaintext highlighter-rouge">item</code>字段的值为<a href="https://docs.mongodb.com/manual/reference/bson-types/">BSON Type</a> Null(其对应的type number为10):</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { item : { $type: 10 } } )</code></pre></figure>
<p>上述查询只返回item字段值为<code class="language-plaintext highlighter-rouge">null</code>的document。</p>
<h6 id="existence-check">Existence Check</h6>
<p>下面的示例查询不含某一字段的documents。</p>
<blockquote>
<p>注:从MongoDB 4.2版本开始,用户不能使用query filter<code class="language-plaintext highlighter-rouge">$type: 0</code>作为<code class="language-plaintext highlighter-rouge">$exists: false</code>的等价。</p>
</blockquote>
<p><code class="language-plaintext highlighter-rouge">{ item : { $exists: false } }</code>匹配不含<code class="language-plaintext highlighter-rouge">item</code>字段的documents:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.inventory.find( { item : { $exists: false } } )</code></pre></figure>
<p>上述例子只返回不含<code class="language-plaintext highlighter-rouge">item</code>字段的documents。</p>
<h3 id="36-iterate-a-cursor-in-mongosh">3.6 Iterate a Cursor in mongosh</h3>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a>会返回一个<code class="language-plaintext highlighter-rouge">游标</code>(cursor)。要访问这些documents,你需要遍历该cursor。然而,在<a href="https://docs.mongodb.com/mongodb-shell/#mongodb-binary-bin.mongosh">mongosh</a>中,假如返回的cursor没有赋值给一个使用<code class="language-plaintext highlighter-rouge">var</code>关键字定义的变量时,则cursor会自动的遍历20次以打印结果集中最开头的20个documents。</p>
<p>下面的例子描述了一种方法可以手动(manually)的遍历cursor从而访问结果集中的documents,或者使用iterator index来访问结果集中的元素。</p>
<h6 id="manually-iterate-the-cursor">Manually Iterate the Cursor</h6>
<p>在<a href="https://docs.mongodb.com/mongodb-shell/#mongodb-binary-bin.mongosh">mongosh</a>中,当你将<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">find()</a>方法返回的结果集赋值给一个由<code class="language-plaintext highlighter-rouge">var</code>关键字定义的变量时,则cursor将不会自动的遍历。</p>
<p>你可以在mongosh中遍历该cursor多达20次,以打印结果集中的元素,请参看如下示例:</p>
<blockquote>
<p>Note: You can set the <code class="language-plaintext highlighter-rouge">DBQuery.shellBatchSize</code> attribute to change the number of documents from the default value of 20.</p>
</blockquote>
<figure class="highlight"><pre><code class="language-string" data-lang="string">var myCursor = db.users.find( { type: 2 } );
myCursor</code></pre></figure>
<p>你也可以使用cursor method<a href="https://docs.mongodb.com/manual/reference/method/cursor.next/#mongodb-method-cursor.next">next()</a>来访问结果集中的元素,参看如下示例:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">var myCursor = db.users.find( { type: 2 } );
while (myCursor.hasNext()) {
print(tojson(myCursor.next()));
}</code></pre></figure>
<p>作为另一种打印方式,考虑<code class="language-plaintext highlighter-rouge">printjson()</code>帮助方法以代替<code class="language-plaintext highlighter-rouge">print(tojson())</code>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">var myCursor = db.users.find( { type: 2 } );
while (myCursor.hasNext()) {
printjson(myCursor.next());
}</code></pre></figure>
<p>另外,你也可以使用<a href="https://docs.mongodb.com/manual/reference/method/cursor.forEach/#mongodb-method-cursor.forEach">forEach()</a>来遍历cursor,并访问其中的documents,参看如下示例:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">var myCursor = db.users.find( { type: 2 } );
myCursor.forEach(printjson);</code></pre></figure>
<h6 id="iterator-index">Iterator Index</h6>
<p>在mongosh中,你可以使用<a href="https://docs.mongodb.com/manual/reference/method/cursor.toArray/#mongodb-method-cursor.toArray">toArray()</a>方法来遍历cursor,并通过一个数组来返回documents,参看如下:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">var myCursor = db.inventory.find( { type: 2 } );
var documentArray = myCursor.toArray();
var myDocument = documentArray[3];</code></pre></figure>
<p><a href="https://docs.mongodb.com/manual/reference/method/cursor.toArray/#mongodb-method-cursor.toArray">toArray</a>方法会将cursor所返回的所有documents加载到RAM。<a href="https://docs.mongodb.com/manual/reference/method/cursor.toArray/#mongodb-method-cursor.toArray">toArray()</a>会exhausts cursor。</p>
<p>另外有一些<a href="https://docs.mongodb.com/drivers/">Driver</a>会针对cursor提供索引(index)来访问其中的documents(比如:<code class="language-plaintext highlighter-rouge">cursor[index]</code>)。这仅仅知识调用<a href="https://docs.mongodb.com/manual/reference/method/cursor.toArray/#mongodb-method-cursor.toArray">toArray()</a>的一种简写形式,然后在结果集数组上使用索引。</p>
<p>考虑下面的示例:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">var myCursor = db.users.find( { type: 2 } );
var myDocument = myCursor[1];</code></pre></figure>
<p>其中<code class="language-plaintext highlighter-rouge">myCursor[1]</code>等价于如下:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">myCursor.toArray() [1];</code></pre></figure>
<h6 id="cursor-behaviors">Cursor Behaviors</h6>
<p>1) <strong>Cursors Opened Within a Session</strong></p>
<p>从MongoDB 5.0(以及4.4.8)版本开始,在一个<a href="https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/">client session</a>中所创建的cursors会在对应的<a href="https://docs.mongodb.com/manual/reference/server-sessions/">server session</a>接收到<a href="https://docs.mongodb.com/manual/reference/command/killSessions/#mongodb-dbcommand-dbcmd.killSessions">killSessions</a>命令后被关闭,或者遇到session超时,或者client已经exhausted对应的cursor。</p>
<p>默认情况下,server session的超时时间为30分钟。要改变该值,在启动mongod时请设置<a href="https://docs.mongodb.com/manual/reference/parameters/#mongodb-parameter-param.localLogicalSessionTimeoutMinutes">localLogicalSessionTimeoutMinutes</a>参数。</p>
<p>2) <strong>Cursors Opened Outside of a Session</strong></p>
<p>3) <strong>Cursor Isolation</strong></p>
<p>4) <strong>Cursor Batches</strong></p>
<h6 id="cursor-information">Cursor Information</h6>
<p><br />
<br />
<strong>[参看]</strong>:</p>
<ol>
<li>
<p><a href="https://www.mongodb.com/">mongodb官网</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/">mongodb method</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/command/find/">mongodb command</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/weixin_43031412/article/details/97632341">MongoDB Projection</a></p>
</li>
<li>
<p><a href="https://www.runoob.com/mongodb/mongodb-query.html">mongodb菜鸟教程</a></p>
</li>
<li>
<p><a href="http://blog.itpub.net/31556440/viewspace-2672431/">了解 MongoDB 看这一篇就够了</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
MongoDB的安装
2020-05-02T00:00:00+00:00
/post/database/2020/05/02/mongodb_part6
<p>MongoDB server有两个版本: Community版本和Enterprise版本</p>
<p>本章我们介绍MongoDB的安装。</p>
<ul>
<li>
<p>将当前部署环境升级到MongoDB 5.0,请参看<a href="https://docs.mongodb.com/manual/release-notes/5.0/#std-label-5.0-upgrade">Upgrade Procedures</a></p>
</li>
<li>
<p>为当前版本升级最新版本,请参看<a href="https://docs.mongodb.com/manual/tutorial/upgrade-revision/">Upgrade to the Latest Revision of MongoDB </a></p>
</li>
</ul>
<!-- more -->
<h2 id="1-相关参考文档说明">1. 相关参考文档说明</h2>
<p>1) MongoDB安装手册</p>
<p>如下我们介绍MongoDB社区版(Conmunity Edition)在Linux平台上安装的一些参考手册:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/">Install MongoDB Community Edition on Red Hat or CentOS</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/">Install MongoDB Community Edition on Ubuntu</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/install-mongodb-on-debian/">Install MongoDB Community Edition on Debian</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/install-mongodb-on-suse/">Install MongoDB Community Edition on SUSE</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/install-mongodb-on-amazon/">Install MongoDB Community Edition on Amazon Linux</a></li>
</ul>
<p>2) <strong>更新社区版本到企业版本</strong></p>
<ul>
<li><a href="https://docs.mongodb.com/manual/tutorial/upgrade-to-enterprise-standalone/">Upgrade to MongoDB Enterprise (Standalone)</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/upgrade-to-enterprise-replica-set/">Upgrade to MongoDB Enterprise (Replica Set)</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/upgrade-to-enterprise-sharded-cluster/">Upgrade to MongoDB Enterprise (Sharded Cluster)</a></li>
</ul>
<h2 id="2-在redhat或centos上安装mongodb社区版">2. 在RedHat或Centos上安装MongoDB社区版</h2>
<p>由于</p>
<p>本节我们介绍使用yum包管理器在Red Hat Enterprise Linux、Centos Linux、Oracle Linux上安装MongoDB 5.0社区版。</p>
<p>当前操作系统环境为:</p>
<pre>
# uname -a
Linux localhost.localdomain 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
# cat /etc/redhat-release
CentOS Linux release 7.3.1611 (Core)
</pre>
<h3 id="21-install-mongodb-community-edition">2.1 Install MongoDB Community Edition</h3>
<p>1) <strong>配置yum源</strong></p>
<p>创建/etc/yum.repos.d/mongodb-org-5.0.repo文件如下:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">[mongodb-org-5.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/5.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-5.0.asc</code></pre></figure>
<p>我们也可以直接从<a href="https://repo.mongodb.org/yum/redhat/">MongoDB repository</a>仓库中下载对应的rpm包来安装。</p>
<p>对于MongoDB 5.0之前的版本,<code class="language-plaintext highlighter-rouge">奇数</code>版本表示开发版(development releases)。从MongoDB 5.1版本开始,MongoDB有一个季度版本。</p>
<p>2) <strong>安装MongoDB包</strong></p>
<p>安装最新版本的MongoDB,请执行如下命令:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string"># sudo yum install -y mongodb-org
...
Installed:
mongodb-org.x86_64 0:5.0.5-1.el7
Dependency Installed:
mongodb-database-tools.x86_64 0:100.5.1-1 mongodb-mongosh.x86_64 0:1.1.9-1.el7 mongodb-org-database.x86_64 0:5.0.5-1.el7
mongodb-org-database-tools-extra.x86_64 0:5.0.5-1.el7 mongodb-org-mongos.x86_64 0:5.0.5-1.el7 mongodb-org-server.x86_64 0:5.0.5-1.el7
mongodb-org-shell.x86_64 0:5.0.5-1.el7 mongodb-org-tools.x86_64 0:5.0.5-1.el7
Complete!</code></pre></figure>
<p>另外,如果要安装一个特定版本的MongoDB的话,请单独指定每一个组件的名称及对应的版本号,如下所示:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string"># sudo yum install -y mongodb-org-5.0.5 mongodb-org-database-5.0.5 mongodb-org-server-5.0.5 mongodb-org-shell-5.0.5 mongodb-org-mongos-5.0.5 mongodb-org-tools-5.0.5</code></pre></figure>
<p>然而使用上面命令安装的时候,如果MongoDB有一个更新的版本可用,那么yum会自动的更新到最新版本。为了防止此种情况出现,我们必须钉住(pin)相应的包。要钉住一个package,在/etc/yum.conf中添加如下<code class="language-plaintext highlighter-rouge">exclude</code>指令:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">exclude=mongodb-org,mongodb-org-database,mongodb-org-server,mongodb-org-shell,mongodb-org-mongos,mongodb-org-tools</code></pre></figure>
<p>安装完成后,我们可以看看默认的/etc/mongod.conf配置文件:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string"># mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
# Where and how to store data.
storage:
dbPath: /var/lib/mongo
journal:
enabled: true
# engine:
# wiredTiger:
# how the process runs
processManagement:
fork: true # fork and run in background
pidFilePath: /var/run/mongodb/mongod.pid # location of pidfile
timeZoneInfo: /usr/share/zoneinfo
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.
#security:
#operationProfiling:
#replication:
#sharding:
## Enterprise-Only Options
#auditLog:
#snmp:</code></pre></figure>
<h3 id="22-运行mongodb社区版">2.2 运行MongoDB社区版</h3>
<h6 id="221-先决条件">2.2.1 先决条件</h6>
<p>1) <strong>ulimit</strong></p>
<p>大多数Unix-like操作系统都会限制一个进程可使用的系统资源。这些限制可能会对MongoDB的操作产生负面影响,在安装时我们可能需要适当的调整。请参看<a href="https://docs.mongodb.com/manual/reference/ulimit/">UNIX ulimit</a>以了解更详细的设置。</p>
<blockquote>
<p>注:从MongoDB 4.4版本开始,假如ulimit的值低于64000时,在启动的时候会报告相应的错误。</p>
</blockquote>
<p>这里,我们以root用户启动,调整root用户下ulimit值如下;</p>
<pre>
# ulimit -n 64000
# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 14985
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 64000
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 14985
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
</pre>
<h6 id="222-设置目录">2.2.2 设置目录</h6>
<p>1) <strong>使用默认目录</strong></p>
<p>默认情况下,MongoDB会使用<code class="language-plaintext highlighter-rouge">mongod</code>账户来运行,并使用如下的默认目录:</p>
<ul>
<li>/var/lib/mongo(数据目录)</li>
<li>/var/log/mongodb(日志目录)</li>
</ul>
<p>通过yum安装时,包管理器会自动这两个默认目录。并且这两个目录的owner及group都是<code class="language-plaintext highlighter-rouge">mongod</code>。如下:</p>
<pre>
# ls -al /var/lib/mongo
total 4
drwxr-xr-x. 2 mongod mongod 6 Dec 2 08:58 .
drwxr-xr-x. 55 root root 4096 Jan 19 05:23 ..
# ls -al /var/log/mongodb
total 4
drwxr-xr-x. 2 mongod mongod 24 Jan 19 05:23 .
drwxr-xr-x. 21 root root 4096 Jan 19 05:23 ..
-rw-r-----. 1 mongod mongod 0 Dec 2 08:58 mongod.log
</pre>
<p>2) <strong>使用非默认默认</strong></p>
<p>要使用非默认的数据目录及日志目录的话:</p>
<p>2.1) 创建新的目录</p>
<p>2.2) 修改配置文件/etc/mongod.conf的如下字段</p>
<ul>
<li>storage.dbPath指定一个新的目录(比如: /some/data/directory)</li>
<li>systemLog.path指定一个新的日志目录(比如:/some/log/directory/mongod.log)</li>
</ul>
<p>2.3) 确保运行mongodb的用户有对应目录的访问权限</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string"># sudo chown -R mongod:mongod <directory></code></pre></figure>
<p>假如你想更改运行MongoDB进程的用户,你必须给新用户访问这些目录的权限。</p>
<p>2.4) 配置SELinux</p>
<p>这里我们直接关闭防火墙(更高级的配置请参看<a href="https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/">Configure SeLinux</a>):</p>
<pre>
# systemctl stop firewalld.service
# systemctl disable firewalld.service
# setenforce 0
# sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
</pre>
<h6 id="223-运行">2.2.3 运行</h6>
<p>遵循如下的步骤来运行MongoDB。如下的这些指令假设你使用的是默认的配置。</p>
<p>1) <strong>Init system</strong></p>
<p>要运行和管理<code class="language-plaintext highlighter-rouge">mongod</code>进程,你需要使用到操作系统内置的<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-init-system">init system</a>。当前新版的Linux操作系统都倾向于使用<code class="language-plaintext highlighter-rouge">systemd</code>(用systemctl命令);而老版本的Linux系统使用的是<code class="language-plaintext highlighter-rouge">System V init</code>(用service命令)</p>
<p>假如你不确定你当前的操作系统使用的是哪一种init system的话,执行如下命令:</p>
<pre>
# ps --no-headers -o comm 1
systemd
</pre>
<p>这里我们当前Centos 7.0使用的是systemd。</p>
<p>2) <strong>启动MongoDB</strong></p>
<p>执行如下命令启动<a href="https://docs.mongodb.com/manual/reference/program/mongod/#mongodb-binary-bin.mongod">mongod</a>进程:</p>
<pre>
# sudo systemctl start mongod
# ps -ef | grep mongod
mongod 4746 1 5 06:09 ? 00:00:00 /usr/bin/mongod -f /etc/mongod.conf
</pre>
<p>假如在启动mongod过程中遇到如下错误:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">Failed to start mongod.service: Unit mongod.service not found.</code></pre></figure>
<p>执行如下命令:</p>
<pre>
# sudo systemctl daemon-reload
# sudo systemctl start mongod
</pre>
<p>mongoDB启动后,我们来看看其所使用的端口:</p>
<pre>
# netstat -nlp | grep mongo
tcp 0 0 127.0.0.1:27017 0.0.0.0:* LISTEN 4746/mongod
unix 2 [ ACC ] STREAM LISTENING 44414 4746/mongod /tmp/mongodb-27017.sock
</pre>
<p>3) <strong>检查MongoDB是否启动成功</strong></p>
<p>可以使用如下命令检查mongodb是否启动成功:</p>
<pre>
# sudo systemctl status mongod
● mongod.service - MongoDB Database Server
Loaded: loaded (/usr/lib/systemd/system/mongod.service; enabled; vendor preset: disabled)
Active: active (running) since Wed 2022-01-19 06:09:39 PST; 2min 44s ago
Docs: https://docs.mongodb.org/manual
Process: 4743 ExecStart=/usr/bin/mongod $OPTIONS (code=exited, status=0/SUCCESS)
Process: 4738 ExecStartPre=/usr/bin/chmod 0755 /var/run/mongodb (code=exited, status=0/SUCCESS)
Process: 4735 ExecStartPre=/usr/bin/chown mongod:mongod /var/run/mongodb (code=exited, status=0/SUCCESS)
Process: 4733 ExecStartPre=/usr/bin/mkdir -p /var/run/mongodb (code=exited, status=0/SUCCESS)
Main PID: 4746 (mongod)
CGroup: /system.slice/mongod.service
└─4746 /usr/bin/mongod -f /etc/mongod.conf
Jan 19 06:09:38 localhost.localdomain systemd[1]: Starting MongoDB Database Server...
Jan 19 06:09:38 localhost.localdomain mongod[4743]: about to fork child process, waiting until server is ready for connections.
Jan 19 06:09:38 localhost.localdomain mongod[4743]: forked process: 4746
Jan 19 06:09:39 localhost.localdomain systemd[1]: Started MongoDB Database Server.
</pre>
<p>可以使用如下命令来设置MongoDB开机启动:</p>
<pre>
# sudo systemctl enable mongod
</pre>
<p>4) <strong>关闭MongoDB</strong></p>
<p>执行如下命令来关闭MongoDB:</p>
<pre>
# sudo systemctl stop mongod
</pre>
<p>5) <strong>重启MongoDB</strong></p>
<p>执行如下命令来重启MongoDB:</p>
<pre>
# sudo systemctl restart mongod
</pre>
<p>我们可以查看/var/log/mongodb/mongod.log文件来了解mongod进行运行的一些重要信息。</p>
<p>6) <strong>Begin using MongoDB</strong></p>
<p>在与<a href="https://docs.mongodb.com/mongodb-shell/#mongodb-binary-bin.mongosh">mongod</a>运行的同一台主机上,启动一个<a href="https://docs.mongodb.com/mongodb-shell/#mongodb-binary-bin.mongosh">mongosh</a>。我们可以直接不添加任何选项启动mongosh,这样mongosh会自动连接本机的27017端口。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string"># mongosh
# mongosh --host 127.0.0.1 --port 27017
Current Mongosh Log ID: 61e82c6da95d3d0cd54cf7b8
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.1.9
Using MongoDB: 5.0.5
Using Mongosh: 1.1.9
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
------
The server generated these startup warnings when booting:
2022-01-19T06:09:39.109-08:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
2022-01-19T06:09:39.110-08:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
2022-01-19T06:09:39.110-08:00: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. We suggest setting it to 'never'
------
test> </code></pre></figure>
<p>与了解更多mongosh相关内容,请参看:</p>
<ul>
<li>
<p><a href="https://docs.mongodb.com/mongodb-shell/">mongosh documentation</a></p>
</li>
<li>
<p><a href="https://api.mongodb.com/"> Start Developing with MongoDB</a></p>
</li>
</ul>
<h3 id="23-卸载mongodb社区版">2.3 卸载MongoDB社区版</h3>
<p>要完整的从操作系统中卸载MongoDB的话,你必须删除掉MongoDB应用程序本身、配置文件、以及包含数据和日志的目录。如下我们介绍一下必要的步骤。</p>
<p>1) <strong>停止MongoDB</strong></p>
<p>执行如下命令停止MongoDB:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string"># sudo service mongod stop</code></pre></figure>
<p>2) <strong>移除安装包</strong></p>
<p>执行如下命令移除mongoDB安装包:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string"># sudo yum erase $(rpm -qa | grep mongodb-org)</code></pre></figure>
<p>3) <strong>删除数据目录</strong></p>
<p>执行如下命令删除数据目录和日志目录:</p>
<pre>
# sudo rm -r /var/log/mongodb
# sudo rm -r /var/lib/mongo
</pre>
<h3 id="24-additional-information">2.4 Additional Information</h3>
<p>1) <strong>Localhost Binding by Default</strong></p>
<p>默认情况下,MongoDB启动时bindIp会被设置为127.0.0.1,即被绑定到本地网络接口。这意味着<code class="language-plaintext highlighter-rouge">mongod</code>只能接受本地连接,远程客户端将不能够连接到<code class="language-plaintext highlighter-rouge">mongod</code>,并且mongod也不能够初始化一个<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-replica-set">replica set</a>。除非我们在<code class="language-plaintext highlighter-rouge">mongod</code>启动时绑定到一个对外网卡上。</p>
<p>我们可以有两种方法配置:</p>
<ul>
<li>
<p>修改MongoDB配置文件中的<code class="language-plaintext highlighter-rouge">bindIp</code></p>
</li>
<li>
<p>通过命令行参数<code class="language-plaintext highlighter-rouge">--bind_ip</code></p>
</li>
</ul>
<blockquote>
<p>WARNING: 在你绑定到一个公有IP地址之前,确保unauthorized access不会影响到集群的安全。完整的安全列表建议,请参看<a href="https://docs.mongodb.com/manual/administration/security-checklist/">Security Checklist</a>。至少,我们应该考虑<a href="https://docs.mongodb.com/manual/administration/security-checklist/#std-label-checklist-auth">enabling authentication</a>以及<a href="https://docs.mongodb.com/manual/core/security-hardening/">hardening network infrastructure</a></p>
</blockquote>
<p>更多关于bindIp的知识,请参看<a href="https://docs.mongodb.com/manual/core/security-mongodb-configuration/">IP Binding</a></p>
<p>2) <strong>MongoDB Community Edition Packages</strong></p>
<p>MongoDB社区版有自己专用的仓库(repository),官方支持如下一些安装包:</p>
<ul>
<li>
<p>mongodb-org: 是一个metapackage,用于自动的安装如下的一些组件(component packages)</p>
</li>
<li>
<p>mongodb-org-database: 是一个metapackage,用于自动的安装如下组件</p>
<ul>
<li>mongodb-org-server: 含有mongod守护进程,相关的初始化脚本,以及一个配置文件(/etc/mongod.conf)。你可以使用初始化脚本,以默认配置文件的方式来启动mongod</li>
<li>mongodb-org-mongos: 包含mongos守护进程</li>
<li>mongodb-org-shell: 包含经典mongo shell</li>
</ul>
</li>
<li>
<p>mongodb-mongosh: 包含MongoDB Shell(mongosh)</p>
</li>
<li>
<p>mongodb-org-tools: 是一个metapackage,用于自动安装如下组件</p>
<ul>
<li>mongodb-database-tools: 包含如下一些数据库工具</li>
</ul>
</li>
</ul>
<blockquote>
<p><a href="https://docs.mongodb.com/database-tools/mongodump/#mongodb-binary-bin.mongodump">mongodump</a></p>
<p><a href="https://docs.mongodb.com/database-tools/mongorestore/#mongodb-binary-bin.mongorestore">mongorestore</a></p>
<p><a href="https://docs.mongodb.com/database-tools/bsondump/#mongodb-binary-bin.bsondump">bsondump</a></p>
<p><a href="https://docs.mongodb.com/database-tools/mongoimport/#mongodb-binary-bin.mongoimport">mongoimport</a></p>
<p><a href="https://docs.mongodb.com/database-tools/mongoexport/#mongodb-binary-bin.mongoexport">mongoexport</a></p>
<p><a href="https://docs.mongodb.com/database-tools/mongostat/#mongodb-binary-bin.mongostat">mongostat</a></p>
<p><a href="https://docs.mongodb.com/database-tools/mongotop/#mongodb-binary-bin.mongotop">mongotop</a></p>
<p><a href="https://docs.mongodb.com/database-tools/mongofiles/#mongodb-binary-bin.mongofiles">mongofiles</a></p>
</blockquote>
<ul>
<li>mongodb-org-database-tools-extra: 包含<a href="https://docs.mongodb.com/manual/reference/program/install_compass/#std-label-install-compass">install_compass</a>脚本</li>
</ul>
<h2 id="3-使用tgz压缩包来安装mongodb">3. 使用.tgz压缩包来安装MongoDB</h2>
<p>本节我们介绍如何使用一个下载的<code class="language-plaintext highlighter-rouge">.tgz</code>压缩包来在Red Hat Enterprise Linux、Centos Linux或Oracle Linux上装MongoDB 5.0 Community Edition.</p>
<h3 id="31-considerations">3.1 Considerations</h3>
<p>1) <strong>MongoDB Shell, mongosh</strong></p>
<p>当你使用<code class="language-plaintext highlighter-rouge">.tgz</code>压缩包来安装MongoDB时,你需要遵循<a href="https://docs.mongodb.com/mongodb-shell/install/">mongosh installation instructions</a>来单独的下载和安装<a href="https://docs.mongodb.com/mongodb-shell/">mongosh</a></p>
<h3 id="32-install-mongodb-community-edition">3.2 Install MongoDB Community Edition</h3>
<h6 id="321-prerequisites">3.2.1 Prerequisites</h6>
<p>首先需要安装MongoDB Community Edition的一些依赖:</p>
<pre>
# sudo yum install libcurl openssl xz-libs
</pre>
<h6 id="322-procedure">3.2.2 Procedure</h6>
<p>遵循如下的步骤,使用<code class="language-plaintext highlighter-rouge">.tgz</code>来安装MongoDB社区版。</p>
<p>1) <strong>Download the tarball</strong></p>
<p>在你安装了上述必要的依赖包之后,可以到<a href="https://www.mongodb.com/try/download/community?tck=docs_server">MongoDB Download Center</a>下载对应的社区版本:</p>
<ol>
<li>
<p>Version下拉列表, 选择对应的MongoDB版本</p>
</li>
<li>
<p>Platform下拉列表, 选择对应的操作系统和架构</p>
</li>
<li>
<p>Package下拉列表,选择tgz</p>
</li>
</ol>
<p>这里我们执行如下命令下载mongodb-linux-x86_64-rhel70-5.0.5.tgz:</p>
<pre>
# mkdir -p /opt/mongodb
# cd -p /opt/mongodb
# wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-5.0.5.tgz
</pre>
<p>2) <strong>解压安装包</strong></p>
<p>这里使用如下命令解压安装包:</p>
<pre>
# tar -zxvf mongodb-linux-x86_64-rhel70-5.0.5.tgz
</pre>
<p>3) <strong>配置环境变量</strong></p>
<p>这里我们修改/etc/profile,添加如下配置:</p>
<pre>
export MONGO_HOME = /opt/mongodb
export PATH = $PATH:$MONGO_HOME
</pre>
<p>然后执行如下命令生效:</p>
<pre>
# source /etc/profile
</pre>
<blockquote>
<p>注:我们使用软链接将其链接到/usr/local/bin目录也可以</p>
<p>sudo ln -s /path/to/the/mongodb-directory/bin/* /usr/local/bin/</p>
</blockquote>
<p>4) <strong>安装MongoDB Shell(mongosh)</strong></p>
<p>这里我们下载对应版本的mongodb shell安装包来进行安装:</p>
<pre>
# mkdir /opt/mongosh
# wget https://fastdl.mongodb.org/linux/mongodb-shell-linux-x86_64-rhel70-5.0.5.tgz
# tar -zxvf mongodb-shell-linux-x86_64-rhel70-5.0.5.tgz
</pre>
<p>解压安装完成后配置环境变量,修改/etc/profile:</p>
<pre>
export MONGOSH_HOME=/opt/mongosh/bin/
export PATH = $PATH:$MONGOSH_HOME
</pre>
<h3 id="33-run-mongodb-community-edition">3.3 Run MongoDB Community Edition</h3>
<h6 id="331-prerequisites">3.3.1 Prerequisites</h6>
<p>1.1) <strong>ulimit</strong></p>
<p>关于ulimit配置,请参看上文。</p>
<h6 id="332-directory-paths">3.3.2 Directory Paths</h6>
<p>这里我们创建如下目录:</p>
<pre>
# sudo mkdir -p /var/lib/mongo
# sudo mkdir -p /var/log/mongodb
</pre>
<p>默认情况下,MongoDB使用<code class="language-plaintext highlighter-rouge">mongod</code>用户来运行,因此我们要创建mongod用户和组,并更改对应目录的所有者和所属组:</p>
<pre>
# sudo chown -R mongod:mongod /var/lib/mongo
# sudo chown -R mongod:mongod /var/log/mongodb
</pre>
<h6 id="333-procedure">3.3.3 Procedure</h6>
<p>1) <strong>创建数据目录和日志目录</strong></p>
<pre>
# sudo mkdir -p /var/lib/mongo
# sudo mkdir -p /var/log/mongodb
</pre>
<p>假如你打算自己启动MongoDB的话,你需要确保有访问对应目录的权限:</p>
<pre>
sudo chown `whoami` /var/lib/mongo # Or substitute another user
sudo chown `whoami` /var/log/mongodb # Or substitute another user
</pre>
<p>2) <strong>运行mongodb</strong></p>
<p>执行如下命令运行mongodb:</p>
<pre>
# mongod --dbpath /var/lib/mongo --logpath /var/log/mongodb/mongod.log --fork
</pre>
<p>3) <strong>检查mongodb是否启动成功</strong></p>
<p>查看启动日志/var/log/mongod/mongod.log,看mongodb是否启动成功。如果出现如下信息表示启动正常:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">[initandlisten] waiting for connections on port 27017</code></pre></figure>
<p>4) <strong>使用mongodb</strong></p>
<p>使用mongosh来连接MongoDB:</p>
<pre>
# mongosh
</pre>
<p><br />
<br />
<strong>[参看]</strong>:</p>
<ol>
<li>
<p><a href="https://www.mongodb.com/">mongodb官网</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/">mongodb manual</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/">mongodb method</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/command/find/">mongodb command</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/weixin_43031412/article/details/97632341">MongoDB Projection</a></p>
</li>
<li>
<p><a href="https://www.runoob.com/mongodb/mongodb-query.html">mongodb菜鸟教程</a></p>
</li>
<li>
<p><a href="http://blog.itpub.net/31556440/viewspace-2672431/">了解 MongoDB 看这一篇就够了</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
MongoDB设置分片键
2020-05-02T00:00:00+00:00
/post/database/2020/05/02/mongodb_part14
<p>本文记录一下如何为MongoDB设置分片键。</p>
<!-- more -->
<p><br />
<br />
<strong>[参看]</strong>:</p>
<ol>
<li>
<p><a href="https://www.mongodb.com/">mongodb官网</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/shihao99/article/details/82380882">mongodb集合设置分片键</a></p>
</li>
<li>
<p><a href="https://www.mongodb.com/docs/manual/reference/command/shardCollection/">shardCollection</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
MongoDB介绍(BSON Types)
2020-05-01T00:00:00+00:00
/post/database/2020/05/01/mongodb_part5
<p>本文介绍BSON类型(types)相关内容。</p>
<!-- more -->
<h2 id="1-bson-types">1. BSON Types</h2>
<p><a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-BSON">BSON</a>是一种二进制序列化格式,MongoDB使用该格式来存储documents和远程过程调用(RPC).</p>
<p>每一种BSON类型都有整数(integer)和字符串(string)两种标识,如下表所示:</p>
<pre>
Type Number Alias Notes
--------------------------------------------------------------------------------------------
Double 1 "double"
String 2 "string"
Object 3 "object"
Array 4 "array"
Binary data 5 "binData"
Undefined 6 "undefined" Deprecated
ObjectId 7 "objectId"
Boolean 8 "bool"
Date 9 "date"
Null 10 "null"
Regular Expression 11 "regex"
DBPointer 12 "dbPointer" Deprecated
JavaScript 13 "javascript"
Symbol 14 "symbol" Deprecated
JavaScript code with scope 15 "javascriptWithScope" Deprecated in MongoDB 4.4
32-bit integer 16 "int"
Timestamp 17 "timestamp"
64-bit integer 18 "long"
Decimal128 19 "decimal" New in version 3.4
Min key -1 "minKey"
Max key 127 "maxKey"
</pre>
<ul>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/operator/query/type/#mongodb-query-op.-type">$type</a>操作符支持使用这些值来根据BSON Type查询字段。同时<code class="language-plaintext highlighter-rouge">$type</code>也支持<code class="language-plaintext highlighter-rouge">number</code>别名,用于匹配integer、decimal、double、以及long类型</p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/operator/aggregation/type/#mongodb-expression-exp.-type">$type</a>聚合操作符返回其参数的BSON类型</p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/operator/aggregation/isNumber/#mongodb-expression-exp.-isNumber">$isNumber</a>聚合操作符在其参数为BSON integer、decimal、double、long类型时返回<code class="language-plaintext highlighter-rouge">true</code>(New in version 4.4).</p>
</li>
</ul>
<p>为了确定一个字段的类型,请参看<a href="https://docs.mongodb.com/manual/core/shell-types/#std-label-check-types-in-shell"> Check Types in the mongo Shell</a></p>
<p>假如你将BSON转换为JSON,请参看<a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/">Extended JSON</a>.</p>
<p>下面我们会描述特定的BSON类型的一些特殊考量。</p>
<h3 id="11-objectid">1.1 ObjectId</h3>
<p>ObjectIds are small, likely unique, fast to generate, and ordered。ObjectId的值占用12字节长度,组成如下:</p>
<ul>
<li>
<p>4字节时间戳,代表了ObjectId的创建时间,单位为Unix epoch到当前的秒数。</p>
</li>
<li>
<p>由每个进程所产生的5字节随机值。该随机值对于该机器(machine)和进程(process)来说唯一</p>
</li>
<li>
<p>3字节增量计数,初始化为一个随机值。</p>
</li>
</ul>
<p>BSON格式本身是小端格式(little-endian),timestamp以及counter value是大端格式(big-endian)。</p>
<p>在MongoDB中,存储在collection中的每一个document需要一个唯一的<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-_id">_id</a>字段来作为primary key。假如在执行document插入时省略了<code class="language-plaintext highlighter-rouge">_id</code>字段,那么MongoDB driver会自动的为<code class="language-plaintext highlighter-rouge">_id</code>字段产生一个ObjectId。</p>
<p>当通过update来进行document插入时,如果设置<a href="https://docs.mongodb.com/manual/reference/method/db.collection.update/#std-label-upsert-parameter">upsert:true</a>,则同样会自动产生ObjectId。</p>
<p>MongoDB客户端应该为<code class="language-plaintext highlighter-rouge">_id</code>字段添加一个唯一的ObjectId。此外,使用ObjectId来为<code class="language-plaintext highlighter-rouge">_id</code>赋值还具有如下好处:</p>
<ul>
<li>
<p>在<a href="https://docs.mongodb.com/mongodb-shell/#mongodb-binary-bin.mongosh">mongosh</a>中,你可以使用<a href="https://docs.mongodb.com/manual/reference/method/ObjectId.getTimestamp/#mongodb-method-ObjectId.getTimestamp">ObjectId.getTimestamp()</a>方法来获取ObjectId的创建时间</p>
</li>
<li>
<p>当<code class="language-plaintext highlighter-rouge">_id</code>字段存放的值为ObjectId时,如果对<code class="language-plaintext highlighter-rouge">_id</code>字段排序时,等价于按创建时间进行排序。</p>
</li>
</ul>
<blockquote>
<p>注:由于ObjectId的值应该随时间而增长,但它们却并没有必要保持是单调的。原因如下</p>
<p>1) 由于创建ObjectId时时间的分辨率是秒,因此在同一秒内的ObjectId并不能保证顺序</p>
<p>2) ObjectId可能是由客户端产生的,不同的客户端也可能有不同的系统时钟</p>
</blockquote>
<h3 id="12-string">1.2 String</h3>
<p>BSON字符串是采用UTF-8编码。通常来说,每一种编程语言的驱动都会在序列化与反序列化BSON时,将对应语言的字符串编码转换成UTF-8编码格式。这使得采用BSON字符串可以很容易的存储大部分的国际化字符。另外,MongoDB的正则表达式查询(<a href="https://docs.mongodb.com/manual/reference/operator/query/regex/#mongodb-query-op.-regex">$regex queries</a>)也支持UTF-8格式的正则字串。</p>
<p>给定UTF-8字符集的字串,使用<a href="https://docs.mongodb.com/manual/reference/method/cursor.sort/#mongodb-method-cursor.sort">sort()</a>来进行排序,通常是合理的、正确的。然而,由于内部sort()使用C++的strcmp api,因此在排序时对于某一些字符可能有误。</p>
<h3 id="13-timestamps">1.3 Timestamps</h3>
<p>BSON有一种特别的timestamp类型供MongoDB内部使用,并且与通常的<a href="https://docs.mongodb.com/manual/reference/bson-types/#std-label-document-bson-type-date">Date类型</a>没有关系。该内部的时间戳是一个64位的值:</p>
<ul>
<li>
<p>数字的高32位是一个time_t类型的值(seconds since the Unix epoch)</p>
</li>
<li>
<p>数字的低32位是在一秒内操作的增量值(incrementing ordinal)</p>
</li>
</ul>
<p>由于BSON采用小端格式(little-endian),因此首先存放的是低有效位。<a href="https://docs.mongodb.com/manual/reference/program/mongod/#mongodb-binary-bin.mongod">mongod实例</a>在所有平台上,不管是大端还是小端,在比较两个内部timestamps时,总是先比较time_t部分,然后再是ordinal部分。</p>
<p>在单个<a href="https://docs.mongodb.com/manual/reference/program/mongod/#mongodb-binary-bin.mongod">mongod实例</a>内,timestamp值总是唯一的。</p>
<p>在执行复制(replication)操作时,<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-oplog">oplog</a>有一个<code class="language-plaintext highlighter-rouge">ts</code>字段。该字段的值反映了操作时间(operation time),其也是一个BSON timestamp类型的值。</p>
<blockquote>
<p>Note:</p>
<p>BSON timestamp是MongoDB内部使用的。在大多数情况下,进行应用程序开发时,我们都是使用BSON date类型。参看<a href="https://docs.mongodb.com/manual/reference/bson-types/#std-label-document-bson-type-date">Date</a>以了解更多信息。</p>
</blockquote>
<p>当插入一个document到collection时,如果top-level fields中有空时间戳值(empty timestamp values)的话,MongoDB会将对应的空时间戳值替换为当前时间,除非遇到如下场景。假如<code class="language-plaintext highlighter-rouge">_id</code>字段的值也是一个empty timestamp的话,其总是会被原模原样的被插入进去。</p>
<p>示例:插入一个empty timestamp值的document到collection中</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.test.insertOne( { ts: new Timestamp() } );</code></pre></figure>
<p>然后执行<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.test.find()</a>方法,返回的查询结果 类似如下:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{ "_id" : ObjectId("542c2b97bac0595474108b48"), "ts" : Timestamp(1412180887, 1) }</code></pre></figure>
<p>从上面的结果我们看到,服务器已经将<code class="language-plaintext highlighter-rouge">ts</code>替换成了该document插入时的时间戳值。</p>
<h3 id="14-date">1.4 Date</h3>
<p>BSON Date是一个64位的整数,用于表示从Unix epoch(1970年1月1日)到当前的毫秒(milliseconds)数。这样一个64bit的整数可以表示的时间范围有290000000年。</p>
<p>BSON Date是signed类型,负值表示1970年以前的日期。</p>
<p>1) 示例1</p>
<p>在<a href="https://docs.mongodb.com/mongodb-shell/#mongodb-binary-bin.mongosh">mongosh</a>中使用new Date()构造函数来创建一个Date对象</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">var mydate1 = new Date()</code></pre></figure>
<p>2) 示例2</p>
<p>在<a href="https://docs.mongodb.com/mongodb-shell/#mongodb-binary-bin.mongosh">mongosh</a>中使用ISODate()构造函数创建Date对象:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">var mydate2 = ISODate()</code></pre></figure>
<p>3) 示例3</p>
<p>以字符串形式返回Date对象:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">mydate1.toString()</code></pre></figure>
<p>4) 示例4</p>
<p>返回Date对象的month部分。month是从0开始的,因此January的month值为0:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">mydate1.getMonth()</code></pre></figure>
<h2 id="2-comparisonsort-order">2. Comparison/Sort Order</h2>
<p>当比较不同<a href="https://docs.mongodb.com/manual/reference/bson-types/#std-label-bson-types">BSON types</a>之间的值时,MongoDB按如下的顺序来进行比较(从低到高):</p>
<pre>
MinKey (internal type)
Null
Numbers (ints, longs, doubles, decimals)
Symbol, String
Object
Array
BinData
ObjectId
Boolean
Date
Timestamp
Regular Expression
MaxKey (internal type)
</pre>
<h3 id="21-numeric-types">2.1 Numeric Types</h3>
<p>在进行比较操作时,MongoDB会将一些类型做同等看待。例如,数字类型(numeric types)在进行比较之前会进行转换。</p>
<h3 id="22-strings">2.2 Strings</h3>
<p>1) <strong>Binary Comparison</strong></p>
<p>默认情况下,在进行字符串比较时,MongoDB会使用简单的二进制比较。</p>
<p>2) <strong>Collation</strong></p>
<blockquote>
<p>New in version 3.4</p>
</blockquote>
<p><a href="https://docs.mongodb.com/manual/reference/collation/">Collation</a>允许用户在进行字符串比较时,使用语言特定的规则。比如,字母大小写(lettercase)以及音调(accent)标识。</p>
<p>校正手册有如下的一些语法:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{
locale: <string>,
caseLevel: <boolean>,
caseFirst: <string>,
strength: <int>,
numericOrdering: <boolean>,
alternate: <string>,
maxVariable: <string>,
backwards: <boolean>
}</code></pre></figure>
<p>当指定collation时,<code class="language-plaintext highlighter-rouge">locale</code>字段是强制性必须有的,所有其他字段是可选的。对这些字段的描述,请参看<a href="https://docs.mongodb.com/manual/reference/collation/#std-label-collation-document-fields">Collation Document</a>.</p>
<p>假如并未给collection指定collation,或者并未给对应的操作(operation)指定collation,那么MongoDB会使用简单的二进制比较法。</p>
<h3 id="23-arrays">2.3 Arrays</h3>
<p>对于数组(arrays)来说,less-than比较或者升序(ascending sort)会比较数组元素的最小值;greater-than比较或降序(decending sort)会比较数组元素的最大值。假如某个字段的值是一个只拥有一个元素的数组(例如<code class="language-plaintext highlighter-rouge">[1]</code>),当其与非数组字段(例如<code class="language-plaintext highlighter-rouge">2</code>)进行比较时,则是将1与2进行比较。</p>
<blockquote>
<p>A comparison of an empty array (e.g. [ ]) treats the empty array as less than null or a missing field.</p>
</blockquote>
<h3 id="24-objects">2.4 Objects</h3>
<p>MongoDB在比较BSON对象时,使用如下的顺序:</p>
<ol>
<li>
<p>递归地比较按出现顺序在BSON对象中的key-value对</p>
</li>
<li>
<p>比较字段的类型。对于字段类型,MongoDB使用如下的比较顺序(从低到高):</p>
</li>
</ol>
<pre>
a. MinKey (internal type)
b. Null
c. Numbers (ints, longs, doubles, decimals)
d. Symbol, String
e. Object
f. Array
g. BinData
h. ObjectId
i. Boolean
j. Date
k. Timestamp
l. Regular Expression
m. MaxKey (internal type)
</pre>
<ol>
<li>
<p>假如字段类型相同,比较<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-field-names">key field names</a></p>
</li>
<li>
<p>假如key field names相同,比较field values</p>
</li>
<li>
<p>假如field values也相同,比较下一个key/value对(返回到第1步)。An object without further pairs is less than an object with further pairs.</p>
</li>
</ol>
<h3 id="25-dates-and-timestamps">2.5 Dates and Timestamps</h3>
<p>Date对象排在Timestamp对象之前。</p>
<h3 id="26-non-existent-fields">2.6 Non-existent Fields</h3>
<p>当比较一个不存在的字段时,会将其看作是一个empty BSON object。因此,如果比较<code class="language-plaintext highlighter-rouge">{}</code>与<code class="language-plaintext highlighter-rouge">{a: null}</code>这两个document中的<code class="language-plaintext highlighter-rouge">a</code>字段时,则会认为它们是相等的。</p>
<h3 id="27-bindata">2.7 BinData</h3>
<p>MongoDB采用如下的顺序来对BinData进行排序:</p>
<ol>
<li>First, the length or size of the data.</li>
<li>Then, by the BSON one-byte subtype.</li>
<li>Finally, by the data, performing a byte-by-byte comparison.</li>
</ol>
<h2 id="3-mongodb-extended-jsonv2">3. MongoDB Extended JSON(v2)</h2>
<p>JSON只能够表示<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-BSON">BSON</a>类型的一个子集。为了保持类型信息(type information),MongoDB针对JSON格式添加了如下扩展。</p>
<ul>
<li>Canonical Mode</li>
</ul>
<p>一种字符串格式,会重点保留类型信息,但是可能会降低可读性(readability)和交互性。即,从<code class="language-plaintext highlighter-rouge">canonical</code>模式转变成BSON可以保留类型信息(极少数特殊情况除外)。</p>
<ul>
<li>Relaxed Mode</li>
</ul>
<p>一种字符串格式,着重强调可读性(readability)与交互性(interoperability),但可能会丧失类型信息。即,从relaxed格式转换成BSON格式可能会丢失类型信息。</p>
<p>两种格式都符合<a href="http://www.json.org/">JSON RFC</a>,并且可以被众多MongoDB drivers及tools所解析。</p>
<h3 id="31-mongodb-extended-json-v2-usage">3.1 MongoDB Extended JSON v2 Usage</h3>
<p>1) <strong>Drivers</strong></p>
<p>如下的drivers使用Extended JSON v2.0:</p>
<pre>
C Java PHPC
C++ Node Python
Go Perl Scala
</pre>
<p>2) <strong>MongoDB Database Tools</strong></p>
<p>从MongoDB 2.2版本开始:</p>
<ul>
<li>
<p>bsondump: 使用Extended JSON v2.0(Canonical mode) format</p>
</li>
<li>
<p>mongodump: 对于元数据使用Extended JSON v2.0(Canonical mode)格式。要求<a href="https://docs.mongodb.com/database-tools/mongorestore/#mongodb-binary-bin.mongorestore">mongorestore</a>的版本为4.2或更高,并支持Extended JSON v2.0(Canonical mode 或 Relax mode)格式。</p>
</li>
</ul>
<blockquote>
<p>注:通常来说,mongodump与mongorestore的版本要相匹配。</p>
</blockquote>
<ul>
<li>
<p>mongoexport: 默认情况下,以Extended JSON v2.0(Relax mode)方式输出。如果要以Extended JSON v2.0(canonical mode)方式输出,请使用<code class="language-plaintext highlighter-rouge">--jsonFormat</code>指定</p>
</li>
<li>
<p>mongoimport: 默认情况下导入的数据应该为Extended JSON v2.0(Relaxed或Canonical模式)格式。如果要识别Extended JSON v1.0格式的话,我们可以通过<code class="language-plaintext highlighter-rouge">--legacy</code>选项。</p>
</li>
</ul>
<blockquote>
<p>注:通常来说,mongoexport与mongoimport的版本要相匹配。</p>
</blockquote>
<h3 id="32-bson-data-types-and-associated-representations">3.2 BSON Data Types and Associated Representations</h3>
<p>如下列出了一些常用的BSON数据类型,以及Canonical模式、Relaxed模式下用Extended JSON如何表示。</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-Array">Array</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-Decimal128">Decimal128</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-Int32">Int32</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-MinKey">MinKey</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-Binary">Binary</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-Document">Document</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-Int64">Int64</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-ObjectId">ObjectId</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-Date">Date</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-Double">Double</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-MaxKey">MaxKey</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-Regular-Expression">Regular Expression</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/#mongodb-bsontype-Timestamp">Timestamp</a></li>
</ul>
<p>完整的扩展JSON列表,请参看<a href="https://github.com/mongodb/specifications/blob/master/source/extended-json.rst#conversion-table">extended-json</a></p>
<p>1) <strong>Array</strong></p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">Canonical Relaxed
-------------------------------------------------------------------------------------
[<elements>] <same as Canonical></code></pre></figure>
<p>数组元素如下:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge"><elements></code>
<ul>
<li>数组元素使用Extended JSON</li>
<li>如果要指定一个空数组,使用[]</li>
</ul>
</li>
</ul>
<p>2) <strong>Binary</strong></p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">Canonical Relaxed
-------------------------------------------------------------------------------------
{ "$binary": <Same as Canonical>
{
"base64": "<payload>",
"subType": "<t>"
}
}</code></pre></figure>
<p>相关说明如下:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">"<payload>"</code>: Base64编码payload字符串</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge"><ts></code>: 对应BSON binary子类型的16进制字符串表示。参看<a href="http://bsonspec.org/spec.html">bson spec</a>以了解subtypes.</p>
</li>
</ul>
<p>3) <strong>Date</strong></p>
<p>For dates between years 1970 and 9999, inclusive:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">Canonical Relaxed
-------------------------------------------------------------------------------------
{"$date": {"$numberLong": "<millis>"}} {"$date": "<ISO-8601 Date/Time Format>"}</code></pre></figure>
<p>For dates before year 1970 or after year 9999:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">Canonical Relaxed
-------------------------------------------------------------------------------------
{"$date": {"$numberLong": "<millis>"}} <Same as Canonical></code></pre></figure>
<h3 id="33-examples">3.3 Examples</h3>
<figure class="highlight"><pre><code class="language-string" data-lang="string">Example Field Name Canonical Format Relaxed Format
-------------------------------------------------------------------------------------------------------------------
"_id:" {"$oid":"5d505646cf6d4fe581014ab2"} {"$oid":"5d505646cf6d4fe581014ab2"}
"arrayField": ["hello",{"$numberInt":"10"}] ["hello",10]
"dateField": {"$date":{"$numberLong":"1565546054692"}} {"$date":"2019-08-11T17:54:14.692Z"}
"dateBefore1970": {"$date":{"$numberLong":"-1577923200000"}} {"$date":{"$numberLong":"-1577923200000"}}
"decimal128Field": {"$numberDecimal":"10.99"} {"$numberDecimal":"10.99"}</code></pre></figure>
<p><br />
<br />
<strong>[参看]</strong>:</p>
<ol>
<li>
<p><a href="https://www.mongodb.com/">mongodb官网</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/">mongodb method</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/command/find/">mongodb command</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/weixin_43031412/article/details/97632341">MongoDB Projection</a></p>
</li>
<li>
<p><a href="https://www.runoob.com/mongodb/mongodb-query.html">mongodb菜鸟教程</a></p>
</li>
<li>
<p><a href="http://blog.itpub.net/31556440/viewspace-2672431/">了解 MongoDB 看这一篇就够了</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
MongoDB介绍(Documents)
2020-05-01T00:00:00+00:00
/post/database/2020/05/01/mongodb_part4
<p>本章介绍以下MongoDB中的document相关概念。</p>
<!-- more -->
<p>MongoDB将数据记录作为BSON documents来进行存储。BSON是<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-JSON">JSON</a> documents的一种二进制表示方式,但是其具有比JSON更多的数据类型(data types)。参看<a href="http://bsonspec.org/">bsonspec.org</a>与<a href="https://docs.mongodb.com/manual/reference/bson-types/">BSON Types</a>.</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/db/mongodb/crud-annotated-document.bakedsvg.svg" alt="mongodb-bson" /></p>
<h2 id="1-document-structure">1. Document Structure</h2>
<p>MongoDB文档是由field-value对所组成,并且有如下结构:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{
field1: value1,
field2: value2,
field3: value3,
...
fieldN: valueN
}</code></pre></figure>
<p>其中value字段可以是任何的BSON <a href="https://docs.mongodb.com/manual/reference/bson-types/">data types</a>,包括其他的documents、arrays以及arrays of documents。例如,下面的document包含多种类型的values:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">var mydoc = {
_id: ObjectId("5099803df3f4948bd2f98391"),
name: { first: "Alan", last: "Turing" },
birth: new Date('Jun 23, 1912'),
death: new Date('Jun 07, 1954'),
contribs: [ "Turing machine", "Turing test", "Turingery" ],
views : NumberLong(1250000)
}</code></pre></figure>
<p>上面的字段含有如下数据类型:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">_id</code>维持着一个<a href="https://docs.mongodb.com/manual/reference/bson-types/#std-label-objectid">ObjectId</a></p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">name</code>维持着一个内嵌的document,该document含有first及last两个字段;</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">birth</code>及<code class="language-plaintext highlighter-rouge">death</code>维持着日期类型的值</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">contribs</code>维持着字符串数组</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">views</code>维持着一个NumberLong类型的值</p>
</li>
</ul>
<p>1) <strong>FieldNames</strong></p>
<p>字段的名称是字符串类型。</p>
<p>documents的字段名称具有如下限制:</p>
<ul>
<li>
<p>字段名称<code class="language-plaintext highlighter-rouge">_id</code>保留,用作主键(primary key)。其值在整个集合中唯一,并且是不可修改的,可以是除数组之外的任何类型。假如<code class="language-plaintext highlighter-rouge">_id</code>包含子字段(subfields)的话,则子字段的名称不能以<code class="language-plaintext highlighter-rouge">$</code>符号开头</p>
</li>
<li>
<p>字段的名称不能包含<code class="language-plaintext highlighter-rouge">null</code>字符</p>
</li>
<li>
<p>服务器允许存储的字段名称包含<code class="language-plaintext highlighter-rouge">.</code>(dot)和美元符号($)</p>
</li>
<li>
<p>MongoDB 5.0对字段名称中的<code class="language-plaintext highlighter-rouge">$</code>及<code class="language-plaintext highlighter-rouge">.</code>符号提供了更高级的支持。同时也有一些限制,请参看<a href="https://docs.mongodb.com/manual/core/dot-dollar-considerations/#std-label-crud-concepts-dot-dollar-considerations">Field Name Considerations</a>以了解更多细节。</p>
</li>
</ul>
<p>BSON文档可以有多个字段含有相同的名称。然而大部分<a href="https://docs.mongodb.com/drivers/">MongoDB接口</a>的内部数据结构实现都不支持有相同的字段名称。假如你需要操作含有相同字段名称的documents,请参看<a href="https://docs.mongodb.com/drivers/">driver documentation</a>。</p>
<p>有一些由MongoDB内部进程所创建的documents可能含有相同名称的字段,但是并没有相关的方法可以向用户documents中添加重复的字段。</p>
<p>2) <strong>Field Value Limit</strong></p>
<p>从MongoDB 2.6到<a href="https://docs.mongodb.com/manual/reference/command/setFeatureCompatibilityVersion/#std-label-view-fcv">featureCompatibilityVersion (fCV)</a>被设置为<code class="language-plaintext highlighter-rouge">4.0</code>的版本:针对<a href="https://docs.mongodb.com/manual/indexes/">indexed collections</a>,索引字段的值的长度有<a href="https://docs.mongodb.com/manual/reference/limits/#mongodb-limit-Index-Key-Limit">Maximum Index Key Length</a>限制。请参看<a href="https://docs.mongodb.com/manual/reference/limits/#mongodb-limit-Index-Key-Limit">Maximum Index Key Length</a>以了解更多细节。</p>
<h3 id="11-dot-notation">1.1 Dot Notation</h3>
<p>MongoDB使用<code class="language-plaintext highlighter-rouge">.</code>符号来访问数组元素和内嵌的document字段。</p>
<p>1) <strong>Arrays</strong></p>
<p>可以通过<code class="language-plaintext highlighter-rouge">.</code>符号来设置或访问下标从0开始的数组元素。具体方式为:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">"<array>.<index>"</code></pre></figure>
<p>其中<code class="language-plaintext highlighter-rouge"><array></code>为数组名,<code class="language-plaintext highlighter-rouge"><index></code>为从0开始的下标。另外注意外层的<code class="language-plaintext highlighter-rouge">引号</code>。</p>
<p>下面的例子中,含有一个字段为contribs的document:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{
...
contribs: [ "Turing machine", "Turing test", "Turingery" ],
...
}</code></pre></figure>
<p>如果要指定<code class="language-plaintext highlighter-rouge">contribs</code>数组的第三个元素,请使用<code class="language-plaintext highlighter-rouge">"contribs.2"</code>。</p>
<p>对于查询数组,请参看:</p>
<ul>
<li>
<p><a href="https://docs.mongodb.com/manual/tutorial/query-arrays/">Query An Array</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/tutorial/query-array-of-documents/">Query an Array of Embedded Documents</a></p>
</li>
</ul>
<blockquote>
<p>See Also:</p>
<p><a href="https://docs.mongodb.com/manual/reference/operator/update/positional-all/#mongodb-update-up.---">$[]</a>针对更新操作的所有位置操作符</p>
<p>[$[<identifier>]](https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#mongodb-update-up.---identifier--)针对更新操作的过滤的位置操作符</identifier></p>
<p><a href="https://docs.mongodb.com/manual/reference/operator/update/positional/#mongodb-update-up.-">$</a>针对更新操作的位置操作符</p>
<p><a href="https://docs.mongodb.com/manual/reference/operator/projection/positional/#mongodb-projection-proj.-">$</a>当数组索引位置不确定时的projection操作符</p>
<p><a href="https://docs.mongodb.com/manual/tutorial/query-arrays/#std-label-read-operations-arrays">Query An Array</a>点符号访问数组示例</p>
</blockquote>
<p>2) <strong>Embedded Documents</strong></p>
<p>可以使用<code class="language-plaintext highlighter-rouge">.</code>符号来设置或访问内嵌document的字段。具体方式如下:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">"<embedded document>.<field>"</code></pre></figure>
<blockquote>
<p>注: 外层含有引号</p>
</blockquote>
<p>例如,给定含有下面字段的document:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{
...
name: { first: "Alan", last: "Turing" },
contact: { phone: { type: "cell", number: "111-222-3333" } },
...
}</code></pre></figure>
<ul>
<li>
<p>要设置<code class="language-plaintext highlighter-rouge">name</code>字段的<code class="language-plaintext highlighter-rouge">last</code>分量的值时,可以使用<code class="language-plaintext highlighter-rouge">"name.last"</code></p>
</li>
<li>
<p>要设置<code class="language-plaintext highlighter-rouge">contact</code>字段的电话号码时,可以使用<code class="language-plaintext highlighter-rouge">"contact.phone.number"</code></p>
</li>
</ul>
<p>更多查询内嵌document的示例,请参看:</p>
<ul>
<li>
<p><a href="https://docs.mongodb.com/manual/tutorial/query-embedded-documents/">Query on Embedded/Nested Documents</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/tutorial/query-array-of-documents/">Query an Array of Embedded Documents</a></p>
</li>
</ul>
<h3 id="12-document-limitations">1.2 Document Limitations</h3>
<p>Documents有如下的一些属性:</p>
<p>1) <strong>Document Size Limit</strong></p>
<p>BSON document的最大大小为16M字节。最大大小的限制确保了单个document不会占用太多的内存,在进行传输时也不会消耗太多的带宽。要存储超过最大大小的documents,MongoDB提供了GridFs API。参看<a href="https://docs.mongodb.com/database-tools/mongofiles/#mongodb-binary-bin.mongofiles">mongofiles</a>,以及关于GridFs驱动的相关文档</p>
<p>2) <strong>Document Field Order</strong></p>
<p>与JavaScript对象不同,BSON中的字段是有顺序的。</p>
<p>2.1) Field Order in Queries</p>
<p>对于查询,字段的顺序行为如下:</p>
<ul>
<li>
<p>当进行documents比较时,field的顺序也是有意义的。例如,当比较含有<code class="language-plaintext highlighter-rouge">a</code>、<code class="language-plaintext highlighter-rouge">b</code>两个字段的document时:</p>
<ul>
<li>{a: 1, b: 1}等于{a: 1, b: 1}</li>
<li>{a: 1, b: 1}不等于{b: 1, a: 1}</li>
</ul>
</li>
<li>
<p>在执行高效的查询时,查询引擎可能会在进行查询处理时对字段进行排序。其他场景下,在执行<code class="language-plaintext highlighter-rouge">$project</code>、<code class="language-plaintext highlighter-rouge">$addFields</code>、<code class="language-plaintext highlighter-rouge">$set</code>、<code class="language-plaintext highlighter-rouge">$unset</code>这些projection操作符时也可能会对字段进行排序</p>
<ul>
<li>查询返回的中间结果或最后结果都有可能对字段进行排序</li>
<li>由于有些操作可能对字段进行排序,因此在你使用上面列出的projection操作符时,不应该依赖于查询结果中字段的顺序</li>
</ul>
</li>
</ul>
<p>2.2) Field Order in Write Operations</p>
<p>对于写操作,MongoDB会保留document字段的顺序,除非遇到如下场景:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">_id</code>字段总是作为document的第一个字段</p>
</li>
<li>
<p>包含对字段重命名(<a href="https://docs.mongodb.com/manual/reference/operator/update/rename/#mongodb-update-up.-rename">rename</a>)的更新操作可能会更改document字段的顺序</p>
</li>
</ul>
<p>2.3) The _id Field</p>
<p>在MongoDB中,存放在collection中的每一个document都有一个唯一的<code class="language-plaintext highlighter-rouge">_id</code>字段作为该document的主键(primary key)。假如一个插入的document省略了<code class="language-plaintext highlighter-rouge">_id</code>字段,则MongoDB会自动的为<code class="language-plaintext highlighter-rouge">_id</code>字段产生一个<a href="https://docs.mongodb.com/manual/reference/bson-types/#std-label-objectid">ObjectId</a></p>
<p>这同样适用于<code class="language-plaintext highlighter-rouge">insert:true</code>的update操作。</p>
<p><code class="language-plaintext highlighter-rouge">_id</code>字段具有如下行为和限制:</p>
<ul>
<li>
<p>默认情况下,MongoDB会在创建collection时为<code class="language-plaintext highlighter-rouge">_id</code>字段创建一个唯一索引</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">_id</code>字段总是作为documents的一个字段。假如服务器收到了一个document,其<code class="language-plaintext highlighter-rouge">_id</code>字段不是第一个字段,那么服务器会自动的将该字段移动为第一个字段</p>
</li>
<li>
<p>假如<code class="language-plaintext highlighter-rouge">_id</code>字段的值包含子字段,则字段的fieldName不能以<code class="language-plaintext highlighter-rouge">$</code>符号开头。</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">_id</code>字段的值可以是任何<a href="https://docs.mongodb.com/manual/reference/bson-types/">BSON data type</a>,但是不能为array、regex、或undefined</p>
</li>
</ul>
<blockquote>
<p>警告:为了确保功复制功能运行正常,不要在_id字段存放BSON正则表达式</p>
</blockquote>
<p>如下是存储<code class="language-plaintext highlighter-rouge">_id</code>字段的值时的一些常用选项:</p>
<ul>
<li>
<p>使用一个<a href="https://docs.mongodb.com/manual/reference/bson-types/#std-label-objectid">ObjectId</a></p>
</li>
<li>
<p>假如可行的话,使用一个自然的唯一标识符。这可以节省存储空间,并且可以避免额外的索引。</p>
</li>
<li>
<p>产生一个自增(auto-incrementing)数字</p>
</li>
<li>
<p>通过应用程序代码产生一个UUID。为了更高效的在collection以及<code class="language-plaintext highlighter-rouge">_id</code>索引中存储UUID的值,将UUID作为一个BSON <code class="language-plaintext highlighter-rouge">BinData</code>类型。</p>
</li>
</ul>
<p>采用<code class="language-plaintext highlighter-rouge">BinData</code>类型作为索引的key在如下的条件下可以获得更高的效率:</p>
<ul>
<li>二进制子类型的值在0~7或128~135范围内</li>
<li>
<p>字节数组的长度为:0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 或32.</p>
</li>
<li>使用驱动(driver)所提供的UUID设备来产生UUIDs。需要知道的是,driver所提供的UUID序列化与反序列化可能有不同的实现逻辑,因此可能与其他的driver不完全兼容。参看<a href="https://api.mongodb.com/">driver documentation</a>以了解更多UUID互操作性信息。</li>
</ul>
<blockquote>
<p>Note:</p>
<p>大多数MongoDB驱动客户端都会包含_id字段,并在执行插入操作之前产生一个ObjectId。然而,假如客户端发送一个不带_id字段的document,那么MongoDB将会增加一个_id字段,并为其生成一个ObjectId</p>
</blockquote>
<h3 id="13-other-uses-of-the-document-structure">1.3 Other Uses of the Document Structure</h3>
<p>除了用于定义数据记录(data record),MongoDB还全面使用document structure,包括且不限于:<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter">query filters</a>, <a href="https://docs.mongodb.com/manual/core/document/#std-label-document-update-specification">update specifications documents</a>, <a href="https://docs.mongodb.com/manual/core/document/#std-label-document-index-specification">index specification documents</a></p>
<p>1) <strong>Query Filter Documents</strong></p>
<p>通过指定过滤条件来查询特定的documents以进行读取(read)、update、delete操作。</p>
<p>我们可以使用<code class="language-plaintext highlighter-rouge"><field>:<value></code>表达式来指定相等条件(equality condition)以及<a href="https://docs.mongodb.com/manual/reference/operator/query/">query operator</a>表达式。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{
<field1>: <value1>,
<field2>: { <operator>: <value> },
...
}</code></pre></figure>
<p>其他示例,请参看:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-documents/">Query Documents</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-embedded-documents/">Query on Embedded/Nested Documents</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-arrays/">Query an Array</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/query-array-of-documents/">Query an Array of Embedded Documents</a></li>
</ul>
<p>2) <strong>Update Specification Documents</strong></p>
<p>使用<a href="https://docs.mongodb.com/manual/reference/operator/update/#std-label-update-operators">update operators</a>来更新特定的documents的指定字段。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{
<operator1>: { <field1>: <value1>, ... },
<operator2>: { <field2>: <value2>, ... },
...
}</code></pre></figure>
<p>更多示例,请参看<a href="https://docs.mongodb.com/manual/tutorial/update-documents/#std-label-update-documents-modifiers">Update specifications</a></p>
<p>3) <strong>Index Specification Documents</strong></p>
<p>Index specification documents用于定义索引的字段(field)以及索引类型:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{ <field1>: <type1>, <field2>: <type2>, ... }</code></pre></figure>
<h3 id="14-further-reading">1.4 Further Reading</h3>
<p>关于MongoDB文档模型(document model)的更多信息,请下载<a href="https://www.mongodb.com/modernize?tck=docs_server"> MongoDB Application Modernization Guide</a></p>
<p>可以从中下载到如下资源:</p>
<ul>
<li>
<p>MongoDB数据模型的方法论介绍</p>
</li>
<li>
<p>从RDBMS数据模型迁移到MongoD的最佳实践及考量白皮书(white paper)</p>
</li>
<li>
<p>与RDBMS所对应的MongoDB schema参考</p>
</li>
<li>
<p>Application Modernization scorecard</p>
</li>
</ul>
<p><br />
<br />
<strong>[参看]</strong>:</p>
<ol>
<li>
<p><a href="https://www.mongodb.com/">mongodb官网</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/">mongodb method</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/command/find/">mongodb command</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/weixin_43031412/article/details/97632341">MongoDB Projection</a></p>
</li>
<li>
<p><a href="https://www.runoob.com/mongodb/mongodb-query.html">mongodb菜鸟教程</a></p>
</li>
<li>
<p><a href="http://blog.itpub.net/31556440/viewspace-2672431/">了解 MongoDB 看这一篇就够了</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
MongoDB数据库与集合(2)
2020-05-01T00:00:00+00:00
/post/database/2020/05/01/mongodb_part3
<p>这里我们接着上文,介绍另外一种集合:Time Series Collections。</p>
<blockquote>
<p>Note: New in version 5.0</p>
</blockquote>
<!-- more -->
<h2 id="1-time-series-collections">1. Time Series Collections</h2>
<blockquote>
<p>New in version 5.0</p>
</blockquote>
<p><a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-time-series-collection">Time series collections</a>能够高效的存储时序度量数据。时序数据是任何随时间而变化而收集的数据,并唯一的由一个或多个unchanging参数所标识。通常保持不变(unchanging)的那一部分为数据的<code class="language-plaintext highlighter-rouge">元数据</code>。</p>
<pre>
Example Measurement Metadata
----------------------------------------------------------------------------------
Weather data Temperature Sensor identifier, location
Stock data Stock price Stock ticker, exchange
Website visitors View count URL
</pre>
<p>与其他平常的collection相比较,将时序数据存放在Time Series Collections中可以提高查询效率,减少磁盘占用。</p>
<h6 id="11-procedures">1.1 Procedures</h6>
<p>1) <strong>Create a Time Series Collection</strong></p>
<blockquote>
<p>注:只能在<a href="https://docs.mongodb.com/manual/reference/command/setFeatureCompatibilityVersion/#std-label-view-fcv">featureCompatibilityVersion</a>设置为5.0的系统上创建时序集合</p>
</blockquote>
<p>在插入数据到time series collection之前,必须使用<a href="https://docs.mongodb.com/manual/reference/method/db.createCollection/#mongodb-method-db.createCollection">db.createCollection()</a>方法或<a href="https://docs.mongodb.com/manual/reference/command/create/#mongodb-dbcommand-dbcmd.create">create</a>命令显式创建collection:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.createCollection(
"weather",
{
timeseries: {
timeField: "timestamp",
metaField: "metadata",
granularity: "hours"
}
}
)</code></pre></figure>
<p>当创建time series collection时,指定如下选项:</p>
<pre>
Field Type Description
-----------------------------------------------------------------------------------------------------------------
timeseries.timeField string (Required)指定在每一个时序document中哪一个字段包含date数据。在Time
Series Collection中的documents必须有一个有效的BSON date字段,并
将该字段作为timeField。
timeseries.metaField string (Optional)用于指定时序document中哪一个字段含有metadata。其中metadata
需要唯一的标识serial documents。metadata应该要基本保持不变。
另外,metaField字段的名称不能为_id,也不能与timeField字段同名。该字段的
可以为任意类型。
timeseries.granularity string (Optional)可选值可以为'seconds','minutes',或'hours'。默认情况下,
MongoDB会将granularity设置为'seconds'以应对高频的数据存储。
通过优化数据在时序集合中的存放方式,从而设置相应的granularity值可以提高
性能。通常来说,granularity的值应该与连续插入到集合中的数据在时域跨度上
保持一致。
假如指定了timeseries.metaField字段,考虑在对应的时间跨度内,连续插入的数据
通常具有相同的metaField字段。
假如未指定timeseries.metaField字段,考虑所有插入到集合中的数据的时间跨度。
expireAfterSeconds string (Optional)指定时序集合中的documents的过期时间,在过期后,会自动的删除对应
的documents数据。
</pre>
<p>针对<code class="language-plaintext highlighter-rouge">timeseries</code>还有如下的一些选项:</p>
<ul>
<li>storageEngine</li>
<li>indexOptionDefaults</li>
<li>collation</li>
<li>writeConcern</li>
<li>comment</li>
</ul>
<p>2)<strong>Insert Measurements into a Time Series Collection</strong></p>
<p>插入到时序集合中的每一个document都需要包含一个单独的measurement。如果要插入多个documents的话,执行如下方法:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.weather.insertMany( [
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-18T00:00:00.000Z"),
"temp": 12
},
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-18T04:00:00.000Z"),
"temp": 11
},
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-18T08:00:00.000Z"),
"temp": 11
},
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-18T12:00:00.000Z"),
"temp": 12
},
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-18T16:00:00.000Z"),
"temp": 16
},
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-18T20:00:00.000Z"),
"temp": 15
}, {
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-19T00:00:00.000Z"),
"temp": 13
},
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-19T04:00:00.000Z"),
"temp": 12
},
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-19T08:00:00.000Z"),
"temp": 11
},
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-19T12:00:00.000Z"),
"temp": 12
},
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-19T16:00:00.000Z"),
"temp": 17
},
{
"metadata": { "sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-19T20:00:00.000Z"),
"temp": 12
}
] )</code></pre></figure>
<p>如果只是插入一个document,那么可以使用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertOne/#mongodb-method-db.collection.insertOne">db.collection.insertOne()</a>方法</p>
<p>3) <strong>Query a Time Series Collectionicons</strong></p>
<p>要从时序集合中查询数据的话,可以执行如下命令:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.weather.findOne({
"timestamp": ISODate("2021-05-18T00:00:00.000Z")
})</code></pre></figure>
<p>4) <strong>Run Aggregations on a Time Series Collection</strong></p>
<p>对于一些额外的查询功能,可以使用<a href="https://docs.mongodb.com/manual/aggregation/#std-label-aggregation-framework">聚合框架( aggregation framework)</a>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.weather.aggregate( [
{
$project: {
date: {
$dateToParts: { date: "$timestamp" }
},
temp: 1
}
},
{
$group: {
_id: {
date: {
year: "$date.year",
month: "$date.month",
day: "$date.day"
}
},
avgTmp: { $avg: "$temp" }
}
}
] )</code></pre></figure>
<p>上面的例子中,聚合管道(aggregation pipeline)会将所有的documents按日期进行分组,然后返回当天温度这一meansurements的平均值:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string"> {
"_id" : {
"date" : {
"year" : 2021,
"month" : 5,
"day" : 18
}
},
"avgTmp" : 12.714285714285714
}
{
"_id" : {
"date" : {
"year" : 2021,
"month" : 5,
"day" : 19
}
},
"avgTmp" : 13
}</code></pre></figure>
<p>5) <strong>Check if a Collection is of Type Time Series</strong></p>
<p>执行如下命令检查一个Collection是否为时序集合:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.runCommand( { listCollections: 1.0 } )</code></pre></figure>
<p>假如为time series collection的话,其返回如下结果:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{
cursor: {
id: <number>,
ns: 'test.$cmd.listCollections',
firstBatch: [
{
name: <string>,
type: 'timeseries',
options: {
expireAfterSeconds: <number>,
timeseries: { ... }
},
...
},
...
]
}
}</code></pre></figure>
<h6 id="12-behavior">1.2 Behavior</h6>
<p>时序集合与普通的集合类似,可以用平常的方法来向时序集合中插入数据,也可以从时序集合中查询数据。MongoDB将时序集合(time series collection)当作内部普通集合的一个视图(writable non-materialized views),只不过是这个内部普通集合的数据存储方式进行了优化。</p>
<p>当查询时序集合时,每次查询一个document。在时序集合上做查询可以充分的利用内部优化的存储格式,并能更快的返回查询结果。</p>
<p>1) <strong>Index</strong></p>
<p>时序集合在实现上是采用优化过的collection,可以降低磁盘占用并提高查询速度。Time series collections会自动的对数据进行排序,并按时间建立索引。针对<code class="language-plaintext highlighter-rouge">时序集合</code>其内部的索引,我们并不能够使用<a href="https://docs.mongodb.com/manual/reference/command/listIndexes/#mongodb-dbcommand-dbcmd.listIndexes"> listIndexes</a>命令来获取。</p>
<blockquote>
<p>注:要提高查询效率,我们可以手动的在metaField以及timeField上增加二级索引。</p>
</blockquote>
<p>2) <strong>Default Compression Algorithm</strong></p>
<p>在创建collection时,如果没有指定<code class="language-plaintext highlighter-rouge">storageEngine</code>的话,则默认采用<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-zstd">zstd</a>压缩算法。对于Time Series Collection来说,其会忽略该默认的压缩算法,而采用<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-snappy">snappy</a>压缩算法。下面的例子将<code class="language-plaintext highlighter-rouge">weather</code>这个集合的压缩算法更改为<code class="language-plaintext highlighter-rouge">snappy</code>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.createCollection(
"weather",
{
timeseries: {
timeField: "timestamp"
},
storageEngine: {
wiredTiger: {
configString: "block_compressor=snappy"
}
}
}
)</code></pre></figure>
<p>其他有效的block_compressor还有:</p>
<ul>
<li>snappy</li>
<li>zlib</li>
<li>zstd (default)</li>
<li>none</li>
</ul>
<h2 id="2-时序集合的一些限制">2. 时序集合的一些限制</h2>
<p>1) <strong>Constraints</strong></p>
<p>所要度量(meansurements)的document最大size为4MB。</p>
<p>2) <strong>Updates and Deletes</strong></p>
<p>从MongoDB 5.05版本开始,我们可以执行删除(delete)与更新(updates)操作。</p>
<p>删除命令必须满足如下要求:</p>
<ul>
<li>
<p>查询条件只能够匹配metaField字段的值</p>
</li>
<li>
<p>删除命令不能限制所删除的documents的数量。在执行删除命令时,必须设置为<code class="language-plaintext highlighter-rouge">justOne: false</code>参数,或者调用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.deleteMany/#mongodb-method-db.collection.deleteMany">deleteMany()</a>方法。</p>
</li>
</ul>
<p>更新命令必须满足如下要求:</p>
<ul>
<li>
<p>查询只能够匹配metaField字段的值</p>
</li>
<li>
<p>更新命令只能够修改metaField字段的值</p>
</li>
</ul>
<h2 id="3-为时序集合创建自动删除功能ttl">3. 为时序集合创建自动删除功能(TTL)</h2>
<h2 id="4-设置时序集合的粒度granularity">4. 设置时序集合的粒度(Granularity)</h2>
<h2 id="5-在metafield与timefield上创建二级索引">5. 在metaField与timeField上创建二级索引</h2>
<h2 id="6-将数据迁移到时序集合">6. 将数据迁移到时序集合</h2>
<h2 id="7-在时序数据上构建materialized-views">7. 在时序数据上构建Materialized Views</h2>
<p><br />
<br />
<strong>[参看]</strong>:</p>
<ol>
<li>
<p><a href="https://www.mongodb.com/">mongodb官网</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/">mongodb method</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/command/find/">mongodb command</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/weixin_43031412/article/details/97632341">MongoDB Projection</a></p>
</li>
<li>
<p><a href="https://www.runoob.com/mongodb/mongodb-query.html">mongodb菜鸟教程</a></p>
</li>
<li>
<p><a href="http://blog.itpub.net/31556440/viewspace-2672431/">了解 MongoDB 看这一篇就够了</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
MongoDB数据库与集合
2020-05-01T00:00:00+00:00
/post/database/2020/05/01/mongodb_part2
<p>本章介绍以下MongoDB数据库与collections相关内容,在此做个记录。</p>
<!-- more -->
<h2 id="1-databases-and-collections">1. Databases and Collections</h2>
<p>1) <strong>Overview</strong></p>
<p>MongoDB将数据记录作为documents方式来进行存储(特别是<a href="https://docs.mongodb.com/manual/core/document/#std-label-bson-document-format">BSON documents</a>),集中起来保存在一个<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-collection">collections</a>中。一个数据库可以一个或多个documents collections。</p>
<p>2) <strong>Databases</strong></p>
<p>在MongoDB中,database可以保存一个或多个documents集合。在<a href="https://docs.mongodb.com/mongodb-shell/#mongodb-binary-bin.mongosh">mongosh</a>中,要选择一个数据库的话,可以使用<code class="language-plaintext highlighter-rouge">use <db></code>语句,参看如下示例:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> use myDB</code></pre></figure>
<ul>
<li>Create a Database</li>
</ul>
<p>假如一个数据库不存在的话,则当你第一次存入数据时,MongoDB就会自动创建。因此,你可以switch到一个不存在的database,然后再mongosh中执行如下下命令:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> use myNewDB
>>> db.myNewCollection1.insertOne( { x: 1 } )</code></pre></figure>
<p>上面的insertOne()操作会同时创建<code class="language-plaintext highlighter-rouge">myNewDB</code>数据库与<code class="language-plaintext highlighter-rouge">myNewCollection1</code>集合(假如相应数据库和集合不存在的话)。请确保database与collection的名称遵循MongoDB <a href="https://docs.mongodb.com/manual/reference/limits/#std-label-restrictions-on-db-names">Naming Restrictions</a>。</p>
<p>3) <strong>Collections</strong></p>
<p>MongoDB会将documents存放再collections中。collections与关系型数据库中的tables类似。</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/db/mongodb/crud-annotated-collection.bakedsvg.svg" alt="mongodb-documents" /></p>
<ul>
<li>Create a Collection</li>
</ul>
<p>假如一个collection不存在,MongoDB会在你第一次存入数据时创建该collection:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.myNewCollection2.insertOne( { x: 1 } )
db.myNewCollection3.createIndex( { y: 1 } )</code></pre></figure>
<p>上面insertOne()与createIndex()操作都会创建对应的collection(假如不存在的话)。请确保collection名称符合MongoDB <a href="https://docs.mongodb.com/manual/reference/limits/#std-label-restrictions-on-db-names">Naming Restrictions</a>。</p>
<ul>
<li>Explicit Creation</li>
</ul>
<p>MongoDB提供了db.createCollection()方法来显式创建一个collection,该方法有许多选项,比如设置maximum size或者documentation校验规则。假如你不指定这些选项的话,你不必显式的创建collection,因为MongoDB会在第一次存入数据时自动帮你创建。</p>
<p>要修改collection选项的话,请参看<a href="https://docs.mongodb.com/manual/reference/command/collMod/#mongodb-dbcommand-dbcmd.collMod">collMod</a></p>
<ul>
<li>Document Validation</li>
</ul>
<blockquote>
<p>New in version 3.2.</p>
</blockquote>
<p>默认情况下,collection并不要求其documents都具有相同的schema。比如,单个collection中的documents并不需要含有相同的fields,documents间相同field的数据类型也可以不同。</p>
<p>从MongoDB 3.2版本开始,在对collection进行更新与插入操作时可以强制进行<a href="https://docs.mongodb.com/manual/core/schema-validation/">document validation rules</a>校验。想要了解更多细节,请参看<a href="https://docs.mongodb.com/manual/core/schema-validation/">Schema Validation</a>。</p>
<ul>
<li>Modifying Document Structure</li>
</ul>
<p>如果要改变collection中documents的结构,比如添加新的fields、移除已存在的fields、修改field值为新类型、更新documents为一个新的结构。</p>
<ul>
<li>Unique Identifiers</li>
</ul>
<blockquote>
<p>New in version 3.6
Note: featureCompatibilityVersion 在3.6及之后版本中必须被设置</p>
</blockquote>
<p>collections会被指定一个不可变的UUID值。collection的UUID在replica set所有成员间保持一致。</p>
<p>要获取一个collection的UUID,可以执行<code class="language-plaintext highlighter-rouge">listCollections</code>命令或者db.getCollectionInfos(方法。</p>
<h3 id="11-views">1.1 Views</h3>
<p>MongoDB视图是一个可查询的object,其内容由<a href="https://docs.mongodb.com/manual/core/aggregation-pipeline/#std-label-aggregation-pipeline">aggregation pipeline</a>在其他collections或views上生成。MongoDB并不会持久化视图内容到硬盘上。视图的内容会在客户端执行查询时根据需要进行计算。MongoDB可以控制客户端是否有查询视图的权限。MongoDB禁止在视图上执行写操作。</p>
<p>例如,通过视图你可以实现:</p>
<ul>
<li>
<p>在雇员信息集中创建视图,并排除具体的雇员隐私信息。这样应用程序就可以通过查询视图,但获取不到雇员的隐私信息。</p>
</li>
<li>
<p>在摄像头数据集上创建视图,并添加一些经过计算的fields及metrics。应用程序可以使用简单的查询操作来查询数据</p>
</li>
<li>
<p>联合<code class="language-plaintext highlighter-rouge">库存</code>(inventory)与<code class="language-plaintext highlighter-rouge">历史订单</code>(order history)两个collection来创建一个视图。应用程序可以查询joined的数据,而不需要了解底层复杂的pipeline。</p>
</li>
</ul>
<p>当客户端执行<a href="https://docs.mongodb.com/manual/core/views/#std-label-views-supported-operations">视图查询</a>时,MongoDB会将客户端查询追加到底层的pipeline,然后将相应的结果返回给客户端。MongoDB也可能会返回的combined pipeline上运用<a href="https://docs.mongodb.com/manual/core/aggregation-pipeline-optimization/">aggregation pipeline optimizations</a>。</p>
<h6 id="111-create-view">1.1.1 Create View</h6>
<p>要创建或定义一个视图,可采用如下方法:</p>
<ul>
<li>使用db.createCollection()方法或者create命令</li>
</ul>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.createCollection(
"<viewName>",
{
"viewOn" : "<source>",
"pipeline" : [<pipeline>],
"collation" : { <collation> }
}
)</code></pre></figure>
<ul>
<li>使用db.createView()方法</li>
</ul>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.createView(
"<viewName>",
"<source>",
[<pipeline>],
{
"collation" : { <collation> }
}
)</code></pre></figure>
<blockquote>
<p>Note: 你必须在与source collection相同的数据库上创建视图
另外,在视图定义中pipeline不能包含$out及$merge stage。</p>
</blockquote>
<h6 id="112-behavior">1.1.2 Behavior</h6>
<p>在视图上可以执行如下行为:</p>
<p>1) <strong>Read Only</strong></p>
<p>视图是只读的,不允许在视图上执行写操作,否则会报告相应的错误。</p>
<p>可以在视图上执行如下读取操作:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find">db.collection.find()</a></li>
<li>db.collection.findOne()</li>
<li>db.collection.aggregate()</li>
<li>db.collection.countDocuments()</li>
<li>db.collection.estimatedDocumentCount()</li>
<li>db.collection.count()</li>
<li>db.collection.distinct()</li>
</ul>
<p>2) <strong>Index Use and Sort Operations</strong></p>
<ul>
<li>
<p>视图(Views)会使用底层Collection的索引</p>
</li>
<li>
<p>由于索引是在底层collection上,因此不能直接在视图(view)上创建、删除、重建索引,也不能在视图(views)上获得索引列表信息;</p>
</li>
<li>
<p>从MongoDB 4.4开始,当在视图(view)上执行<a href="https://docs.mongodb.com/manual/reference/command/find/">find命令</a>时,可以指定按<code class="language-plaintext highlighter-rouge">$natural</code>排序。在之前老的版本,是不支持在视图上按<code class="language-plaintext highlighter-rouge">$natural</code>排序的。</p>
</li>
<li>
<p>对于blocking sort以及blocking group操作来说,视图的底层聚合 pipeline有100MB的内存限制。从MongoDB 4.4开始,当使用find命令在视图上执行blocking sort和blocking group操作时,可以使用<code class="language-plaintext highlighter-rouge">allowDiskUse: true</code>选项,以允许MongoDB使用临时文件。</p>
</li>
</ul>
<blockquote>
<p>注:在MongoDB 4.4之前,只允许在<code class="language-plaintext highlighter-rouge">aggregate</code>命令上使用allowDiskUse选项</p>
</blockquote>
<p>3) <strong>Projection Restrictions</strong></p>
<p>在视图上执行find()操作时,并不支持如下的projection操作符:</p>
<ul>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/operator/projection/positional/#mongodb-projection-proj.-">$</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/operator/projection/elemMatch/#mongodb-projection-proj.-elemMatch">$elemMatch</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/operator/projection/slice/#mongodb-projection-proj.-slice">$slice</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/operator/aggregation/meta/#mongodb-expression-exp.-meta">$meta</a></p>
</li>
</ul>
<p>4) <strong>Immutable Name</strong></p>
<p>创建视图之后,我们不能对视图进行重命名。</p>
<p>5) <strong>View Creation</strong></p>
<ul>
<li>
<p>在进行读取操作(read operations)的时候会根据需要进行视图计算,MongoDB会将视图(view)的读取操作作为底层聚合管道(aggregation pipeline)的一部分。因此,视图并不支持如下操作:</p>
<ul>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/db.collection.mapReduce/#mongodb-method-db.collection.mapReduce">db.collection.mapReduce()</a></p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">$text</code>操作符,这是由于$text操作只在aggregation的第一个stage有效</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">$geoNear</code> pipeline stage</p>
</li>
</ul>
</li>
<li>
<p>假如使用了聚合管道(aggregation pipeline)来创建视图,这会抑制<code class="language-plaintext highlighter-rouge">_id</code>字段,因此在视图中的document没有<code class="language-plaintext highlighter-rouge">_id</code>字段;</p>
</li>
</ul>
<p>当你在视图(views)上执行查询时:</p>
<ul>
<li>
<p>db.collection.find()方法中的filter、projection、sort、skip、limit等操作会被转化为等价的<a href="https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/#std-label-aggregation-pipeline-operator-reference">aggregation pipeline stages</a></p>
</li>
<li>
<p>转化后的aggregation pipeline stages会被添加到视图的<a href="https://docs.mongodb.com/manual/core/aggregation-pipeline/#std-label-aggregation-pipeline">aggregation pipeline</a>。这并不会修改视图底层的pipeline(视图底层的pipeline是在创建视图时设置的)</p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/core/aggregation-pipeline-optimization/">Aggregation pipeline optimizer</a>会重新调整aggregation pipeline stages,以提高性能。这并不会影响查询结果。</p>
</li>
</ul>
<p>6) <strong>Sharded View</strong></p>
<p>假如底层的集合(collection)做了shard的话,那么我们认为视图(view)也是做了shard。因此,在执行<a href="https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#mongodb-pipeline-pipe.-lookup">$lookup</a>和<a href="https://docs.mongodb.com/manual/reference/operator/aggregation/graphLookup/#mongodb-pipeline-pipe.-graphLookup">$graphLookup</a>操作时不能为<code class="language-plaintext highlighter-rouge">from</code>字段指定一个sharded view。</p>
<p>7) <strong>Views and Collation</strong></p>
<ul>
<li>
<p>你可以在创建视图时指定一个默认的<a href="https://docs.mongodb.com/manual/reference/collation/">collation</a>。假如不指定collation,则视图的默认collation为simple binary comparison collator。即,视图并不会继承集合的默认collation</p>
</li>
<li>
<p>对于在视图上执行字符串的比较,会采用视图默认的collation。如果一个操作试图修改或覆盖view的默认collation的话,将会失败。</p>
</li>
<li>
<p>假如是基于另外一个view来创建视图的话,所指定的collation必须与源view的collation一致。</p>
</li>
<li>
<p>假如一个聚合操作涉及到多个视图(view),比如<code class="language-plaintext highlighter-rouge">$lookup</code>或者<code class="language-plaintext highlighter-rouge">$graphLookup</code>,那么要求这些视图具有相同的collation。</p>
</li>
</ul>
<p>8) <strong>Public View Definition</strong></p>
<p>用于列出(list) collection的操作,比如<a href="https://docs.mongodb.com/manual/reference/method/db.getCollectionInfos/#mongodb-method-db.getCollectionInfos">db.getCollectionInfo()</a>、<a href="https://docs.mongodb.com/manual/reference/method/db.getCollectionNames/#mongodb-method-db.getCollectionNames">db.getCollectionNames()</a>,都会在对应的输出中包含相应的视图信息。</p>
<blockquote>
<p>IMPORTANT:</p>
<p>视图的定义是public的,在视图上执行db.getCollectionInfos()、explain等操作时输出将会含有视图定义时对应的pipeline。因此,避免在视图定义时直接引用一些敏感的字段和值。</p>
</blockquote>
<h6 id="113-drop-a-view">1.1.3 Drop a view</h6>
<p>要删除一个视图的话,可以在视图上执行<a href="https://docs.mongodb.com/manual/reference/method/db.collection.drop/#mongodb-method-db.collection.drop"> db.collection.drop()</a>方法。</p>
<h6 id="114-modify-a-view">1.1.4 Modify a View</h6>
<p>你可以通过删除,然后再重建的方式来修改视图,也可以通过<a href="https://docs.mongodb.com/manual/reference/command/collMod/#mongodb-dbcommand-dbcmd.collMod">collMod</a>命令来修改视图。</p>
<h6 id="115-supported-operations">1.1.5 Supported Operations</h6>
<p>如下的操作提供对视图的支持(上文提到可能会有一些限制):</p>
<pre>
Commands Methods
=================================================================
db.createCollection()
create
db.createView()
-----------------------------------------------------------------
collMod
------------------------------------------------------------------
db.getCollection()
db.getCollectionInfos()
db.getCollectionNames()
------------------------------------------------------------------
db.collection.aggregate()
db.collection.find()
find db.collection.findOne()
distinct db.collection.countDocuments()
count db.collection.estimatedDocumentCount()
db.collection.count()
db.collection.distinct()
</pre>
<h3 id="12-on-demand-materialized-views">1.2 On-Demand Materialized Views</h3>
<p>从4.2版本开始,MongoDB对于<a href="https://docs.mongodb.com/manual/core/aggregation-pipeline/">aggregation pipeline</a>添加了<a href="https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#mongodb-pipeline-pipe.-merge">$merge stage</a>。该stage可以将pipeline的结果合并到一个已存在的collection上,而不是完全的替换该collection。此功能允许用户创建on-demand materialized views,当对应的pipeline运行时视图也会得到更新。</p>
<h6 id="121-示例">1.2.1 示例</h6>
<p>假设在2019年1月末,<code class="language-plaintext highlighter-rouge">bakesales</code>这个collection所含有的销售信息如下:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.bakesales.insertMany( [
{ date: new ISODate("2018-12-01"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
{ date: new ISODate("2018-12-02"), item: "Cake - Peanut Butter", quantity: 5, amount: new NumberDecimal("90") },
{ date: new ISODate("2018-12-02"), item: "Cake - Red Velvet", quantity: 10, amount: new NumberDecimal("200") },
{ date: new ISODate("2018-12-04"), item: "Cookies - Chocolate Chip", quantity: 20, amount: new NumberDecimal("80") },
{ date: new ISODate("2018-12-04"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
{ date: new ISODate("2018-12-05"), item: "Pie - Key Lime", quantity: 3, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-01-25"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-01-25"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
{ date: new ISODate("2019-01-26"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-01-26"), item: "Cookies - Chocolate Chip", quantity: 12, amount: new NumberDecimal("48") },
{ date: new ISODate("2019-01-26"), item: "Cake - Carrot", quantity: 2, amount: new NumberDecimal("36") },
{ date: new ISODate("2019-01-26"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-01-27"), item: "Pie - Chocolate Cream", quantity: 1, amount: new NumberDecimal("20") },
{ date: new ISODate("2019-01-27"), item: "Cake - Peanut Butter", quantity: 5, amount: new NumberDecimal("80") },
{ date: new ISODate("2019-01-27"), item: "Tarts - Apple", quantity: 3, amount: new NumberDecimal("12") },
{ date: new ISODate("2019-01-27"), item: "Cookies - Chocolate Chip", quantity: 12, amount: new NumberDecimal("48") },
{ date: new ISODate("2019-01-27"), item: "Cake - Carrot", quantity: 5, amount: new NumberDecimal("36") },
{ date: new ISODate("2019-01-27"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-01-28"), item: "Cookies - Chocolate Chip", quantity: 20, amount: new NumberDecimal("80") },
{ date: new ISODate("2019-01-28"), item: "Pie - Key Lime", quantity: 3, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-01-28"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
] );</code></pre></figure>
<p>1) <strong>定义On-Demand Materialized View</strong></p>
<p>如下的<code class="language-plaintext highlighter-rouge">updateMonthlySales</code>函数定义了<code class="language-plaintext highlighter-rouge">monthlybakesales</code>生成视图,其包含有每个月的销售信息统计。在下面的例子中,函数接受一个<code class="language-plaintext highlighter-rouge">日期</code>参数来更新从哪一天开始的月销售额信息:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">updateMonthlySales = function(startDate) {
db.bakesales.aggregate( [
{ $match: { date: { $gte: startDate } } },
{ $group: { _id: { $dateToString: { format: "%Y-%m", date: "$date" } }, sales_quantity: { $sum: "$quantity"}, sales_amount: { $sum: "$amount" } } },
{ $merge: { into: "monthlybakesales", whenMatched: "replace" } }
] );
};</code></pre></figure>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">$match</code>阶段过滤大于等于startDate的销售数据</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">$group</code>阶段按月为单位对销售数据进行分组。此阶段产生的输出形式如下:</p>
</li>
</ul>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{ "_id" : "<YYYY-mm>", "sales_quantity" : <num>, "sales_amount" : <NumberDecimal> }</code></pre></figure>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">$merge</code>阶段将将对应的文档输出信息写入到<code class="language-plaintext highlighter-rouge">monthlybakesales</code>集合中。基于<code class="language-plaintext highlighter-rouge">_id</code>字段(对于unshared collections,默认为<code class="language-plaintext highlighter-rouge">_id</code>字段),此阶段会检查聚合(aggregation)结果中的document是否匹配集合中已存在的document:</p>
<ul>
<li>
<p>当有一个匹配的话(比如对应year-month的document已经存在),则本阶段会使用聚合中的document来替换原来老的document;</p>
</li>
<li>
<p>当并没有相匹配的,则此阶段会插入一个新的document到集合中</p>
</li>
</ul>
</li>
</ul>
<p>2) <strong>Perform Initial Run</strong></p>
<p>初次运行时,你可以传递<code class="language-plaintext highlighter-rouge">new ISODate("1970-01-01")</code>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">updateMonthlySales(new ISODate("1970-01-01"));</code></pre></figure>
<p>在初始运行之后,<code class="language-plaintext highlighter-rouge">monthlybakesales</code>包含如下documents。比如执行db.monthlybakesales.find().sort( { _id: 1 } )查询:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{ "_id" : "2018-12", "sales_quantity" : 41, "sales_amount" : NumberDecimal("506") }
{ "_id" : "2019-01", "sales_quantity" : 86, "sales_amount" : NumberDecimal("896") }</code></pre></figure>
<p>3) <strong>Refresh Materialized View</strong></p>
<p>假设在2019年2月的第一周,<code class="language-plaintext highlighter-rouge">bakesales</code>集合发生了改变,有新的销售信息更新。特别是增加了1月和2月的销售额:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.bakesales.insertMany( [
{ date: new ISODate("2019-01-28"), item: "Cake - Chocolate", quantity: 3, amount: new NumberDecimal("90") },
{ date: new ISODate("2019-01-28"), item: "Cake - Peanut Butter", quantity: 2, amount: new NumberDecimal("32") },
{ date: new ISODate("2019-01-30"), item: "Cake - Red Velvet", quantity: 1, amount: new NumberDecimal("20") },
{ date: new ISODate("2019-01-30"), item: "Cookies - Chocolate Chip", quantity: 6, amount: new NumberDecimal("24") },
{ date: new ISODate("2019-01-31"), item: "Pie - Key Lime", quantity: 2, amount: new NumberDecimal("40") },
{ date: new ISODate("2019-01-31"), item: "Pie - Banana Cream", quantity: 2, amount: new NumberDecimal("40") },
{ date: new ISODate("2019-02-01"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-02-01"), item: "Tarts - Apple", quantity: 2, amount: new NumberDecimal("8") },
{ date: new ISODate("2019-02-02"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-02-02"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
{ date: new ISODate("2019-02-03"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") }
] )</code></pre></figure>
<p>这时,如果要刷新<code class="language-plaintext highlighter-rouge">monthlybakesales</code>在1月和2月的数据的话,可以再次执行updateMonthlySales()函数以返回aggregation pipeline,函数的起始时间可设置为<code class="language-plaintext highlighter-rouge">new ISODate("2019-01-01")</code>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">updateMonthlySales(new ISODate("2019-01-01"));</code></pre></figure>
<p>执行完成之后,<code class="language-plaintext highlighter-rouge">monthlybakesales</code>的内容就得到了更新,以反映bakesales集合中最近的数据。执行db.monthlybakesales.find().sort( { _id: 1 } )返回的结果为:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">{ "_id" : "2018-12", "sales_quantity" : 41, "sales_amount" : NumberDecimal("506") }
{ "_id" : "2019-01", "sales_quantity" : 102, "sales_amount" : NumberDecimal("1142") }
{ "_id" : "2019-02", "sales_quantity" : 15, "sales_amount" : NumberDecimal("284") }</code></pre></figure>
<h6 id="122-additional-information">1.2.2 Additional Information</h6>
<p><code class="language-plaintext highlighter-rouge">$merge</code>阶段(stage):</p>
<ul>
<li>
<p>可以将结果输出到相同或不同的数据库;</p>
</li>
<li>
<p>假如输出集合(collection)不存在的话,此阶段会自动创建对应的集合;</p>
</li>
<li>
<p>可以将结果合并(insert new documents, merge documents, replace documents, keep existing documents, fail the operation, process documents with a custom update pipeline)到一个已存在的集合中</p>
</li>
<li>
<p>可以将结果输出到一个sharded collection中</p>
</li>
</ul>
<p>关于<a href="https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#mongodb-pipeline-pipe.-merge">$merge</a>,可查看如下更多内容:</p>
<ul>
<li>
<p>More information on <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#mongodb-pipeline-pipe.-merge">$merge</a> and available options</p>
</li>
<li>
<p>Example: <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#std-label-merge-mat-view-init-creation">On-Demand Materialized View: Initial Creation</a></p>
</li>
<li>
<p>Example: <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#std-label-merge-mat-view-refresh">On-Demand Materialized View: Update/Replace Data</a></p>
</li>
<li>
<p>Example: <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#std-label-merge-mat-view-insert-only">Only Insert New Data</a></p>
</li>
</ul>
<h3 id="13-capped-collections">1.3 Capped Collections</h3>
<h6 id="131-overview">1.3.1 Overview</h6>
<p><a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-capped-collection">capped collections</a>是一种固定大小的collection,其支持高吞吐量,并可以根据插入顺序来获取document。Capped collections工作方式类似于循环buffers: 一旦一个collection填充满了所分配的空间,那么新插入的documents就会覆盖原来最旧的document数据。</p>
<p>参看<a href="https://docs.mongodb.com/manual/reference/method/db.createCollection/#mongodb-method-db.createCollection">createCollection()</a>或<a href="https://docs.mongodb.com/manual/reference/command/create/#mongodb-dbcommand-dbcmd.create">create()</a>以获得更多关于创建capped collections的信息。</p>
<h6 id="132-behavior">1.3.2 Behavior</h6>
<p>1) <strong>Insertion Order</strong></p>
<p>Capped Collection会确保维持document的插入顺序。因此,在查询时并不需要通过一个索引来返回documents的插入顺序。没有了indexing的额外负担后,capped collection就可以支持更高的插入吞吐量。</p>
<p>2)<strong>Automatic Removal of Oldest Documents</strong></p>
<p>为了给新插入的documents腾出空间,capped collections会自动的移除collection中最旧的documents,而不需要其他额外的脚本或移除操作。</p>
<p>考虑capped collections如下潜在的使用场景:</p>
<ul>
<li>
<p>存储其他high-volume系统所产生的日志信息。插入documents到一个无索引的capped collection可获得与直接写文件相近的速度。更为重要的是,内置的先进先出属性确保了事件的先后顺序。</p>
</li>
<li>
<p>通过capped collections来缓存少量的数据。</p>
</li>
</ul>
<p>例如,<code class="language-plaintext highlighter-rouge">oplog.rs</code>使用capped collection将日志信息存放到一个<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-replica-set">副本集(replica set)</a>中。从MongoDB 4.0版本开始,不像是其他capped collection,oplog增长可以超过所配置的限额,以此来避免删除<a href="https://docs.mongodb.com/manual/reference/command/replSetGetStatus/#mongodb-data-replSetGetStatus.optimes.lastCommittedOpTime">majority commit point</a>.</p>
<p>3) <strong>_id Index</strong></p>
<p>Capped collections有一个<code class="language-plaintext highlighter-rouge">_id</code>字段,并且默认在该字段上有索引。</p>
<h6 id="133-restrictions-and-recommendations">1.3.3 Restrictions and Recommendations</h6>
<p>1) <strong>Reads</strong></p>
<p>从MongoDB 5.0开始,当从capped collection中读取documents时,不能使用读相关的<code class="language-plaintext highlighter-rouge">snapshot</code>。</p>
<p>2) <strong>Updates</strong></p>
<p>假如你打算更新capped collection中的documents,请创建一个索引,这样相应的更新操作就不需要扫描整个collection。</p>
<p>3) <strong>Document Size</strong></p>
<blockquote>
<p>Changed in version 3.2</p>
</blockquote>
<p>假如一个更新或替换操作会改变document size,那么对应的操作会失败。</p>
<p>4) <strong>Document Deletion</strong></p>
<p>你不能够从capped collection中删除documents。要移除collection中所有documents的话,请使用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.drop/#mongodb-method-db.collection.drop">drop()</a>直接删除整个collection,然后再重新创建capped collection。</p>
<p>5)<strong>Sharding</strong></p>
<p>不能对capped collection做shard。</p>
<p>6)<strong>Query Efficiency</strong></p>
<p>使用自然(nature)顺序来从集合中获取最新插入的元素是最高效的。这有点类似于在一个日志文件上做<code class="language-plaintext highlighter-rouge">tail</code>命令。</p>
<p>7)<strong>Aggregation $out</strong></p>
<p>aggregation pipeline的<code class="language-plaintext highlighter-rouge">$out</code>阶段不能将结果写到capped collection.</p>
<p>8) <strong>Transactions</strong></p>
<p>从MongoDB 4.2开始,不能以事务(transactions)的方式向capped collection写入数据。</p>
<h6 id="134-procedures">1.3.4 Procedures</h6>
<p>1) <strong>Create a Capped Collection</strong></p>
<p>你必须显式的使用<a href="https://docs.mongodb.com/manual/reference/method/db.createCollection/#mongodb-method-db.createCollection"> db.createCollection()</a>方法来创建capped collection,这是一个mongosh create命令的帮助函数。当创建capped collection时,你必须指定collection所占用的最大字节数,MongoDB会为所创建的capped collection预分配对应的空间。capped collection内部也会占用少量额外的空间。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.createCollection( "log", { capped: true, size: 100000 } )</code></pre></figure>
<p>假如size字段小于等于4096,那么该collection的容量为4096字节。否则,MongoDB会将size上调为256的倍数。</p>
<p>另外,你也可以使用<code class="language-plaintext highlighter-rouge">max</code>字段来指定一个collection所含有的documents的数量:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.createCollection("log", { capped : true, size : 5242880, max : 5000 } )</code></pre></figure>
<blockquote>
<p>Important:</p>
<p>即使在指定了max字段时,size字段也是必需的。在documents所占用的字节数达到了size大小,即使documents本身的数量未达到max,此时MongoDB也是会移除其中最旧的document数据。</p>
</blockquote>
<p>2) <strong>Query a Capped Collection</strong></p>
<p>假如你对capped collection执行find()操作,并且未指定查询顺序(ordering),此时MongoDB会确保按插入顺序返回查询结果。</p>
<p>如果要与插入相反的顺序返回查询结果的话,那么在执行find()时搭配<a href="https://docs.mongodb.com/manual/reference/method/cursor.sort/#mongodb-method-cursor.sort">sort()</a>方法,并向sort()传递<code class="language-plaintext highlighter-rouge">$natural</code>为-1的参数。参看如下示例:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.cappedCollection.find().sort( { $natural: -1 } )</code></pre></figure>
<p>3) <strong>Check if a Collection is Capped</strong></p>
<p>执行如下方法检查一个collection是否是capped:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.collection.isCapped()</code></pre></figure>
<p>4) <strong>Convert a Collection to Capped</strong></p>
<p>我们可以将一个non-capped collection转变为capped collection。使用<a href="https://docs.mongodb.com/manual/reference/command/convertToCapped/#mongodb-dbcommand-dbcmd.convertToCapped">convertToCapped</a>命令:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">db.runCommand({"convertToCapped": "mycoll", size: 100000});</code></pre></figure>
<p>其中size参数指定capped collection可以占用的字节数。</p>
<p>在执行上述转换操作时,需要获得数据库的互斥锁。其他需要锁database的操作此时将会被阻塞,直到转换操作完成。参看<a href="What locks are taken by some common client operations">What locks are taken by some common client operations</a>以了解更多关于数据库锁的信息。</p>
<p>5) <strong>Tailable Cursor</strong></p>
<p>可以在capped collection上使用<a href="https://docs.mongodb.com/manual/reference/glossary/#std-term-tailable-cursor">tailable cursor</a>。与Unix系统中的<code class="language-plaintext highlighter-rouge">tail -f</code>命令类似,tailable cursor可以获取capped collection的尾部documents。当一个新的document插入到capped collection,你可以使用tailable cursor来继续获取documents。</p>
<p><br />
<br />
<strong>[参看]</strong>:</p>
<ol>
<li>
<p><a href="https://www.mongodb.com/">mongodb官网</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/">mongodb method</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/command/find/">mongodb command</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/weixin_43031412/article/details/97632341">MongoDB Projection</a></p>
</li>
<li>
<p><a href="https://www.runoob.com/mongodb/mongodb-query.html">mongodb菜鸟教程</a></p>
</li>
<li>
<p><a href="http://blog.itpub.net/31556440/viewspace-2672431/">了解 MongoDB 看这一篇就够了</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
MongoDB的介绍及简单使用
2020-05-01T00:00:00+00:00
/post/database/2020/05/01/mongodb_part1
<p>如下是MongoDB官网对自身的介绍:</p>
<blockquote>
<p>MongoDB was founded in 2007 by Dwight Merriman, Eliot Horowitz and Kevin Ryan – the team behind DoubleClick.</p>
<p>At the Internet advertising company DoubleClick (now owned by Google), the team developed and used many custom data stores to work around the shortcomings of existing databases. The business served 400,000 ads per second, but often struggled with both scalability and agility. Frustrated, the team was inspired to create a database that tackled the challenges it faced at DoubleClick.</p>
<p>This was when MongoDB was born.</p>
</blockquote>
<!-- more -->
<h2 id="1-mongodb产品矩阵">1. MongoDB产品矩阵</h2>
<p>MongoDB公司目前主要有如下产品矩阵:</p>
<ul>
<li>Atlas</li>
<li>Enterprise Advanced</li>
<li>Community Edition</li>
<li>Realm</li>
</ul>
<p>下面我们分别对其进行简单介绍。</p>
<p>1) <strong>Atlas</strong></p>
<p>MongoDB Atlas是由公司创始团队所开发的一款多云数据库服务(multi-cloud database service)。当我们需要在(不同供应商的)云服务器上构建弹性、高效的全球应用时,Atlas可以帮助我们简化数据库的部署和管理。如下图所示:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/db/mongodb/atlas-plp-hero.svg" alt="mongodb-atlas" /></p>
<blockquote>
<p>More: https://docs.atlas.mongodb.com/</p>
</blockquote>
<p>2) <strong>Enterprise Advanced</strong></p>
<p>企业版提供了一系列的产品(products)和服务(services)来提高MongoDB数据的安全性、性能,以及帮助用户更好的管理MongoDB数据库。</p>
<ul>
<li>Database Management</li>
</ul>
<p>在数据库管理方面,提供了相应的管理工具来简化MongoDB数据库的运维。主要包括: Automation、Monitoring、Optimization、Backups等</p>
<ul>
<li>Data and Business Protection</li>
</ul>
<p>提供先进的访问控制(access control)和数据安全特性来保护数据库。可以很容易的将已存在的安全设施、工具等与MongoDB继承。主要包括: Auditing、Encryption、Authentication、Commercial License</p>
<ul>
<li>Support and Services</li>
</ul>
<p>购买了MongoDB企业版的客户,可以获得MongoDB公司的技术支持与服务。</p>
<p>3) <strong>Community Edition</strong></p>
<p>MongoDB社区版分布式数据库实现了灵活的文档数据模型(document date model),并支持ad-hoc查询、二级索引、实时聚合等功能,从而为数据的访问与分析提供有力的支撑。</p>
<p>4) <strong>Realm</strong></p>
<p>MongoDB Realm是一个开发平台,主要用于支持现代数据驱动的应用程序。可以使用Realm来构建mobile、web、desktop、以及IOT应用。</p>
<h2 id="2-mongodb介绍">2. MongoDB介绍</h2>
<p>欢迎使用MongoDB 5.0手册!MongoDB是一个文档数据库,旨在简化开发和扩展(scaling)。手册(manual)会介绍MongoDB关键概念(key concepts)、查询语言,并提供MongoDB操作及管理上的一些思考与流程。</p>
<p>MongoDB提供了本地部署与云(cloud-hosted)部署两种选项:</p>
<ul>
<li>对于本地部署(locally hosted deployments), MongoDB又分为<code class="language-plaintext highlighter-rouge">社区版</code>与<code class="language-plaintext highlighter-rouge">企业版</code>
<ul>
<li>MongoDB社区版是<a href="https://github.com/mongodb/mongo">开源并免费使用</a>的版本</li>
<li>MongoDB企业版作为MongoDB Enterprise Advanced订阅的一部分提供,并对MongoDB的部署提供全面的支持。此外,MongoDB企业版也增加了一些特性的支持,比如LDAP以及Kerberos支持、磁盘加密和审计。</li>
</ul>
</li>
<li>MongoDB Atlas是云中托管的 MongoDB Enterprise 服务选项,无需安装开销,并提供免费层级来开始使用。</li>
</ul>
<h3 id="21-文档数据库document-database">2.1 文档数据库(Document Database)</h3>
<p>在MongoDB中一条记录(record)就是一个文档(document),其是由field/value对所组成的一个数据结构。MongoDB documents与JSON Objects类似。values字段可以为其他documents、arrays或arrays of documents。如下所示:</p>
<p><img src="https://ivanzz1001.github.io/records/assets/img/db/mongodb/crud-annotated-document.bakedsvg.svg" alt="mongodb-documents" /></p>
<p>使用documents具有如下优点:</p>
<ul>
<li>
<p>在许多编程语言中,documents对应于原生数据类型(native data types)</p>
</li>
<li>
<p>内嵌documents和数组可以降低昂贵连接(join)的需求</p>
</li>
<li>
<p>动态schema可以更好的支持多种形态</p>
</li>
</ul>
<p>1) <strong>集合/视图/按需实例化视图</strong></p>
<p>MongoDB将文档存储在集合中。集合类似于关系数据库中的表。另外,对于集合(collections),MongoDB还支持:</p>
<ul>
<li>
<p>Read-only Views(Staring in MongoDB 3.4)</p>
</li>
<li>
<p>On-Demand Materialized Views (Starting in MongoDB 4.2)</p>
</li>
</ul>
<h3 id="22-key-features">2.2 Key Features</h3>
<p>1) <strong>High Performance¶</strong></p>
<p>MongoDB提供了高性能的数据持久化能力。特别是:</p>
<ul>
<li>
<p>支持内嵌数据模型以降低数据库系统IO活动</p>
</li>
<li>
<p>通过索引实现高效的数据查询,并且索引的keys可以来自于内嵌documents与arrays</p>
</li>
</ul>
<p>2) <strong>Rich Query Language</strong></p>
<p>MongoDB提供了丰富的查询语言(query language)以支持读写操作(CRUD),另外还支持如下:</p>
<ul>
<li>
<p><a href="https://docs.mongodb.com/manual/core/aggregation-pipeline/">Data Aggregation</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/text-search/">Text Search</a>和<a href="https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/">Geospatial Queries</a></p>
</li>
</ul>
<blockquote>
<p>注:请参看如下
<a href="https://docs.mongodb.com/manual/reference/sql-comparison/">SQL to MongoDB Mapping Chart</a>
<a href="https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/">SQL to Aggregation Mapping Chart</a></p>
</blockquote>
<p>3) <strong>High Availability</strong></p>
<p>MongoDB中实现的复制设施,称为<a href="https://docs.mongodb.com/manual/replication/">replica set</a>,实现了:</p>
<ul>
<li>
<p>automatic failover</p>
</li>
<li>
<p>data redundancy</p>
</li>
</ul>
<p>一个<code class="language-plaintext highlighter-rouge">replica set</code>是一组维护相同数据集的MongoDB服务器,提供数据冗余,并增强数据的可用性。</p>
<p>4) <strong>Horizontal Scalability</strong></p>
<p>MongoDB在核心功能层面提供水平扩展能力:</p>
<ul>
<li>
<p>通过Sharding方式将数据存放到集群中</p>
</li>
<li>
<p>从3.4版本开始,MongoDB支持基于shard key的方式来创建zone。在一个平衡的集群中,MongoDB将zone所覆盖的读取和写入操作仅定向到zone中对应的分片。</p>
</li>
</ul>
<p>5) <strong>Support for Multiple Storage Engines</strong></p>
<p>MongoDB支持多存储引擎:</p>
<ul>
<li>
<p><a href="https://docs.mongodb.com/manual/core/wiredtiger/">WiredTiger Storage Engine</a> (including support for <a href="https://docs.mongodb.com/manual/core/security-encryption-at-rest/">Encryption at Rest</a>)</p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/core/inmemory/">In-Memory Storage Engine</a></p>
</li>
</ul>
<p>另外,MongoDB提供可插拔的存储引擎API以支持第三方开发新的存储引擎。</p>
<h2 id="3-getting-started">3. Getting Started</h2>
<p>在本节我们会介绍向MongoDB中插入数据以及查询数据。可以使用<a href="https://mws.mongodb.com/?version=latest">MongoDB Web Shell</a>控制台来进行实验,暂时无需安装MongoDB。</p>
<p>1) <strong>Switch Database</strong></p>
<p>当我们连接上<code class="language-plaintext highlighter-rouge">MongoDB Web Shell</code>控制台后,默认会有一个当前数据库。输入<code class="language-plaintext highlighter-rouge">db</code>命令就可以显示当前数据库:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> db
test
test>
>>></code></pre></figure>
<p>从上面我们看到,返回结果为<code class="language-plaintext highlighter-rouge">test</code>,这是当前所连MongoDB的默认数据库。</p>
<p>如果想要切换到其他数据库,可以使用<code class="language-plaintext highlighter-rouge">use <db></code>命令来进行切换。例如,切换到<code class="language-plaintext highlighter-rouge">examples</code>数据库:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> use examples
switched to db examples
examples>
>>></code></pre></figure>
<p>值得指出的是,在你进行切换(switch)之前并不需要创建数据库。MongoDB会在你第一次存入数据时自动的创建相应的库。</p>
<p>为了检验我们确实切换到了<code class="language-plaintext highlighter-rouge">examples</code>数据库,我们可以在<code class="language-plaintext highlighter-rouge">MongoDB Web Shell</code>控制台继续输入<code class="language-plaintext highlighter-rouge">db</code>命令:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> db
examples
examples>
>>></code></pre></figure>
<p>2) <strong>Populate a Collection (Insert)</strong></p>
<p>MongoDB将documents存放到<a href="https://docs.mongodb.com/manual/core/databases-and-collections/">collections</a>中。collections与传统关系型数据库中的表(tables)类似。假如一个collection不存在,MongoDB会在你第一次将数据存放到该collection时进行创建。</p>
<p>下面的例子使用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#mongodb-method-db.collection.insertMany">db.collection.insertMany()</a>方法插入新的documents到<code class="language-plaintext highlighter-rouge">movies</code>集合中。你可以拷贝如下示例到<code class="language-plaintext highlighter-rouge">MongoDB Web Shell</code>:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> use examples
switched to db examples
examples>
>>> db.movies.insertMany([
{
title: 'Titanic',
year: 1997,
genres: [ 'Drama', 'Romance' ],
rated: 'PG-13',
languages: [ 'English', 'French', 'German', 'Swedish', 'Italian', 'Russian' ],
released: ISODate("1997-12-19T00:00:00.000Z"),
awards: {
wins: 127,
nominations: 63,
text: 'Won 11 Oscars. Another 116 wins & 63 nominations.'
},
cast: [ 'Leonardo DiCaprio', 'Kate Winslet', 'Billy Zane', 'Kathy Bates' ],
directors: [ 'James Cameron' ]
},
{
title: 'The Dark Knight',
year: 2008,
genres: [ 'Action', 'Crime', 'Drama' ],
rated: 'PG-13',
languages: [ 'English', 'Mandarin' ],
released: ISODate("2008-07-18T00:00:00.000Z"),
awards: {
wins: 144,
nominations: 106,
text: 'Won 2 Oscars. Another 142 wins & 106 nominations.'
},
cast: [ 'Christian Bale', 'Heath Ledger', 'Aaron Eckhart', 'Michael Caine' ],
directors: [ 'Christopher Nolan' ]
},
{
title: 'Spirited Away',
year: 2001,
genres: [ 'Animation', 'Adventure', 'Family' ],
rated: 'PG',
languages: [ 'Japanese' ],
released: ISODate("2003-03-28T00:00:00.000Z"),
awards: {
wins: 52,
nominations: 22,
text: 'Won 1 Oscar. Another 51 wins & 22 nominations.'
},
cast: [ 'Rumi Hiiragi', 'Miyu Irino', 'Mari Natsuki', 'Takashi Naitè' ],
directors: [ 'Hayao Miyazaki' ]
},
{
title: 'Casablanca',
genres: [ 'Drama', 'Romance', 'War' ],
rated: 'PG',
cast: [ 'Humphrey Bogart', 'Ingrid Bergman', 'Paul Henreid', 'Claude Rains' ],
languages: [ 'English', 'French', 'German', 'Italian' ],
released: ISODate("1943-01-23T00:00:00.000Z"),
directors: [ 'Michael Curtiz' ],
awards: {
wins: 9,
nominations: 6,
text: 'Won 3 Oscars. Another 6 wins & 6 nominations.'
},
lastupdated: '2015-09-04 00:22:54.600000000',
year: 1942
}
])
{
acknowledged: true,
insertedIds:{
'0': ObjectId("61d11d5c168ab70a91d75c6b"),
'1': ObjectId("61d11d5c168ab70a91d75c6c"),
'2': ObjectId("61d11d5c168ab70a91d75c6d"),
'3': ObjectId("61d11d5c168ab70a91d75c6e")
}
}
examples>
>>></code></pre></figure>
<p>上面的插入操作返回结果为一个document,其包含应答标识<code class="language-plaintext highlighter-rouge">acknowledged</code>及数组,数组的每一个元素为成功插入的document所对应的<code class="language-plaintext highlighter-rouge">_id</code>。</p>
<p>3) <strong>Select All Documents</strong></p>
<p>要从一个collection中查询documents,我们可以使用<a href="https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find"> db.collection.find()</a>方法。要查询collection中的所有documents,可以传递一个empty document作为查询过滤条件.</p>
<blockquote>
<p>查询过滤条件,请参看<a href="https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter">query filter document</a></p>
</blockquote>
<p>在<code class="language-plaintext highlighter-rouge">MongoDB Web Shell</code>中,输入如下命令查询上面<code class="language-plaintext highlighter-rouge">步骤2)</code>所插入到<strong>movies</strong>这个collection中的数据:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> db.movies.find( { } )
[
{
_id: ObjectId("61d11d5c168ab70a91d75c6b"),
title: 'Titanic',
year: 1997,
genres: [ 'Drama', 'Romance' ],
rated: 'PG-13',
languages: [ 'English', 'French', 'German', 'Swedish', 'Italian', 'Russian' ],
released: ISODate("1997-12-19T00:00:00.000Z"),
awards: {
wins: 127,
nominations: 63,
text: 'Won 11 Oscars. Another 116 wins & 63 nominations.'
},
cast: [
'Leonardo DiCaprio',
'Kate Winslet',
'Billy Zane',
'Kathy Bates'
],
directors: [ 'James Cameron' ]
},
{
_id: ObjectId("61d11d5c168ab70a91d75c6c"),
title: 'The Dark Knight',
year: 2008,
genres: [ 'Action', 'Crime', 'Drama' ],
rated: 'PG-13',
languages: [ 'English', 'Mandarin' ],
released: ISODate("2008-07-18T00:00:00.000Z"),
awards: {
wins: 144,
nominations: 106,
text: 'Won 2 Oscars. Another 142 wins & 106 nominations.'
},
cast: [
'Christian Bale',
'Heath Ledger',
'Aaron Eckhart',
'Michael Caine'
],
directors: [ 'Christopher Nolan' ]
},
{
_id: ObjectId("61d11d5c168ab70a91d75c6d"),
title: 'Spirited Away',
year: 2001,
genres: [ 'Animation', 'Adventure', 'Family' ],
rated: 'PG',
languages: [ 'Japanese' ],
released: ISODate("2003-03-28T00:00:00.000Z"),
awards: {
wins: 52,
nominations: 22,
text: 'Won 1 Oscar. Another 51 wins & 22 nominations.'
},
cast: [
'Rumi Hiiragi',
'Miyu Irino',
'Mari Natsuki',
'Takashi Naitè'
],
directors: [ 'Hayao Miyazaki' ]
},
{
_id: ObjectId("61d11d5c168ab70a91d75c6e"),
title: 'Casablanca',
genres: [ 'Drama', 'Romance', 'War' ],
rated: 'PG',
cast: [
'Humphrey Bogart',
'Ingrid Bergman',
'Paul Henreid',
'Claude Rains'
],
languages: [ 'English', 'French', 'German', 'Italian' ],
released: ISODate("1943-01-23T00:00:00.000Z"),
directors: [ 'Michael Curtiz' ],
awards: {
wins: 9,
nominations: 6,
text: 'Won 3 Oscars. Another 6 wins & 6 nominations.'
},
lastupdated: '2015-09-04 00:22:54.600000000',
year: 1942
}
]</code></pre></figure>
<p>4) <strong>Filter Data with Comparison Operators</strong></p>
<p>对于相等匹配(<field> equals <value>),可以向db.collection.find()方法中传入```<field>: <value>```查询条件:</value></field></value></field></p>
<ul>
<li>在MongoDB Web Shell中,执行如下命令查询由<code class="language-plaintext highlighter-rouge">Christopher Nolan</code>所导演的movies</li>
</ul>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> db.movies.find( { "directors": "Christopher Nolan" } );
[
{
_id: ObjectId("61d11d5c168ab70a91d75c6c"),
title: 'The Dark Knight',
year: 2008,
genres: [ 'Action', 'Crime', 'Drama' ],
rated: 'PG-13',
languages: [ 'English', 'Mandarin' ],
released: ISODate("2008-07-18T00:00:00.000Z"),
awards: {
wins: 144,
nominations: 106,
text: 'Won 2 Oscars. Another 142 wins & 106 nominations.'
},
cast: [
'Christian Bale',
'Heath Ledger',
'Aaron Eckhart',
'Michael Caine'
],
directors: [ 'Christopher Nolan' ]
}
]</code></pre></figure>
<p>此外,我们也可以使用比较操作符(<a href="https://docs.mongodb.com/manual/reference/operator/query-comparison/#std-label-query-selectors-comparison">comparison operators</a>)来执行更高级的查询。</p>
<ul>
<li>执行如下命令查询在2000年之前所上映的movies</li>
</ul>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> db.movies.find( { "released": { $lt: ISODate("2000-01-01") } } );
[
{
_id: ObjectId("61d11d5c168ab70a91d75c6b"),
title: 'Titanic',
year: 1997,
genres: [ 'Drama', 'Romance' ],
rated: 'PG-13',
languages: [ 'English', 'French', 'German', 'Swedish', 'Italian', 'Russian' ],
released: ISODate("1997-12-19T00:00:00.000Z"),
awards: {
wins: 127,
nominations: 63,
text: 'Won 11 Oscars. Another 116 wins & 63 nominations.'
},
cast: [
'Leonardo DiCaprio',
'Kate Winslet',
'Billy Zane',
'Kathy Bates'
],
directors: [ 'James Cameron' ]
},
{
_id: ObjectId("61d11d5c168ab70a91d75c6e"),
title: 'Casablanca',
genres: [ 'Drama', 'Romance', 'War' ],
rated: 'PG',
cast: [
'Humphrey Bogart',
'Ingrid Bergman',
'Paul Henreid',
'Claude Rains'
],
languages: [ 'English', 'French', 'German', 'Italian' ],
released: ISODate("1943-01-23T00:00:00.000Z"),
directors: [ 'Michael Curtiz' ],
awards: {
wins: 9,
nominations: 6,
text: 'Won 3 Oscars. Another 6 wins & 6 nominations.'
},
lastupdated: '2015-09-04 00:22:54.600000000',
year: 1942
}
]</code></pre></figure>
<ul>
<li>执行如下命令查询获得超过100个奖项的movies</li>
</ul>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> db.movies.find( { "awards.wins": { $gt: 100 } } );
[
{
_id: ObjectId("61d11d5c168ab70a91d75c6b"),
title: 'Titanic',
year: 1997,
genres: [ 'Drama', 'Romance' ],
rated: 'PG-13',
languages: [ 'English', 'French', 'German', 'Swedish', 'Italian', 'Russian' ],
released: ISODate("1997-12-19T00:00:00.000Z"),
awards: {
wins: 127,
nominations: 63,
text: 'Won 11 Oscars. Another 116 wins & 63 nominations.'
},
cast: [
'Leonardo DiCaprio',
'Kate Winslet',
'Billy Zane',
'Kathy Bates'
],
directors: [ 'James Cameron' ]
},
{
_id: ObjectId("61d11d5c168ab70a91d75c6c"),
title: 'The Dark Knight',
year: 2008,
genres: [ 'Action', 'Crime', 'Drama' ],
rated: 'PG-13',
languages: [ 'English', 'Mandarin' ],
released: ISODate("2008-07-18T00:00:00.000Z"),
awards: {
wins: 144,
nominations: 106,
text: 'Won 2 Oscars. Another 142 wins & 106 nominations.'
},
cast: [
'Christian Bale',
'Heath Ledger',
'Aaron Eckhart',
'Michael Caine'
],
directors: [ 'Christopher Nolan' ]
}
]</code></pre></figure>
<ul>
<li>执行如下命令,查询语言为<code class="language-plaintext highlighter-rouge">Japanese</code>或<code class="language-plaintext highlighter-rouge">Mandarin</code>的movies</li>
</ul>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> db.movies.find( { "languages": { $in: [ "Japanese", "Mandarin" ] } } )
[
{
_id: ObjectId("61d11d5c168ab70a91d75c6c"),
title: 'The Dark Knight',
year: 2008,
genres: [ 'Action', 'Crime', 'Drama' ],
rated: 'PG-13',
languages: [ 'English', 'Mandarin' ],
released: ISODate("2008-07-18T00:00:00.000Z"),
awards: {
wins: 144,
nominations: 106,
text: 'Won 2 Oscars. Another 142 wins & 106 nominations.'
},
cast: [
'Christian Bale',
'Heath Ledger',
'Aaron Eckhart',
'Michael Caine'
],
directors: [ 'Christopher Nolan' ]
},
{
_id: ObjectId("61d11d5c168ab70a91d75c6d"),
title: 'Spirited Away',
year: 2001,
genres: [ 'Animation', 'Adventure', 'Family' ],
rated: 'PG',
languages: [ 'Japanese' ],
released: ISODate("2003-03-28T00:00:00.000Z"),
awards: {
wins: 52,
nominations: 22,
text: 'Won 1 Oscar. Another 51 wins & 22 nominations.'
},
cast: [
'Rumi Hiiragi',
'Miyu Irino',
'Mari Natsuki',
'Takashi Naitè'
],
directors: [ 'Hayao Miyazaki' ]
}
]</code></pre></figure>
<blockquote>
<p>Tips: <a href="https://docs.mongodb.com/manual/reference/operator/query/#std-label-query-projection-operators-top">Query and Projection Operators</a></p>
</blockquote>
<p>5) <strong>Specify Fields to Return (Projection)</strong></p>
<p>我们可以通过向db.collection.find(<query document="">, <projection document="">)方法传递```projection document```的方式来指定查询结果所要返回的字段。在```projection document```中指定:</projection></query></p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge"><field>: 1</code>表示需要在结果中返回对应的字段</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge"><field>: 0</code>表示在结果中不返回对应的字段</p>
</li>
</ul>
<p>在MongoDB Web Shell中执行如下查询,返回movies集合中的<code class="language-plaintext highlighter-rouge">id</code>、<code class="language-plaintext highlighter-rouge">title</code>、<code class="language-plaintext highlighter-rouge">directors</code>以及<code class="language-plaintext highlighter-rouge">year</code>字段:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> db.movies.find( { }, { "title": 1, "directors": 1, "year": 1 } );
[
{
_id: ObjectId("61d11d5c168ab70a91d75c6b"),
title: 'Titanic',
year: 1997,
directors: [ 'James Cameron' ]
},
{
_id: ObjectId("61d11d5c168ab70a91d75c6c"),
title: 'The Dark Knight',
year: 2008,
directors: [ 'Christopher Nolan' ]
},
{
_id: ObjectId("61d11d5c168ab70a91d75c6d"),
title: 'Spirited Away',
year: 2001,
directors: [ 'Hayao Miyazaki' ]
},
{
_id: ObjectId("61d11d5c168ab70a91d75c6e"),
title: 'Casablanca',
directors: [ 'Michael Curtiz' ],
year: 1942
}
]</code></pre></figure>
<p>默认情况下会自动返回<code class="language-plaintext highlighter-rouge">_id</code>,因此我们并不需要在查询中特别指定<code class="language-plaintext highlighter-rouge">_id</code>。如果我们不想返回该字段的话,那么将该字段设置为0即可。例如,执行如下查询命令返回所匹配的<code class="language-plaintext highlighter-rouge">title</code>和<code class="language-plaintext highlighter-rouge">genres</code>字段:</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> db.movies.find( { }, { "_id": 0, "title": 1, "genres": 1 } );
[
{ title: 'Titanic', genres: [ 'Drama', 'Romance' ] },
{ title: 'The Dark Knight', genres: [ 'Action', 'Crime', 'Drama' ] },
{
title: 'Spirited Away',
genres: [ 'Animation', 'Adventure', 'Family' ]
},
{ title: 'Casablanca', genres: [ 'Drama', 'Romance', 'War' ] }
]</code></pre></figure>
<p>6) <strong>Aggregate Data ($group)</strong></p>
<p>可以使用聚集(aggregation)来对documents中的值进行分组,然后返回结果。在MongoDB中Aggregation是由<a href="https://docs.mongodb.com/manual/aggregation/#std-label-aggregation-framework">aggregation pipeline</a>。</p>
<p>我们可以使用find()操作来获取数据,然而如果想做一些比<a href="https://docs.mongodb.com/manual/crud/#std-label-crud">CRUD operations</a>更复杂的操作的话,比如manipulate data、perform calculations、write more expressive queries,我们可以使用aggregation pipeline。</p>
<p>在Shell中,执行如下的aggregation pipeline可以统计每一个<code class="language-plaintext highlighter-rouge">genres</code>值的出现次数。</p>
<figure class="highlight"><pre><code class="language-string" data-lang="string">>>> db.movies.aggregate( [
{ $unwind: "$genres" },
{
$group: {
_id: "$genres",
genreCount: { $count: { } }
}
},
{ $sort: { "genreCount": -1 } }
] )
[
{ _id: 'Drama', genreCount: 3 },
{ _id: 'Romance', genreCount: 2 },
{ _id: 'Action', genreCount: 1 },
{ _id: 'Adventure', genreCount: 1 },
{ _id: 'Animation', genreCount: 1 },
{ _id: 'Crime', genreCount: 1 },
{ _id: 'Family', genreCount: 1 },
{ _id: 'War', genreCount: 1 }
]</code></pre></figure>
<p>上面命令在pipeline中使用了:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">$unwind</code>为genres数组中的每一个元素输出一个document</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">$group</code>与<code class="language-plaintext highlighter-rouge">$count</code>累加器计算genres中元素的出现次数,次数会被存放在<code class="language-plaintext highlighter-rouge">genreCount</code>字段中;</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">$sort</code>用于对输出结果根据genreCount字段按降序返回</p>
</li>
</ul>
<p><br />
<br />
<strong>[参看]</strong>:</p>
<ol>
<li>
<p><a href="https://www.mongodb.com/">mongodb官网</a></p>
</li>
<li>
<p><a href="https://www.mongodb.com/try/download/community">mongodb软件包官方下载</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/">mongodb官方文档</a></p>
</li>
<li>
<p><a href="https://www.mongodb.org.cn/">mongodb中文网</a></p>
</li>
<li>
<p><a href="https://github.com/mongodb">mongodb github</a></p>
</li>
<li>
<p><a href="https://www.mongodb.com/en/company">MongoDB公司介绍</a></p>
</li>
<li>
<p><a href="https://docs.mongoing.com/">MongoDB中文手册</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/command/">mongoDB数据库命令</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/core/authorization/#std-label-authorization">Role-Based Access Control</a></p>
</li>
<li>
<p><a href="https://docs.mongodb.com/manual/reference/method/">mongodb method</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
构建一个精简Linux
2020-03-31T00:00:00+00:00
/post/kernel/2020/03/31/kernel_intro_part3
<!-- more -->
<p><br />
<br /></p>
<p><strong>[参看]</strong></p>
<ol>
<li>
<p><a href="https://www.jb51.net/LINUXjishu/798229.html">如何从零开始制作一个linux iso镜像</a></p>
</li>
<li>
<p><a href="http://datascript.top/2020/09/13/linux_core/">构建一个精简的Linux操作系统</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/guoyiyan1987/article/details/80243324">如何使用busybox编译和生成最简linux根文件系统</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/LEON1741/article/details/78159754">浅谈linux中的根文件系统</a>
<br />
<br />
<br /></p>
</li>
</ol>
浅谈Linux根文件系统(转)
2020-03-31T00:00:00+00:00
/post/kernel/2020/03/31/kernel_intro_part2
<p>Linux中有一个让很多初学者都特别清除的概念,叫做<code class="language-plaintext highlighter-rouge">“根文件系统”</code>。我接触Linux前前后后也好几年了,但是对这个问题,至今也不是特别清楚,至少没法给出一个很全面很到位的解释。于是,今天我们就来理一理这个话题。</p>
<!-- more -->
<h2 id="1-先交代一下文件系统">1. 先交代一下文件系统</h2>
<p>在开始讨论根文件系统这个话题之前,我们必须首先交代一下文件系统这个概念。毕竟,根文件系统只是文件系统中一种比较特殊的形式而已。根据百度百科:</p>
<blockquote>
<p>文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。文件系统由三部分组成:文件系统的接口,对对象操作和管理的软件集合,对象及属性。从系统角度来看,文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。</p>
</blockquote>
<p>文件系统地重要性,我想大家都很清楚,不用多说了。这里有一句话,我觉得非常精辟而且到位地点出了文件系统在Linux中地重要性:</p>
<blockquote>
<p>尽管内核是Linux地核心,但文件却是用户与操作系统交互所采用地主要工具。这对Linux来说尤其如此,这是因为在UNIX传统中,它使用文件IO机制管理硬件设备和数据文件。</p>
</blockquote>
<h2 id="2-什么是根文件系统">2. 什么是根文件系统?</h2>
<p><br />
<br /></p>
<p><strong>[参看]</strong></p>
<ol>
<li><a href="https://blog.csdn.net/LEON1741/article/details/78159754">浅谈linux中的根文件系统</a>
<br />
<br />
<br /></li>
</ol>
linux kernel基本介绍
2020-03-31T00:00:00+00:00
/post/kernel/2020/03/31/kernel_intro_part1
<!-- more -->
<p><br />
<br /></p>
<p><strong>[参看]</strong></p>
<ol>
<li>
<p><a href="http://www.360doc.com/content/19/1103/16/36367108_870849346.shtml">一文看懂Linux内核</a></p>
</li>
<li>
<p><a href="https://www.kernel.org/doc/">Linux内核文档</a></p>
</li>
<li>
<p><a href="https://linux-kernel-labs.github.io/refs/heads/master/lectures/fs.html#lecture-objectives">Linux kernel labs</a></p>
</li>
<li>
<p><a href="https://www.kernel.org/">kernel官网</a></p>
</li>
<li>
<p><a href="http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/">kernel加速下载</a></p>
</li>
<li>
<p><a href="https://elixir.bootlin.com/linux/v2.6.31-rc1/source/mm/pdflush.c">linux内核源代码阅读</a></p>
</li>
<li>
<p><a href="https://www.cnblogs.com/youngerchina/p/5624457.html">Linux 3.2中回写机制的变革</a></p>
</li>
<li>
<p><a href="https://www.zhihu.com/question/439985037">从零开始学习linux内核</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>
微服务相关介绍
2020-01-01T00:00:00+00:00
/post/microservice/2020/01/01/ms-part1
<!-- more -->
<p><br />
<br />
<strong>[参看]:</strong></p>
<ol>
<li><a href="https://zhuanlan.zhihu.com/p/108846492">Envoy-入门介绍与xDS协议</a></li>
</ol>
<p><br />
<br />
<br /></p>
mac brewhome的安装
2019-08-19T00:00:00+00:00
/post/brewhome/2019/08/19/brewhome-install
<p>国内mac上安装brewhome时经常遇到各种问题,例如:</p>
<pre>
mac安装 homeBrew时报错,显示:curl: (7) Failed to connect to raw.githubusercontent.com port 443: Connection refused
</pre>
<h2 id="1-国内安装brewhome">1. 国内安装brewhome</h2>
<p>在知乎上找到这样的一个网址:已经替换为国内的网址可以正常安全访问下载:</p>
<pre>
# /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
</pre>
<p><br />
<br /></p>
<p><strong>[参看]</strong></p>
<ol>
<li><a href="https://www.bilibili.com/read/cv10229149">mac安装brew错误解决</a></li>
</ol>
<p><br />
<br />
<br /></p>
conful的使用
2019-06-03T00:00:00+00:00
/post/lb/2019/06/03/lb_conful_part1
<p>本文介绍一下conful的相关原理及使用。</p>
<!-- more -->
<p><br />
<br /></p>
<p><strong>[参看]</strong></p>
<ol>
<li>
<p><a href="https://baijiahao.baidu.com/s?id=1662186873931155593&wfr=spider&for=pc">GitHub 负载均衡器深度解析</a></p>
</li>
<li>
<p><a href="https://www.cnblogs.com/wikiz/p/10725072.html">负载均衡之nginx+consul(自动更新路由)</a></p>
</li>
<li>
<p><a href="https://www.cnblogs.com/wikiz/p/10821046.html">nginx+keepalived+consul 实现高可用集群</a></p>
</li>
</ol>
<p><br />
<br />
<br /></p>