mongo官网:https://www.mongodb.com/

我们找Server的相关文档

MongoDB官方文档 mongodb官网文档_数据

 

 

 打开该链接后,是一个MongoDb的详细介绍文档,现在我们以这个文档为基础进行讲解

MongoDB提供了2个版本,云端的mongo服务实例MongoDb Atlas和本地MongoDb Server。其中本地MongoDb Server又分为Enterprise版和Community版。Enterprise版提供了额外功能,比如LDAP和Kerberos支持、磁盘加密和审计等。

注:为便于随时操作mongo,可以注册一个免费的MongoDb Atlas

有免费的512M存储空间。然后下载mongosh连接工具连接mongo集群。登陆方式:
E:\software\chromeDownload\mongosh-1.5.0-win32-x64\mongosh-1.5.0-win32-x64\bin>mongosh "mongodb+srv://XXX"
use admin
db.auth('AAA','XXX');

 

1 总览

1.1 采用文档存储的优势

Documents correspond to native data types in many programming languages.
Embedded documents and arrays reduce need for expensive joins.
Dynamic schema supports fluent polymorphism.

1.2 mongo中的对象

databases

集合

除了集合,mongo还支持

Read-only Views (Starting in MongoDB 3.4) 视图

On-Demand Materialized Views (Starting in MongoDB 4.2). 按需物化视图

1.3 核心特性

1 高性能:
mongo提供了高性能的数据持久化,特别是:
支持内嵌文档,减少数据库IO
索引支持更快的查询,并且可以包含内嵌文档中的键。
2 高可用:
自动故障转移
数据冗余
3 水平伸缩:
数据分片
4 支持多种存储引擎
默认WiredTiger
In-Memory

2 集合

2.1 文档

MongoDb存储数据的格式是BSON文档。BSON是二进制的JSON,且扩充了数据类型,比如ObjectId、Date和BinData类型等。

文档的_id字段是必须给的,_id的默认类型是ObjectId,我们可以显式的给它赋其他类型的值:

自增数字

uuid

2.2 BSON中的数据类型

每个BSON类型都有对应一个整数代码和字符串标识符进行标识,比如:

MongoDB官方文档 mongodb官网文档_数据_02

 

 

ObjectId

ObjectId共12字节,由三部分组成:

前4字节:时间戳

中间5字节:不同进程、不同机器,该信息不同。相同进程和机器对应相同的值

后3字节:初始化为随机值,后续递增

Timestamps

 

Date

64bit,表示1970年以来的毫秒数。Mongo中把它转换为UTC时间来显示。UTC时间中文名称是协调世界时。即世界公用时间。但是每一个时区会根据当地天色,使用本地时间。比如北京时间比UTC时间快8小时。

其他数据类型的介绍略

2.3 Capped集合

Capped集合是固定大小的集合,和普通的集合比支持更高的吞吐量。其工作原理类似于循环缓冲,如果集合满了又有新数据插入时,将删除最老的文档以便腾出空间给新插入的数据。

特性:

保证插入顺序

自动删除旧文档

注意:
从MongoDB 5.0开始,当从一个Capped集合读取数据时,你不能使用读关注"snapshot"
如果更新Capped集合,请创建索引,以便这些更新操作不需要进行全表扫描
如果更新或替换操作更改了文档大小,则该操作将失败。
不能从Capped集合中删除文档。要从集合中删除所有文档,请使用drop()方法删除集合并重新创建Capped集合。
不能对Capped集合进行分片。
使用自然排序可以有效地从集合中检索最近插入的元素。这类似于在日志文件上使用tail命令。
聚合管道stage $out 不能将结果写入到Capped集合。
从MongoDB 4.2开始,你不能在事务中写Capped集合。

创建Capped集合

db.createCollection( "log", { capped: true, size: 100000 } )

注意size单位是字节,且当size小于4096时设置为4096,当size大于4096时必须是256的整数倍

我们可以指定max参数,其代表最大允许的文档数。

db.createCollection("log", { capped : true, size : 5242880, max : 5000 } )

查询Capped集合

find()默认按照插入的顺序输出,如果想反方向输出,使用如下方式

db.cappedCollection.find().sort( { $natural: -1 } )

检查一个集合是否是Capped: db.collection.isCapped()

把普通集合转为Capped: db.runCommand({"convertToCapped": "mycoll", size: 100000});

2.4 时间序列集合

时间序列集合适合存储随时间变化收集到的数据,,能够自动按照内部优化的存储结构组织数据。可以提高查询效能和减少磁盘使用。

时间序列集合自动在timestamp上添加了索引,并且使用listIndex命令不会列出该索引。

创建

db.createCollection(
    "weather",
    {
       timeseries: {
          timeField: "timestamp",
          metaField: "metadata",
          granularity: "hours"
       }
    }
)

注意:timeseries.timeField:必选项,指定了哪一列作为时间列

      timeseries.metaField:可选,该条记录的元信息,该信息应该精简且不易变化。

     timeseries.granularity:可选,值域[seconds,minutes,hours]

   expireAfterSeconds:可选,指定多长时间之后该文档可以删除。

上面创建了一个时间序列集合weather。

向集合中插入数据

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
   }
] )

查询

db.weather.findOne({
   "timestamp": ISODate("2021-05-18T00:00:00.000Z")
})

