我学习分布式mongodb,做了这些笔记

1,003 阅读23分钟

分布式

集群

image-20211013082800790

架构说明

  • 数据分片(Shards) 分片用于存储真正的集群数据,可以是一个单独的 Mongod实例,也可以是一个副本集。 生产环境下Shard一般是一个 Replica Set,以防止该数据片的单点故障。 对于分片集合(sharded collection)来说,每个分片上都存储了集合的一部分数据(按照分片键切分),如果集合没有分片,那么该集合的数据都存储在数据库的 Primary Shard中。
  • 配置服务器(Config Servers) 保存集群的元数据(metadata),包含各个Shard的路由规则,配置服务器由一个副本集(ReplicaSet)组成。
  • 查询路由(Query Routers) Mongos是 Sharded Cluster 的访问入口,其本身并不持久化数据 。Mongos启动后,会从 Config Server 加载元数据,开始提供服务,并将用户的请求正确路由到对应的Shard。 Sharding 集群可以部署多个 Mongos 以分担客户端请求的压力。

分片机制

分片集群的组成

构建一个 MongoDB 的分片集群,需要三个重要的组件,分别是分片服务器(Shard Server)、配置服务器(Config Server)和路由服务器(Route Server)。

image-20211015091114709

Shard Server

每个 Shard Server 都是一个 mongod 数据库实例,用于存储实际的数据块。整个数据库集合分成多个块存储在不同的 Shard Server 中。

在实际生产中,一个 Shard Server 可由几台机器组成一个副本集来承担,防止因主节点单点故障导致整个系统崩溃。

Config Server

这是独立的一个 mongod 进程,保存集群和分片的元数据,在集群启动最开始时建立,保存各个分片包含数据的信息。

Route Server(Mongos)

这是独立的一个 mongos 进程,Route Server 在集群中可作为路由使用,客户端由此接入,让整个集群看起来像是一个单一的数据库,提供客户端应用程序和分片集群之间的接口。

Route Server 本身不保存数据,启动时从 Config Server 加载集群信息到缓存中,并将客户端的请求路由给每个 Shard Server,在各 Shard Server 返回结果后进行聚合并返回客户端。

以上介绍了 MongoDB 的三种集群模式,副本集已经替代了主从复制,通过备份保证集群的可靠性,分片机制为集群提供了可扩展性,以满足海量数据的存储和分析的需求。

在实际生产环境中,副本集和分片是结合起来使用的,可满足实际应用场景中高可用性和高可扩展性的需求。

数据如何切分

基于分片切分后的数据块称为 chunk,一个分片后的集合会包含多个 chunk,每个 chunk 位于哪个分片(Shard) 则记录在 Config Server(配置服务器)上。 Mongos 在操作分片集合时,会自动根据分片键找到对应的 chunk,并向该 chunk 所在的分片发起操作请求。

数据是根据分片策略来进行切分的,而分片策略则由 分片键(ShardKey)+分片算法(ShardStrategy)组成。

MongoDB 支持的分片算法

  • 范围分片

image-20211013083403109

如上图所示,假设集合根据x字段来分片,x的取值范围为[minKey, maxKey](x为整型,这里的minKey、maxKey为整型的最小值和最大值),将整个取值范围划分为多个chunk,每个chunk(默认配置为64MB)包含其中一小段的数据: 如Chunk1包含x的取值在[minKey, -75)的所有文档,而Chunk2包含x取值在[-75, 25)之间的所有文档...

范围分片能很好的满足范围查询的需求,比如想查询x的值在[-30, 10]之间的所有文档,这时 Mongos 直接能将请求路由到 Chunk2,就能查询出所有符合条件的文档。

缺点:如果 ShardKey 有明显递增(或者递减)趋势,则新插入的文档多会分布到同一个chunk,无法扩展写的能力,比如使用_id作为 ShardKey,而MongoDB自动生成的id高位是时间戳,是持续递增的。

  • 哈希分片

image-20211013083617068

Hash分片是根据用户的 ShardKey 先计算出hash值(64bit整型),再根据hash值按照范围分片的策略将文档分布到不同的 chunk。 由于 hash值的计算是随机的,因此 Hash 分片具有很好的离散性,可以将数据随机分发到不同的 chunk 上。

