MongoDB知识点提要

时间:2021-06-20 18:39:42   收藏:0   阅读:0

MongoDB概述

MongoDB是一款NoSQL类型的文档型数据库
NoSQL
NoSQL是一种非关系型DMS,不需要固定的架构,可以避免joins链接,并且易于扩展。NoSQL数据库用于具有庞大数据存储需求的分布式数据存储。NoSQL用于大数据和实时Web应用程序。

MongoDB特点

MongoDB存储方式

MongoDB是文档型数据库,文件以BSON格式存储在硬盘中。
BSON是JSON的一种二进制形式的存储格式。
注:MongoDB内部执行引擎为JS解释器,把文档存储成bson结构,在查询时,转换为JS对象,并可以通过熟悉的JS语法来操作。

MongoDB常用术语

MongoDB和RDBMS区别

SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins index 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

MongoDB聚合操作

Pipline操作
MongoDB的聚合管道(Pipeline)将MongoDB文档在一个阶段(Stage)处理完毕后将结果传递给下一个阶段(Stage)处理。阶段(Stage)操作是可以重复的。
表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其他的文档。

聚合框架中常用的Stages:

MongoDB数据逻辑结构

MongoDB数据逻辑结构分为数据库(database)、集合(collection)、文档(document)三层:

数据库

一个数据库中可以创建多个集合,原则上我们通常把逻辑相近的集合都放在一个数据库中,当然出于性能或者数据量的关系,也可能进行拆分。

在MongoDB中有几个内建的数据库:

集合

集合由若干条文档记录构成

文档

文档是MongoDB中数据的基本存储单元,它以一种叫做BSON文档的结构表示。BSON,即Binary JSON,多个键及其关联的值有序地存放在其中,类似映射,散列或字典。

MongoDB引擎-WiredTiger

WiredTiger(以下简称WT)是一个优秀的单机数据库存储引擎,它拥有诸多的特性,既支持BTree索引,也支持LSM Tree索引,支持行存储和列存储,实现ACID级别事务、支持大到4G的记录等。

WT的产生不是因为这些特性,而是和计算机发展的现状息息相关。
现代计算机近20年来CPU的计算能力和内存容量飞速发展,但磁盘的访问速度并没有得到相应的提高,WT就是在这样的一个情况下研发出来。

它设计了充分利用CPU并行计算的内存模型的无锁并行框架,使得WT引擎在多核CPU上的表现优于其他存储引擎。

针对磁盘存储特性,WT实现了一套基于BLOCK/Extent的友好的磁盘访问算法,使得WT在数据压缩和磁盘I/O访问上优势明显。

实现了基于snapshot技术的ACID事务,snapshot技术大大简化了WT的事务模型,摒弃了传统的事务锁隔离又同时能保证事务的ACID。

WT根据现代内存容量特性实现了一种基于Hazard Pointer 的LRU cache模型,充分利用了内存容量的同时又能拥有很高的事务读写并发。

存储引擎及常用数据结构

存储引擎要做的事情无外乎是将磁盘上的数据读到内存并返回给应用,或者将应用修改的数据由内存写到磁盘上。
目前大多数流行的存储引擎是基于B-Tree或LSM(Log Strctured Merge)Tree这两种数据结构来设计的。

B-Tree
像Oracle、SQL Server、DB2、MySQL(InnoDB)和PostgreSQL这些传统的关系数据库依赖的底层存储引擎是基于B-Tree开发的。
B-Tree可以在查找数据的过程中减少磁盘I/O的次数

LSM Tree
像Cassandra、Elasticsearch (Lucene)、Google Bigtable、Apache HBase、LevelDB和RocksDB这些当前比较流行的NoSQL数据库存储引擎是基于LSM开发的。

插件式兼容上述两种
当然有些数据库采用了插件式的存储引擎架构,实现了Server层和存储引擎层的解耦,可以支持多种存储引擎,如MySQL既可以支持B-Tree结构的InnoDB存储引擎,还可以支持LSM结构的RocksDB存储引擎。

对于MongoDB来说,也采用了插件式存储引擎架构,底层的WiredTiger存储引擎还可以支持B-Tree和LSM两种结构组织数据,但MongoDB在使用WiredTiger作为存储引擎时,目前默认配置是使用了B-Tree结构