当对一个时间序列集合进行数据查询时,将按照各个度量值(比如timeField、metaField、granularity)去匹配数据,使得查询和返回数据都快一些。

限制:

时间序列集合的文档最大支持4M。

不能使用$out和$merge操作输出或者merge到一个时间序列集合。

3 视图

3.1 普通视图

mongo支持视图,但是mongo不保存视图数据,当客户端查询视图时,mongo根据视图定义去查询底层集合,并将查询结果返回给客户端。视图是只读的,不支持写视图

db.createCollection(
  "<viewName>",
  {
    "viewOn" : "<source>",
    "pipeline" : [<pipeline>],
    "collation" : { <collation> }
  }
)
db.createView(
  "<viewName>",
  "<source>",
  [<pipeline>],
  {
    "collation" : { <collation> }
  }
)

视图可以使用底层集合的索引,不能在视图上创建索引,MongoDB 4.4开始视图中可以使用$natural排序。视图底层聚合管道在进行块排序和块分组时有100M内存的限制,MongoDB 4.4开始可以使用allowDiskUse: true 来设置使用磁盘临时文件解决这个问题
不可以修改视图名

例子:

创建视图

db.createView(
   "firstYears",
   "students",
   [ { $match: { year: 1 } } ]
)
db.createCollection(
   "graduateStudents",
   {
      viewOn: "students",
      pipeline: [ { $match: { $expr: { $gt: [ "$year", 4 ] } } } ],
      collation: { locale: "en", caseFirst: "upper" }
   }
)

例子2

db.createView( "sales", "orders", [//视图名sales,基于orders集合
   {
      $lookup:
         {
            from: "inventory",//这里连表查询,join的表为inventory,匹配的列为prodId,匹配到的文档作为一个数组放到inventoryDocs中
            localField: "prodId",
            foreignField: "prodId",
            as: "inventoryDocs"
         }
   },
   {
      $project://project操作进行列的过滤
         {
           _id: 0,
           prodId: 1,
           orderId: 1,
           numPurchased: 1,
           price: "$inventoryDocs.price"
         }
   },
      { $unwind: "$price" }
] )

对视图的查询

db.sales.aggregate( [
   {
      $group:
         {
            _id: "$prodId",
            amountSold: { $sum: { $multiply: [ "$price", "$numPurchased" ] } }
         }
   }
] )

输出:

[
  { _id: 100, amountSold: 600 },
  { _id: 103, amountSold: 1105 },
  { _id: 101, amountSold: 150 },
  { _id: 102, amountSold: 90 }
]

 

3.2 按需物化视图

有时我们希望这样一种场景:我们使用聚合管道进行查询时,希望每次的查询结果放到一个新的集合中,以便其他人使用这个中间结果。当再次进行聚合管道查询时,聚合结果merge到新集合中。这就是mongo中的按需物化视图。

传统意义上说,其不能称之为一个视图。按需物化视图只不过是使用了Mongo的新特性$merge。从4.2版本开始MongoDb在聚合管道中可以使用$merge。这个操作可以把聚合结果merge到一个已经存在的collection中。

举例:

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" } }
   ] );
};

上述例子,在bakesales上使用aggregate操作,并把结果merge到monthlybacksales中。

 4 存储引擎

MongoDB支持两种存储引擎,分别是WiredTiger和In-Memory

4.1 WiredTiger

WiredTiger是MongoDB默认的存储引擎

其特性有:

文档级别的并发写

因此,不同的客户端可以同时往一个collection中的不同文档进行写操作。

快照和检查点

快照和检查点说的是一回事,很多地方快照说的也就是检查点。

WiredTiger使用多版本并发控制(MVCC)。在一次操作开始,WiredTiger提供即时快照(比如说某个文档的一个副本),保存在内存中,每60s或者数据达到2GB时将当前数据持久化WiredTiger.wt,这个文件存储检查点。如果服务挂掉,最多有60s的数据丢失。这也是WiredTiger使用时需要注意的地方。如果需要保证数据不丢失,需要开启journal日志。

使用参数minSnapshotHistoryWindowInSeconds可以指定保持快照的时间,单位为秒。快照保存在mongo数据目录下

MongoDB官方文档 mongodb官网文档_数据_03

 

collection-xxx.wt和index-xxx.wt类文件:集合和索引对应的文件

storage.bson文件:与WiredTiger存储引擎配置有关,可以通过MongoDB提供的bsondump命令工具查看其内容

sizeStorer.wt文件:存储所有集合的容量信息,如集合中包含的文档数、总数据大小

_mdb_catalog.wt文件:存储的是集合表名与磁盘上数据文件和索引文件间的对应关系

WiredTiger.wt文件:存储checkpoint信息

WiredTiger文件:存储的是WiredTiger存储引擎的版本号,编译时间等信息。
WiredTiger.turtle文件:存储的是WiredTiger.wt这个文件的checkpoint数据信息。相当于对保存有所有集合checkpoints信息的文件WiredTiger.wt又进行了一次checkpoint。
diagnostic.data文件夹:存放的是MongoDB启动运行时的诊断数据。
journal文件夹:开启Jouranl日志功能后,存放的是Write ahead log事务日志,当数据库意外crash时,可通过log来恢复数据

journal日志

WiredTiger通过检查点和journal日志保证数据持久化。

