MongoDB

1.相关概念

1.业务场景

  • 三高需求:

    • 对数据库高并发读写的需求
    • 对海量数据的高效率存储和访问的需求
    • 对数据库的高可扩展性和高可用性的需求
  • 具体应用场景:

    • 社交场景:存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附件的人,地点等功能
    • 游戏场景:存储用户信息,用户的装备,积分等直接以内嵌文档的形式存储,方便查询,高效率存储和访问
    • 物流场景:存储订单信息,订单状态在运送过程中会不断更新,以内嵌数组的形式来存储,一次查询就能将订单的所有变更读取出来
    • 物联网场景:存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析
    • 视频直播场景:存储用户信息,点赞互动信息等
  • 共同特点:

    • 数据量大
    • 写入操作频繁
    • 价值较低的数据,对事务的要求不高

2.MongoDB

  • 开源,高性能,无模式的文档型数据库
  • 数据结构非常松散,BSON格式(类似于JSON)

3.体系结构

  • 集合对应于MySQL中的表
  • 文档对应于MySQL中的行
  • 不支持表连接,而是通过嵌入文档来代替多连接

2.基本常用命令

1.数据库的创建和删除

  • 选择和创建数据库
1
use 数据库名

# 选择数据库(如果数据库不存在,不会报错;会隐式创建:先存在内存中,当后期该数据库有数据时自动创建,再存到磁盘中)

# 默认的数据库为test,没有选择数据库时集合存放在test数据库中

  • 数据库名满足:

    • 不能是空字符串
    • 不能含有空格、.、$、/、\和\0
    • 应全部小写
    • 最多64字节
  • 部分数据库名是保留的:

    • admin:“root”数据库,将一个用户添加到这个数据库中,这个用户自动继承所有数据库的权限,一些特定的服务端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器
    • local:这个数据库永远不会被复制,可以用来存储限于本地单台服务器的任意集合
    • config:当Mongo用于分片设置时,config数控在内部使用,用于保存分片的相关信息
  • 查看有权限查看的所有数据库

1
show databases 或show dbs
  • 查看当前正在使用的数据库
1
db
  • 删除数据库
1
db.dropDatabase()

# 先选中数据库

2.集合的创建和删除

  • 集合的显式创建
1
db.createCollection('集合名')
  • 集合的隐式创建

  • 直接向未创建的集合中插入数据

  • 查看所有集合

1
show collections
  • 删除集合
1
db.集合名.drop()

3.文档的增删改查

1.插入

  • 单个文档的插入
1
2
3
4
5
6
7
db.集合名.insert/save(
<document or array of documents>,
{
writeConcern:<document>,
ordered:<boolean>
}
)
  • 参数:
    • document:要插入到集合中的文档或文档的数组(数据结构和json基本一致)
    • writeConcern:性能
    • ordered:排序

# 数字默认情况下是double类型,如果要存整型,必须使用函数NumberInt(整数)

# 插入当前日期使用new Date()

# 插入的数据没有指定_id,会自动生成主键值

# 如果某个字段没有值,可以赋值为null,或者不写该字段

  • 批量插入
1
2
3
4
5
6
7
db.集合名.insertMany(
[<document 1>,<document 2>,……],
{
writeConcern:<document>,
ordered:<boolean>
}
)
  • try catch
  • 对于可能存在异常的插入使用
1
2
3
4
5
6
7
8
9
10
11
12
13
try{
db.集合名.insertMany(
[<document 1>,<document 2>,……],
{
writeConcern:<document>,
ordered:<boolean>
}
);
}
catch(e){
print(e);
}

2.查询

  • 查询集合中的所有文档
1
2
3
db.集合名.find() 
或
db.集合名.find({})
  • 如果要按一定的条件来查询,在find()中添加参数,参数也是json格式

  • 如果只要返回符合条件的第一条数据,则使用findOne()并加参数

  • 投影查询:只需要返回部分字段

1
db.集合名.find(条件,[查询的字段1:1,查询的字段2:1])

# _id是会默认显示的,如果要不显示 _id则写_id:0

3.更新

  • 更新文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.集合名.update(query,update,options)
或
db.集合名.update(
<query>,
<update>,
{
upsert:<boolean>,
multi:<boolean>,
writeConcern:<document>,
collation:<document>,
arrayFilters:[<filterdocument 1>,……],
hint:<document|string>
}
)

# 默认是覆盖修改,即db.集合名.update(条件,要修改的字段),修改完之后只有被修改的字段

