|
作者:hazenweng,腾讯QQ音乐后台开发工程师MongoDB作为一款优秀的基于分布式文件存储的NoSQL数据库,在业界有着广泛的应用。下文对MongoDB的一些基础概念进行简单介绍。1MongoDB特点面向集合存储:MongoDB是面向集合的,数据以collection分组存储。每个collection在数据库中都有唯一的名称。模式自由:集合的概念类似MySQL里的表,但它不需要定义任何模式。结构松散:对于存储在数据库中的文档,不需要设置相同的字段,并且相同的字段不需要相同的数据类型,不同结构的文档可以存在同一个collection里。高效的二进制存储:存储在集合中的文档,是以键值对的形式存在的。键用于唯一标识一个文档,一般是ObjectId类型,值是以BSON形式存在的。BSON=BinaryJSON,是在JSON基础上加了一些类型及元数据描述的格式。支持索引:可以在任意属性上建立索引,包含内部对象。MongoDB的索引和MySQL的索引基本一样,可以在指定属性上创建索引以提高查询的速度。除此之外,MongoDB还提供创建基于地理空间的索引的能力。支持mapreduce:通过分治的方式完成复杂的聚合任务。支持failover:通过主从复制机制,可以实现数据备份、故障恢复、读扩展等功能。基于复制集的复制机制提供了自动故障恢复的功能,确保了集群数据不会丢失。支持分片:MongoDB支持集群自动切分数据,可以使集群存储更多的数据,实现更大的负载,在数据插入和更新时,能够自动路由和存储。支持存储大文件:MongoDB中BSON对象最大不能超过16MB。对于大文件的存储,BSON格式无法满足。GridFS机制提供了一个存储大文件的机制,可以将一个大文件分割成为多个较小的文档进行存储。2MongoDB要素database:数据库。collection:数据集合,相当于MySQL的table。document:数据记录行,相当于MySQL的row。field:数据域,相当于MySQL的column。index:索引。primarykey:主键。3MongoDB数据库一个MongoDB实例可以创建多个database。连接时如果没开启免认证模式的话,需要连接到admin库进行认证。如果开启免认证模式,若不指定database进行连接,默认连接一个叫db的数据库,该数据库存储在data目录中。通过showdbs命令可以查看所有的数据库。数据库名不能包含空字符。数据库名不能为空并且必须小于64个字符。MongoDB预留了几个特殊的database。admin:admin数据库主要是保存root用户和角色。例如,system.users表存储用户,system.roles表存储角色。一般不建议用户直接操作这个数据库。将一个用户添加到这个数据库,且使它拥有admin库上的名为dbAdminAnyDatabase的角色权限,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如关闭服务器。local:local数据库是不会被复制到其他分片的,因此可以用来存储本地单台服务器的任意collection。一般不建议用户直接使用local库存储任何数据,也不建议进行CRUD操作,因为数据无法被正常备份与恢复。config:当MongoDB使用分片设置时,config数据库可用来保存分片的相关信息。一个MongoDB实例的数据结构如下图:4MongoDB集合MongoDB集合存在于数据库中,没有固定的结构,可以往集合插入不同格式和类型的数据。集合不需要事先创建。当第一个文档插入,或者第一个索引创建时,集合就会被创建。集合名必须以下划线或者字母符号开始,并且不能包含$,不能为空字符串(比如""),不能包含空字符,且不能以system.为前缀。cappedcollection是固定大小的集合,支持高吞吐的插入操作和查询操作。它的工作方式与循环缓冲区类似,当一个集合填满了被分配的空间,则通过覆盖最早的文档来为新的文档腾出空间。和标准的collection不同,cappedcollection需要显式创建,指定大小,单位是字节。cappedcollection可以按照文档的插入顺序保存到集合中,而且这些文档在磁盘上存放位置也是按照插入顺序来保存的,所以更新cappedcollection中的文档,不可以超过之前文档的大小,以便确保所有文档在磁盘上的位置一直保持不变。5MongoDB视图视图基于已有的集合进行创建,是只读的,不实际存储硬盘,通过视图进行写操作会报错。视图使用其上游集合的索引。由于索引是基于集合的,所以你不能基于视图创建、删除或重建索引,也不能获取视图的索引列表。如果视图依赖的集合是分片的,那么视图也视为分片的。视图是实时计算并读取的。6MongoDB索引MongoDB支持丰富的索引方式。如果没有索引,读操作就必须扫描集合中的每个文档并筛选符合查询条件的记录。索引能够在很大程度上提高查询速度。单字段索引:有三种方式,(1)在单个字段上创建索引;(2)在嵌入式字段上创建索引;(3)在内嵌文档上创建索引。复合索引:支持在多个字段上匹配的查询。对任何复合索引施加32个字段的限制。对于复合索引,MongoDB可以使用索引来支持对索引前缀的查询。多键索引:为了索引包含数组值的字段,MongoDB为数组中的每个元素创建一个索引键。这些多键索引支持对数组字段的高效查询。文本索引:支持对字符串内容的文本搜索查询。文本索引可以包含任何值为字符串或字符串元素数组的字段。一个集合最多可以有一个文本索引。通配符索引:支持针对未知或任意字段的查询。例如:db.collection.createIndex({"a.$**":1})可支持诸如db.collection.find({"a.b":1})、db.collection.find({"a.c":{$lt:2}})等查询,提高查询效率。不能使用通配符索引来分片集合。不能为通配符创建复合索引。通配符文本索引:通配符文本索引不同于通配符索引。通配符索引不支持使用$text操作符的查询。通配符文本索引为集合中每个文档中包含字符串数据的每个字段建立索引。索引的创建方式示例:db.collection.createIndex({"$**":"text"})。2dsphere索引:支持球体上的地理空间查询:包含、相交和邻近度查询。hashed索引:支持使用哈希的分片键进行分片。基于哈希的分片使用字段的散列索引作为分片键,以便跨分片集群对数据进行分区。MongoDB支持任何单个字段的哈希索引,但不支持创建具有多个哈希字段的复合索引,也不能在索引上指定唯一哈希索引。ttl索引:一种特殊的单字段索引,支持在一定的时间或特定的期限后自动从集合中删除文档。TTL索引不能保证过期数据在过期时立即删除。默认每60秒运行一次删除过期文档的后台进程。cappedcollection不支持ttl索引。唯一索引:确保索引字段不会存储重复值。如果集合已经存在了违反索引的唯一约束的文档,则后台创建唯一索引会失败。部分索引:只索引集合中满足指定筛选器表达式的文档。例如:db.collection.createIndex({a:1},{partialFilterExpression:{b:{$lt:100}}})表示只对集合中b字段小于100的文进行索引,大于等于100的文档不会被索引。这可以有效提高存储效率。稀疏索引:只包含有索引字段的文档的条目,即使索引字段包含空值。索引会跳过任何缺少索引字段的文档。非稀疏索引包含集合中的所有文档,为那些不包含索引字段的文档存储空值。7MongoDBObjectIdObjectId可以快速生成并排序,长度为12个字节,包括:一个4字节的时间戳,表示unix时间戳5字节随机值3字节递增计数器,初始化为随机值在MongoDB中,存储在集合中的每个文档都需要一个唯一的_id字段作为主键。如果插入的文档省略了_id字段,则自动为文档生成一个_id。8MongoDB复制集MongoDB的复制集又称为副本集(ReplicaSet),是一组维护相同数据集合的mongod进程。复制集包含多个数据节点和一个可选的仲裁节点(arbiter)。在数据节点中,有且仅有一个成员为主节点(primary),其他节点为从节点(secondary)。一个典型的复制集架构图如下:8.1复制集节点类型主节点:接收所有的写操作,并将集合所有的变化记录到操作日志中,即oplog。从节点:通过复制主节点的操作来维护一个相同的数据集。从节点有几个选配项:v参数决定是否具有投票权;priority参数决定节点选主过程时的优先级;hidden参数决定是否对客户端可见;slaveDelay参数表示复制n秒之前的数据,保持与主节点的时间差。从节点可以配置成0优先级,阻止它在选举中成为主节点,适用于将该节点部署在备用数据中心,或者将它作为一个冷节点;可以配置为隐藏复制集,防止应用程序从它读取数据,适用于在该节点上运行需要与正常流量分离的程序;可以配置为延迟复制集,保持一个历史快照,以便做按特定时间的故障恢复。仲裁节点:如果将一个mongod实例作为仲裁节点添加到一个复制集中,该节点可以参与主节点选举,但不保存数据。仲裁节点永远只能是仲裁节点。8.2复制集选主MongoDB的副本集协议(又称为pv1),是一种raft-like协议,即基于raft协议的理论思想实现,并且对之进行了一些扩展。当往复制集添加一个节点,或当主节点无法和集群中其他节点通信的时间超过参数electionTimeoutMillis配置的期限时,从节点会尝试通过pv1协议发起选举来推荐自己成为新主节点。在选举前具有投票权的节点之间两两互相发送心跳,以侦测节点是否存活。复制集节点每两秒向彼此发送心跳。如果心跳未在10秒内返回,则发送心跳的一方将被发送方标记为不可访问,也就是说,默认当5次心跳未收到时判断为节点失联。如果失联的是主节点,从节点会发起选举,选出新的主节点;如果失联的是从节点则不会产生新的选举。选举基于raft一致性算法实现,在大多数投票节点存活下选举出主节点。只有能够与多数节点建立连接且具有较新的oplog的节点才可能被选举为主节点,如果集群里的节点配置了优先级,那么具有较高的优先级的节点更可能被选举为主节点。复制集中最多可以有50个节点,但具有投票权的节点最多7个。8.3复制集作用主节点发生故障时自动选举出一个新的主节点,以实现failover。将数据从一个数据中心复制到另一个数据中心,减少另一个数据中心的读延迟。实现读写分离。实现容灾,可以在数据中心故障时快速切换到同城或异地的数据中心。9MongoDB分片集MongoDB支持通过分片技术来支持海量数据存储。解决数据增长的扩展方式有两种:垂直扩展和水平扩展。垂直扩展通过增加单个服务器的能力来实现,比如磁盘空间、内存容量、CPU数量等;水平扩展则通过将数据存储到多个服务器上来实现。MongoDB通过分片实现水平扩展。一个典型的分片集群架构如下:9.1分片集组件shard:每个分片上可以保存一个集合的子集,所有分片上的子集的数据互不相交,构成完整的集合。每个分片可以被部署为复制集架构。最大为1024个分片。mongos:充当查询路由器,在客户端和分片集之间提供读写接口。mongos提供集群单一入口,转发应用端请求,选择合适的数据节点进行读写,合并多个数据节点的返回。mongos是无状态的,分片集群一般需要配置至少2个mongos。configserver:存储分片集的相关配置信息。9.2分片键MongoDB集合若要采用分片,必须要指定分片键(shardkey)。分片键由文档中的一个或多个字段组成。分片集合必须具有支持分片键的索引,索引可以是分片键的索引,也可以是以分片键是索引前缀的复合索引。要对已填充的集合进行分片,该集合必须具有以分片键开头的索引;分片一个空集合时,如果该集合还没有包含指定分片键的索引,则MongoDB会默认给分片键创建索引。对于一个即将要分片的集合,如果该集合具有其他唯一索引,则无法分片该集合。对于已分片的集合,不能在其他字段上创建唯一索引。4.2版本开始可以更改文档的分片键值,除非分片键字段为不可变的_id字段。更新分片键时必须在事务中或以可重试写入的方式在mongos上运行,不能直接在分片上执行操作。在此之前文档的分片键字段值是不可变的。4.4版本开始,可以向现有片键中添加一个或多个后缀字段以优化集合的片键。5.0版本开始,实现了实时重新分片(liveresharding),可以实现分片键的完全重新选择。liveresharding机制下,数据将根据新的分片规则进行迁移,不过有一些限制,比如一个实例中有且只能有一个集合在相同的时间下resharding等。数据库可以混合使用分片和未分片集合。分片集合被分区并分布在集群中的各个分片中。而未分片集合仅存储在主分片中。设置shardkey时应该充分考虑取值基数和取值分布。分片键应被尽可能多的业务场景用到。尽可能避免使用单调递增或递减的字段作为分片键。9.3分片策略MongoDB将分片数据拆分成块。每个分块都有一个基于分片键的上下限范围。分片策略包括哈希分片、范围分片和自定义zone分片。哈希分片会计算分片键字段的哈希值,这个值被用作片键,然后根据哈希值的散列为每个块分配一个范围。范围分片根据分片键的值将数据划分为多个连续范围。,然后基于分片键的值分配每个块的范围。当片键的基数大、频率低且值非单调变更时,范围分片更高效。自定义zone分片基于shardkey创建。每个zone与集群中的一个或者更多分片关联。一个分片可以和任意数目的非冲突zone相关联。10MongoDB聚合MongoDB聚合框架(AggregationFramework)是一个计算框架,功能是:作用在一个或几个集合上。对集合中的数据进行的一系列运算。将这些数据转化为期望的形式。MongoDB提供了三种执行聚合的方法:聚合管道,map-reduce和单一目的聚合方法(如count、distinct等方法)。10.1聚合管道在聚合管道中,整个聚合运算过程称为管道(pipeline),它是由多个步骤(stage)组成的,每个管道的工作流程是:接受一系列原始数据文档对这些文档进行一系列运算结果文档输出给下一个stage聚合计算基本格式如下:pipeline = [$stage1, $stage2, ...$stageN]; db.collection.aggregate( pipeline, { options } )10.2map-reducemap-reduce操作包括两个阶段:map阶段处理每个文档并将key与value传递给reduce函数进行处理,reduce阶段将map操作的输出组合在一起。map-reduce可使用自定义JavaScript函数来执行map和reduce操作,以及可选的finalize操作。通常情况下效率比聚合管道低。10.3单一目的聚合方法主要包括以下三个:db.collection.estimatedDocumentCount()db.collection.count()db.collection.distinct()11MongoDB一致性分布式系统有个PACELC理论。根据CAP,在一个存在网络分区(P)的分布式系统中,要面临在可用性(A)和一致性(C)之间的权衡,除此之外(E),即使没有网络分区的存在,在实际系统中,我们也要面临在访问延迟(L)和一致性(C)之间的权衡。MongoDB的一致性模型对读写操作L和C的选择提供了丰富的选项。11.1因果一致性单节点的数据库由于为读写操作提供了顺序保证,因此实现了因果一致性。分布式系统同样可以提供这些保证,但必须对所有节点上的相关事件进行协调和排序。以下是一个不遵循因果一致性的例子:为了保持因果一致性,必须有以下保证:实现因果一致性的单号读写应遵循以下流程:为了建立复制集和分片集事件的全局偏序关系,MongoDB实现了一个逻辑时钟,称为lamportlogicalclock。每个写操作在应用于主节点时都会被分配一个时间值。这个值可以在副本和分片之间进行比较。从驱动到查询路由器再到数据承载节点,分片集群中的每个成员都必须在每条消息中跟踪和发送其最新时间值,从而允许分片之间的每个节点在最新时间保持一致。主节点将最新的时间值赋值给后续的写入,这为任何一系列相关操作创建了一个因果顺序。节点可以使用这个因果顺序在执行所需的读或写之前等待,以确保它在另一个操作之后发生。从MongoDB3.6开始,在客户端会话中开启因果一致性,保证readconcern为majority的读操作和writeconcern为majority的写操作的关联序列具有因果关系。应用程序必须确保一次只有一个线程在客户端会话中执行这些操作。对于因果相关的操作:客户端开启客户端会话,需满足以下条件:readconcern为majority,数据已被大多数复制集成员确认并且是持久化的;writeconcern为majority,确认该操作已应用于复制集中大多数可投票成员。当客户端发出readconcern为majority的读操作和writeconcern为majority的写操作的序列时,客户端将会话信息包含在每个操作中。对于与会话相关联的每个readconcern为majority的读操作和writeconcern为majority的写操作,即使操作出错,MongoDB也会返回操作时间和集群时间。相关的客户端会话会跟踪这两个时间字段。11.2线性一致性线性一致性又被称为强一致性。CAP中的C指的就是线性一致性。顺序一致性中进程只关心各自的顺序一样就行,不需要与全局时钟一致。线性一致性是顺序一致性的进化版,要求顺序一致性的这种偏序(partialorder)要达到全序(totalorder)。在实现了线性一致性的系统中,任何操作在该系统生效的时刻都对应时间轴上的一个点。把这些时刻连接成一条线,则这条线会一直沿时间轴向前,不会反向。任何操作都需要互相比较决定发生的顺序。以下是一个线性一致性的系统示例:在以上系统中,写操作生效之前的任何时刻,读取值均为1,生效后均为2。也就是说,任何读操作都能读到某个数据的最近一次写的数据。系统中的所有进程看到的操作顺序,都遵循全局时钟的顺序。11.3readconcernreadconcern是针对读操作的配置。它控制读取数据的新近度和持久性。readconcern选项控制数据读取的一致性,分为local、available、majority、linearizable四种,它们对一致性的承诺依次由弱到强。其中linearizable表示线性一致性,另外3种级别代表了MongoDB在实现最终一致性时,对访问延迟和一致性的取舍。local/available:语义基本一致,都是读操作直接读取本地最新的数据,但不保证该数据已被写入大多数复制集成员。数据可能会被回滚。默认是针对主节点读。如果读取操作与因果一致的会话相关联,则针对副节点读。唯一的区别在于,avaliable在分片集群场景下,为了保证性能,可能返回孤儿文档。majority:读取majoritycommitted的数据,可以保证读取的数据不会被回滚,但是并不能保证读到本地最新的数据。受限于不同节点的复制进度,可能会读取到更旧的值。当写操作对应的writeconcern配置中w的值越大,则写操作在扩散到更多的复制集节点上之后才返回写成功,这时通过readconcern被配置为majority的读操作进行读取数据,就有更大的概率读取到最新的数据。linearizable:读取majoritycommitted的数据,但会等待在读之前所有的majoritycommitted确认。它承诺线性一致性,要求读写顺序和操作真实发生的时间完全一致,既保证能读取到最新的数据,也保证读到数据不会被回滚。只对读取单个文档时有效,且可能导致非常慢的读,因此总是建议配合使用maxTimeMS使用。linearizable只能用在主节点的读操作上,考虑到写操作也只能发生在主节点上,相当于说MongoDB的线性一致性被限定在单机环境下实现。实现linearizable,读取的数据应该是被writeconcern为majority的写操作写入到MongoDB集群中的、且持久化到日志中的数据。如果数据写入到多数节点后,没有在日志中持久化,当这些节点发生重启恢复,那么之前通过配置readconcern为linearizable的读操作读取到的数据就可能丢失。可以通过writeConcernMajorityJournalDefault选项保证指定writeconcern为majority的写操作在日志中是否持久化。如果写操作持久化到了日志中,但是没有复制到多数节点,在重新选主后,同样可能会发生数据丢失,违背一致性承诺。snapshot:与关系型数据库中的快照隔离级别语义一致。最高隔离级别,接近于serializable。是伴随着MongoDB4.0版本中新出现的多文档事务而设计的,只能用在显式开启的多文档事务中。如果事务是因果一致会话的一部分,且writeconcern为majority,则在事务提交后,读操作可以保证已从多数提交数据的快照中读取,该快照提供与该事务开始之前的操作的因果一致性。它读取majoritycommitted的数据,但可能读不到最新的已提交数据。snapshot保证在事务中的读不出现脏读、不可重复读和幻读。因为所有的读都将使用同一个快照,直到事务提交为止该快照才被释放。下面借用一张图展示majority和linearizable的区别:11.4writeconcernwriteconcern是针对写操作的配置,表示写请求对独立mongod实例或复制集或分片集进行写操作的确认级别。它主要是控制数据写入的持久性。包含三个选项:w:指定了写操作需要复制并应用到多少个复制集成员才能返回成功,可以为数字或majority。w:0表示客户端不需要收到任何有关写操作是否执行成功的确认,就直接返回成功,具有最高性能。w:1表示写主成功则返回。w:majority需要收到多数节点(含主节点)关于操作执行成功的确认,具体个数由MongoDB根据复制集配置自动得出。w值越大,对客户端来说,数据的持久性保证越强,写操作的延迟越大。w:1要求事务只要在本地成功提交即可,而w:majority要求事务在复制集的多数派节点提交成功。w:all表示全部节点确认才返回成功。j:表示写操作对应的修改是否要被持久化到存储引擎日志中,只能选填true或false。j:false表示写操作到达内存即算作成功。j:true表示写操作落到journal文件中才算成功。w:0如果指定j:true,则优先使用j:true来请求独立或复制集主副本的确认。j:true本身并不能保证不会因复制集主故障转移而回滚写操作。wtimeout:主节点在等待足够数量的确认时的超时时间,单位为毫秒。超时返回错误,但并不代表写操作已经执行失败。跟w有关,比如:w是1,则是带主节点确认的超时时间;w为0,则永不返回错误;w为majority,表示多数节点确认的超时时间。12MongoDBWiredTiger引擎从3.2版本开始,默认使用WiredTiger存储引擎,每个被创建的表和索引,都对应各自独立的WiredTiger表。为了保证MongoDB中数据的持久性,使用WiredTiger的写操作会先写入cache,并持久化到WAL(writeaheadlog),每60s或日志文件达到2GB,就会做一次checkpoint,定期将缓存数据刷到磁盘,将当前的数据持久化产生一个新的快照。12.1WiredTiger数据结构MongoDB采用插件式存储引擎架构,实现了服务层和存储引擎层的解耦,可支持使用多种存储引擎。除此之外,底层的WiredTiger引擎还支持使用B+树和LSM两种数据结构进行数据管理和存储,默认使用B+树结构做存储。使用B+树时,WiredTiger以page为单位往磁盘读写数据,B+树的每个节点为一个page,包含三种类型的page,即rootpage、internalpage和leafpage。以下是B+树的结构示意图:rootpage是B+树的根节点。internalpage是不实际存储数据的中间索引节点。leafpage是真正存储数据的叶子节点,包含页头(pageheader)、块头(blockheader)和真正的数据(key-value对)。pageheader定义了页的类型、页存储的记录条数等信息;块头定义了页的校验和checksum、块在磁盘上的寻址位置等信息。真正的数据由一个WT_ROW结构的数组变量进行存储,每一条记录还有一个cell_offset变量,表示这条记录在page上的偏移量。WiredTiger有一个用来为page分配block的块设备管理模块。定位文档位置时,先计算block的位置,通过block的位置找到它对应的page,再通过page找到文档行数据的相对位置。leafpage为了实现MVCC,还会维护一个WT_UPDATE结构的数组变量,每条记录对应一个数组元素,每个元素是一个链表,将所有修改值以链表形式保存。12.2WiredTiger压缩WiredTiger支持在内存和磁盘上对索引进行压缩,通过前缀压缩的方式减少RAM的使用。12.3WiredTiger一致性原理WiredTiger使用了二级缓存WiredTigerCache和FileSystemCache来保证Disk上DatabaseFile数据的最终一致性。WiredTigerCache:通过B+树缓存未压缩的数据,并通过淘汰算法确保内存占用在合理范围内。FileSystemCache:由操作系统管理,缓存压缩后的数据。DatabaseFile:存储压缩后的数据。每个WiredTiger表对应一个独立的磁盘文件。磁盘文件划分成多个按4KB对齐的extent,并通过3个链表来管理:availablelist(可分配的extent列表),discardlist(废弃的extent列表)和allocatelist(当前已分配的extent列表)12.4WiredTigerMVCCWiredTiger使用MVCC进行写操作,多个客户端可以并发同时修改集合的不同文档。事务开始时,WiredTiger为操作提供反映内存数据的一致视图的时间点快照。MVCC通过非锁机制进行读写操作,是一种乐观并发控制模式。WiredTiger仅在全局、数据库和集合级别使用意向锁。当存储引擎检测到两个操作之间存在冲突时,将引发写冲突,从而导致MongoDB自动重试该操作。使用WiredTiger,如果没有journal记录,MongoDB能且仅能从最后一个检查点恢复。如果需要恢复最后一次checkpoint之后所做的更改,那么开启日志是必要的。13MongoDB数据读写13.1读偏好ReadPerference默认情况下,客户端读取复制集主节点上的数据。但客户端可以指定一个readperference改变读取行为,以便对复制集上的其他节点进行直接读操作。可选值包括:13.2在复制集上进行读写操作读操作由客户端指定的readprefenence选项决定。所有的写操作都在集合的主节点上执行。主节点执行写操作并将操作记录在操作日志或oplog上。oplog是local数据库的一个集合,叫local.oplog.rs。这是一个cappedcollection,是固定大小,循环使用的。oplog是对数据集的可重复操作序列,其记录的每个操作都是幂等的,也就是说,对目标数据集应用一次或多次oplog操作都会产生相同的结果。从节点从上一次结束时间点建立tailablecursor,不断的从同步源拉取oplog并重放应用到自身,且严格按照原始的写顺序对给定的文档执行写操作。mongodb使用多线程批量执行写操作来提高并发,根据文档id进行分批执行。MongoDB为了提升同步效率,将拉取oplog以及重放oplog分到了不同的线程来执行。大致的写流程如下:producerthread不断的从主节点上拉取oplog,并把它加入到一个blockQueue里,blockQueue不是无限容量的,当超过最大存储容量,producerthread就必须等到oplog被replBatcherthread从队列里取出后才能继续拉取oplog。replBatcherthread不断从producerthread对应的blockQueue里取出oplog,放到自己的内存队列里,内存队列也不是无限容量,一旦满了,就需要等待被oplogApplicationthread消费。oplogApplicationthread不断取出replBatchthread内存队列里的所有元素,分散到不同的replWriterthread,由replWriterthread根据oplog进行写操作。等待所有oplog都应用完毕,oplogApplicationhread将所有的oplog顺序写入到local.oplog.rs集合。13.3在分片集群上进行读写操作对于分片集群,需要一个mongos实例提供客户端应用程序和分片集群之间的接口。在客户端看来,该mongos实例的行为与其他MongoDB实例是相同的。客户端向路由节点mongos发送请求,由该节点决定往哪个分片进行读写。对于读取操作,若能定向到特定分片时,效率最高。一般而言,分片集合的查询应包含集合的分片键,以避免低效的全分片查询。在这种情况下,mongos可以使用配置数据库config中的集群元数据信息,将查询路由到分片。如果查询不包含分片键,则mongos节点必须将查询定向到集群中的所有分片,然后在mongos上聚合所有分片的查询结果,返回给客户端。对于写操作,mongos定向到负责数据集特定部分的分片,config数据库上有集合相关的分片键信息,mongos从中读取配置,并路由写操作到适当的分片。14MongoDB事务14.1ACID特性MongoDB在一定程度上支持了事务的ACID特性。MongoDB4.0版本开始支持复制集上的多文档事务,4.2版本引入了分布式事务,它增加了对分片群集上多文档事务的支持。原子性:成功提交事务时,事务中所有数据更新将完全进行成功,并在事务外部可见。在提交事务之前,事务外部看不到在事务中进行的任何数据更新。当事务被打断或终止时,事务中进行的所有数据更新都将被丢弃,对事务外部完全不可见。但是当事务写入多个分片时,并非所有事务外的读操作都需要等待事务提交后所有分片上数据完全可见。隔离性:MongoDB提供snapshot隔离级别,在事务开始创建一个WiredTigersnapshot,然后在整个事务过程中,便可以使用这个快照提供事务读。持久性:事务使用writeconcern指定{j:true}时,MongoDB会保证事务日志提交才返回,即使发生crash,也能根据事务日志来恢复;而如果没有指定{j:true}级别,即使事务提交成功了,在故障恢复之后,事务的也可能被回滚掉。一致性:参考前文提到的MongoDB一致性。14.2事务的使用限制仅WiredTiger引擎支持事务。对集合的创建和删除操作,不能出现在事务中。对索引的创建和删除操作,不能出现在事务中。不能对系统级别的数据库和集合进行操作。默认情况下,事务大小的限制在16MB。默认情况下,事务操作整体不允许超过60秒。事务不能在session外运行。一个session只能运行一个事务,多个session可以并行运行事务。不能对cappedcollection进行操作。不能使用explain操作做查询分析。14.3事务与readconcern事务中的操作使用事务级别的readconcern。事务内部忽略在集合和数据库级别设置的任何readconcern。事务支持设置readconcern为local、majority和snapshot其中之一。当readconcern为local时,可读取节点可用的最新数据,但数据可能回滚。对于分片群集上的事务,local不能保证数据是从整个分片的同一快照视图获取。当readconcern为majority时,如果在提交事务时指定了writeconcern为majority级别,则返回大多数副本成员已确认的数据(即无法回滚数据)。如果事务未指定writeconcern为majority级别,则不保证读操作可以读取多数提交的数据。对于分片群集上的事务,不能保证数据是从整个分片的同一快照视图中获取。当readconcern为snapshot时,如果在提交事务时指定了writeconcern为majority级别,则从大多数已提交数据的快照中返回数据。如果事务未指定writeconcern为majority级别,则不保证读操作使用了majoritycommited的数据的快照。对于分片群集上的事务,snapshot跨分片同步。14.4事务与writeconcern事务使用事务级别的writeconcern来进行写操作提交,可以通过配置w选项设置节点个数,来决定事务写入是否成功,默认情况下为1。w:0表示事务写入不关注是否成功,默认为成功。w:1表示事务写入到主节点就开始往客户端发送确认写入成功。w:majority表示大多数节点成功原则,例如一个复制集3个节点,2个节点成功就认为本次事务写入成功。w:all表示所有节点都写入成功,才认为事务提交成功。j:false表示写操作到达内存就算事务成功。j:true表示写操作只有记录到日志文件才算事务成功。wtimeout:写入超时时间,过期表示事务失败。15MongoDBChangeStream15.1变更流使用场景MongoDB3.6引入了changestream(变更流)。它的使用场景包括:数据同步:多个MongoDB集群之间的增量数据同步。审计:对MongoDB操作进行审计、监控。数据订阅:外部程序订阅MongoDB的数据变更,可离线数据同步、计算或分析等。15.2变更流特点changestream允许外部程序访问实时数据更改,而不会增加MongoDB基础操作的复杂性,也不会导致oplog延迟的风险。应用程序可以使用changestream来订阅单个集合、数据库或整个集群中的所有数据变更。若要开启changestream,必须使用WiredTiger存储引擎。changestream可应用于复制集和分片集。应用于复制集时,可以在复制集中任意一个节点上开启监听;应用于分片集时,则只能在mongos上开启监听。在mongos上发起监听,是利用全局逻辑时钟提供了整个分片上变更的总体排序,确保监听事件可以按接收到的顺序安全地解释。mongos会一直检查每个分片,查看每个分片是否存在最新的变更。如果多个分片上一直很少出现变更,则可能会对changestream的响应时间产生负面影响,因为mongos仍必须检查这些冷分片保持总体有序。15.3变更流监听事件类型从changestream中能监听到的变更事件包括:insert、update、replace、delete、drop、rename、dropDatabase和invalidate。15.4变更流故障恢复MongoDB4.0之后,可以通过指定startAtOperationTime来控制从某个特定的时间点开启监听,但该时间点必须在所选择节点的有效oplog时间范围内。changestream监听返回的字段中有个_id字段,表示的是resumetoken,这是唯一标志changestream流中的位置的字段。如果changestream监听比中止后需要继续监听,那么可指定resumeAfter恢复订阅。指定resumeAfter为changestream中断处的_id字段即可。当监听的集合发生rename、drop或dropDatabase事件,就会导致invalidate事件;当监听的数据库出现dropDatabase事件,也会导致无效事件。invalidate事件后changestream的游标会被关闭,这时就需要使用resumeAfter选项来恢复changestream的监听,在4.2版本后也可以通过startAfter选项创建新的更改流来恢复监听。15.5变更流使用限制changestream无法配置到系统库或者system.xxx表上。changestream依赖于oplog,因此中断时间不可超过oplog回收的最大时间窗。16MongoDB性能问题定位方式可以为mongod实例启用数据库分析。数据库分析器既可以在实例上启用,也可以在单个数据库层面上启用。它收集在实例上执行的CRUD操作、游标、命令、配置等详细信息,并将它收集的所有数据写到system.profile集合。这是一个cappedcollection,默认情况下,system.profile容量大小为4M。开启实时数据库分析往往伴随着副作用,请谨慎使用。使用db.currentOp()操作。它返回一个文档,其中包含有关数据库实例正在进行的操作的信息。使用db.serverStatus()命令。它返回一个文档,提供数据库状态的概述,通过它可以收集有关该实例的统计信息。使用explain来评估查询性能,例如cursor.explain()或db.collection.explain()方法可以用来返回关于查询执行的信息。借用一些商业工具,比如MongoDBOpsManager、Percona等。腾讯程序员视频号最新视频欢迎点赞
|
|