Iawen's Blog

我喜欢这样自由的随手涂鸦, 因为我喜欢风......

MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB是一个介于关系数据库和非关系数据库之间的产品, 是非关系数据库当中功能最丰富, 最像关系数据库的。它支持的数据结构非常松散, 是类似json的bson格式, 因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大, 其语法有点类似于面向对象的查询语言, 几乎可以实现类似关系数据库单表查询的绝大部分功能, 而且还支持对数据建立索引。

1. MongoDB 介绍

1.1 安装

# https://www.mongodb.com/try/download/community
wget -c https://repo.mongodb.org/yum/redhat/8/mongodb-org/5.0/x86_64/RPMS/mongodb-org-server-5.0.5-1.el8.x86_64.rpm
yum install mongodb-org-server-5.0.5-1.el8.x86_64.rpm

# vim /etc/mongod.conf

1.2 Sql与mongodb的术语对比

SQL Mongodb
表(Talbe) 集合(Collection)
行(Row) 文档(Document)
列(Col) 字段(Field)
主键(Primary Key) 对象ID(ObjectId)
索引(Index) 索引(Index)
嵌套表(Embeded Table) 嵌入式文档(Embeded Document)
数组(Array) 数组(Array)

1.3 MongoDB 数据类型

数据类型 描述
String 字符串。存储数据常用的数据类型。在 MongoDB 中, UTF-8 编码的字符串才是合法的。
Integer 整型数值。用于存储数值。根据你所采用的服务器, 可分为 32 位或 64 位。
Boolean 布尔值。用于存储布尔值(真/假)。
Double 双精度浮点值。用于存储浮点值。
Min/Max keys 将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。
Arrays 用于将数组或列表或多个值存储为一个键。
Timestamp 时间戳。记录文档修改或添加的具体时间。
Object 用于内嵌文档。
Null 用于创建空值。
Symbol 符号。该数据类型基本上等同于字符串类型, 但不同的是, 它一般用于采用特殊符号类型的语言。
Date 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间: 创建 Date 对象, 传入年月日信息。
Object ID 对象 ID。用于创建文档的 ID。
Binary Data 二进制数据。用于存储二进制数据。
Code 代码类型。用于在文档中存储 JavaScript 代码。
Regular expression 正则表达式类型。用于存储正则表达式。

4

1.4 MongoDB 基本操作

1.4.1 基本管理命令

show dbs    // 查询所有的数据库
show collections
use test

1.4.2 插入数据

db.blog.insert(doc)  // 插入文档
db.blog.save(doc)  // 插入文档
db.blog.batchInsert(docs)  // 插入文档

1.4.3 查询数据

db.blog.find() // 查看所有的文档
db.blog.find({key:value})
db.blog.findOne({key:value})

1.4.4 修改数据

db.student.update({age:1000},{$set:{strenth:10}}) 如何设置属性, 没有属性可以增加属性
db.student.upset()

1.4.5 删除数据

db.student.remove(doc,isSingle)
db.student.remove({})
db.student.deleteBatch(doc,isSingle)
db.student.deleteOne()
db.student.deleteMany()

1.4.6 操作符一览

操作符 说明
$eq 匹配字段值等于指定值的文档
$gt 匹配字段值大于指定值的文档
$gte 匹配字段值大于等于指定值的文档
$lt 匹配字段值小于指定值的文档
$lte 匹配字段值小于等于指定值的文档
$ne 匹配字段值不等于指定值的文档, 包括没有这个字段的文档
$in 匹配字段值等于指定数组中的任何值
$nin 字段值不在指定数组或者不存在
$or 文档至少满足其中的一个表达式
$not 字段值不匹配表达式或者字段值不存在
$nor 字段值不匹配所有的表达式的文档, 包括那些不包含这些字段的文档
$exists 等于true时, 字段存在, 包括字段值为null的文档
$type 匹配字段值为指定数据类型的文档
$mod 匹配字段值被除有指定的余数的文档
$regex 正则表达式可以匹配到的文档
$text 针对创建了全文索引的字段进行文本搜索
$where 可以通过js表达式或js函数来查询文档
$all 字段值是包含所有指定元素的数组的文档
$elemMatch 数组字段至少一个元素满足所有指定查询条件的文档
$size 匹配数组字段元素个数等于指定数量的文档
$ (projection) 限定查询结果中指定数组字段返回满足条件的第一个元素
$elemMatch (projection) 限定查询结果中指定数组字段返回满足条件的第一个元素
$slice (projection) 控制指定数组字段返回元素个数
$inc 给一个字段增加指定值
$setOnInsert upsert为true时, 有插入文档操作时插入指定字段值
$unset 删除指定字段
$min 指定值小于当前值则更新为指定值
$max 指定值大于当前值则更新为指定值
$currentDate 设置字段值为当前日期
$ 更新指定数组的第一个元素
$addToSet 数组字段增加一个值
$pop 删除数组字段中的第一个或最后一个元素
$pullAll 删除数组字段中所有指定值, 如果指定值为数组, 则删除匹配数组内的元素
$pull 符合条件的值将被删除
$pushAll 向数组中追加多个指定值
$push 向数组中追加值
$each 用于 $addToSet添加多个值到数组中

