MongoDB 闪亮登场
自我介绍
MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
MongoDB 最大的特点就是无 Schema 限制,灵活度很高。数据格式是 BSON,BSON 是一种类似 JSON 的二进制形式的存储格式,简称 Binary JSON 它和 JSON 一样,支持内嵌的文档对象和数组对象。
跟关系型数据库概念对比
Mysql | MongoDB |
---|---|
Database(数据库) | Database(数据库) |
Table(表) | Collection(集合) |
Row(行) | Document(文档) |
Column(列) | Field(字段) |
数据格式
MongoDB 将数据存储为一个文档,BSON 格式。由 key 和 value 组成。
{
"_id" : ObjectId("5e141148473cce6a9ef349c7"),
"title" : "批量更新",
"url" : "http://cxytiandi.com/blog/detail/8",
"author" : "yinjihuan",
"tags" : [
"java",
"mongodb",
"spring"
],
"visit_count" : NumberLong(10),
"add_time" : ISODate("2019-02-11T07:10:32.936+0000")
}
使用场景
大数据量存储场景
MongoDB 自带副本集和分片,天生就适用于大数量场景,无需开发人员通过中间件去分库分表,非常方便。
操作日志存储
很多时候,我们需要存储一些操作日志,可能只需要存储比如最近一个月的,一般的做法是定期去清理,在 MongoDB 中有固定集合的概念,我们在创建集合的时候可以指定大小,当数据量超过大小的时候会自动移除掉老数据。
爬虫数据存储
爬下来的数据有网页,也有 Json 格式的数据,一般都会按照表的格式去存储,如果我们用了 MongoDB 就可以将抓下来的 Json 数据直接存入集合中,无格式限制。
社交数据存储
在社交场景中使用 MongoDB 存储存储用户地址位置信息,通过地理位置索引实现附近的人,附近的地点等。
电商商品存储
不同的商品有不同的属性,常见的做法是抽出公共的属性表,然后和 SPU 进行关联,如果用 MongoDB 的话那么 SPU 中直接就可以内嵌属性。
自我陶醉
MongoDB 的功能点很多,但是大部分场景下我们只用了最简单的 CRUD 操作。下面隆重的介绍下 MongoDB 的功能点,就像你去相亲一样,不好好介绍自己的优点又怎能让你对面的菇凉心动呢?
CRUD
CRUD 也就是增删改查,这是数据库最基本的功能,查询还支持全文检索,GEO 地理位置查询等。
db.collection.insertOne()
单个文档插入到集合中
db.collection.insertMany()
多个文档插入到集合中
db.collection.insert()
单个或者多个文件插入到集合中
db.collection.find( )
查询数据
db.inventory.updateOne()
更新单条
db.inventory.updateMany()
更新多条
db.inventory.deleteOne( )
删除单条文档
db.inventory.deleteMany()
删除多条文档
Aggregation
聚合操作用于数据统计方面,比如 Mysql 中会有 count,sum,group by 等功能,在 MongoDB 中相对应的就是 Aggregation 聚合操作。
聚合下面有两种方式来实现我们需要对数据进行统计的需求,一个是 aggregate,一个是 MapReduce。
下图展示了 aggregate 的执行原理:
聚合内置了很多函数,使用好了这些函数我们就可以统计出我们想要的数据。
$project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
:用于过滤数据,只输出符合条件的文档。match 使用 MongoDB 的标准查询操作。
$limit:用来限制 MongoDB 聚合管道返回的文档数。
$skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
$group:将集合中的文档分组,可用于统计结果。
$sort:将输入文档排序后输出。
$geoNear:输出接近某一地理位置的有序文档。
$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
下图展示了 MapReduce 的执行原理:
总共 4 条数据,query 指定了查询条件,只处理 status=A 的数据。
map 阶段对数据进行分组聚合,也就是形成了第三部分的效果,根据 cust_id 去重统计。
reduce 中的 key 也就是 cust_id, values 也就是汇总的 amount 集合。然后进行 sum 操作,最终的结果通过 out 输出到一个集合中。
Transactions
MongoDB 最开始是不支持事务的,在 MongoDB 中,对单个文档的操作是原子性操作。所以再设计的时候可以使用嵌入的文档和数组来描述数据之间的关系,这样就不用跨多个文档和集合进行操作,也就通过了单文档原子性消除了许多实际用例对多文档事务的需要。
任何事物都是有限制的,某些场景还是不能完全通过内嵌的方式来描述数据的关系,还是会存在多个集合,对于使用 MongoDB 的用户来说,如果能支持事务就很方便了。
不负众望,MongoDB 4.0 版本的发布,为我们带来了原生的事务操作。
Indexes
索引不用我多说了,作用大家都知道。单索引,组合索引,全文索引,Hash 索引等。
db.collection.createIndex(
{user_id: 1, add_time: 1}, {background: true}
)
创建索引特别要注意的是将 background 设置为 true,在建索引的过程会阻塞其它数据库操作,background 可指定以后台方式创建索引,默认为 false。这可是血的教训呀,切记切记。
Security
MongoDB 中的安全需要重视,目前启动不知道有没有强制的限制,以前启动的时候可以不指定认证的方式,也就是不需要密码即可访问,然后很多人都直接用的默认端口,暴露在公网上,给不法分子有机可乘,出现了数据被删,需要用比特币来找回数据的案例比比皆是。
还是要开启安全认证,内置了很多角色,不同的角色可操作的内容不一样,控制的比较细。
Replication
副本集是一组相同数据集的 MongoDB 实例,同时在多个节点存储数据,提高了可用性。主节点负责写入,从节点负责读取,提高整体性能。
副本集由下面的组件构成:
Primary:主节点接收所有的写操作。
Secondaries:从节点会从主节点进行数据的复制,维护跟主节点相同的数据。用于查询操作。
Arbiter:仲裁节点本身不存储数据,只参与选举。
Sharding
分片是 MongoDB 绝对的亮点,将数据水平拆分到多个节点。MongoDB 的分片是全自动的,我们只需要配置好分片的规则,它就能自动维护数据并存储到不同节点。MongoDB 使用分片来支持大数据量的存储和高吞吐量的操作。
下图是 Mongodb 的分片集群架构图:
MongoDB 分片集群由以下组件够成:
Shard:每个 shard 的数据都是独立完整的一份。并且可以作为副本集部署。
mongos:mongos 是查询路由器,在客户端和服务端中间的一层,请求会直接到 mongos,由 mongos 路由到具体的 Shard。
Config Servers:存储集群所有节点、分片数据路由信息。
GridFS
GridFS 是 MongoDB 的一个子模块,主要用于在 MongoDB 中存储文件,相当于 MongoDB 内置的一个分布式文件系统。
本质上还是讲文件的数据分块存储在集合中,默认的文件集合分为 fs.files 和 fs.chunks。
fs.files 是存储文件的基本信息,比如文件名,大小,上传时间,md5 等。fs.chunks 是存储文件真正数据的地方,一个文件会被分割成多个 chunk 块进行存储,一般为 256k/个。
如果你的项目中用到了 MongoDB,那么你可以使用 GridFS 来构建一个文件系统,这样就不用去购买第三方的存储服务了。
GridFS 的好处是你不用单独去搭建一个文件系统,直接使用 Mongodb 自带的即可,备份,分片都依赖 MongoDB,维护起来也方便。
知识点总结
下图是我自己总结的一些知识点,作为一个后端开发来说,能掌握下面的内容就已经不错了,毕竟我们又不是要去抢 DBA 的饭碗,如果大家业余时间要学习的话可以按照下面的点进行学习,几年前我录制了一套视频,在我的网站上,大部分内容都覆盖到了。
工作必用
MongoDB 跟 Mysql 的语法对比
Spring Boot 中集成 MongoDB
加入 MongoDB 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
配置 MongoDB 的信息:
spring.data.mongodb.database=test
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
// 用户名,密码省略.......
直接注入 MongoTemplate 就可以操作 MongoDB:
@Autowired
private MongoTemplate mongoTemplate;
使用示列
创建一个实体类,对应 MongoDB 的集合
@Data
@Document(collection = "article_info")
public class Article {
@Id
@GeneratedValue
private Long id;
@Field("title")
private String title;
@Field("url")
private String url;
@Field("author")
private String author;
@Field("tags")
private List<String> tags;
@Field("visit_count")
private Long visitCount;
@Field("add_time")
private Date addTime;
}
最终存储到数据中的格式如下:
{
"_id" : ObjectId("5e141148473cce6a9ef349c7"),
"title" : "批量更新",
"url" : "http://cxytiandi.com/blog/detail/8",
"author" : "yinjihuan",
"tags" : [
"java",
"mongodb",
"spring"
],
"visit_count" : NumberLong(10),
"add_time" : ISODate("2019-02-11T07:10:32.936+0000")
}
插入数据
Article article = new Article();
article.setTitle("MongoTemplate 的基本使用 ");
article.setAuthor("yinjihuan");
article.setUrl("http://cxytiandi.com/blog/detail/1");
article.setTags(Arrays.asList("java", "mongodb", "spring"));
article.setVisitCount(0L);
article.setAddTime(new Date());
mongoTemplate.save(article);
数据库语法
db.article_info.save({
"title": "批量更新",
"url": "http://cxytiandi.com/blog/detail/8",
"author": "yinjihuan",
"tags": [
"java",
"mongodb",
"spring"
],
"visit_count": NumberLong(10),
"add_time": ISODate("2019-02-11T07:10:32.936+0000")
})
更新数据
Query query = Query.query(Criteria.where("author").is("yinjihuan"));
Update update = Update.update("title", "MongoTemplate")
.set("visitCount", 10);
mongoTemplate.updateMulti(query, update, Article.class);
数据库语法
db.article_info.updateMany(
{"author":"yinjihuan"},
{"$set":
{
"title":"MongoTemplate",
"visit_count": NumberLong(10)
}
}
)
删除数据
Query query = Query.query(Criteria.where("author").is("yinjihuan"));
mongoTemplate.remove(query, Article.class);
数据库语法
db.article_info.remove({"author":"yinjihuan"})
查询数据
Query query = Query.query(Criteria.where("author").is("yinjihuan"));
List<Article> articles = mongoTemplate.find(query, Article.class);
数据库语法
db.article_info.find({"author":"yinjihuan"})
存储文件
File file = new File("/Users/yinjihuan/Downloads/logo.png");
InputStream content = new FileInputStream(file);
// 存储文件的额外信息,比如用户ID,后面要查询某个用户的所有文件时就可以直接查询
DBObject metadata = new BasicDBObject("userId", "1001");
ObjectId fileId = gridFsTemplate.store(content, file.getName(), "image/png", metadata);
源码参考
https://github.com/yinjihuan/spring-cloud/tree/master/Spring-Cloud-Book-Code-2/ch-17/mongodb[1]
客户端推荐
下载地址:
https://studio3t.com/download/[2]
spring-boot-starter-mongodb-pool
最后推荐一个我自己写的小框架:Spring Boot 中增强 Mongodb 的配置,多数据源,连接池
https://github.com/yinjihuan/spring-boot-starter-mongodb-pool[3]
参考资料
[1]
mongodb: https://github.com/yinjihuan/spring-cloud/tree/master/Spring-Cloud-Book-Code-2/ch-17/mongodb
[2]studio3t: https://studio3t.com/download/
[3]spring-boot-starter-mongodb-pool: https://github.com/yinjihuan/spring-boot-starter-mongodb-pool