B-Tree数据结构

在整个B-Tree中,从上往下依次为Root结点、内部结点和叶子结点,
每个结点就是一个Page,数据以Page为单位在内存和磁盘间进行调度,每个Page的大小决定了相应结点的分支数量,
每条索引记录会包含一个数据指针,指向一条数据记录所在文件的偏移量
技术图片

WiredTiger在磁盘上的基础数据结构

对于WiredTiger存储引擎来说,集合所在的数据文件和相应的索引文件都是按B-Tree结构来组织的,
不同之处在于数据文件对应的B-Tree叶子结点上除了存储键名外(keys),还会存储真正的集合数据(values),所以数据文件的存储结构也可以认为是一种b+ Tree
技术图片

从上图可以看到,B+ Tree中的leaf page包含一个页头(page header)、块头(block header)和真正的数据(key/value)
其中页头定义了页的类型、页中实际载荷数据的大小、页中记录条数等信息;块头定义了此页的checksum、块在磁盘上的寻址位置等信息

WiredTiger有一个块设备管理的模块,用来为page分配block。如果要定位某一行数据(key/value)的位置,可以先通过block的位置找到此page(相对于文件起始位置的偏移量),再通过page找到行数据的相对位置,最后可以得到行数据相对于文件起始位置的偏移量offsets。由于offsets是一个8字节大小的变量,所以WiredTiger磁盘文件的大小,其最大值可以非常大(264bit)

WiredTiger在内存上的基础数据结构

WiredTiger会按需将磁盘的数据以Page为单位加载到内存,同时在内存会构造相应的B-Tree来存储这些数据。

为了高效的支撑CRUD等操作以及将内存里面发生变化的数据持久化到磁盘上,WiredTiger也会在内存里面维护其它几种数据结构:
技术图片

page的其它数据结构

对于一个面向行存储的leaf page来说,包含的数据结构除了上面提到的WT_ROW(keys/values)、WT_UPDATE(修改数据)、WT_INSERT_HEAD(插入数据)外,还有如下几种重要的数据结构:

Page生命周期

数据以page为单位加载到cache、cache里面又会生成各种不同类型的page及为不同类型的page分配不同大小的内存、eviction触发机制和reconcile动作都发生在page上、page大小持续增加时会被分割成多个小page,所有这些操作都是围绕一个page来完成的。

Page的典型生命周期如下:
技术图片

第一步:pages从磁盘读到内存
第二步:pages在内存中被修改
第三步:被修改的脏pages在内存被reconcile(page被淘汰到底发生了什么),完成后将discard这些pages
第四步:pages被选中,加入淘汰队列,等待被evict(Page被淘汰的时机)线程淘汰出内存
第五步:evict线程会将“干净“的pages直接从内存丢弃(因为相对于磁盘page来说没做任何修改),将经过reconcile处理后的磁盘映像写到磁盘再丢弃“脏的”page

pages的状态是在不断变化的,因此,对于读操作来说,它首先会检查pages的状态是否为WT_REF_MEM,然后设置一个hazard指针指向要读的pages,如果刷新后,pages的状态仍为WT_REF_MEM,读操作才能继续处理。

与此同时,evict线程想要淘汰pages时,它会先锁住pages,即将pages的状态设为WT_REF_LOCKED,然后检查pages上是否有读操作设置的hazard指针,如有,说明还有线程正在读这个page则停止evict,重新将page的状态设置为WT_REF_MEM;如果没有,则pages被淘汰出去。

Page的各种状态

针对一页page的每一种状态,详细描述如下:

Page的大小参数

无论将数据从磁盘读到内存,还是从内存写到磁盘,都是以page为单位调度的,但是在磁盘上一个page到底多大?是否是最小分割单元?以及内存里面各种page的大小对存储引擎的性能是否有影响?
参考以上问题,page大小涉及的相关参数如下:

参数名称 默认配置值 含义
allocation_size 4kb 磁盘上最小分配单元
memory_page_max 5mb 内存中允许的最大page值
internal_page_max 4kb 磁盘上允许的最大internal page 值
leaf_page_max 32kb 磁盘上允许的最大leaf page 值
internal_key_max 1/10*internal_page internal page 上允许的最大key值
leaf_key_max 1/10*leaf_page leaf page上允许的最大key值
leaf_key_value 1/2*leaf_page leaf page上允许的最大value值
split_pct 75% reconciled的page的分割百分比