Hash 分片可以充分的扩展写能力,弥补了范围分片的不足,但不能高效的服务范围查询,所有的范围查询要查询多个 chunk 才能找出满足条件的文档。

如何保证均衡

如前面的说明中,数据是分布在不同的 chunk上的,而 chunk 则会分配到不同的分片上,那么如何保证分片上的 数据(chunk) 是均衡的呢?

在真实的场景中,会存在下面两种情况:

  • A. 全预分配,chunk 的数量和 shard 都是预先定义好的,比如 10个shard,存储1000个chunk,那么每个shard 分别拥有100个chunk。 此时集群已经是均衡的状态(这里假定)
  • B. 非预分配,这种情况则比较复杂,一般当一个 chunk 太大时会产生分裂(split),不断分裂的结果会导致不均衡;或者动态扩容增加分片时,也会出现不均衡的状态。 这种不均衡的状态由集群均衡器进行检测,一旦发现了不均衡则执行 chunk数据的搬迁达到均衡。

MongoDB 的数据均衡器运行于 Primary Config Server(配置服务器的主节点)上,而该节点也同时会控制 Chunk 数据的搬迁流程。

image-20211013083812939

对于数据的不均衡是根据两个分片上的 Chunk 个数差异来判定的,阈值对应表如下:

image-20211013083836899

MongoDB 的数据迁移对集群性能存在一定影响,这点无法避免,目前的规避手段只能是将均衡窗口对齐到业务闲时段。

应用高可用

应用节点可以通过同时连接多个 Mongos 来实现高可用,如下:

image-20211014080920875

当然,连接高可用的功能是由 Driver 实现的。

副本集

副本集可以作为 Shard Cluster 中的一个Shard(片)之外,对于规模较小的业务来说,也可以使用一个单副本集的方式进行部署。 MongoDB 的副本集采取了一主多从的结构,即一个Primary Node + N* Secondary Node的方式,数据从主节点写入,并复制到多个备节点。

典型的架构如下:

image-20211014081039357

利用副本集,我们可以实现::

  • 数据库高可用,主节点宕机后,由备节点自动选举成为新的主节点;
  • 读写分离,读请求可以分流到备节点,减轻主节点的单点压力。

请注意,读写分离只能增加集群"读"的能力,对于写负载非常高的情况却无能为力。 对此需求,使用分片集群并增加分片,或者提升数据库节点的磁盘IO、CPU能力可以取得一定效果。

选举

MongoDB 副本集通过 Raft 算法来完成主节点的选举,这个环节在初始化的时候会自动完成,如下面的命令:

config = {
    _id : "my_replica_set",
    members : [
        {_id : 0, host : "rs1.example.net:27017"},
        {_id : 1, host : "rs2.example.net:27017"},
        {_id : 2, host : "rs3.example.net:27017"},
  ]
}
rs.initiate(config)

initiate 命令用于实现副本集的初始化,在选举完成后,通过 isMaster()命令就可以看到选举的结果:

> db.isMaster()

{
    "hosts" : [
    "192.168.100.1:27030",
    "192.168.100.2:27030",
    "192.168.100.3:27030"
    ],
    "setName" : "myReplSet",
    "setVersion" : 1,
    "ismaster" : true,
    "secondary" : false,
    "primary" : "192.168.100.1:27030",
    "me" : "192.168.100.1:27030",
    "electionId" : ObjectId("7fffffff0000000000000001"),
    "ok" : 1
}

受 Raft算法的影响,主节点的选举需要满足"大多数"原则,可以参考下表:

image-20211014081526828

因此,为了避免出现平票的情况,副本集的部署一般采用是基数个节点,比如3个,正所谓三人行必有我师..

心跳

在高可用的实现机制中,心跳(heartbeat)是非常关键的,判断一个节点是否宕机就取决于这个节点的心跳是否还是正常的。 副本集中的每个节点上都会定时向其他节点发送心跳,以此来感知其他节点的变化,比如是否失效、或者角色发生了变化。 利用心跳,MongoDB 副本集实现了自动故障转移的功能,如下图:

image-20211014081614352

默认情况下,节点会每2秒向其他节点发出心跳,这其中包括了主节点。 如果备节点在10秒内没有收到主节点的响应就会主动发起选举。 此时新一轮选举开始,新的主节点会产生并接管原来主节点的业务。 整个过程对于上层是透明的,应用并不需要感知,因为 Mongos 会自动发现这些变化。 如果应用仅仅使用了单个副本集,那么就会由 Driver 层来自动完成处理。

复制

主节点和备节点的数据是通过日志(oplog)复制来实现的,这很类似于 mysql 的 binlog。 在每一个副本集的节点中,都会存在一个名为local.oplog.rs的特殊集合。 当 Primary 上的写操作完成后,会向该集合中写入一条oplog, 而 Secondary 则持续从 Primary 拉取新的 oplog 并在本地进行回放以达到同步的目的。

下面,看看一条 oplog 的具体形式:

{
"ts" : Timestamp(1446011584, 2),
"h" : NumberLong("1687359108795812092"),
"v" : 2,
"op" : "i",
"ns" : "test.nosql",
"o" : { "_id" : ObjectId("563062c0b085733f34ab4129"), "name" : "mongodb", "score" : "100" }
}

其中的一些关键字段有:

  • ts 操作的 optime,该字段不仅仅包含了操作的时间戳(timestamp),还包含一个自增的计数器值。
  • h 操作的全局唯一表示
  • v oplog 的版本信息
  • op 操作类型,比如 i=insert,u=update..
  • ns 操作集合,形式为 database.collection
  • o 指具体的操作内容,对于一个 insert 操作,则包含了整个文档的内容

MongoDB 对于 oplog 的设计是比较仔细的,比如:

  • oplog 必须保证有序,通过 optime 来保证。
  • oplog 必须包含能够进行数据回放的完整信息。
  • oplog 必须是幂等的,即多次回放同一条日志产生的结果相同。
  • oplog 集合是固定大小的,为了避免对空间占用太大,旧的 oplog 记录会被滚动式的清理。

事务一致性

一直以来,"不支持事务" 是 MongoDB 一直被诟病的问题,当然也可以说这是 NoSQL 数据库的一种权衡(放弃事务,追求高性能、高可扩展) 但实质上,MongoDB 很早就有事务的概念,但是这个事务只能是针对单文档的,即单个文档的操作是有原子性保证的。 在4.0 版本之后,MongoDB 开始支持多文档的事务:

  • 4.0 版本支持副本集范围的多文档事务。
  • 4.2 版本支持跨分片的多文档事务(基于两阶段提交)。

在事务的隔离性上,MongoDB 支持快照(snapshot)的隔离级别,可以避免脏读、不可重复读和幻读。 尽管有了真正意义上的事务功能,但多文档事务对于性能有一定的影响,应用应该在充分评估后再做选用。

一致性

一致性是一个复杂的话题,而一致性更多从应用角度上提出的,比如:

向系统写入一条数据,应该能够马上读到写入的这个数据。

在分布式架构的CAP理论以及许多延续的观点中提到,由于网络分区的存在,要求系统在一致性和可用性之间做出选择,而不能两者兼得。

image-20211014082511130

在 MongoDB 中,这个选择是可以由开发者来定的。 MongoDB 允许客户端为其操作设定一定的级别或者偏好,包括:

  • read preference 读取偏好,可指定读主节点、读备节点,或者是优先读主、优先读备、取最近的节点
  • write concern 写关注,指定写入结果达到什么状态时才返回,可以为无应答(none)、应答(ack),或者是大多数节点完成了数据复制等等
  • read concern 读关注,指定读取的数据版本处于怎样的状态,可以为读本地、读大多数节点写入,或者是线性读(linearizable)等等。

使用不同的设定将会产生对于C(一致性)、A(可用性)的不同的抉择,比如:

  • 将读偏好设置为 primary,此时读写都在主节点上。 这保证了数据的一致性,但一旦主节点宕机会导致失败(可用性降低)
  • 将读偏好设置为 secondaryPrefered,此时写主,优先读备,可用性提高了,但数据存在延迟(出现不一致)
  • 将读写关注都设置为 majority(大多数),一致性提升了,但可用性也同时降低了(节点失效会导致大多数写失败)