# 要局部修改就要在修改的字段前加$set

  • 在有条件相同的数据时,默认只修改第一条数据
  • 批量修改
1
db.集合名.update(条件,要修改的字段,{multi:true})
  • 列值增长
  • 在字段前加$inc字段后加:增长的数

4.删除

  • 删除文档
1
db.集合名.remove(条件)

# 如果不写条件则会全部删除

4.文档的分页查询

1.统计查询

  • 统计符合条件的记录数
1
db.集合名.count(条件)

# 不写条件则是统计所有

2. 分页列表查询

  • limit()函数读取指定数量的数据
  • skip()函数跳过指定数量的数据
1
db.集合名.find().skip().limit()

3.排序查询

  • sort()方法对数据进行排序,用1和-1来指定排序的方式
1
db.集合名.find().sort(字段1:1,字段2:-1)

# 1为升序,-1为降序

5.文档的更多查询

1.正则表达式

1
db.集合名.find({字段:/正则表达式/})

# 正则就是js的语法

2.比较查询

1
2
3
4
5
db.collection.find({ "field" : { $gt: value }}) // 大于: field > value
db.collection.find({ "field" : { $lt: value }}) // 小于: field < value
db.collection.find({ "field" : { $gte: value }}) // 大于等于: field >= value
db.collection.find({ "field" : { $lte: value }}) // 小于等于: field <= value
db.collection.find({ "field" : { $ne: value }}) // 不等于: field != value

3.包含查询

  • 包含使用$in操作符
1
db.comment.find({field:{$in:[value1,value2]}})
  • 不包含使用$nin操作符
1
db.comment.find({field:{$nin:[value1,value2]}})

4.条件查询

  • 并且$and
1
db.comment.find({$and:[{条件1},{条件2}]})
  • 或者$or
1
db.comment.find({$or:[{条件1},{条件2}]})

3.索引

1.概述

索引支持在MongoDB中高效地执行查询。如果没有索引,MongoDB必须执行全集合扫描,即扫描集合中的每个文档。

索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或一组字段的值,按字段值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB还可以使用索引中的排序返回排序结果。

2.索引的类型

1.单字段索引

  • 在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引

2.复合索引

  • 复合索引中列出的字段顺序即排序顺序,由前到后排序

3.其他索引

  • 地理空间索引 Geospatial Index
    • 为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引: 返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引。
  • 文本索引 Text Indexes
    • MongoDB提供了一种文本索引类型,支持在集合中搜索字符串内容.这些文本索引不存储特定于语言的停止词(例如 “the”, “a”, “or”), 而将集合中的词作为词干,只存储根词。
  • 哈希索引 Hashed Indexes
    • 为了支持基于散列的分片,MongoDB 提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询。

3. 索引的管理操作

1.索引的查看

  • 查看一个集合中的索引
1
db.collection.getIndexes()

# 默认是_id索引

# _id索引是唯一索引,此值不能重复

# 在分片集群中,通常使用_id作为片键

2.索引的创建

1
db.collection.createIndex(keys1,keys2, options)
  • keys:
    • 包含字段和值对
      • 字段是索引键
      • 值对是该字段的索引类型,对于字段上的升序索引指定为:1,降序索引指定为:-1
  • option:
    • background:
      • 建索引过程会阻塞其他数据库操作,background可以指定以后台方式创建索引,默认值为false
    • unique:
      • 建立的索引是否唯一。指定为true创建唯一索引。默认值为false
    • name:
      • 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
    • dropDups:
      • 3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定true 创建唯一索引。默认值为 false
    • spare:
      • 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档。默认值为false
    • expireAfterSeconds:
      • 指定一个以秒为单位的数值,完成TTL设定,设定集合的生存时间。
    • v:
      • 索引的版本号。默认的索引版本取决于mongodb创建索引时运行的版本。
    • weights:
      • 索引权重值,数值在1到99,999之间,表示该索引相对于其他索引字段的得分权重。
    • default_language:
      • 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。默认为英语
    • language_override:
      • 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为language

3.索引的删除

  • 删除某个索引
1
db.collection.dropIndex(index)
  • 删除全部索引
1
db.collection.dropIndexes()

# _id字段的索引是无法删除的

4.索引使用

1.执行计划

  • 分析查询性能 (Analyze Query Performance) 通常使用执行计划 (解释计划 - Explain Plan) 来查看查询的情况
1
db.<collection_name>.find( query, options ).explain(options)

