什么是Mongodb
MongoDB是NoSQL数据库中的佼佼者,目前是排名第一的文档型数据库。该数据库基于灵活的JSON文档模型,非常适合敏捷式的快速开发。与此同时,其与生俱来的高可用、高水平扩展能力使得它在处理海量、高并发的数据应用时颇具优势。
面向文档设计
在我们的系统中,通常会用分层来描述现实中的模型,如图所示:
从下往上看,每一层都提供了更简单、更容易表述的模型来隐藏下层的复杂性。最为典型的是,数据库系统屏蔽了所有磁盘中文件如何存取、压缩/解压等细节,向应用程序展示了一些通用的数据模型,如SQL表、列、或是基于JSON、XML的文档模型。而应用程序方面,面向对象的模型也已经被绝大多数人所熟知并接受。
在处理SQL数据模型时,应用程序需要通过代码做一些必要的转换工作,一般可以借助一些ORM框架来减少工作量,例如Hibernate。然而,SQL模型与面向对象之间仍然存在不少差异,这些差异并不能完全通过框架屏蔽。相较之下,基于JSON的文档模型则更能契合面向对象的设计准则,对于开发者来说,这在一定程度上降低了使用数据的门槛。
MongoDB是基于JSON来描述数据的,所有的“数据行”都可以通过一个JSON格式的文档来表示。基于JSON格式的数据模型可读性非常强,也更加灵活;除了基本的数据类型,文档中还可以使用数组、内嵌子对象等高级的字段类型。
此外,JSON还具备无模式的特点,可以轻松地进行扩展。在访问MongoDB地“表”之前,并不需要事先对表模型进行声明(尽管你可以这么做)。同时,当数据模型发生变更时,MongoDB不会强制要求你去执行表结构更新地相关操作,这提供了很大地便利性。
在MongoDB内部,BSON(一种二进制版本的JSON扩展)被真正用来存储这些JSON形式的文档数据。在JSON的基础上,进行了一些易用性方面的扩展,例如增加日期、二进制等类型的支持。
虽然MongoDB沿袭了JSON的特点,但是将它归为无模式数据库是不合适的。实际上,所有的读写都是基于一种内部隐含的模式,模式采取按需变更而非提前声明,因此动态模式一词更适合它。
特性
- 完备的索引
与大多数数据库一样,mongodb支持各种丰富的索引类型,包括单键索引、复合索引,唯一索引等一些常用的结构。由于采用了灵活可变的文档类型,因此它也同样支持对嵌套字段、数组进行索引。通过建立合适的索引,我们可以极大提升数据的检索速度。值得一提的是,mongodb的索引实现与一般的关系型数据索引并没有太大区别,因此,我们几乎可以使用某种“一致的思路”来设计索引或完成一些性能调优的任务。 - 跨平台,支持各种编程语言
MongoDB是用C++语言编写的,其官方网站提供了各种平台的编译版本,在客户端方面,mongodb提供了多种编程语言实现的驱动程序,除了Java、C/C++/C#等传统语言,像Python、NodeJS等动态语言也有对应的实现。 - 强大的聚合计算
聚合计算是MongoDB面向数据分析领域的重要特性,可以用于实现数据的分类统计或一些管道计算。mongodb为聚合框架提供了大量常用的函数以简化开发,除此之外,聚合框架还用到了一种叫“管道”(pipeline)的概念,用于抽象各个数据处理的阶段,一个管道由多个“阶段”(stage)组成,通过对不同的阶段进行自由组合,我们就可以灵活应对各种场景中的计算需求。 - 复制、分布式
mongodb通过副本集来实现数据库的高可用,这点类似于mysql的master/slave复制架构,不同的是,一个副本集可以由一个主节点和多个从节点组成,主节点和从节点基于oplog来实现数据同步。在主节点发生故障时,从节点将重新选举出新的主节点以继续提供服务,整个切换过程是自动完成的。
在海量数据处理方面,mongodb原生就支持分布式计算能力。在一个分布式集群中,多个文档被划入一个逻辑数据块(chunk),这些数据块可以被存储于不同的计算节点(分片)上,在新的计算节点加入时,数据块可以借助自动均衡的算法机制被迁移到合适的位置(通常是压力较小的分片)。通过这种自动化的调度以及均衡工作,整个集群的数据库读写压力可以被分摊到多个节点上,从而实现负载均衡和水平扩展。
数据结构
SQL概念 | MongoDB概念 |
---|---|
数据库(database) | 数据库(database) |
表(table) | 集合(collection) |
行(row) | 文档(document) |
列(column) | 字段(field) |
索引(index) | 索引(index) |
主键(primary key) | _id(字段) |
视图(view) | 视图(view) |
表连接(table join) | 聚合操作($lookup) |
尽管这些概念大多与sql标准定义类似,但是mongodb与传统的RDBMS仍然存在差异,包括:
- 半结构化,在一个集合中,文档所拥有的字段并不需要是相同的,而且也不需要对所用的字段进行声明。
- 弱关系,mongodb没有外键的约束,也没有非常强大的表连接能力。类似的功能需要使用聚合管道技术来弥补
Mongodb下载
各位可以参考这篇博客,笔者亲测使用第1种手动安装的方式有效。
Mac安装MongoDb保姆级教程以及踩坑笔记(图文详解)
因为,笔者的电脑是MacBook的m芯片系列,下载最新的mongodb的安装包时,发现在bin目录下缺少mongo脚本,这时各位可以下载旧版本(5.x版本)的mongo安装包,直接将mongo脚本脚本复制到对应的目录下即可。
连接Mongodb服务
一般,我们在代码程序中是通过连接字符串的方式来连接数据库服务的,类似如下格式:
mongodb://abc:123456@localhost:27017,localhost:27018,localhost:27019/hzu_lwy?replicaSet=replica&authSource=admin
首先,我们先逐部分解释这个连接字符串的含义:
- 协议部分
- mongodb://:这是协议头,表示使用MongoDB协议进行连接。
- 用户认证信息
- abc:123456@:这部分包含了用于身份验证的用户名和密码。
- abc:用户名。
- 123456:密码。
@:分隔符,表示用户名和密码的结束。
- abc:123456@:这部分包含了用于身份验证的用户名和密码。
- 主机和端口
- localhost:27017,localhost:27018,localhost:27019:这是MongoDB集群中3个节点的主机地址和端口。
- localhost:27017:第一个节点的IP地址和端口。
- localhost:27018:第二个节点的IP地址和端口。
- localhost:27019:第三个节点的IP地址和端口。
通过指���多个节点,客户端可以在连接时选择可用的节点,从而实现高可用性。
- localhost:27017,localhost:27018,localhost:27019:这是MongoDB集群中3个节点的主机地址和端口。
- 数据库名称
- /hzu_lwy:斜杠后面指定了要连接的数据库名称。
- hzu_lwy:这是默认连接的数据库名称。在进行身份验证时,如果没有指定authSource,则使用这个数据库。
- /hzu_lwy:斜杠后面指定了要连接的数据库名称。
- 连接选项
- ?replicaSet=replica&authSource=admin:这是连接字符串的查询参数部分,用于指定连接选项。
- replicaSet=replica:指定要连接的副本集名称为replica。这告诉客户端MongoDB集群是一个副本集,并且应该使用副本集的功能(如自动故障转移)。
- authSource=admin:指定用于身份验证的数据库为admin。这意味着用户凭据存储在admin数据库中,而不是在默认连接的数据库中。
- ?replicaSet=replica&authSource=admin:这是连接字符串的查询参数部分,用于指定连接选项。
总结:这条连接字符串用于连接到一个MongoDB副本集,使用指定的用户名和密码进行身份验证,连接到hzu_lwy数据库,并且身份验证信息存储在admin数据库中。通过指定多个节点的IP和端口,客户端可以在连接时选择可用的节点,从而实现高可用性和故障转移。
启动Mongodb服务
我们通过配置文件的方式来启动mongodb服务。如果读者是通过brew的方式来安转mongodb的,那么配置文件会自动下载,具体的下载目录,各位就google一下;如果是手动安装mongodb的,那么我们就需要自己设置配置文件。
接下来,我们新建一个conf目录用来存放配置文件,配置文件部分信息如下:
# MongoDB 配置文件示例
# 系统日志配置
systemLog:
destination: file # 日志输出到文件
path: /usr/local/mongodb/log/mongod.log # 日志文件路径
logAppend: true # 以追加方式写入日志文件
# 存储配置
storage:
dbPath: /usr/local/mongodb/data/db # 数据目录路径
# 网络配置
net:
bindIp: 0.0.0.0 # 允许所有ip连接
port: 27017 # 端口号
# 安全配置
security:
authorization: enabled # 启用认证
# 如果是副本集,还需要指定keyFile
keyFile: /usr/local/mongodb/ssl/key
# 复制配置(在配置复制集时使用)
replication:
replSetName: replica # 复制集名称
当在MongoDB副本集中启用授权时,必须使用keyFile来进行成员之间的身份验证。这是因为副本集中的各个成员需要相互认证,以确保安全通信。
当在MongoDB副本集中启用授权时,必须使用keyFile来进行成员之间的身份验证。这是因为副本集中的各个成员需要相互认证,以确保安全通信。
解决方法:
创建一个Key File。首先,你需要创建一个密钥文件。这个文件应该包含一个随机生成的字符串,长度在6到1024个字符之间。可以使用以下命令生成一个随机密钥:
openssl rand -base64 756 > /path/to/keyfile
确保密钥文件的权限是严格的,只允许MongoDB用户读取:chmod 400 /path/to/keyfile。必须设置不然,mongodb是启动不起来的,笔者就被坑了,最后还是通过系统日志(配置文件中配置mongod.log文件)发现原因所在。
接下来按照如下步骤:
- 启动多个MongoDB实例
mongod --config /path/to/mongod.conf --port 27017 --dbpath /usr/local/mongodb/data/db1 #命令行中指定了dbpath参数,会覆盖配置文件里面默认设置的 mongod --config /path/to/mongod.conf --port 27018 --dbpath /usr/local/mongodb/data/db2 mongod --config /path/to/mongod.conf --port 27019 --dbpath /usr/local/mongodb/data/db3
- 初始化副本集
选择一个实例(例如,端口27017)作为初始配置的主节点,使用MongoDB Shell(基于js语法,可以在shell)连接到该实例:
在MongoDB Shell中,使用rs.initiate()命令初始化副本集:./mongo --host localhost:27017
rs.initiate({ _id: "replica", members: [ { _id: 0, host: "localhost:27017" }, { _id: 1, host: "localhost:27018" }, { _id: 2, host: "localhost:27019" } ] })
在mongodb shell中设置用户名和密码
use admin db.createUser({"user":"abc","pwd":"123456","roles":[{role:"root",db:"admin"},{role:"clusterAdmin",db:"admin"}]}) db.auth('abc','123456')
注意:上面着条命令一定要在admin数据库下才能执行,并且命令中db属性必须时admin。
验证mogndb启动了副本集
rs.status()
在mongodb shell中执行上述命令,我们会看一共新建了3个mongodb节点,其中localhost:27017为主节点,localhost:27018和localhost:27019为从节点。并且,我们只能在主节点中执行数据库的增删改查命令,如果在从节点上执行则是会报错,并且当我们停掉任意一个节点时,剩下的2个节点会随机选举一个成为主节点。
BSON协议与类型
大多数情况下,使用JSON作为数据交互格式是理想的选择,但是JSON基于文本的解析效率并不是最好的,在某些场景下往往会考虑选择更合适的编/解码格式,如:
- 在微服务架构中,使用gRPC(基于Google的Protobuf)可以获得更好的网络利用率。
- 分布式中间件、数据库,使用私有定制的TCP数据包格式来提供高性能、低延时的计算能力。
BSON是二进制版本的JSON,其在性能方面有更优的表现。BSON在许多方面和JSON保持一致,其同样也支持内嵌的文档对象和数组结构。二者最大的区别在于JSON是基于文本的,而BSON则是二进制编/解码的形式。除此之外,BSON还提供了一些扩展的数据类型,比如日期,二进制数据等。mongodb在文档存储、命令协议上都采用了BSON作为编/解码格式。
注意:mongodb在使用日期类型时,通常需要注意时区的问题。mongodb的日期类型使用UTC进行存储,也就是+0时区的时间。一般客户端会根据本地时区自动转换为UTC时间。
我们在mongdb shell中进行如下测试:
> var date1=Date()
> var date2=new Date()
> var date3 = ISODate()
> var dateObject={d1:date1,d2:date2,d3:date3}
> db.dates.insert(dateObject)
> db.dates.find().pretty()
{
"_id" : ObjectId("67a8dde169a818fdad7af3b9"),
"d1" : "Mon Feb 10 2025 00:52:15 GMT+0800",
"d2" : ISODate("2025-02-09T16:52:23.442Z"),
"d3" : ISODate("2025-02-09T16:52:33.071Z")
}
可以看到,使用new Date和ISODate的语义是相同的,两者最终都会生成ISODate类型的字段(对应于UTC时间)。而Date与二者都不同,它会以字符串形式返回当前的系统时间。由于当前正处于+8时区,因此输出的时间值比ISODate多8个小时。
固定集合
固定集合(capped collection)是一种限定大小的集合,其中capped是覆盖、限额的意思。跟普通集合相比,数据在写入这种集合时遵循FIFO原则。可以将这种集合想象为一个环状的队列,新文档在写入时被插入队列的末尾,如果队列已满,那么之前的文档就会被新写入的文档覆盖。
使用示例
> db.createCollection("logs",{capped:true,size:4096,max:10})
{ "ok" : 1 }
> for(var i=0;i<15;i++){db.logs.insert({t:"row-"+i})}
WriteResult({ "nInserted" : 1 })
> db.logs.find()
{ "_id" : ObjectId("67a8e14e69a818fdad7af3bf"), "t" : "row-5" }
{ "_id" : ObjectId("67a8e14e69a818fdad7af3c0"), "t" : "row-6" }
{ "_id" : ObjectId("67a8e14e69a818fdad7af3c1"), "t" : "row-7" }
{ "_id" : ObjectId("67a8e14e69a818fdad7af3c2"), "t" : "row-8" }
{ "_id" : ObjectId("67a8e14e69a818fdad7af3c3"), "t" : "row-9" }
{ "_id" : ObjectId("67a8e14e69a818fdad7af3c4"), "t" : "row-10" }
{ "_id" : ObjectId("67a8e14e69a818fdad7af3c5"), "t" : "row-11" }
{ "_id" : ObjectId("67a8e14e69a818fdad7af3c6"), "t" : "row-12" }
{ "_id" : ObjectId("67a8e14e69a818fdad7af3c7"), "t" : "row-13" }
{ "_id" : ObjectId("67a8e14e69a818fdad7af3c8"), "t" : "row-14" }
> db.logs.stats()
{
"ns" : "damin.logs",
"size" : 355,
"count" : 10,
"avgObjSize" : 35,
"storageSize" : 16384,
"capped" : true,
"max" : 10,
"maxSize" : 4096,//必须是2的n次方。如果设定值不符合条件,则会被自动对齐。例如固定集合时指定size为500,那么最终的maxSize就是512
"sleepCount" : 0,
"sleepMS" : 0,
"wiredTiger" : {
- max:指集合的文档数量最大值,这里是10条。
- size:指集合的空间占用最大值,这里是4096字节。
这两个参数会同时对集合的上限产生影响。也就是说,只要任一条件达到阈值都会认为集合已经写满。其中size是必选,max是可选。
特征与限制
固定集合在底层使用的是顺序I/O操作,而普通集合使用的是随机I/O。总所周知,顺序I/O在磁盘操作上由于寻道次数少而比随机I/O要高效得多,因此固定集合的写入性能是很高的。此外,如果按写入顺序进行数据读取,也会获得非常好的性能表现。
但它也存在一些限制:
- 无法动态修改存储的上限,如果需要修改max或size,则只能先执行collection.drop命令,将集合删除再重建。
- 无法删除已有的数据。
- 对已有的数据进行修改,新文档大小必须与原来的文档大小一致。
- 默认情况下,固定集合只有一个_id索引,而且最好是按数据写入的顺序进行读取。
- 固定集合不支持分片,同时,在mongodb4.2版本中规定了事务中无法对固定集合执行写操作。
适用场景
固定集合很适合用来存储一些“临时态”的数据。该数据在一定程度上可以被丢弃。同时,用户还应该关注最新的数据,随着时间的推移,数据的重要性逐渐减低,直至被淘汰处理。
- 系统日志。
- 存储少量文档,如最新发布的Top N条文章信息。得益于内部缓存的作用,对于这种少量文档的查询非常高效。