关于这种权衡的讨论会一直存在,而 MongoDB 除了提供多样化的选择之外,其主要是通过复制、基于心跳的自动failover等机制来降低系统发生故障时产生的影响,从而提升整体的可用性。

数据块chunk

mongodb 默认配置下,每个chunk大小为16MB。超过该大小就需要执行chunk分裂。chunk分裂是由mongos发起的,而数据放在mongod处,因此mongos无法准确判断每个增删改操作后某个chunk的数据实际大小。因此mongos采用了一种启发式的触发分裂方式:

mongos在内存中记录一份 chunk_id -> incr_delta 的哈希表。

对于insert和update操作,估算出incr_delta的上界(WriteOp::targetWrites), 当incr_delta超过阈值时,执行chunk分裂。

值得注意的是:

  • chunk_id->incr_delta 是维护在mongos内存里的一份数据,重启后丢失

  • 不同mongos之间的这份数据相互独立

  • 不带shardkey的update 无法对 chunk_id->incr_delta 作用

因此这个启发式的分裂方式很不精确,而除了手工以命令的方式分裂之外,这是mongos自带的唯一的chunk分裂方式。

当Chunk增长到一定的数据大小(默认为64m)的时候,MongoDB会对Chunk进行分裂,简称Chunk Split,分裂方式可以分为手动和自动,由Balancer触发迁移;

MongoDB将文档分组为块,每个块由给定片键特定范围内的文档组成,一个块只存在一个分片上。在进行写入和删除的操作的时候,块内的文档数量和大小可能会发生变化,某个块增长到一定程度,MongoDB会自动将其拆分为两个较小的块。

mongos 上有个 sharding.autoSplit 的配置项,可用于控制是否自动触发 chunk 分裂,默认是开启的。如无专业人士指导,强烈建议不要关闭 autoSplit,更好的方式是使用「预分片」的方式来提前分裂,后面会详细介绍。

mongoDB 的自动 chunk 分裂只会发生在 mongos 写入数据时,当写入的数据超过一定量时,就会触发 chunk 的分裂,写入数据时,当 chunk 上写入的数据量,超过分裂阈值时,就会触发 chunk 的分裂,chunk 分裂后,当出现各个 shard 上 chunk 分布不均衡时,就会触发 chunk 迁移。

均衡器

均衡器负责数据迁移,周期性的检查分片是否存在不均衡,如果不存在则会开始块的迁移,config.locks集合里的state表示均衡器是否找正在运行,0表示非活动状态,2表示正在均衡。均衡迁移数据的过程会增加系统的负载:目标分片必须查询源分片的所有文档,将文档插入目标分片中,再清除源分片的数据。可以关闭均衡器(不建议):关闭会导致各分片数据分布不均衡,磁盘空间得不到有效的利用。

Balancer 运行在 ConfigServer 的 Primary 节 点。 默认为 开启状态。

基础常识

垂直扩容(Scale Up) VS 水平扩容(Scale Out):

垂直扩容 : 用更好的服务器,提高 CPU 处理核 数、内存数、带宽等

水平扩容 : 将任务分配到多台计算机上

分片标签

无论是选择哈希分片还是范围分片,都无法决定chunk所在的位置。换句话说,分片策略只影响数据所在的chunk,而chunk所在的分片则是由均衡器来调整的,这具有非常大的随机性。那么,是否存在干预的手段呢?答案是有的。MongoDB允许通过为分片添加标签(tag)的方式来控制数据分发。一个标签可以关联到多个分片区间(TagRange)。如此达到的结果是,均衡器会优先考虑chunk是否正处于某个分片区间上(被完全包含),如果是则会将chunk迁移到分片区间所关联的分片,否则按一般情况处理。

读缓存

MongoDB可以提供近似内存式的读写性能。WiredTiger引擎实现了数据的二级缓存,第一层是操作系统的页面缓存,第二层则是引擎提供的内部缓存