# 返回的stage中COLLSCAN表示全集合扫描,IXSCAN表示基于索引的扫描

2.涵盖的查询

  • 当查询条件和查询的投影仅包含索引字段时,MongoDB直接从索引返回结果,,而不扫描任何文档或将文档带入内存,这些覆盖的查询十分有效

4.副本集 Replica Sets

1.简介

  • MongoDB中的副本集(Replica Set)是一组维护相同数据集的mongod服务。 副本集可提供冗余和高可用性,是所有生产部署的基础。

  • 也可以说,副本集类似于有自动故障恢复功能的主从集群。通俗的讲就是用多台机器进行同一数据的异步同步,从而使多台机器拥有同一数据的多个副本,并且当主库当掉时在不需要用户干预的情况下自动切换其他备份服务器做主库。而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载。

1.冗余和数据可用性

  • 复制提供冗余并提高数据可用性。 通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别的容错功能,以防止丢失单个数据库服务器。
  • 在某些情况下,复制可以提供增加的读取性能,因为客户端可以将读取操作发送到不同的服务上, 在不同数据中心维护数据副本可以增加分布式应用程序的数据位置和可用性。 还可以为专用目的维护其他副本,例如灾难恢复,报告或备份。

2.MongoDB 中的复制

  • 副本集是一组维护相同数据集的 mongod 实例。 副本集包含多个数据承载节点和可选的一个仲裁节点。 在承载数据的节点中,一个且仅一个成员被视为主节点,而其他节点被视为次要(从)节点。
  • 主节点接收所有写操作。 副本集只能有一个主要能够确认具有{w:“most”}写入关注的写入; 虽然在某些情况下,另一个 mongod 实例可能暂时认为自己也是主要的。主要记录其操作日志中的数据集的所有更改,即 oplog。
  • 辅助(副本)节点复制主节点的oplog并将操作应用于其数据集,以使辅助节点的数据集反映主节点的数据 集。 如果主要人员不在,则符合条件中的将举行选举以选出新的主要人员。

3.主从复制和副本集区别

  • 主从集群和副本集最大的区别就是副本集没有固定的”主节点”;整个集群会选出一个”主节点”,当其挂掉后,又在剩下的从节点中选中其他节点为主节点,副本集总有一个活跃点 (主、primary) 和一个或多个备份节点 (从、secondary)

2.副本集的三个角色

  • 两种类型:
    • 主节点(Primary)类型:数据操作的主要连接点,可读写
    • 次要(辅助、从)节点(Secondary)类型:数据冗余备份节点,可以读或选举
  • 三种角色:
    • 主要成员(Primary):主要接收所有写操作。就是主节点
    • 副本成员(Replicate):从主节点通过复制操作以维护相同的数据集,即备份数据,不可写操作,但可以读操作(但需要配置)。是默认的一种从节点类型
    • 仲裁者(Arbiter):不保留任何数据的副本,只具有投票选举作用。当然也可以将仲裁服务器维护为副本集的一部分,即副本成员同时也可以是仲裁者。也是一种从节点类型。

# 可以将额外的 mongod 实例添加到副本集作为仲裁者。 仲裁者不维护数据集。 仲裁者的目的是通过响应其他副本集成员的心跳和选举请求来维护副本集中的仲裁。 因为它们不存储数据集,所以仲裁器可以是提供副本集仲裁功能的好方法,其资源成本比具有数据集的全功能副本集成员更便宜。

# 如果副本集具有偶数个成员,请添加仲裁者以获得主要选举中的大多数投票。 仲裁者不需要专用硬件。

# 仲裁者将永远是仲裁者,而主要人员可能会退出并成为次要人员,而次要人员可能成为选举期间的主要人员。

# 如果副本+主节点的个数是偶数,建议加一个仲裁者,形成奇数,容易满足大多数的投票。

# 如果副本+主节点的个数是奇数,可以不加仲裁者。

3.副本集构建

1.目标

  • 一个主节点
  • 一个副节点
  • 一个仲裁节点

# 三个节点要用三个不同的端口号

# 副本集的名称要相同

2.创建节点

  • 三个节点都要创建存放数据和日志的目录。即db和log
  • 修改配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
systemLog:
destination: file
path: "/usr/local/mongodb/log/mongodb.log"
logAppend: true
storage:
dbPath: "/usr/local/mongodb/data"
journal:
enabled: true
processManagement:
fork: true
pidFilePath: "/usr/local/mongodb/run/mongod.pid"
net:
bindIp: 0.0.0.0
port: 27017
replication:
replSetName: rs