journal日志记录了检查点之间的所有数据修改,如果服务在检查点之间退出,当服务重启后,将会根据journal日志从最新的检查点开始重新执行一次journal日志中记录的所有数据修改。

journal日志默认是压缩的,默认的压缩工具是snappy。我们可以使用storage.wiredTiger.engineConfig.journalCompressor参数修改压缩工具和算法。或者设置为不压缩。

journal日志默认是开启的,我们可以使用storage.journal.enabled参数设置是否开启journal日志。

对集合和索引以及journal的压缩存储

WiredTiger默认对journal进行压缩,对集合和索引根据工作负载自动判断是否进行压缩存储,以减少磁盘占用。对集合进行块压缩,对索引进行前缀压缩。我们可以使用如下两个参数进行设置storage.wiredTiger.collectionConfig.blockCompressor和storage.wiredTiger.indexConfig.prefixCompression。

对存储的高效利用

WiredTiger同时使用了内存cache和磁盘cache

默认内存占用:(RAM-1G)/2或256M,比如机器内存4G,WiredTiger将占用(4-1)/2=1.5G内存。

WiredTiger默认将耗尽除内存cache和其他进程所使用内存外的所有其他内存作为磁盘cache。磁盘cache的存在减少了磁盘IO,提高了读写效率

我们可以使用storage.wiredTiger.engineConfig.cacheSizeGB和--wiredTigerCacheSizeGB参数对cache进行设置。

5 journal

WiredTiger每60秒设置一个checkpoints,用来保存各个时间点的数据快照。但是如果在下一个检查点没有形成之前服务挂了,将丢失这60秒的数据更改。这时就需要journal日志来保证数据持久性的要求了。

journal日志是默认打开的,我们可以使用如下参数进行设置:--nojournal或者storage.journal.enabled

开启了journal日志后,恢复过程为:

1 在数据文件中获取到最后一个检查点的信息

2 在journal日志中匹配到最后一个检查点

3 从匹配到的位置开始,根据日志中的步骤重新更新数据。

WiredTiger使用内存cache存储journal日志,并且定时refresh到磁盘文件中。refresh的时机如下:

1 默认每100ms refresh一次,此值可以修改storage.journal.commitIntervalMs

2 当新增一个journal文件时(默认journal最大100M,当超过100M时将新增一个journal文件)

3 当写操作包含或者隐含write concern:j:true

journal日志的位置:数据路径下的journal文件夹下

[root@16s2 journal]# pwd
/micro16s/mongo/data/journal
[root@16s2 journal]# ll
total 307200
-rw------- 1 root root 104857600 Jul 14 17:13 WiredTigerLog.0000000426
-rw------- 1 root root 104857600 Sep 26  2021 WiredTigerPreplog.0000000146
-rw------- 1 root root 104857600 Jul 12 00:58 WiredTigerPreplog.0000000297
[root@16s2 journal]#

默认对journal日志进行压缩存储,默认的压缩工具是snappy,可以使用命令storage.wiredTiger.engineConfig.journalCompressor设置其他压缩工具和算法。

journal日志文件的大小限制是100M,一旦日志文件超过这个大小,将创建新的journal文件。

WiredTiger自动删除旧的journal日志文件,只保留能够从最近checkpoint恢复的journal文件。

WiredTiger预先分配journal文件的磁盘空间,这也是为什么我们看到最新的journal日志大小也是100M

有了journal日志,还是不能保证数据的绝对持久性,因为journal日志缓存默认100ms执行一次refresh操作,可能会丢失100ms的数据。为了保证数据的绝对安全,我们可以:1 缩小这个refresh时间,2 采用commit ack机制,Write Concern机制

6 GridFS

GridFS通过把文件拆分成若干chunks,并且把这些chunks存储于不同的文档中,默认,chunk的大小是255kb。

GridFS使用两个collections存储文件。一个集合存储chunks,另一个集合存储文件元信息。

GridFS是MongoDB之上的一个简单的文件系统抽象,类似于Hadoop的HDFS。

使用GridFS的场景:

1 相较于传统的文件系统,GridFS提供了数据库备份来备份这些放到GridFS中的文件

2 删除和拷贝文件变得简单,如同删除数据库中的一个对象一样简单

3 文件上传到GridFS后,会被分割为256kb的块,并单独存放,当需要修改文件时,不需要把整个文件加载到内存,只需加载相应的块就可以

4 普通文档的大小限制是16M,而GridFS没有这个限制。

5 可以把存储在GridFS中的文件直接服务于web服务器或者文件系统,只需要使用GridFS-Fuse插件或者GridFS-Nginx插件即可。

7 Replica set

副本集是一组mongod进程,其维持了相同的数据集,并提供冗余和高可用性。

冗余和高可用性

副本集提供数据的多个副本,这些副本分布在不同的节点上。副本集为单个节点上数据的丢失提供了容错能力。

提供更高的读通量

在读数据时,我们可以把读请求分发给不同的节点,这样会提高数据读通量。

7.1 复制集工作流程

MongoDB官方文档 mongodb官网文档_时间序列_04

MongoDB官方文档 mongodb官网文档_数据_05

MongoDB官方文档 mongodb官网文档_检查点_06

 

 主节点接收所有的写操作,然后产生oplog日志

可以从任意节点读,但是默认只从主节点读