读取数据时的流程如下:

● 数据库发起Buffer I/O读操作,由操作系统将磁盘数据页加载到文件系统的页缓存区。

● 引擎层读取页缓存区的数据,进行解压后存放到内部缓存区。

● 在内存中完成匹配查询,将结果返回给应用。

可以看出,如果数据已经被存储在内部缓存中,MongoDB则可以发挥最佳的读性能。稍差的情况是内部缓存中找不到,但数据仍然被存储在操作系统的页缓存中,此时需要花费一些数据解压缩的开销。直接从磁盘加载数据的性能是最差的,因此MongoDB为了尽可能保证业务查询的“热数据”能快速被访问,其内部缓存的默认大小达到了内存的一半,该值由wiredTigerCacheSize参数指定

写缓冲

当数据发生写入时,MongoDB并不会立即持久化到磁盘上,而是先在内存中记录这些变更,之后通过CheckPoint机制将变化的数据写入磁盘。为什么要这么处理?主要有以下两个原因。

● 如果每次写入都触发一次磁盘I/O,那么开销太大,而且响应时延会比较大。

● 多个变更的写入可以尽可能进行I/O合并,降低资源负荷。

高频面试题

解释什么是MongoDB?

Mongo-DB是一个文档数据库,一种非关系型数据库,可提供高性能,高可用性和易扩展性。

什么是非关系型数据库

非关系型数据库是对不同于传统关系型数据库的统称,非关系型数据库的显著特点是不使用SQL作为查询语言,数据存储不需要特定的表格模式。

由于简单的设计和非常好的性能所以被用于大数据和Web Apps等。

NoSQL与RDBMS的区别?什么时候选用NoSQL数据库?

NoSQL是非关系型数据库,NoSQL = Not Only SQL。

关系型数据库采用的结构化的数据,NoSQL采用的是键值对的方式存储数据。

在处理非结构化/半结构化的大数据时;在水平方向上进行扩展时;随时应对动态增加的数据项时可以优先考虑使用NoSQL数据库。

在考虑数据库的成熟度;支持;分析和商业智能;管理及专业性等问题时,应优先考虑关系型数据库。

MongoDB的优势有哪些

  • 面向文档的存储:以 JSON 格式的文档保存数据。
  • 任何属性都可以建立索引。
  • 复制以及高可扩展性。
  • 自动分片。

什么是集合

集合就是一组 MongoDB 文档,相当于mysql中的表这种概念,集合位于单独的一个数据库中。

什么是文档

文档由一组key value组成。文档是动态模式,这意味着同一集合里的文档不需要有相同的字段和结构。

在mysql中table中的每一条记录相当于MongoDB中的一个文档。

什么是”mongod“

mongod是处理MongoDB系统的主要进程,它处理数据请求,管理数据存储,和执行后台管理操作。

当我们运行mongod命令意味着正在启动MongoDB进程,并且在后台运行。

什么是"mongo"

它是一个命令行工具,用于连接一个特定的mongod实例。

什么是MongoDB中的“命名空间”?

集合名称和数据库名称的串联称为命名空间。

MongoDB支持哪些数据类型

  • String
  • Integer
  • Double
  • Boolean
  • Object
  • Object ID
  • Arrays
  • Min/Max Keys
  • Datetime
  • Code
  • Regular Expression等

为什么要在MongoDB中用"Code"数据类型

"Code"类型用于在文档中存储 JavaScript 代码。

为什么要在MongoDB中用"Regular Expression"数据类型

"Regular Expression"类型用于在文档中存储正则表达式

为什么在MongoDB中使用"Object ID"数据类型

"ObjectID"数据类型用于存储文档id

什么是聚合

聚合操作能将多个文档中的值组合起来,对成组数据执行各种操作,返回单一的结果,它相当于 SQL 中的 count(*) 组合 group by。

对于 MongoDB 中的聚合操作,应该使用aggregate()方法。

mongodb中的journal有什么作用

mongodb的journal,简单来说就是用于数据故障恢复和持久化数据的,它以日志方式来记录。