详细说明如下:

WiredTired Cache分配规则(Page淘汰前置原因)

WiredTired启动的时候会向操作系统申请一部分内存给自己使用,这部分内容我们称为Internal Cache,如果主机上只运行MongoDB相关的服务进程,则剩下的内存可以作为文件系统的缓存(File System Cache)并由操作系统负责管理
技术图片

MongoDB启动时,首先从整个主机内存中切一大块出来分给WiredTiger的Internal Cache,用于构建B-Tree中的各种page以及基于这些page的增删改查等操作。

从MongoDB3.4版本开始,默认的Internal Cache大小由下面的规则决定:比较50% of (RAM – 1 GB)和256MB的大小,取其中较大者。例如,假设主机内存为10GB,则Internal Cache取值为50% of (10GB – 1 GB),等于4.5GB;

如果主机内存为1.2GB,则Internal Cache取值为256MB。

然后,会从主机内存再额外划一小块给MongoDB创建索引专用,默认最大值为500MB,这个规则适用于所有索引的构建,包括多个索引同时构建时。

最后,会将主机剩余的内存(排除其它进程的使用)作为文件系统缓存,供MongoDB使用,这样MongoDB可将压缩的磁盘文件也缓存到内存中,从而减少磁盘I/O。

为了节省磁盘空间,集合和索引在磁盘上的数据是被压缩的,默认情况下集合采取的是块压缩算法,索引采取的是前缀压缩算法。因此,同一份数据在磁盘、文件系统缓存和Internal Cache三个位置的格式是不一样的,如下描述:

Page被淘汰的时机-Page淘汰机制(Page eviction)

当cache里面的"脏页"达到一定比例或cache使用量达到一定比例时就会触发相应的evict page线程来将pages(包含干净的pages和脏pages)按一定的算法(LRU队列)淘汰出去,以便腾挪出内存空间,保障后面新的插入或修改等操作。

参数名称 默认配置值 含义
eviction_target 80% 当cache的使用量达到80%时触发work thread 淘汰page
eviction_trigger 90% 当cache的使用量达到90%时触发application thread 和 work thread 淘汰page
eviction_dirty_target 5% 当脏数据所占cache比例达到5%时触发work thread 淘汰 page
eviction_dirty_trigger 20% 当脏数据所占cache比例达到20%时触发applicationthread和 work thread 淘汰page

第一种情况:当cache的使用量占比达到参数eviction_ target设定值时(默认为80%),会触发后台线程执行page eviction,此时应用线程未阻塞,读写操作仍在正常进行;

如果使用量继续增长达到eviction_trigger参数设定值时(默认为90%),应用线程支撑的读写操作等请求将被阻塞,应用线程也参与到页面的淘汰中,加速淘汰内存中pages。

第二种情况:当cache里面的“脏数据”达到参数eviction_dirty_target设定值时(默认为5%),会触发后台线程执行page eviction,此时应用线程未阻塞,读写操作仍在正常进行;

如果“脏数据”继续增长达到参数eviction_dirty_trigger设定值(默认为20%),同时会触发应用线程来执行page eviction,应用线程支撑的读写操作等请求将被阻塞

还有一种特性情况:当在page上不断进行插入或更新时,如果页上内容占用内存空间的大小大于系统设定的最大值(memory_page_max),则会强制触发page eviction动作。

先通过将此大的page拆分为多个小的page,再通过reconcile将这些小的pages保存到磁盘上,一旦reconcile写入磁盘完成,这些pages就能从cache中淘汰出去,从而为后面更多的写入操作腾出空间。

默认情况下WiredTiger只使用一个后台线程来完成page eviction,为了提升eviction的性能,我们可以通过参数threads_min和threads_max来设定evict server启动的后台线程数。

通过设定合理值,加速页面淘汰,避免淘汰不及时导致应用线程也被迫加入到淘汰任务中来,造成应用线程对其它正常请求操作的阻塞。

淘汰一个page时,会先锁住这个page,再检查这个page上是否有其它线程还在使用(判断是否有hazard point指针指向它),如有则不会evict这个page。