从节点复制主节点的oplog日志并以此来更新自己的数据

如果主节点挂了,选举机制将在从节点中选举一个节点作为新的主节点。

对于从节点,我们可以配置其不能成为主节点以便我们设置其为从节点数据中心或者作为冷备份。我们还可以配置不允许在某个从节点读。

在某些情形中,为了成本考虑,没有那么多机器。比如只有一个主节点和一个从节点,我们会找一个低配的节点作为仲裁节点,以便进行选举之用。

 7.2 异步复制

从节点异步的拷贝主节点的oplog日志并执行oplog日志。

数据同步延迟和流量控制:数据同步延迟指的是从节点拷贝oplog并执行oplog的延迟,当写操作频繁时,这个延迟会加大。从4.2开始写操作的流量控制默认打开,当数据同步延迟大于flowControlTargetLagSeconds参数所配置的时间时,将会自动对写操作的流量进行控制,以便数据同步延迟处于flowControlTargetLagSeconds以下。

7.3 自动故障转移

当从节点超过10秒接收不到主节点的心跳,则认为主节点挂了,这时从节点将提名自己参与选举,选举出来一个新的主节点,这就是自动故障转移的过程。

MongoDB官方文档 mongodb官网文档_数据_07

 

 在选举期间,复制集将不能进行写操作。当我们配置了可读从节点时,在主节点挂了的期间,仍然可以进行读操作。

 electionTimeoutMillis参数用于设置从从节点获取主节点心跳的最长时间,默认是10秒。我们可以设置这个值更新一些,这样可以更早的监控到主节点是否offline。但是这样也会增加可能的选举次数,有时网络抖动会影响心跳接收的时长。

7.4 读偏好read preference

默认情况下,客户端从主节点读。但是可以设置read preference从从节点进行读。

需要注意的是:

异步复制机制使得从节点的数据可能会有延迟。从节点读到的数据可能不是最新的数据。

当一个事务中包含了多文档时,读偏好必须设置为读主节点。一个事务中的所有操作必须路由到相同的成员。

MongoDB官方文档 mongodb官网文档_数据_08

 7.5 数据可见性

依赖于read concern,客户端可以在数据持久化之前读到这部分数据。

如果不考虑write concern,当前客户端在写数据后但是还没有收到acknowledge之前。其他客户端通过使用"local"或者"avaliable" read concern可以读到这些数据。

在故障转移过程中,客户端使用"local"或者"avaliable" read concern可能读到最终回滚的数据。

在事务中,在事务提交之前,其他客户端是无法读取到未提交的数据的。

当事务包含写多个分片时,在事务外读操作,并不是所有的读操作都会等待事务提交的结果在各个分片上可见。比如一个事务已经提交,写1在分片A上可见,写2在分片B不可见,此时read concern "local"可以读到1,但是不能读到2.

7.6 Mirrored Reads

主节点会按照一定比例把读流量复制到备库上执行,这样做的目的是使从节点也保持一定比例的读缓存在内存中。这样当出现故障转移或者主从切换时,查询的内存命中率就会大大提高,不至于重新构建内存cache,这样对切换阶段的查询性能起到一定的保障作用。具体分配多大比例的读流量到从节点上,是可以通过mirrorReads配置的,默认是1%

Mirrored Reads支持如下这些操作:

count

distinct

find

findAndModify

update

从4.4版本开始,Mirrored Reads是默认开启的,默认流量分发比例是0.01。如果要禁用Mirrored Reads,可设置mirrorReads为0

db.adminCommand( {
  setParameter: 1,
  mirrorReads: { samplingRate: 0.0 }
} )

7.7 事务

从4.0版本开始,复制集支持多文档事务。

在复制集中使用事务进行读操作必须使用读偏好primary,事务中的所有操作必须路由到相同的成员节点
只有事务提交之后,事务中的操作才可以在其他客户端可见
当事务包含写多个分片时,在事务外读操作,并不是所有的读操作都会等待事务提交的结果在各个分片上可见。比如一个事务已经提交,写1在分片A上可见,写2在分片B不可见,此时read concern "local"可以读到1,但是不能读到2.

7.8 Change Streams

MongoDB从3.6版本开始支持 Change Stream,用于订阅 MongoDB 内部的修改操作,change stream 可用于 MongoDB 之间的增量数据迁移、同步,也可以将 MongoDB 的增量订阅应用到其他的关联系统

7.9 优先级为0的从节点

我们可以配置从节点的优先级,优先级越高的从节点,当发生选举时越有机会成为主节点。

我们可以配置某个从节点prority=0,从而使该从节点没有机会成为主节点,也不能触发选举。但是该节点具有投票权,可以参与投票。当写操作的write concern为“majority”时,无投票权的成员不会参与计算majority的成员数量。

MongoDB官方文档 mongodb官网文档_MongoDB官方文档_09

 

 当某个节点单独处于一个数据中心,且和其他节点的网络延迟比较大时,且希望这个节点只服务于本地的读请求,我们可以把它设置为优先级0

有时候我们希望某个节点作为备选节点用,可以吧优先级为0。该节点持有数据的一个拷贝,当某个节点挂了,我们可以使用这个节点顶上。

7.10 hidden节点

hidden节点同样持有当前数据集的拷贝,但是对客户端不可见。

hidden节点,其prority必须为0

