【Java劝退师】MongoDB知识脑图-文档导向的

MongoDB

MongoDB

文档导向的 NoSQL 数据库

非关系型数据库中功能最丰富,最像关系型数据库

一、选用前提条件

○ 满足其中 2 个条件,选 Mongo 就很好

○ 满足其中 3 个条件,直接选 Mongo

  • 不需要 事务、复杂 join - 强事务场景 选择 关系型数据库

  • 数据模型无法确定

  • 需要 2000 以上的 读写 QPS

  • 需要 TB、PB 级别的数据存储

  • 需要 能快速水平扩展

    ○ 水平扩展 : 增加服务器数量,扩充系统性能

    ○ 垂直扩展 : 提升单机处理能力

  • 需要 99.999% 高可用

  • 大量 地理位置、文本查找

二、适用场景

○ 适合 关系型数据库中,【一对多】的场景

○ 适合 大尺寸、低价值 的 BSON 数据存储

  • 游戏场景 - 玩家装备、积分
  • 物流场景 - 物流订单状态
  • 社交场景 - 附近的人、地点
  • 物联网场景 - 智能设备日志信息
  • 直播场景 - 主播礼物

三、名词解释

  • NoSQL : Not Only SQL - 不要求结构化存储

    SQL 为结构化查找语言

  • BSON : Binary JSON

    • 类 JSON 的二进制存储格式
    • 有一些 JSON 没有的数据类型
    • 优点 - 灵活性高
    • 缺点 - 空间利用率不理想 - 存在重复键名之类的冗余信息

    ○ JSON 是使用 字符串 方式存储数据

    ○ BSON 是使用 二进制 方式存储数据

四、概念对比

RDBMS MongoDB
database 数据库 database 数据库
table 表 collection 集合
row 行 document BSON 文档
column 列 field 字段
index 索引 index 索引
join 主外键关联 embedded Document 嵌套文档
primary key 指定 1 至 N 个列做主键 primary key 指定 _id field 做为主键

五、BSON 类型

【插入数据】

○ db.集合名.insert(文档) - 插入数据主键已存在,则报异常

○ db.集合名.insertOne(文档) - 插入数据主键存在则更新,不存在则插入

  • String : 字符串 - { key : "cba" }

  • Integer : 整数 - { key : 1 }

  • Boolean : 布尔值 - { key : true }

  • Double : 双精度符点 - { key : 3.14 }

  • ObjectId : 对象 ID - { _id : new ObjectId() }

    由 时间戳、机器码、进程ID、随机数 组成

  • Array : 数组 - { arr : [ "a", "b"] }

  • Timestamp : 时间戳 - { ts : new Timestamp() }

  • Object : 内嵌文档 - { o : { foo : "bar" } }

  • Null : 空值 - { key : null }

  • Date、ISODate : 格林威治时间 - { birth : new Date() }

  • Code : 代码 - { x : function(){} }

  • File : 文档 - fs.files、fs.chunks

六、条件查找

db.集合名.find({条件}).sort({排序字段:排序方式})).skip(跳过的行数).limit(一页显示多少数据)