page被淘汰到底发生了什么(Page reconcile)

数据从磁盘page加载到内存后被查询和修改,被修改的数据和新插入的数据也需要从内存写到磁盘进行保存
WiredTiger实现了一个叫reconcile模块来完成将内存里面的修改的数据生成相应磁盘映像(与磁盘上的page格式匹配),然后再将这些磁盘映像写到磁盘上。
技术图片

checkpoint原理

Checkpoint主要有两个目的
一是将内存里面发生修改的数据写到数据文件进行持久化保存,确保数据一致性;
二是实现数据库在某个时刻意外发生故障,再次启动时,缩短数据库的恢复时间。
WiredTiger存储引擎中的Checkpoint模块就是来实现这个功能的。

本质上来说,checkpoint相当于一个日志,记录了上次Checkpoint后相关数据文件的变化
一个CheckPoint包含关键信息如下图所示:
技术图片
每个checkpoint包含一个root page、三个指向磁盘具体位置上pages的列表以及磁盘上文件的大小。

详细字段信息描述如下:
root page
包含rootpage的大小(size),在文件中的位置(offset),校验和(checksum),创建一个checkpoint时,会生成一个新root page。

allocated list pages
用于记录最后一次checkpoint之后,在这次checkpoint执行时,由WiredTiger块管理器新分配的pages,会记录每个新分配page的size,offset和checksum。

discarded list pages
用于记录最后一次checkpoint之后,在这次checkpoint执行时,丢弃的不在使用的pages,会记录每个丢弃page的size,offset和checksum。

available list pages
在这次checkpoint执行时,所有由WiredTiger块管理器分配但还没有被使用的pages;当删除一个之前创建的checkpoint时,它所附带的可用pages将合并到最新的这个checkpoint的可用列表上,也会记录每个可用page的size,offset和checksum。

file size
在这次checkpoint执行后,磁盘上数据文件的大小。

Checkpoint执行的完整流程

技术图片
流程描述如下:

Checkpoint执行的触发时机

触发checkpoint执行,通常有如下几种情况:

WiredTired事务实现

WT在实现事务的时使用主要是使用了三个技术:snapshot(事务快照)、MVCC (多版本并发控制)和redo log(重做日志),为了实现这三个技术,它还定义了一个基于这三个技术的事务对象和全局事务管理器。

事务对象描述如下

wt_transaction{

    transaction_id:    本次事务的**全局唯一的ID**,用于标示事务修改数据的版本号

    snapshot_object:   当前事务开始或者操作时刻其他正在执行且并未提交的事务集合,用于事务隔离

    operation_array:   本次事务中已执行的操作列表,用于事务回滚。

    redo_log_buf:      操作日志缓冲区。用于事务提交后的持久化

    state:             事务当前状态

}

WT的事务快照

事务开始或者进行操作之前对整个WT引擎内部正在执行或者将要执行的事务进行一次截屏,保存当时整个引擎所有事务的状态,确定哪些事务是对自己见的,哪些事务都自己是不可见。

WT引擎中的snapshot_oject是有一个最小执行事务snap_min、一个最大事务snap max和一个处于[snap_min, snap_max]区间之中所有正在执行的写事务序列组成。

WT的多版本并发控制

WT中的MVCC是基于key/value中value值的链表,这个链表单元中存储有当先版本操作的事务ID和操作修改后的值。

描述如下

wt_mvcc{

    transaction_id:    本次修改事务的ID

    value:             本次修改后的值

}

WT中的数据修改都是在这个链表中进行append操作,每次对值做修改都是append到链表头上,每次读取值的时候读是从链表头根据值对应的修改事务transaction_id和本次读事务的snapshot来判断是否可读,如果不可读,向链表尾方向移动,直到找到读事务能都的数据版本。

全局事务管理器

要创建整个系统事务的快照截屏,就需要一个全局的事务管理来进行事务截屏时的参考。

wt_txn_global{

     current_id:       全局写事务ID产生种子,一直递增

     oldest_id:        系统中最早产生且还在执行的写事务ID

     transaction_array: 系统事务对象数组,保存系统中所有的事务对象

     scan_count:     正在扫描transaction_array数组的线程事务数,用于建立snapshot过程的无锁并发

}