hidden节点具有投票权,如果要停止一个具有投票权的hidden节点,请确保集群中的其他节点能够保证大多数存活,否则因为达不到集群节点数要求,主节点将停止,集群将不可用。

MongoDB官方文档 mongodb官网文档_MongoDB官方文档_10

 

客户端不会把读操作分配给一个hidden节点。除了oplog复制之外,这些hidden节点不会有任何其他流量。

在分片集群中,mongos不会与hidden节点交互。

当hidden节点的目的是备份时,我们可以把备份的数据拷贝到其他地方(比如新增一个节点,或者一个新的复制集)。具体可参考如下做法:

1 在该hidden节点上执行db.fsyncLock(),保证数据文件安全的拷贝。

2 然后使用cp、scp等命令拷贝数据文件到新的节点上。

3 在新的节点上启动mongod服务,使用拷贝过来的数据文件

hidden节点可以确认write concern。但是对于确认write concern w:majority,有额外的限制,就是hidden成员必须具有投票权。没有投票权的hidden成员不参与majority的write concern应答。

7.11 Delayed节点

MongoDB官方文档 mongodb官网文档_检查点_11

 

 

Delayed成员同样持有复制集的一份拷贝,但是这个拷贝是延迟的。比如我们可以延迟1小时拷贝主节点的oplog日志。

当人为错误操作导致复制集数据进行了错误的写操作时,我们可以利用Delayed节点进行一定的恢复工作。

Delayed节点建议必须是priority 0,这样避免其成为主节点

Delayed节点建议必须是hidden,这样避免一个查询操作被分配到Delayed节点上。

Delayed节点建议必须无投票权,这样可以提高性能。因为Delayed节点同步oplog日志是延迟的,当参与投票时,可能会有很大的延迟,这有时是不可接受的。

当然Delayed节点可以是priority 1的,也可以不是hidden的,也可以有投票权,但是这样设置会带来很大的性能问题。

7.12 oplog

MongoDB的主节点执行写操作,同时会在主节点上生成oplog日志。其他从节点异步拷贝oplog并且执行oplog中的改动。每个从节点都持有一份oplog的拷贝,存储在local.oplog.rs集合中。

为了提高复制效率,一个复制集成员发送心跳到所有其他成员,任意一个复制集成员可以从其他任何节点拷贝oplog。

oplog是一个特殊的capped collection。和其他capped collection不同,oplog可以超过其配置的大小。oplog的默认大小:剩余磁盘空间的5%。

oplog删除规则:1 达到配置的最大大小。2 oplog条目比配置的小时数更早。

oplog中的操作都是幂等的,也就是说,oplog中的操作被执行1次还是多次都会产生相同的结果。

如果oplog写满了,根据capped collection的特点,新插入的文档会覆盖老的文档。我们来想像一种场景,当其中一个从节点挂了,oplog被分配了5%磁盘空间,并且在24小时之内写满了,当超过24小时从节点重新启动起来,这个从节点将根据最新的检查点+oplog进行数据恢复,但是oplog写满了,oplog只能复制最近的24小时内的oplog,超过24小时的部分已经被覆盖了,此时,从节点将一直处于RECOVERING状态。此时我们就需要如下做:

1 删除该节点上的数据文件。在其中一台正常节点上执行db.fsyncLock(),保证数据文件安全的拷贝。

2 然后使用cp、scp等命令拷贝数据文件到故障的节点上。

3 在故障节点上启动mongod服务,使用拷贝过来的数据文件。

在4.0版本以后,oplog可以超过配置的大小限制,以避免删除majority commit point。

从4.4版本开始,可以通过storage.oplogMinRetentionHours或者--oplogMinRetentionHours设置最小oplog保持时长,在这个时长之内的oplog不会删除,超过这个时长的oplog条目将会被删除。oplog的删除规则变为:

超过最大配置大小时将会删除

配置的最小oplog保持周期之外的oplog条目将会被删除。

如果客户端写操作比较少的话,我们可以相应调整oplog大小为一个比较小的值。

什么情况下需要更大的oplog?

1 一次更新多个文档。为了保持操作的幂等性,oplog中的操作会被拆分为多个操作。

2 删除的数据量和插入的数据量基本相等。此种情况下,数据文件占用的磁盘空间没变,但是oplog会有显著的增加。

3 大量的更新操作。大量的更新操作,数据文件的大小不会有变化,但是oplog记录会有很大的增加。

数据同步延迟和流量控制:数据同步延迟指的是从节点拷贝oplog并执行oplog的延迟,当写操作频繁时,这个延迟会加大。从4.2开始写操作的流量控制默认打开,当数据同步延迟大于flowControlTargetLagSeconds参数所配置的时间时,将会自动对写操作的流量进行控制,以便数据同步延迟处于flowControlTargetLagSeconds以下。

7.13 复制集数据同步

MongoDB使用两种形式的数据同步:1 使用整个数据集初始同步一个新的节点。 2 对数据集进行持续更新

init Sync

初始同步,从一个复制集成员copy所有数据到另一个复制集成员。从4.4开始,你可以指定首选的初始同步源,方式时在启动mogod时使用 initialSyncSourceReadPreference

初始同步的过程:

1 克隆除本地数据库外的所有数据库。包括建立的索引,也包括新增的oplog。

2 执行克隆过来的oplog