# 主要修改bindIp,port,replSetName

3.初始化配置副本集和主节点

  • 将三个节点启动
  • 使用客户端命令连接任意一个节点
  • 初始化新的副本集
1
rs.initiate()
  • 显示SECONDARY>表示从节点,按回车,显示PRIMARY>表示主节点
  • 返回ok的值为1则成功

4.查看配置

1
rs.conf(configuration)
  • 会显示
    • _id:副本集名称:
      • 副本集的配置数据存储的主键值,默认就是副本集的名字
    • members:
      • 副本集成员数组,此时只有一个:“host” : “180.76.159.126:27017”
    • arbiterOnly:false:
      • 该成员不是仲裁节点
    • priority:1:
      • 优先级(权重值)
    • settings:
      • 副本集的参数配置。

5.查看副本集状态

  • 返回包含状态信息的文档。此输出使用从副本集的其他成员发送的心跳包中获得的数据反映副本集的当前状态
1
rs.status()

6.添加副本节点以及仲裁节点

  • 在主节点添加从节点,将其他成员加入到副本集
1
rs.add(host, arbiterOnly)
  • host填地址
  • arbiterOnly:
    • 为true时,添加的是仲裁者

# 一般不加arbiterOnly参数,要添加仲裁节点之间用仲裁节点添加方法

1
rs.addArb(host)

7.副本集的数据读写操作

  • 副本节点 (SECONDARY) 默认不能 read , 更不可能 write 数据
1
rs.slaveOk()
  • 副本节点承认自己是副本节点

# 不执行这个命令前进行查看会显示"not master and slaveOk=false"

# 数据会自动同步, 但是会有延迟

# rs.slaveOk(false)则不再是副本节点

# 仲裁者节点, 不存放任何数据rs.slaveOk()也看不到数据

8.主节点的选举原则

  • 主节点选举的触发条件

    • 主节点故障
    • 主节点网络不可达 (默认心跳信息为 10 秒)
    • 人工干预rs.stepDown(600)
  • 选举规则是根据票数来决定谁获胜

  • 票数最高,且获得了“大多数”成员的投票支持的节点获胜。

    • “大多数”的定义为:
      • 假设复制集内投票成员数量为N,则大多数为 N/2 + 1。例如:3个投票成员, 则大多数的值是2。当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary, 复制集将无法提供写服务,处于只读状态。
  • 若票数相同,且都获得了“大多数”成员的投票支持的,数据新的节点获胜。

    • 数据的新旧是通过操作日志 oplog 来对比的。

# 在获得票数的时候,优先级(priority)参数影响重大。

# 可以通过设置优先级(priority)来设置额外票数。优先级即权重,取值为0-1000,相当于可额外增加 0-1000的票数,优先级的值越大,就越可能获得多数成员的投票(votes)数。指定较高的值可使成员 更有资格成为主要成员,更低的值可使成员更不符合条件。

# 默认情况下,优先级的值是 1

4.故障测试

1.副本节点故障测试

  • 关闭副本节点
  • 主节点和仲裁节点对副本节点的端口的心跳失败。因为主节点还在,因此,没有触发投票选举。
  • 如果此时,在主节点写入数据。再启动从节点,会发现,主节点写入的数据,会自动同步给从节点。

2.主节点故障测试

  • 关闭主节点
  • 从节点和仲裁节点对主节点的端口的心跳失败,当失败超过10秒,此时因为没有主节点了,会自动发起投票。
  • 而副本节点只有一个,因此,候选人只有一个,开始投票。
  • 仲裁节点向副本节点投了一票,副本节点自带一票,共两票,超过了“大多数”
  • 仲裁节点没有选举权,副本节点不向其投票,其票数是0.
  • 最终结果,副本节点成为主节点。具备读写功能。 在副本节点写入数据查看。

3.仲裁节点和主节点故障

  • 先关掉仲裁节点,再关掉主节点
  • 副本集中没有主节点了,导致此时,副本集是只读状态, 无法写入。
    • 副本节点的票数,没有获得大多数,即没有大于等于2,它只有默认的一票(优先级是1)
    • 如果要触发选举,随便加入一个成员即可。
      • 如果只加入仲裁节点成员,则主节点一定是当前副本节点,因为没得选了,仲裁节点不参与选举, 但参与投票
      • 如果只加入主节点,会发起选举。因为主节点和副本节点都是两票,则按照谁数据新,谁当主节点。