transaction_array保存的是正在执行事务的区间的事务对象序列。在建立snapshot时,会对整个transaction_array做扫描,确定snap_min/snap_max/snap_array这三个参数和更新oldest_id,在扫描的过程中,凡是transaction_id不等于WT_TNX_NONE都认为是在执行中且有修改操作的事务,直接加入到snap_array当中。整个过程是一个无锁操作过程,这个过程如下:

创建snapshot截屏的过程在WT引擎内部是非常频繁,尤其是在大量自动提交型的短事务执行的情况下,由创建snapshot动作引起的CPU竞争是非常大的开销,所以这里WT并没有使用spin lock ,而是采用了上图的一个无锁并发设计,这种设计遵循了我们开始说的并发设计原则。

从WT引擎创建事务snapshot的过程中现在可以确定,snapshot的对象是有写操作的事务,纯读事务是不会被snapshot的,因为snapshot的目的是隔离mvcc list中的记录,通过MVCC中value的事务ID与读事务的snapshot进行版本读取,与读事务本身的ID是没有关系。在WT引擎中,开启事务时,引擎会将一个WT_TNX_NONE( = 0)的事务ID设置给开启的事务,当它第一次对事务进行写时,会在数据修改前通过全局事务管理器中的current_id来分配一个全局唯一的事务ID。这个过程也是通过CPU的CAS_ADD原子操作完成的无锁过程。

MongoDB副本集及分片集群

MongoDB副本集

为什么要引入复制集
保证数据在生产部署时的冗余和可靠性,通过在不同机器上保存副本来保证数据不会因为单点损失而丢失。能够随时应对数据丢失,机器损坏带来的风险。

在MongoDB中就是复制集(replica set): 一组复制集就是一组mongodb实例掌管同一个数据集,实例可以在不同的机器上面。实例中包含一个主导,接受客户端所有的写入操作,其他都是副本实例,从主服务器上获得数据并保持同步。

副本集是一组维护相同数据集合的mongod实例。副本集包含多个数据承载节点和一个可选的仲裁节点。在数据承载节点中,有且仅有一个成员为主节点,其他节点为副本节点。
主节点接收所有的写操作。一个副本集仅有一个主节点能够用写关注点级别来确认写操作,虽然在某些情况下,另一个mongod的实例也可以暂时认为自己是主节点。

复制集成员:
主节点(Primary)
包含了所有的写操作的日志。但是副本服务器集群包含所有的主服务器数据,因此当主服务器挂掉了,就会在副本服务器上重新选取一个成为主服务器。

从节点(Seconary)
正常情况下,复制集的Seconary会参与Primary选举(自身也可能会被选为Primary),并从Primary同步最新写入的数据,以保证与Primary存储相同的数据。

Secondary可以提供读服务,增加Secondary节点可以提供复制集的读服务能力,同时提升复制集的可用性。另外MongoDB支持对复制集的Secondary节点进行灵活的配置,以适应多种场景的需求。

仲裁节点(Arbiter)
Arbiter节点只参与投票,不能被选为Primary,并且不从Primary同步数据。

仲裁节点永远只能是仲裁节点,但在选举过程中主节点也许会降级成为副本节点,副本节点也可能会升级成主节点。

复制集是如何保证数据高可用的

  1. 选举机制
  2. 故障转移期间的回滚

选举机制

复制集通过选举机制来选择主节点

如何选出Primary主节点的?

假设复制集内能够投票的成员数量为N,则大多数为N/2 + 1,当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。

在什么情况下会触发选举机制?

在以下的情况下会触发选举机制:

异步复制及慢操作

副本节点复制主节点的oplog并异步地应用操作到它们的数据集。通过让副本节点的数据集反映主服务器的数据集,副本集可以在一个或多个成员失败的情况下继续运行。

从4.2版本开始(从4.0.6开始也是可行的),副本集的副本成员会记录oplog中应用时间超过慢操作阈值的慢操作条目。这些慢oplog信息被记录在从节点的诊断日志 中,其路径位于REPL 组件的文本applied op: took ms中。这些慢日志条目仅仅依赖于慢操作阈值。它们不依赖于日志级别(无论是系统还是组件级别)、过滤级别,或者慢操作采样比例。过滤器不会捕获慢日志条目。