3 当第二步完成之后,该节点的状态由STARTUP2变为SECONDARY。

init Sync过程中的容错:当节点执行init Sync过程中遇到永久性的网络中断,该节点将在某一个时刻从开始重新执行init Sync过程。

  从4.4开始,如果遇到临时性的网络抖动、collection删除、collection更名等操作,从节点的init Sync过程会试图恢复(类似断点续传)。但是这个特性必须要求同步源的版本也是4.4及以上。默认情况下从节点试图恢复init Sync过程的时间必须在24小时之内。这个时间可以通过initialSyncTransientErrorRetryPeriodSeconds参数进行配置。如果在指定的时间之内不能成功恢复init Sync过程,该节点会重新选择一个健康的同步源从头来重新开始init Sync过程。从节点默认情况下会尝试10次重新开始init Sync过程,超过10次将返回fatal error。

初始化同步源的选择:初始化同步源的选择依赖于 initialSyncSourceReadPreference

  当该参数为primary时,会选择primary节点作为同步源。

  当参数为primaryPreferred时,会把primary节点作为首选项,如果primary节点不可达,会选择其他Second节点

  当参数为second或者secondPreferred时,...

初始化同步源的选择如果选择失败,1秒钟之后将会重新执行选择过程。最多总共会执行10次初始化同步源的选择,超过10次未选择出初始化同步源的话会返回一个fatal error。

增量同步

增量同步过程,指的是init Sync过程结束之后,从节点从其他复制集成员同步oplog来进行本节点的数据更新的过程。

增量同步源的选择:从节点会根据和其他复制集成员的心跳监控情况来选择合适的同步源来同步数据,具体选择过程比较复杂这里不再赘述,如有兴趣可以参考官方文档。

 7.14 复制集部署架构

典型的复制集部署架构是3节点复制集,提供了冗余和容错,尽可能避免了复杂性。但是有些时候根据业务要求,需要部署更多的节点

最大的投票成员数目:不论复制集有多少个节点,最多支持7个具有投票权的节点。

复制集成员中具有选举权的成员个数必须是奇数个。如果具有选举权的成员个数是偶数,新增一个具有选举权的成员,如果成本有限制,新增一个仲裁节点。

需要注意的是不要部署超过1个仲裁节点。

考虑容错

容错是指,当复制集中某些节点挂了之后,剩余的节点能够成功进行主节点的选举从而重新构成复制集。

Number of Members

Majority Required to Elect a New Primary

Fault Tolerance

3

2

1

4

3

1

5

3

2

6

4

2

上表中显示了集群节点数和容错数之间的关系。

复制集中增加成员不总是会提高容错。但是新增的成员往往有其他专门的用途,比如备份。

我们可以使用hidden或者Delayed成员来做其他专门用途,比如备份或报告。

读通量高的集群上的负载均衡

对于读通量高的复制集,我们可以通过把读请求负载均衡到从节点来摊平读通量到每个复制集成员。随着业务查询需求的不断增长,我们可以随时向复制集中增加新的成员以便提高系统的读通量。

跨多个数据中心部署

跨2个数据中心部署复制集比单个数据中心部署具有更高的韧性

如果一个数据中心挂了,另一个数据中心仍然可以构成一个复制集

如果少数节点的数据中心挂了,另一个数据中心仍然可以进行主节点的选举,并构成一个新的复制集

如果多数节点的数据中心挂了,另一个数据中心将不能选举出主节点,复制集将变为只读。

因此,为了保证复制集的高可用性,建议至少把一个成员放入备用数据中心中。比如我们可以在A数据中心放2个节点,在B数据中心放2个节点,在云端放1个节点。这样能够保证一个数据中心挂了,其他节点仍然能够组成大多数,进而仍然能够形成新的复制集。

典型的部署架构-三机部署

最小的复制集是由3节点组成的,推荐P-S-S(Primary-Secondary-Secondary)模式部署。如果资金不允许,可以考虑P-S-A(Primary-Secondary-Arbiter)方式部署。

P-S-S方式:

MongoDB官方文档 mongodb官网文档_MongoDB官方文档_12

 

 这种部署方式提供了两份数据的完全拷贝。提供了容错和高可用能力。如果主节点挂了,剩余两个节点会选举出新的主节点,如下图所示

MongoDB官方文档 mongodb官网文档_MongoDB官方文档_13

 

 当旧的主节点可用之后,会再次自动加入集群。

P-S-A方式:

一个主节点,一个从节点。一个仲裁节点,仲裁节点不持有数据,只进行投票。这种部署方式只持有一份数据的拷贝,提供了有限的容错和数据冗余。

MongoDB官方文档 mongodb官网文档_MongoDB官方文档_14

 

 当主节点挂了,剩余两个节点进行选举(虽然选举结果显而易见)。见下图

MongoDB官方文档 mongodb官网文档_检查点_15

 

 7.15 复制集的高可用性

选举机制保证了复制集的高可用性。触发选举的条件有如下几种:

1 向复制集中添加一个新的节点

2 复制集刚刚初始化的阶段

3 一些副本集维护命令,比如rs.stepDown() 或者rs.reconfig()

4 从节点失去和主节点的连接超过指定的时间(默认10秒)

在选举过程中,复制集不能处理写操作。如果配置了读从节点,复制集可以处理读操作,此时复制集将短暂的处于只读状态