journal除了故障恢复的作用之外,还可以提高写入的性能,批量提交(batch-commit),journal一般默认100ms刷新一次,在这个过程中,所有的写入都可以一次提交,是单事务的,全部成功或者全部失败,刷新时间,可以更改,范围是2-300ms。

当系统非正常情况下突然挂掉,再次启动时候mongodb就会从journal日志中恢复数据,而确保数据不丢失,

更新操作立刻fsync到磁盘?

不会,磁盘写操作默认是延迟执行的。写操作可能在两三秒(默认在60秒内)后到达磁盘。例如,如果一秒内数据库收到一千个对一个对象递增的操作,仅刷新磁盘一次。

MongoDB中的分片是什么?

分片是将数据水平切分到不同的物理节点,水平扩容。

当数据量增长时,单台机器有可能无法存储数据或可接受的读取写入吞吐量,利用分片技术可以添加更多的机器来应对数据量增加以及读写操作的要求。

什么是副本集?

副本集是一组托管相同数据集的mongo实例,在副本集中,一个节点是主节点,另一个是辅助节点,从主节点到辅助节点,所有数据都会复制。

什么是primary?

它是当前备份集群(replica set)中负责处理所有写入操作的主要节点/成员。

在一个备份集群中,当主节点失联时,一个另外的成员会变成primary。

什么是secondary?

Seconday通过跟踪复制oplog(local.oplog.rs)从当前的primary上复制相应的操作。

复制在MongoDB中如何工作?

在多台服务器之间,同步数据的过程称为复制,它通过不同数据库服务器上的多个数据副本提供冗余并提高数据可用性,复制有助于防止数据库丢失,保障数据的安全性、灾难恢复、无需停机维护(如备份,重建索引,压缩)。

在MongoDB中创建集合并将其删除的语法是什么?

在MongoDB中创建集合的语法是db.createCollection(name,options) 在MongoDB中删除收集的语法是db.collection.drop()

Profiler在MongoDB中的作用是什么?

Profiler也就是分析器,MongoDB中包括了一个可以显示数据库中每个操作性能特点的数据库分析器。通过这个分析器你可以找到比预期慢的查询(或写操作);利用这一信息,比如,可以确定是否需要添加索引。

要进行安全备份,可以使用MongoDB中的功能是什么?

日记功能是MongoDB中的功能,可用于执行安全备份。

Objecld由什么组成?

Objectld由以下组成:

时间戳、客户端机器ID、客户端进程ID、3个字节增量计数器

插入文档的命令语法是什么?

插入文档的命令语法是database.collection.insert(文档)

查看是否在主服务器上的命令语法是什么?MongoDB允许多少个主机?

命令语法Db.isMaster()会告诉您是否在主服务器上。

MongoDB仅允许一个主服务器。

MongoDB中的索引是什么?

索引用于高效的执行查询,没有索引MongoDB将扫描查询整个集合中的所有文档,这种扫描效率很低,需要处理大量数据。

索引是一种特殊的数据结构,将一小块数据集保存为容易遍历的形式。

索引能够存储某种特殊字段或字段集的值,并按照索引指定的方式将字段值进行排序。

什么是MongoDB中的GridFS?

为了存储和检索大文件,例如图像,视频文件和音频文件,使用GridFS。

默认情况下,它使用两个文件fs.files和fs.chunks来存储文件的元数据和数据块。

简要说明你能移动moveChunk目录下的旧文件吗?

是的,可以移动moveChunk目录中的旧文件,在正常的碎片操作期间,这些文件作为备份,一旦操作完成就可以删除。

为了进行安全备份,您可以使用MongoDB的哪些特性?

日志是MongoDB中可用于执行安全备份的功能。

解释一下MongoDB中的索引是什么?

索引是MongoDB中的特殊结构,它以易于遍历的形式存储一小部分数据集。索引按索引中指定的字段的值排序,存储特定字段或一组字段的值。

在MongoDB中使用索引的基本语法是什么

db.COLLECTION_NAME.ensureIndex ({KEY:1})

我喜欢学习过程中,会先收集相关资料,有需要的朋友们可以点击,点击链接找我~