自动故障转移

当主节点无法和集群中的其他节点通信的时间超过electionTimeoutMillis配置的期限时(默认10s),一个候选的副本节点会发起选举来推荐自己成为新主节点。集群会尝试完成一次新主节点的选举并恢复正常的操作。

副本集在选举成功前是无法处理写操作的。如果读请求被配置运行在副本节点上,则当主节点下线时,副本集可以继续处理这些请求。

假设采用默认的副本配置选项,集群选择新主节点的中间过渡时间通常不应超过12秒。这包括了将主节点标记为unavailable、发起以及完成一次选举的时间。可以通过修改settings.electionTimeoutMillis 复制配置选项来调整这个时间期限。网络延迟等因素可能会延长完成副本集选举所需的时间,从而影响集群在没有主节点的情况下运行的时间。这些因素取决于实际的集群架构情况。

将electionTimeoutMillis复制配置选项从默认的10000(10秒)降低可以更快地检测主节点故障。然而,由于诸如临时性的网络延迟等因素,集群可能会更频繁地发起选举,即使主节点在其他方面是健康的。这也许会增加w : 1 级别写操作发生回滚的可能性。

应用程序连接逻辑应该包括对自动故障转移和后续选举的容错处理能力。从MongoDB 3.6开始,MongoDB驱动程序可以探测到主节点的丢失,并自动重试某些写操作一次,提供额外的自动故障转移和选举的内置处理:
MongoDB 4.2兼容的驱动程序默认启用可重试写
MongoDB 4.0和3.6兼容的驱动程序必须通过在 连接字符串中包含retryWrites=true来显式地启用可重试写。

复制集中的OpLog

oplog(操作日志)是一个特殊的有上限的集合(老的日志会被overwrite),它保存所有修改数据库中存储的数据的操作的滚动记录。

什么是Oplog

MongoDB在主节点上应用数据库操作,然后将这些操作记录到optlog中。然后从节点通过异步进程复制和应用(数据同步)这些操作。在local.oplog.rs集合中,所有复制集成员都包含oplog的一个副本用来维护数据库的当前状态。

MongoDB 4.4支持以小时为单位指定最小操作日志保留期,其中MongoDB仅在以下情况下删除操作日志条目:

复制集中的数据同步(重点)

复制集中的数据同步是为了维护共享数据集的最新副本,包括复制集的辅助成员同步或复制其他成员的数据。 MongoDB使用两种形式的数据同步:

初始同步(Initial Sync)

从节点当出现如下状况时,需要先进行全量同步

这3个场景分别对应:

Initial Sync流程
  1. 全量同步开始,设置minvalid集合的_initialSyncFlag

  2. 获取同步源上最新oplog时间戳为t1

  3. 全量同步集合数据 (耗时)

  4. 获取同步源上最新oplog时间戳为t2

  5. 重放[t1, t2]范围内的所有oplog

  6. 获取同步源上最新oplog时间戳为t3

  7. 重放[t2, t3]范围内所有的oplog

  8. 建立集合所有索引 (耗时)

  9. 获取同步源上最新oplog时间戳为t4

  10. 重放[t3, t4]范围内所有的oplog

  11. 全量同步结束,清除minvalid集合的_initialSyncFlag

注:

  1. 重试流程:如果同步期间碰到一些标记为RetriableError的错误,则会进行重试,直到重试超过initialSyncTransientErrorRetryPeriodSeconds才会标记为永久性错误。这个时候,会进行切换sync source并重试整个initial sync的流程
  2. Oplog拉取(Oplog Fetching)
复制(Replication)

initial sync结束后,接下来Secondary就会『不断拉取主上新产生的optlog并重放』

问题来了,为什么一个简单的『拉取oplog并重放』的动作要搞得这么复杂?
性能考虑,拉取oplog是单线程进行,如果把重放也放到拉取的线程里,同步势必会很慢;所以设计上producer thread只干一件事。

为什么不将拉取的oplog直接分发给replWriter thread,而要多一个replBatcher线程来中转?
oplog重放时,要保持顺序性,而且遇到createCollection、dropCollection等DDL命令时,这些命令与其他的增删改查命令是不能并行执行的,而这些控制就是由replBatcher来完成的。

优化方案(重点)