4.仲裁节点和从节点故障

  • 先关掉仲裁节点,关掉现在的副本节点
  • 10秒后,主节点自动降级为副本节点。(服务降级)
  • 副本集不可写数据了,已经故障了。

5.分片集群 - Sharded Cluster

1.分片概念

  • 分片 (sharding) 是一种跨多台机器分布数据的方法, MongoDB 使用分片来支持具有非常大的数据集和高吞吐量操作的部署。

  • 换句话说:分片 (sharding) 是指将数据拆分,将其分散存在不同的机器上的过程。有时也用分区 (partitioning) 来表示这个概念。将数据分散到不同的机器上,不需要功能强大的大型计算机就可以储存更多的数据,处理更多的负载。

  • 具有大型数据集或高吞吐量应用程序的数据库系统可以会挑战单个服务器的容量。例如,高查询率会耗尽服务器的 CPU 容量。工作集大小大于系统的 RAM 会强调磁盘驱动器的I/O容量。

  • 有两种解决系统增长的方法:垂直扩展和水平扩展。

    • 垂直扩展
      • 意味着增加单个服务器的容量,例如使用更强大的CPU,添加更多RAM或增加存储空间量。可 用技术的局限性可能会限制单个机器对于给定工作负载而言足够强大。此外基于云的提供商基于可用的硬件配置具有硬性上限。结果,垂直缩放有实际的最大值。
    • 水平扩展
      • 意味着划分系统数据集并加载多个服务器,添加其他服务器以根据需要增加容量。虽然单个机器的总体速度或容量可能不高,但每台机器处理整个工作负载的子集,可能提供比单个高速大容量服务器更高的效率。扩展部署容量只需要根据需要添加额外的服务器,这可能比单个机器的高端硬件的总体 成本更低。权衡是基础架构和部署维护的复杂性增加。

    MongoDB 支持通过分片进行水平扩展。

2.分片集群包含的组件

  • MongoDB 分片群集包含以下组件:
    • 分片(存储):
      • 每个分片包含分片数据的子集。 每个分片都可以部署为副本集。
    • mongos (路由):
      • mongos充当查询路由器,在客户端应用程序和分片集群之间提供接口。
    • config servers (”调度” 的配置):
      • 配置服务器存储群集的元数据和配置设置。 从MongoDB 3.4 开始,必须将配置服务器部署为副本集(CSRS)。

3.分片集群架构目标

graph RL
    subgraph 单个或多个路由节点["单个或多个路由节点 (mongos)"]
        M1["路由节点 1<br/>mongos"]
        M2["路由节点 2<br/>mongos"]
    end

    subgraph 多个副本集["多个副本集 (Shards)"]
        subgraph Shard1["Shard 1 副本集"]
            P1[("主节点<br/>Primary")]
            S1[("从节点<br/>Secondary")]
            A1[("仲裁节点<br/>Arbiter")]
        end
        
        subgraph Shard2["Shard 2 副本集"]
            P2[("主节点<br/>Primary")]
            S2[("从节点<br/>Secondary")]
            A2[("仲裁节点<br/>Arbiter")]
        end
    end

    subgraph 配置节点副本集["配置节点副本集 (Config Servers)"]
        CP[("主节点<br/>Primary")]
        CS[("从节点<br/>Secondary")]
        CS2[("从节点<br/>Secondary")]
    end


    %% 副本集与路由节点双向
    单个或多个路由节点<-->多个副本集


    %% 配置节点副本集分别指向副本集和路由节点
    配置节点副本集 --> 单个或多个路由节点
    配置节点副本集 --> 多个副本集


    class P1,P2,CP primary
    class S1,S2,CS secondary
    class A1,A2,CA arbiter
    class M1,M2 mongos

# 配置节点副本集也是副本集

1.副本集的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
systemLog:
#发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/mongodb/sharded_cluster/myshardrs01_27018/log/mongod.log"
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbpath设置仅适用于mongod。
dbPath: "/mongodb/sharded_cluster/myshardrs01_27018/data/db"
joural:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将其写入其PID
pidFilePath: "/mongodb/sharded_cluster/myshardrs01_27018/log/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定ip
bindIp: localhost,192.168.0.2
#bindIp
#绑定的端口
port: 27018
relication:
#副本集的名称
replSetName: myshardrs01
sharding:
#分片角色
clusterRole: shardsvr