默认情况下选举周期不能超过12秒,这个值可以通过 settings.electionTimeoutMillis

影响选举的因素有如下几种:

1 复制集选举协议。从4.0开始,使用新的选举协议protocol version 1(pv1),废弃过时的pv0.和pv0相比,pv1减少故障转移时间

2 心跳 复制集成员之间每2秒相互发送一次心跳。如果发送心跳之后10秒没有响应,则认为这个节点挂掉了。

3 节点优先级。优先级更高的从节点更容易在选举中成为主节点。优先级为0的从节点不会参与选举

4 Mirrored Reads。从4.4版本开始,主节点把收到的读写操作按照默认1%的比例发送给从节点,帮从节点预热内存cache,以便发生故障转移时,新的主节点有足够的cache命中率,保证主节点切换后的复制集的高可用性。

5 突发的网络隔离。突发的网络隔离,会导致一部分节点在子网A,另一部分节点在子网B。如果主节点在少数节点的子网中,主节点将降级变为从节点。另一个多数节点的子网将会选举出一个新的主节点。

具有选举权的成员(能被选举为primary)

通过members[n].votes和节点的state共同决定该节点是否具有选举权

members[n].votes=0的节点不具有选举权

只有如下状态的节点才具有选举权:

PRIMARY
SECONDARY
STARTUP2 (unless the member was newly added to the replica set)
RECOVERING
ARBITER
ROLLBACK

无选举权的成员(不能被选举为primary)

无选举权的成员必须members[n].votes=0且prority=0

尽管无选举权成员不能选举投票,但是可以持有复制集的拷贝,也可以处理客户端的读请求。

无选举权的成员必须prority=0

MongoDB官方文档 mongodb官网文档_检查点_16

故障转移时的rollback

当发生故障转移时,如果之前的primary节点正在进行写操作,但是该写操作没有同步到其他节点,此时primary挂了,复制集进行了新的选举,过了一段时间之前的主节点恢复并加入到集群中成为一个secondary节点,在该节点上,之前的写操作会回滚,然后再从其他节点复制oplog进行自己数据的更新。

默认情况下,当回滚发生时,回滚的数据将被记录到BSON文件中,其路径为:<dbpath>/rollback/<collectionUUID>(4.4版本,早期版本不是这个路径)

对于复制集,write concern {w:1}只能保证写操作成功写入primary节点。但是如果此时主节点挂了,且写操作还没有被同步到其他节点。当该节点恢复后,会发生rollback,这个显然是不合理的。因此我们应当使用write concern {w:majority}来保证所写的数据被成功同步到大多数节点。且复制集成员节点要开启journal日志。

有一种情况,客户端使用read concern "local"或者"available"可以看到其他客户端没有acknowledge的数据。因此客户端使用read concern "local"或者"available"可能会读到发生故障转移时已经回滚的数据。

7.16 write concern

write concern 描述了复制集对写操作请求的确认级别,具体定义了写操作被成功写入到哪些数据承载节点时认为是写成功的。

定义了如下几种写操作请求确认级别:

w:majority

写操作传播到了大多数数据承载节点。也就是说大多数承载节点确认了这个写操作,则返回写入成功。

在大多数复制集配置中,w:majority是默认的。

j:true和w:majority配合能够确保避免write concern已提交的事务被回滚。

w:1

写入主节点则返回写入成功。

w:n

n可以是任何正整数,表示的是,n个数据承载节点确认了本次写操作,则认为数据写入成功。

MongoDB官方文档 mongodb官网文档_MongoDB官方文档_17

 

客户端发起一次写操作,然后就等待从主节点返回write concern确认信息。直到主节点收到其他所规定数量的数据承载节点的确认之后,主节点才将这个确认信息返回给客户端。

write concern数量定的越大,当发生主节点异常时,所写的数据越不容易回滚。但是这样也会有更大的写延迟。

write concern操作可以携带一个wtimeout参数,限制写超时时间,避免写操作阻塞。

db.products.insertOne(
   { item: "envelopes", qty : 100, type: "Clasp" },
   { writeConcern: { w: "majority" , wtimeout: 5000 } }
)

当超时时间已到,写操作返回error。但是这时可能有一些节点已经包含了写入的数据,且会继续通过oplog复制到集群所有节点。这地方需要引起注意

7.17 read preference

7.17.1 几种read preference

MongoDB官方文档 mongodb官网文档_MongoDB官方文档_18

定义了读首选项,有下面5种

primary
primaryPreferred
secondary
secondaryPreferred
nearest

4.4版本的新特性,Hedged Reads(对冲读)。指的是一个读请求会同时发送给两个secondary节点,哪个节点先返回就用哪个节点返回的数据呈现给客户端。当read preference为nearest时,默认支持该特性。当为primary时不支持该特性。当为其他mode时,需要显式的明确该特性。

primary

复制集默认的mode,所有读操作都会在主节点上进行。

不支持使用tag sets或者maxStalenessSeconds。

多文档事务中,如果包含读操作,必须使用primary read preference。

primaryPreferred

如果主节点正常,读操作首选从主节点获取数据。但是如果主节点不可达,比如故障转移过程中,则读操作将会选择一个从节点进行。如果read preference配置了tag sets或者maxStalenessSeconds,则会根据这些配置选取一个从节点,然后在这个从节点上读。