副本集帮助我们解决读请求扩展、高可用等问题。那么业务场景进一步增长,会出现以下问题:

此时我们需要对MongoDB做分片集群

MongoDB分片集群

分片为应对高吞吐量与大数据量提供了方法。使用分片减少了每个分片需要处理的请求数,因此,通过水平扩展,集群可以提高自己的存储容量和吞吐量。举例来说,当插入一条数据时,应用只需要访问存储这条数据的分片.

MongoDB分片集群是对数据进行水平扩展的一种方式。
MongoDB使用分片集群支持对大数据集和高吞吐量的业务场景

MongoDB分片集群的基本架构

MongoDB分片集群包括以下组件:

MongoDB分片集群(Sharded Cluster)通过将数据分散存储到多个分片(Shard)上,来实现高可扩展性。实现分片集群时,MongoDB引入Config Server来存储集群的元数据,引入mongos作为应用访问的入口,mongos从Config Server读取路由信息,并将请求路由到后端对应的Shard上。

注:

MongoDB分片集群连接Connection String

Connection String URI: mongodb://[username:password@][host1:port1],[host2:port2],...,[hostN:portN]/[database][?options]

通过Java连接的demo:

MongoClientURI connectionString = new MongoClientURI("mongodb://:****@s-m5e80a9241323604.mongodb.rds.aliyuncs.com:3717,s-m5e053215007f404.mongodb.rds.aliyuncs.com:3717/admin"); // ****替换为root密码
MongoClient client = new MongoClient(connectionString);
MongoDatabase database = client.getDatabase("mydb");
MongoCollection collection = database.getCollection("mycoll");

在访问分片集群时,请务必确保 MongoDB URI 里包含2个及以上的mongos地址,来实现负载均衡及高可用

常用连接参数(options):

集合的数据分布

Primary Shard
默认情况下,每个DataBase的集合都是未分片的,存储在一个固定的Shard上,称为Primary Shard
当创建一个新的DataBase时,系统会根据各个Shard目前存储的数据量,选择一个数据量最小的Shard作为新Database的Primary Shard

MongoDB将数据进行分片支持集合级别,已经被分片的集合被切分成多份保存到Shard上。

集合分片键与Chunk管理

chunk
在一个shard server内部,MongoDB还是会把数据分为chunks,每个chunk代表这个shard server内部一部分数据。

chunk的产生,会有以下两个用途:

MongoDB基于ShardKey将Collection拆分为多个子集,每个子集称为一个chunk
ShardedCollection的数据按照ShardKey划分为minKey~maxKey的区间
每个chunk有自己负责的一个区间(前闭后开)
存储ShardedCollection的Shard上有该Collection的一个或多个chunk

chunk分裂
chunk的分裂和迁移非常消耗IO资源;在插入和更新,读数据不会分裂。

调整chunkSize

use config;
db.settings.save({_id:"chunksize",value:<sizeInMB>});

chunksize的选择:
小的chunksize:数据均衡是迁移速度快,数据分布更均匀。数据分裂频繁,路由节点消耗更多资源。
大的chunksize:数据分裂少。数据块移动集中消耗IO资源。通常100-200M

chunkSize 对分裂及迁移的影响

chunk迁移

Targeted Operations & Broadcast Operations
特定目标的操作(Targeted Operations) - 根据分片键计算出目标Shard(s),发起请求并返回结果(实现包含分片键的查询操作,基于分片键的更新、删除、插入操作)
广播的操作(Broadcast Operations) - 将请求发送给所有Shard,合并查询结果并返回给客户端(实现不包含分片键的查询操作,基于_id字段的更新、删除操作)

MongoDB的分片分为范围分片及哈希分片
范围分片 - 根据ShardKey的值进行数据分片,将单个Collection的数据分散存储在多个shard上,用户可以指定根据集合内文档的某个字段即shard key来进行范围分片(range sharding)。
哈希分片 - 根据ShardKey计算哈希值,基于哈希值进行数据分片,基于哈希片键最大的好处就是保证数据在各个节点分布基本均匀。

参考资料

MongoDB教程 - Mongo知识体系详解 --- @pdai
WiredTiger存储引擎系列 --- 郭远威
干货分享| MongoDB 中文社区2021长沙大会

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!