# 主要修改分片角色

  • clusterRole:
    • shardsvr:
      • 分片
    • configsvr:
      • 配置

2.路由节点的创建

  • 只需要创建log目录
  • 修改mongos.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
systemLog:
#发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/mongodb/sharded_cluster/mymongos_27017/log/mongod.log"
logAppend: true
#mongod实例存储其数据的目录。storage.dbpath设置仅适用于mongod。
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将其写入其PID
pidFilePath: "/mongodb/sharded_cluster/mymongos_27017/log/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定ip
bindIp: localhost,192.168.0.2
#bindIp
#绑定的端口
port: 27219
sharding:
#指定配置节点副本集
configDB: myconfigrs/[ip地址]:27019,[ip地址]:27119,[ip地址]:27219
  • 主要修改configDB:配置节点副本集名称/三个副本集的ip:端口

  • 配置完成后启动mongos

# 添加第二个路由节点时就不用再修改configDB,已经添加到配置服务中,启动即可

3.路由节点分片配置

  • 添加分片
1
sh.addShard("副本集名称/IP:Port,IP:Port,IP:Port")
  • 查看分片状态情况
1
sh.status()

# 两个分片都要添加

# 如果添加分片失败,需要先手动移除分片,检查添加分片的信息的正确性后,再次添加分片。

1
2
use admin
db.runCommand( { removeShard: "myshardrs02" } )
  • 如果只剩下最后一个 shard,是无法删除的

  • 移除时会自动转移分片数据,需要一个时间过程

  • 完成后,再次执行删除分片命令才能真正删除

  • 开启分片功能(对库)

1
sh.enableSharding("库名")
  • 开启分片功能(对集合)
1
sh.shardCollection(namespace, key, unique)
  • namespace:库名.集合名
  • key:分片策略
    • 字段:hashed
    • 字段:1或-1
  • unique:片键字段上会限制为确保是唯一索引。哈希策略片键不支持唯一索引。默认为false

# 对集合进行分片时, 需要选择一个片键 (Shard Key) shard key 是每条记录都必须包含的, 且建立了索引的单个字段或复合字段, MongoDB按照片键将数据划分到不同的数据块中,并将数据块均衡地分布到所有分片中. 为了按照片键划分数据块, MongoDB使用基于哈希的分片方式(随机平均分配)或者基于范围的分片方式(数值大小分配) 。

# 一个集合只能有一个分片规则

4.分片策略(规则)

1.哈希策略
  • 对于基于哈希的分片 , MongoDB计算一个字段的哈希值, 并用这个哈希值来创建数据块。
  • 在使用基于哈希分片的系统中, 拥有”相近”片键的文档很可能不会存储在同一个数据块中, 因此数据的分离性更好一些.
2.范围策略
  • 对于基于范围的分片 , MongoDB 按照片键的范围把数据分成不同部分。假设有一个数字的片键 : 想象一个从负无穷到正无穷的直线,每一个片键的值都在直线上画了一个点。MongoDB把这条直线划分为更短的不重叠的片段, 并称之为数据块 ,每个数据块包含了片键在一定范围内的数据。
  • 在使用片键做范围划分的系统中, 拥有”相近”片键的文档很可能存储在同一个数据块中, 因此也会存储在同一个分片中。
3.基于范围的分片方式与基于哈希的分片方式性能对比
  • 基于范围的分片方式提供了更高效的范围查询, 给定一个片键的范围,分发路由可以很简单地确定哪个数据块存储了请求需要的数据,并将请求转发到相应的分片中。不过, 基于范围的分片会导致数据在不同分片上的不均衡,有时候,带来的消极作用会大于查询性能的积极作用。比如, 如果片键所在的字段是线性增长的, 一定时间内的所有请求都会落到某个固定的数据块中, 最终导致分布在同一个分片中。在这种情况下, 一小部分分片承载了集群大部分的数据,系统并不能很好地进行扩展。与此相比, 基于哈希的分片方式以范围查询性能的损失为代价, 保证了集群中数据的均衡。哈希值的随机性使数据随机分布在每个数据块中, 因此也随机分布在不同分片中。但是也正由于随机性, 一个范围查询很难 确定应该请求哪些分片, 通常为了返回需要的结果,需要请求所有分片。
  • 如无特殊情况,一般推荐使用Hash Sharding。而使用_id作为片键是一个不错的选择,因为它是必有的,可以使用数据文档_id的哈希作为片键。这个方案能够使得读和写都能够平均分布,并且它能够保证每个文档都有不同的片键所以数据块能够很精细。似乎还是不够完美,因为这样的话对多个文档的查询必将命中所有的分片。虽说如此,这也是一种比较好的方案了。理想化的shard key可以让 documents 均匀地在集群中分布