maxStalenessSeconds参数:如果主节点不可用而且配置了该参数,则会计算每个从节点的数据陈旧程度,计算方式是根据最近一次写操作,比较各个从节点的最近写入时间,然后客户端会把读请求指向其中一个未超过maxStalenessSeconds的从节点。

tag sets参数:客户端将根据tag sets逐一的比较从节点,获取到一个满足条件的从节点列表,然后客户端将随机的从列表里选择一个从节点,然后在这个从节点上读。

如果同时配置了maxStalenessSeconds和tag sets,则客户端按照先maxStalenessSeconds后tags的顺序来选择所要读取的从节点。

从4.4版本开始,primaryPreferred支持在分片集群中的hedged reads(对冲读)

secondary

读操作只在从节点进行,如果没有从节点,读操作将返回error

如果配置了maxStalenessSeconds,客户端将对每个从节点和主节点比较最近的一次写入操作,以评估数据的陈旧程度。然后客户端会选择一个在maxStalenessSeconds范围之内的从节点,在该节点上读取数据。如果没有主节点,客户端将选择最近的一次写操作的从节点作为比较的参照物。

如果配置了tag set,客户端将会找到满足tag set的从节点列表,然后从列表里随机选取一个从节点,在该节点上读。

如果同时配置了maxStalenessSeconds和tag sets,则客户端按照先maxStalenessSeconds后tags的顺序来选择所要读取的从节点。

从4.4版本开始,primaryPreferred支持在分片集群中的hedged reads(对冲读)。

secondaryPreferred

和上面类似,略

nearest

驱动程序会从网络延迟最小的节点读取数据,而不区分是否主节点还是从节点。

 7.17.2 Tag Sets

给复制集成员设置Tag的方法

members[n].tags

格式如下:

{ "<tag1>": "<string1>", "<tag2>": "<string2>",... }

在read preference中,我们可以设置tag sets,根据tag sets找到匹配的节点,然后执行读操作。

比如,如果一个从节点members[n].tags被设置成 { "region": "South", "datacenter": "A" }

[ { "region": "South", "datacenter": "A" }, { } ]
[ { "region": "South" }, { "datacenter": "A" }, { } ]
[ { "datacenter": "A" }, { "region": "South" }, { } ]
[ { "region": "South" }, { } ]
[ { "datacenter": "A" }, { } ]
[ { } ]

这些tag sets都会匹配到前述从节点,从而在该节点上进行读。

Tag匹配的顺序

如果一个tag sets中有多个文档,MongoDB会按照顺序依次尝试每个文档,直到匹配成功。一旦匹配成功,则根据该匹配成功的文档去匹配其他的节点。这样会得到一个匹配到的节点列表。然后读客户端会随机从这个列表中选取一个节点去进行数据读取。

7.17.3 maxStalenessSeconds

复制集成员数据同步会受网络拥塞、磁盘吞吐量低等的影响,数据可能会有延迟。read preference的maxStalenessSeconds选项能够让我们指定一个最大复制延迟。如果一个从节点的数据陈旧度超过maxStalenessSeconds,客户端将不在此节点进行读操作。

有时某一从节点因为网络拥塞,数据延迟严重,通过maxStalenessSeconds选项,我们可以避免读这些网络拥塞的节点,从而提高所读取数据的实时性和准确性。

如果配置了maxStalenessSeconds,客户端将对每个从节点和主节点比较最近的一次写入操作,以评估数据的陈旧程度。然后客户端会选择一个在maxStalenessSeconds范围之内的从节点,在该节点上读取数据。

如果主节点挂了,客户端将选择最近执行写操作的从节点,在该节点上执行读操作。

默认情况下,read preference不使用该选项。

maxStalenessSeconds的值必须大于90秒,如果太小的话会返回error。因为客户端计算各个从节点的陈旧程度不会频繁执行,所计算出来的节点数据陈旧程度也是一个粗略值。因此,不能强制maxStalenessSeconds的值小于90秒。

7.18 复制集部署指引

三节点复制集部署

官网上有详细的介绍,参考:https://www.mongodb.com/docs/manual/tutorial/deploy-replica-set/

说明几点:

1 配置复制集成员和分片节点时,使用hostname,避免使用ip

2 如果我们希望设置哪些ip可以访问MongoDB,配置文件中使用 net.bindIp 参数或者命令行中使用 --bind_ip

systemLog:
   destination: file
   path: "/root/mongo/logs/mongod.log"
   logAppend: true
storage:
   dbPath: "/root/mongo/data"
net:
   bindIp: 0.0.0.0
   port: 27017

比如上面配置文件,bindIp为0.0.0.0表示任何ip都可访问这个复制集。

需要注意的是,如果bindIp是一个公网ip,需要做好安全策略。

默认情况下,mongod和mongos绑定ip为localhost,也就是说,默认情况下,mongo只接受本机发起的连接请求。

确保各个节点之间的网络传输以及客户端到复制集的网络连接是安全的,具体可以这么做:

1) 如果跨数据中心部署复制集,创建一个VPN(virtual private network),以确保复制集中的各个成员之间的网络通信是在一个局域网下进行。

2) 做好权限控制,以避免未知客户端访问复制集。

3) 配置网络和防火墙规则,以便只允许特定端口的请求发出和进入。

 

其余内容后续更新......