1.5 MongoDB 索引

索引通常能够极大的提高查询的效率, 如果没有索引, MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。

db.blog.createIndex({"author": 1})

接收可选参数, 可选参数列表如下:

Parameter Type Description
background Boolean 建索引过程会阻塞其它数据库操作, background可指定以后台方式创建索引, 即增加 “background” 可选参数。 “background” 默认值为false。
unique Boolean 建立的索引是否唯一。指定为true创建唯一索引。默认值为false.
name string 索引的名称。如果未指定, MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
dropDups Boolean 在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false.
sparse Boolean 对文档中不存在的字段数据不启用索引; 这个参数需要特别注意, 如果设置为true的话, 在索引字段中不会查询出不包含对应字段的文档.。默认值为 false.
expireAfterSeconds integer 指定一个以秒为单位的数值, 完成 TTL设定, 设定集合的生存时间。
v index version 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。
weights document 索引权重值, 数值在 1 到 99,999 之间, 表示该索引相对于其他索引字段的得分权重。
default_language string 对于文本索引, 该参数决定了停用词及词干和词器的规则的列表。 默认为英语
language_override string 对于文本索引, 该参数指定了包含在文档中的字段名, 语言覆盖默认的language, 默认值为 language.

1.6 MongoDB 聚合与管道

1.6.1 聚合

MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等), 并返回计算后的数据结果。有点类似sql语句中的 count(*)。

db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

下表展示了一些聚合的表达式:

表达式 描述 实例
$sum 计算总和。 db.mycol.aggregate([{\(group : {_id : "\)by_user", num_tutorial : {\(sum : "\)likes"}}}])
$avg 计算平均值 db.mycol.aggregate([{\(group : {_id : "\)by_user", num_tutorial : {\(avg : "\)likes"}}}])
$min 获取集合中所有文档对应值得最小值。 db.mycol.aggregate([{\(group : {_id : "\)by_user", num_tutorial : {\(min : "\)likes"}}}])
$max 获取集合中所有文档对应值得最大值。 db.mycol.aggregate([{\(group : {_id : "\)by_user", num_tutorial : {\(max : "\)likes"}}}])
$push 在结果文档中插入值到一个数组中。 db.mycol.aggregate([{\(group : {_id : "\)by_user", url : {\(push: "\)url"}}}])
$addToSet 在结果文档中插入值到一个数组中, 但不创建副本。 db.mycol.aggregate([{\(group : {_id : "\)by_user", url : {\(addToSet : "\)url"}}}])
$first 根据资源文档的排序获取第一个文档数据。 db.mycol.aggregate([{\(group : {_id : "\)by_user", first_url : {\(first : "\)url"}}}])
$last 根据资源文档的排序获取最后一个文档数据 db.mycol.aggregate([{\(group : {_id : "\)by_user", last_url : {\(last : "\)url"}}}])

1.6.2 管道

管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。
MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
表达式: 处理输入文档并输出。表达式是无状态的, 只能用于计算当前聚合管道的文档, 不能处理其它的文档。

聚合框架中常用的几个操作:

  • $project: 修改输入文档的结构。可以用来重命名、增加或删除域, 也可以用于创建计算结果以及嵌套文档。
  • \(match: 用于过滤数据, 只输出符合条件的文档。\)match使用MongoDB的标准查询操作。
  • $limit: 用来限制MongoDB聚合管道返回的文档数。
  • $skip: 在聚合管道中跳过指定数量的文档, 并返回余下的文档。
  • $unwind: 将文档中的某一个数组类型字段拆分成多条, 每条包含数组中的一个值。
  • $group: 将集合中的文档分组, 可用于统计结果。
  • $sort: 将输入文档排序后输出。
  • $geoNear: 输出接近某一地理位置的有序文档。

1.6.3 使用样例

db.crypto_text_increment_result.aggregate(
{
	$match: {
    post_time: {$gt: ISODate("2022-06-01 00:00:00")},
    channel: "Reddit"
  }
},
{
	$group: {
    _id: {
      channel: "$channel",
      day: {"$substr": ["$post_time", 0, 10]}
    },
    "count": {"$sum": 1},
    "sentiment_score": {"$sum": "$sentiment_score"}
  }
},
{
  $sort: {"_id.day": -1}
})

db.crypto_user.find().forEach(
    function(user) {
        user.username = user.username.toLowerCase();
        db.crypto_user.save(user);
    }
);

2. MongoDB 进阶

2.1 MongoDB 副本集

mongodb的复制至少需要两个节点。其中一个是主节点, 负责处理客户端请求, 其余的都是从节点, 负责复制主节点上的数据。mongodb各个节点常见的搭配方式为: 一主一从、一主多从。主节点记录在其上的所有操作oplog, 从节点定期轮询主节点获取这些操作, 然后对自己的数据副本执行这些操作, 从而保证从节点的数据与主节点一致。MongoDB复制结构图如下所示:
0

2.1.1 副本集特征:

  • N 个节点的集群
  • 任何节点可作为主节点
  • 所有写入操作都在主节点上
  • 自动故障转移
  • 自动恢复

2.1.2 MongoDB副本集设置

  • 关闭正在运行的MongoDB服务器。
    现在我们通过指定 –replSet 选项来启动mongoDB。–replSet 基本语法格式如下:
    ``bash
    mongod –port “PORT” –dbpath “YOUR_DB_DATA_PATH” –replSet “REPLICA_SET_INSTANCE_NAME”

+ 副本集添加成员
添加副本集的成员, 我们需要使用多台服务器来启动mongo服务。进入Mongo客户端, 并使用rs.add()方法来添加副本集的成员。
语法: rs.add()
```js
>rs.add(HOST_NAME:PORT)

MongoDB中你只能通过主节点将Mongo服务添加到副本集中, 判断当前运行的Mongo服务是否为主节点可以使用命令db.isMaster() 。
MongoDB的副本集与我们常见的主从有所不同, 主从在主机宕机后所有服务将停止, 而副本集在主机宕机后, 副本会接管主节点成为主节点, 不会出现宕机的情况。

2.2 MongoDB 分片

分片(sharding)是MongoDB用来将大型集合分割到不同服务器(或者说一个集群)上所采用的方法。尽管分片起源于关系型数据库分区, 但MongoDB分片完全又是另一回事。和MySQL分区方案相比, MongoDB的最大区别在于它几乎能自动完成所有事情, 只要告诉MongoDB要分配数据, 它就能自动维护数据在不同服务器之间的均衡。

2.2.1 为什么使用分片

  • 复制所有的写入操作到主节点
  • 延迟的敏感数据会在主节点查询
  • 单个副本集限制在12个节点
  • 当请求量巨大时会出现内存不足。
  • 本地磁盘不足
  • 垂直扩展价格昂贵

2.2.2 分片的目的

高数据量和吞吐量的数据库应用会对单机的性能造成较大压力,大的查询量会将单机的CPU耗尽,大的数据量对单机的存储压力较大,最终会耗尽系统的内存而将压力转移到磁盘IO上。为了解决这些问题,有两个基本的方法: 垂直扩展和水平扩展。

  • 垂直扩展: 增加更多的CPU和存储资源来扩展容量。
  • 水平扩展: 将数据集分布在多个服务器上。水平扩展即分片。

2.2.3 分片集群结构分布

1

上图中主要有如下所述三个主要组件:

  • Shard(Mongod)
    存储应用数据记录。一般有多个Mongod节点, 达到数据分片目的。实际生产环境中一个shard server角色可由几台机器组个一个replica set承担, 防止主机单点故障

  • Config Server
    mongod实例, 存储了整个 ClusterMetadata(所有节点、分片数据路由信息), 其中包括 chunk信息。默认需要配置3个Config Server节点。

  • Query Routers(Mongos)
    提供对外应用访问, 所有操作均通过mongos执行。一般有多个mongos节点。数据迁移和数据自动平衡。前端路由, 客户端由此接入, 且让整个集群看上去像单一数据库, 前端应用可以透明使用。

Mongos本身并不持久化数据, Sharded cluster所有的元数据都会存储到Config Server, 而用户的数据会议分散存储到各个shard。Mongos启动后, 会从配置服务器加载元数据, 开始提供服务, 将用户的请求正确路由到对应的碎片。

2.2.4 分片机制提供了如下三种优势

  • 对集群进行抽象, 让集群“不可见”
    MongoDB自带了一个叫做mongos的专有路由进程。mongos就是掌握统一路口的路由器, 其会将客户端发来的请求准确无误的路由到集群中的一个或者一组服务器上, 同时会把接收到的响应拼装起来发回到客户端。

  • 保证集群总是可读写
    MongoDB通过多种途径来确保集群的可用性和可靠性。将MongoDB的分片和复制功能结合使用, 在确保数据分片到多台服务器的同时, 也确保了每分数据都有相应的备份, 这样就可以确保有服务器换掉时, 其他的从库可以立即接替坏掉的部分继续工作。

  • 使集群易于扩展
    当系统需要更多的空间和资源的时候, MongoDB使我们可以按需方便的扩充系统容量。

2.2.5 数据区分

分片键shard key
MongoDB中数据的分片是、以集合为基本单位的, 集合中的数据通过片键(Shard key)被分成多部分。其实片键就是在集合中选一个键, 用该键的值作为数据拆分的依据。所以一个好的片键对分片至关重要。片键必须是一个索引, 通过sh.shardCollection加会自动创建索引(前提是此集合不存在的情况下)。一个自增的片键对写入和数据均匀分布就不是很好, 因为自增的片键总会在一个分片上写入, 后续达到某个阀值可能会写到别的分片。但是按照片键查询会非常高效。

随机片键对数据的均匀分布效果很好。注意尽量避免在多个分片上进行查询。在所有分片上查询, mongos会对结果进行归并排序。
对集合进行分片时, 你需要选择一个片键, 片键是每条记录都必须包含的, 且建立了索引的单个字段或复合字段, MongoDB按照片键将数据划分到不同的数据块中, 并将数据块均衡地分布到所有分片中。

为了按照片键划分数据块, MongoDB使用基于范围的分片方式或者 基于哈希的分片方式。

注意:

  • 分片键是不可变。
  • 分片键必须有索引。
  • 分片键大小限制512bytes。
  • 分片键用于路由查询。
  • MongoDB不接受已进行collection级分片的collection上插入无分片
  • 键的文档(也不支持空值插入)

以范围为基础的分片Sharded Cluster
Sharded Cluster支持将单个集合的数据分散存储在多shard上, 用户可以指定根据集合内文档的某个字段即shard key来进行范围分片(range sharding)。
2

对于基于范围的分片, MongoDB按照片键的范围把数据分成不同部分。

假设有一个数字的片键:想象一个从负无穷到正无穷的直线, 每一个片键的值都在直线上画了一个点。MongoDB把这条直线划分为更短的不重叠的片段, 并称之为数据块, 每个数据块包含了片键在一定范围内的数据。在使用片键做范围划分的系统中, 拥有”相近”片键的文档很可能存储在同一个数据块中, 因此也会存储在同一个分片中。

基于哈希的分片
分片过程中利用哈希索引作为分片的单个键, 且哈希分片的片键只能使用一个字段, 而基于哈希片键最大的好处就是保证数据在各个节点分布基本均匀。
3

对于基于哈希的分片, MongoDB计算一个字段的哈希值, 并用这个哈希值来创建数据块。在使用基于哈希分片的系统中, 拥有”相近”片键的文档很可能不会存储在同一个数据块中, 因此数据的分离性更好一些。

Hash分片与范围分片互补, 能将文档随机的分散到各个chunk, 充分的扩展写能力, 弥补了范围分片的不足, 但不能高效的服务范围查询, 所有的范围查询要分发到后端所有的Shard才能找出满足条件的文档。

分片键选择建议

  • 1、递增的sharding key
    数据文件挪动小。(优势)
    因为数据文件递增, 所以会把insert的写IO永久放在最后一片上, 造成最后一片的写热点。同时, 随着最后一片的数据量增大, 将不断的发生迁移至之前的片上。

  • 2、随机的sharding key
    数据分布均匀, insert的写IO均匀分布在多个片上。(优势)
    大量的随机IO, 磁盘不堪重荷。

  • 3、混合型key
    大方向随机递增, 小范围随机分布。
    为了防止出现大量的chunk均衡迁移, 可能造成的IO压力。我们需要设置合理分片使用策略(片键的选择、分片算法(range、hash))

分片注意:

  • 分片键是不可变、分片键必须有索引、分片键大小限制512bytes、分片键用于路由查询。
  • MongoDB不接受已进行collection级分片的collection上插入无分片键的文档(也不支持空值插入)
sh.enableSharding("test")
db.users.createIndex({"username": 1})
sh.shardCollection("test.users", {"username": 1})

参考:

MongoDB(四)内置函数、运算符、索引