6.安全认证

1.MongoDB的用户和角色权限简介

  • 默认情况下,MongoDB实例启动运行时是没有启用用户访问权限控制的,也就是说,在实例本机服务器上都可以随意连接到实例进行各种操作,MongoDB不会对连接客户端进行用户验证,这是非常危险的。

  • mongodb官网上说,为了能保障mongodb的安全可以做以下几个步骤

    • 使用新的端口,默认的 27017 端口如果一旦知道了 ip 就能连接上,不太安全
    • 设置 mongodb 的网络环境,最好将 mongodb 部署到公司服务器内网,这样外网是访问不到的。公 司内部访问使用 vpn 等
    • 开启安全认证。认证要同时设置服务器之间的内部认证方式,同时要设置客户端连接到集群的账号 密码认证方式。
  • 为了强制开启用户访问控制(用户验证),则需要在MongoDB实例启动时使用选项 –auth 或在指定启动 配置文件中添加选项auth=true

  • 在开始之前需要了解一下概念

    • 启用访问控制:
      • MongoDB 使用的是基于角色的控制访问(Role-Based Access Control,RBAC)来管理用户对实例的访问。通过对用户授予一个或多个角色来控制用户访问数据库资源的权限和数据库操作的权限,在对用户分配角色之前,用户无法访问实例。
    • 角色:
      • 在MongoDB中通过橘色对用户授予相应数据库资源的操作权限,每个角色当中的权限可以显示指定,也可以通过继承其他角色的权限,或者两者都存在的权限。
    • 权限:
      • 权限由指定的数据库资源(resource)以及允许在指定资源上进行的操作(action)组成。
      • 资源(resource):数据库、集合、部分集合、集群
      • 操作(action):对资源进行增删改查(CRUD)操作
  • 在角色定义时,可以包含一个/多个已存在的角色,新创建的角色会继承包含的角色的所有的权限。在同一个数据库中,新创建的角色可以继承其他角色的权限,在admin数据库中创建的角色可以继承在其他任意数据库中角色的权限。

  • 查看角色权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 查询所有角色权限(仅用户自定义角色)
db.runCommand({ rolesInfo: 1 })

// 查询所有角色权限(包含内置角色)
db.runCommand({ rolesInfo: 1, showBuiltinRoles: true })

//查询当前数据库中的某角色的权限
db.runCommand({ rolesInfo: "<rolename>" })

//查询其他数据库中指定的角色权限
db.runCommand({ rolesInfo: {role: "<rolename>", db: "<database>"} })

//查询多个角色权限
db.runCommand(
{
rolesInfo: [
"<rolename>",
{ role:"<rolename>", db: "database" },
...
]
}
)

  • 常用的内置角色:

    • 数据库用户角色:read、readWrite
    • 所有数据库用户角色:readAnyDatabase、readWriteAnyDatabase、 userAdminAnyDatabase、dbAdminAnyDatabase
    • 数据库管理角色: dbAdmin、dbOwner、userAdmin
    • 集群管理角色: clusterAdmin、clusterManager、clusterMonitor、hostManager
    • 备份恢复角色: backup、restore
    • 超级用户角色: root
    • 内部角色: system
    角色 权限描述
    read 可以读取指定数据库中的任何数据。
    readWrite 可以读写指定数据库中的任何数据,包括创建、重命名、删除集合。
    readAnyDatabase 可以读取所有数据库中任何数据(除了数据库config和local之外)。
    readWriteAnyDatabase 可以读写所有数据库中任何数据(除了数据库config和local之外)。
    userAdminAnyDatabase 可以在指定数据库中创建和修改用户(除了数据库config和local之外)。
    dbAdminAnyDatabase 可以读取任何数据库以及对数据进行清理、修改、压缩、统计获取信息、执行检查等操作(除了数据库config和local之外)。
    dbAdmin 可以读取任何数据库以及对数据进行清理、修改、压缩、统计获取信息、执行检查等操作。
    userAdmin 可以在指定数据库创建和修改用户。
    clusterAdmin 可以对整个集群或数据库系统进行管理操作。
    backup 备份MongoDB数据最小的权限。
    restore 从备份文件中还原恢复MongoDB数据(除了system.profile集合)的权限。
    root 超级账号,超级权限。