操作 条件格式 范例 RDBMS 中的语意
等于 { key : value } db.集合名.find( { 字段名 : 值 } ).pretty() where 字段名 = 值
大于 { key : { $gt : value } } db.集合名.find( { 字段名 : { $gt : 值 } } ).pretty() where 字段名 > 值
小于 { key : { $lt : value } } db.集合名.find( { 字段名 : { $lt : 值 } } ).pretty() where 字段名 < 值
大于等于 { key : { $gte : value } } db.集合名.find( { 字段名 : { $gte : 值 } } ).pretty() where 字段名 >= 值
小于等于 { key : { $lte : value } } db.集合名.find( { 字段名 : { $lte : 值 } } ).pretty() where 字段名 <= 值
不等于 { key : { $ne : value } } db.集合名.find( { 字段名 : { $ne : 值 } } ).pretty() where 字段名 != 值
and { key1 : value1, key2 : value2 } db.集合名.find( { key1 : value1, key2 : value2 } ).pretty() where 字段名1 = 值1 and 字段名2 = 值2
or { $or : [ { key1 : value1 }, { key2 : value2 } ] db.集合名.find( { $or : [ { key1 : value1 }, { key2 : value2 } ] } ).pretty() where 字段名1 = 值1 or 字段名2 = 值2
not { key : { not : { 操作符 : value } } db.集合名.find( { key : { not : { 操作符 : value } } ).pretty() where not 字段名 $操作符 值

七、数据更新

db.集合名.update(
  <查找条件>,
  <更新方式>,
  {
    upsert: <boolean>,
    multi: <boolean>,
    writeConcern: <document>
  }
)
复制代码

1. 更新方式

  • $set : 设置字段值
  • $unset : 删除字段
  • $inc : 对修改的字段值进行增减 - 可以为 正值 或 负值

2. 属性

  • upsert : 如果不存在要 update 的纪录,是否进行插入 - 【默认】false
  • multi : 只更新找到的第一条纪录 - 【默认】false
  • writeConcern : 可靠性属性
    • w
      • 【默认】1 : 所有副本确认
      • 0 : 不要求副本确认
      • majority : 多数副本确认
    • j
      • true : 操作写入硬盘才能返回 ACK
      • false : 操作不必写入硬盘就能返回 ACK
    • wtimeout : 操作时间限制
db.col.update( { name : "张三" }, { $inc : { expectSalary : 3000 } }, { upsert : ture } )
复制代码

八、数据删除

db.集合名.remove(
	<查找条件>,
	{
    	justOne: <boolean>,
    	writeConcern: <document>
    }
)
复制代码

1. 属性

  • justOne : 是否只删除一个文档 - 【默认】false
  • writeConcern : 可靠性属性
db.col.remove( { name : "张三" } )
复制代码

九、聚合操作

1. 单目的聚合

  • count() : 统计数量
  • distinct() : 去重并返回属性值
db.集合名.find({}).count()
复制代码

2. 聚合管道

1. 操作

  • $group : 文档分组
    • 用于统计不同类型数据
  • $project : 修改输出文档结构
    • 重命名
    • 增加、删除 域
    • 创建计算结果
    • 嵌套文档
  • $match : 过滤数据,只输出符合条件的文档
  • $limit : 限制聚合管道返回的文档数
  • $skip : 跳过指定文档数量,返回剩余的文档
  • $sort : 将文档排序后输出
  • $geoNear : 输出接近某一地理位置的有序文档

2. 表达式

  • $sum : 计算总合
  • $avg : 计算平均值
  • $min : 获取集合中所有文档的最小值
  • $max : 获取集合中所有文档的最大值
  • $push : 将结果文档的值,插入到一个数组中
  • $addToSet : 将结果文档的值,插入到一个数组中,但数据不重复
  • $first : 获取第一个文档数据
  • $last : 获取最后一个文档数据
db.col.aggregate([ 
  {$group : {_id: "$city"/*按照 city 字段分组*/, city_count /*聚合结果字段名称*/ : { $sum /*聚合方式*/ : 1 } } } 
])

db.col.aggregate([
  {$group : {_id: "$city", avgSal:{$avg:"$expectSalary"}}},
  {$project : {city: "$city", salary : "$avgSal" }} /*将 avgSal 字段改为 salary*/
])

db.col.aggregate([
  {$group:{_id: "$city",count:{$sum : 1}}},
  {$match:{count:{$gt:1}}} /*统计数量大于 1 才进行显示*/
])
复制代码

3. MapReduce

○ 在多台 Server 上 并行 运行 聚合逻辑

○ 如果一个聚合操作消耗 20% 以上的内存,将被迫中止,并输出错误消息

db.col.mapReduce(
  function() {emit(key,value);}, // Map 方法
  function(key,values) {return reduceFunction}, // Reduce 方法
  {
    out: collection,
    query: document,
    sort: document,
    limit: number,
    finalize: <function>,
    verbose: <boolean>
  }
)

db.col.mapReduce(
  function() { emit(this.city,this.expectSalary); },
  function(key, value) {return Array.avg(value)},
  {
    query:{expectSalary:{$gt: 15000}},
    out:"cityAvgSal"
  }
)
复制代码
  • Map 方法 : Javasript 方法,负责将一个 输入文档 转换成零或多个 输出文档,作为 Reduce 方法的参数

  • Reduce 方法 : Javasript 方法,对 Map 方法的输出进行合并计算 - 相同 key 会前往同一个 Reduce 方法

  • out : 统计结果 存放的集合

  • query : 筛选条件,满足条件的文档才会调用 Map 函数

  • sort : 对发往 Map 函数前的文档进行排序

  • limit : 限制发往 Map 函数的数量

    sort、limit 必须结合使用才有意义

  • finalize : 对 Reduce 输出结果进行修改

  • verbose : 是否包含结果信息中的时间信息 - 【默认】false

十、索引类型

○【目的】提高查找效率

○【默认】对 _id 创建唯一索引

○【底层实现】B树

○【排序方式】1 : 升序,-1 : 降序

  • 单键索引
    • 对单个字段的索引
    • db.集合名.createIndex( { "字段名" : 排序方式 } )
  • 过期索引
    • 文档在一定时间后自动删除
    • 字段必须为日期类型
    • db.集合名.createIndex( { "日期字段" : 排序方式 }, { expireAfterSeconds : 秒数 } )
  • 复合索引
    • 在多个字段上创建索引
    • 需注意 索引顺序、排序方式
    • db.集合名.createIndex( { "字段名1" : 排序方式, "字段名2" : 排序方式 } )
  • 多键索引
    • 针对数组中的每一个元素创建索引
    • 在复合索引中只能有一个多键索引
    • 创建方式与创建普通索引方式相同
    • db.集合名.createIndex( { "字段名" : 排序方式 } )
  • 地理空间索引
    • 针对地理空间座标创建索引
    • 2dsphere : 球面上的点
    • 2d : 平面上的点
    • db.集合名.ensureIndex( { "字段名" : "2dsphere" } )
  • 全文索引
    • 一个集合最多一个全文索引
    • 中文分词器不理想 - 推荐改用 ES
    • db.集合名.createIndex( { "字段名" : "text" } )
  • 哈希索引
    • 仅支持等值查找,不支持范围查找
    • db.集合名.createIndex( { "字段" : "hashed" } )

十一、数据模型

  • 内嵌

    • 文档嵌套文档

    • 适用场景

      • 文档间为 一对一、一对多 关系
      • 经常一起读取的数据
      • 有 Map-Reduce 聚合需求的数据 - Map-Reduce 只能操作单个集合
      {
          "_id":"ObjectId("xxxxx")",
          "name":"Zhang san",
          "classes":[
              {
                  "class":"Math",
                  "credits":"5",
                  "room":"204"
              },
              {
                  "class":"English",
                  "credits":"5",
                  "room":"305"
              }
          ]
      }
      复制代码
  • 引用

    • 文档存储另一个文档的引用信息

    • 当 内嵌 会导致过多数据重复

    • 文档间为 多对多 关系

      {
          "_id":"ObjectId("xxxxx")",
          "name":"Zhang san",
          "classes":[
              {
                  "_id":"ObjectId("xxx")",
                  "class":"Math"
              },
              {
                  "_id":"ObjectId("xxx")",
                  "class":"English"
              }
          ]
      }
      复制代码

十二、高可用

1. 角色

  • Primary 主节点

    • 负责 读写 操作

    • 纪录 增、删、改 操作到 oplog 中 - oplog 具备幂等性

      幂等性 : 多次运行的结果与只运行一次的结果相同

  • Secondary 从节点

    • 负责 读 操作

    • 透过复制 oplog,使数据与 Primary 保持一致

  • ArbiterOnly 仲裁节点

    • 负责 投票选出 Primary
    • 不能 增、删、改 数据
    • 不能 变成 Primary

2. 同步类型

  • 初始化同步
    • 全量从 Primary 同步数据
    • 触发情形
      • Secondary 第一次加入
      • Secondary 落后的数据量超过 oplog 的大小
  • Keep 复制同步
    • 增量从 Primary 同步数据

3. 心跳检测

  • 每 2 秒向其他节点发送一次 Ping 包,10 秒内未响应则将该节点标记为不能访问
  • 每个节点都会维护一份 状态映射表,纪录其他节点的 角色、日志时间戳 ... 等信息
  • Primary 发现自己无法与大部分节点通信,则会把自己降为 Secondary

4. 主节点击举

  • 触发时机
    • Secondary 发现权重比 Primary 高,发起 替换选举
    • Secondary 发现集群中没有 Primary 时,发起 选举
    • Primary 发现不能访问到其他大部分节点时,主动降级
  • 选举过程
    1. 检测自身是否符合候选人资格,若符合则成为 发起者,并进行 FreshnessCheck
    2. 发起者 向 存活节点 发送 Elect 请求
    3. 仲裁者 收到请求会运行合法性检查,若检查通过,则 仲裁者 给 发起者 投一票
    4. 发起者 若获得投票超过半数,则成为 Primary

5. 分片

1.角色

  • Shard Server
    • 负责存储数据
    • 由 一个 或 多个 mongod 进程组成,每个进程保存相同的数据分片
    • 保存不同数据分片的 Shard Server,可组合成 Shards Server 集群
  • Router Server
    • 集群入口,负责将请求转发到对应的 Shard Server 上
  • Config Server
    • 负责存储数据库 路由、分片 配置

2. 概念

  • Shard Key 分片主键
    • 用于确定数据该存放在哪个 Chunk
  • Chunk 区块
    • Shard Server 内的一部分数据
    • 一个 Shard Server 由多个 Chunk 组成
    • 基于 左闭右开 的区间范围

3. 分片策略

  • 范围分片

    • 基于 Shard Key 的值切分数据,每个 Chunk 将分配到一个范围
    • 适合范围查找
    • 缺点 : 当 Shard Key 属于 递增 或 递减 趋势(比如,自动生成ID - 时间戳),则添加的数据都会被分到同一个 Chuck,则该 Chunk 写压力大
  • 哈希分片

    • 将文档随机分散到各个 Chunk,扩展写能力

    • 缺点 : 不能高效范围查找