2.单实例环境

  • 目标:对单实例的MongoDB服务开启安全认证,这里的单实例指的是未开启副本集或分片的MongoDB实例。

1.关闭已开启的服务

  • 在搭建服务时没有添加安全认证则需先关闭服务
1
2
use admin
db.shutdownServer()

2.添加用户和权限

  • 先按照普通无授权认证的配置,来配置服务端的配置文件/mongod/single/mongo.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
systemLog:
#发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其大宋所有诊断日志记录信息的日志文件的路径
path: "/mongodb/single/log/mongod.log"
logAppend: true
#mongod实例存储其数据的目录。storage.dbpath设置仅适用于mongod。
storage:
#mongod实例存储其数据的目录。storage.dbpath设置仅适用于mongod。
dbPath: "/mongodb/single/data/db"
joural:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将其写入其PID
pidFilePath: "/mongodb/single/log/mongod.pid"
net:
#服务实例绑定ip
bindIp: localhost,192.168.0.2
#绑定的端口
port: 27017
  • 按之前未开启认证的方式(不添加–auth参数)来启动MongoDB服务
1
/usr/local/mongodb/bin/mongod -f /mongod/single/mongod.conf
  • 使用Mongo客户端登录
1
/usr/local/mongodb/bin/mongo --host ip地址 --port 27017
  • 创建两个管理员用户,一个是系统的超级管理员myroot,一个是admin库的管理用户myadmin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//切换到admin库
use admin
//创建系统超级用户 myroot,设置密码123456,设置角色root
//> db.createUser({user:"myroot",pwd:"123456",roles:[ {"role" : "root" , "db" : "admin"} ]})
//或
db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
Successfully added user: {"user" : "myroot", "roles" : ["root"]}
//创建专门用来管理admin库的账号myadmin,只用来作为用户权限的管理
db.createUser({user:"myadmin",pwd:"123456",roles:[{role:"userAdminAnyDatabase",db:"admin"}]})
Successfully added user: {
"user" : "myadmin",
"roles" : [
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
]
}
1
2
3
4
5
6
7
8
//查看已创建用户的情况:
db.system.find()

//删除用户
db.dropUser("myadmin")

//修改密码
db.changeUserPassword("myroot","123456")

3.副本集环境

1.关闭已开启的副本集服务

1
2
3
4
5
6
//告知副本集说本机要下线
rs.stepDown()
//切换到admin库
use admin
//关闭服务
db.shutdownServer()

2.通过主节点添加一个管理员账号

  • 只需要在主节点上添加用户,副本集会自动同步。

  • 开启认证之前,创建超管用户

1
2
3
4
myrs:PRIMARY> use admin
switched to db admin
myrs:PRIMARY> db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
Successfully added user: { user:"myroot",pwd:"123456",roles:["root"] }

3.创建副本集认证的key文件

  • 生成一个key文件到当前文件夹中
  • 可以使用任何方法生成密钥文件。例如,以下操作使用openssl生成密码文件,然后使用chmod来更改文件权限,仅为文件提供者提供读取权限。
1
2
3
openssl rand -base64 90 -out ./mongo.keyfile
chmod 400 ./mongo.keyfile
ll mongo.keyfile

# 所有副本集节点都必须要用同一份keyfile,一般是在一台机器上生成,然后拷贝到其他机器上,且必须有读的权限,否则将来会报错permissions on /mongodb/replica_sets/myrs_27017/mongo.keyfile are too open

# 一定要保证密钥文件一致,文件位置随便。但是为了方便查找,建议每台机器都放到一个固定的位置,都放到和配置文件一起的目录中。

# 拷贝文件至多个节点的目录中

4.修改配置文件指定keyfile

  • 分别编辑几个服务的mongod.conf文件,添加相关内容
1
2
3
4
5
security: 
#KeyFile鉴权文件
keyFile: /mongod/replica_sets/myrs_27017/mongo.keyfile
#开启认证方式运行
authorization: enabled

5.重新启动副本集

  • 分别启动副本集节点

6.在主节点上添加普通账号

1
2
3
4
5
6
7
8
9
//先用管理员账号登录
//切换到admin库
use admin
//管理员账号认证
db.auth("myroot","123456")
//切换到要认证的库
use articledb
//添加普通用户
db.createUser({user: "hello", pwd: "123456", roles: ["readWrite"]})

4.分片集群环境

  • 与副本集环境下基本一致