mongoDB
-
数据量会随着用户数增大而增大
-
读多写少
-
价值较低
-
非好友看不到其动态内容
-
地理位置的查询
-
……
针对以上特点,我们来分析一下:
-
mysql:关系型数据库(效率低)
-
redis:redis缓存(微博,效率高,数据格式不丰富)
-
对于数据量大而言,显然不能够使用关系型数据库进行存储,我们需要通过MongoDB进行存储
-
对于读多写少的应用,需要减少读取的成本
-
比如说,一条SQL语句,单张表查询一定比多张表查询要快
交友
-
-
redis:承担的角色是缓存层(提升查询效率) 纯内存存储,内存不足触发淘汰者机制
-
mysql:存储和核心业务数据,账户
MongoDB:是一个高效的非关系型数据库(不支持表关系:只能操作单表)
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的,它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。
MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
MongoDB 最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。它是一个面向集合的,模式自由的文档型数据库。具体特点总结如下:
-
面向集合存储,易于存储对象类型的数据
-
模式自由
-
支持动态查询
-
支持完全索引,包含内部对象
-
支持复制和故障恢复
-
使用高效的二进制数据存储,包括大型对象(如视频等)
-
自动处理碎片,以支持云计算层次的扩展性
-
支持 Python,PHP,Ruby,Java,C,C#,Javascript,Perl及C++语言的驱动程 序, 社区中也提供了对Erlang及.NET 等平台的驱动程序
-
文件存储格式为 BSON(一种 JSON 的扩展)
MYSQL : 用于存储安全性要求比较高的数据
REDIS : 存储数据格式简单 , 并且查询非常多的数据(用户缓存)
MONGDB : 用户存储海量数据, 并且数据的安全性要求不高
mongDB使用场景
游戏装备数据,游戏道具数据
特性:修改频度高
物流行业数据
特性:地理位置信息,海量数据
直播数据,打赏数据,粉丝数据
特性:数据量大,修改频度极高
日志数据
特性:数据量巨大,结构易变
高扩展性
内置数据分片 假如ABC各500G 那么加在一起就能存储1.5T数据
MongoDB 的逻辑结构是一种层次结构。主要由: 文档(document) 集合(collection) 数据库(database)这三部分组成的。逻辑结构是面 向用户的,用户使用 MongoDB 开发应用程序使用的就是逻辑结构。
-
MongoDB 的文档(document),相当于关系数据库中的一行记录。
-
多个文档组成一个集合(collection),相当于关系数据库的表。
-
多个集合(collection),逻辑上组织在一起,就是数据库(database)。
-
一个 MongoDB 实例支持多个数据库(database)。 文档(document) 集合(collection) 数据库(database)的层次结构如下图:
为了更好的理解,下面与SQL中的概念进行对比:
MongoDB术语/概念 | 解释/说明 | |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 表中的一条数据 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
-
数据格式:BSON {aa:bb}
-
null:用于表示空值或者不存在的字段,{“x”:null}
-
布尔型:布尔类型有两个值true和false,{“x”:true}
-
数值:shell默认使用64为浮点型数值。{“x”:3.14}或{“x”:3}。对于整型值,可以使用 NumberInt(4字节符号整数)或NumberLong(8字节符号整数), {“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)}
-
字符串:UTF-8字符串都可以表示为字符串类型的数据,{“x”:“呵呵”}
-
日期:日期被存储为自新纪元依赖经过的毫秒数,不存储时区,{“x”:new Date()}
-
正则表达式:查询时,使用正则表达式作为限定条件,语法与JavaScript的正则表达式相 同,{“x”:/[abc]/}
-
数组:数据列表或数据集可以表示为数组,{“x”: [“a“,“b”,”c”]}
-
内嵌文档:文档可以嵌套其他文档,被嵌套的文档作为值来处理,{“x”:{“y”:3 }}
-
对象Id:对象id是一个12字节的字符串,是文档的唯一标识,{“x”: objectId() }
-
二进制数据:二进制数据是一个任意字节的字符串。它不能直接在shell中使用。如果要 将非utf-字符保存到数据库中,二进制数据是唯一的方式。
通过docker安装MongoDB
在课程资料的虚拟机中已经提供了MongoDB的镜像和容器,我们只需要使用简单的命令即可启动
#进入base目录 cd /root/docker-file/base/ #批量创建启动容器,其中已经包含了redis,zookeeper,mongodb容器 docker-compose up -d #查看容器 docker ps -a
3.1 数据库以及表的操作
#查看所有的数据库 > show dbs #通过use关键字切换数据库 > use admin #创建数据库 #说明:在MongoDB中,数据库是自动创建的,通过use切换到新数据库中,进行插入数据即可自动创建数据库 > use testdb > show dbs #并没有创建数据库 > db.user.insert({id:1,name:'zhangsan'}) #插入数据 > show dbs #查看表 > show tables > show collections #删除集合(表) > db.user.drop() true #如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。 #删除数据库 > use testdb #先切换到要删除的数据中 > db.dropDatabase() #删除数据库
在MongoDB中,存储的文档结构是一种类似于json的结构,称之为bson(全称为:Binary JSON)。
#插入数据 #语法:db.表名.insert(json字符串) > db.user.insert({id:1,username:'zhangsan',age:20}) > db.user.find() #查询数据
update() 方法用于更新已存在的文档。语法格式如下
db.collection.update( <query>, <update>, [ upsert: <boolean>, multi: <boolean>, writeConcern: <document> ]
参数说明:
-
query : update的查询条件,类似sql update查询内where后面的。
-
update : update的对象和一些更新的操作符(如inc.$set)等,也可以理解为sql update查询内set后面的
-
upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
-
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
-
writeConcern :可选,抛出异常的级别。
WriteConcern.NONE:没有异常抛出WriteConcern.NORMAL:仅抛出网络错误异常,没有服务器错误异常WriteConcern.SAFE:抛出网络错误异常、服务器错误异常;并等待服务器完成写操作。WriteConcern.MAJORITY: 抛出网络错误异常、服务器错误异常;并等待一个主服务器完成写操作。WriteConcern.FSYNC_SAFE: 抛出网络错误异常、服务器错误异常;写操作等待服务器将数据刷新到磁盘。WriteConcern.JOURNAL_SAFE:抛出网络错误异常、服务器错误异常;写操作等待服务器提交到磁盘的日志文件。WriteConcern.REPLICAS_SAFE:抛出网络错误异常、服务器错误异常;等待至少2台服务器完成写操作。
#查询全部 > db.user.find() #更新数据 > db.user.update({id:1},{$set:{age:22}}) #注意:如果这样写,会删除掉其他的字段 > db.user.update({id:1},{age:25}) #更新不存在的字段,会新增字段 > db.user.update({id:2},{$set:{sex:1}}) #更新数据 #更新不存在的数据,默认不会新增数据 > db.user.update({id:3},{$set:{sex:1}}) #如果设置第一个参数为true,就是新增数据 > db.user.update({id:3},{$set:{sex:1}},true)
#删除数据 > db.user.remove({}) #插入4条测试数据 db.user.insert({id:1,username:'zhangsan',age:20}) db.user.insert({id:2,username:'lisi',age:21}) db.user.insert({id:3,username:'wangwu',age:22}) db.user.insert({id:4,username:'zhaoliu',age:22}) > db.user.remove({age:22},true) #删除所有数据 > db.user.remove({})
通过remove()方法进行删除数据,语法如下:
db.collection.remove( <query>, { justOne: <boolean>, writeConcern: <document> } )
参数说明: - query :(可选)删除的文档的条件。 - justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。 - writeConcern :(可选)抛出异常的级别。 WriteConcern.NONE:没有异常抛出 WriteConcern.NORMAL:仅抛出网络错误异常,没有服务器错误异常 WriteConcern.SAFE:抛出网络错误异常、服务器错误异常;并等待服务器完成写操作。 WriteConcern.MAJORITY: 抛出网络错误异常、服务器错误异常;并等待一个主服务器完成写操作。 WriteConcern.FSYNC_SAFE: 抛出网络错误异常、服务器错误异常;写操作等待服务器将数据刷新到磁盘。 WriteConcern.JOURNAL_SAFE:抛出网络错误异常、服务器错误异常;写操作等待服务器提交到磁盘的日志文件。 WriteConcern.REPLICAS_SAFE:抛出网络错误异常、服务器错误异常;等待至少2台服务器完成写操作。 实例:
MongoDB 查询数据的语法格式如下:
db.user.find([query],[fields])
-
-
fields :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
条件查询
格式 | 范例 | RDBMS中的类似语句 | |
---|---|---|---|
等于 | {<key>:<value> } |
db.col.find({"by":"黑马程序员"}).pretty() |
where by = '黑马程序员' |
小于 | {<key>:{$lt:<value>}} |
db.col.find({"likes":{$lt:50}}).pretty() |
where likes < 50 |
小于或等于 | {<key>:{$lte:<value>}} |
db.col.find({"likes":{$lte:50}}).pretty() |
where likes <= 50 |
大于 | {<key>:{$gt:<value>}} |
db.col.find({"likes":{$gt:50}}).pretty() |
where likes > 50 |
大于或等于 | {<key>:{$gte:<value>}} |
db.col.find({"likes":{$gte:50}}).pretty() |
where likes >= 50 |
不等于 | {<key>:{$ne:<value>}} |
db.col.find({"likes":{$ne:50}}).pretty() |
#插入测试数据 db.user.insert({id:1,username:'zhangsan',age:20}) db.user.insert({id:2,username:'lisi',age:21}) db.user.insert({id:3,username:'wangwu',age:22}) db.user.insert({id:4,username:'zhaoliu',age:22}) db.user.find() #查询全部数据 db.user.find({},{id:1,username:1}) #只查询id与username字段 db.user.find().count() #查询数据条数 db.user.find({id:1}) #查询id为1的数据 db.user.find({age:{$lte:21}}) #查询小于等于21的数据 db.user.find({$or:[{id:1},{id:2}]}) #查询id=1 or id=2 #分页查询:Skip()跳过几条,limit()查询条数 db.user.find().limit(2).skip(1) #跳过1条数据,查询2条数据 db.user.find().sort({id:-1}) #按照id倒序排序,-1为倒序,1为正序
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构
#创建索引 > db.user.createIndex({'age':1}) #查看索引 > db.user.getIndexes() [ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "testdb.user" } ] #说明:1表示升序创建索引,-1表示降序创建索引。
#插入1000条数据 for(var i=1;i<1000;i++)db.user.insert({id:100+i,username:'name_'+i,age:10+i}) #查看执行计划 db.user.find({age:{$gt:100},id:{$lt:200}}).explain() #测试没有使用索引 db.user.find({username:'zhangsan'}).explain() #winningPlan:最佳执行计划 #"stage" : "FETCH", #查询方式,常见的有COLLSCAN/全表扫描 IXSCAN/索引扫描 FETCH/根据索引去检索文档 SHARD_MERGE/合并分片结果 IDHACK/针对_id进行查询
Spring-data对MongoDB做了支持,使用spring-data-mongodb可以简化MongoDB的操作,封装了底层的mongodb-driver。
地址:https://spring.io/projects/spring-data-mongodb
使用Spring-Data-MongoDB很简单,只需要如下几步即可:
-
导入起步依赖
-
编写配置信息
-
编写实体类(配置注解 @Document,@Id)
-
操作mongodb
-
注入MongoTemplate对象,完成CRUD操作
-
编写Repository接口,注入接口完成基本Crud操作
-
第一步,导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.3.9.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.8</version>
</dependency>
第二步,编写application.yml配置文件
#配置mongo的连接地址
spring:
data:
mongodb:
uri: mongodb://192.168.136.160:27017/test
第三步,编写启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class MongoApplication { public static void main(String[] args) { SpringApplication.run(MongoApplication.class, args); } }
第四步,编写实体类
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.mongodb.core.mapping.Document; @Data @AllArgsConstructor @NoArgsConstructor @Document(value="person") public class Person { @Id private ObjectId id; @Field("myname") private String userName; private int age; private String address; }
import com.itheima.mongo.MongoApplication; import com.itheima.mongo.domain.Person; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest(classes = MongoApplication.class) public class MongoTest { /** * 1、注入MongoTemplate对象 * 2、调用对象方法完成数据的CRUD */ @Autowired private MongoTemplate mongoTemplate; //保存 @Test public void testSave() { Person person = new Person(); person.setName("张三"); person.setAge(18); person.setAddress("北京金燕龙"); mongoTemplate.save(person); } /** * 查询所有 */ @Test public void testFindAll() { List<Person> list = mongoTemplate.findAll(Person.class); for (Person person : list) { System.out.println(person); } } /** * 条件查询 */ @Test public void testFind() { //1、创建Criteria对象,并设置查询条件 Criteria criteria = Criteria.where("myname").is("张三") .and("age").is(18);//is 相当于sql语句中的= //2、根据Criteria创建Query Query query = new Query(criteria); //3、查询 List<Person> list = mongoTemplate.find(query, Person.class);//Query对象,实体类对象字节码 for (Person person : list) { System.out.println(person); } } /** * 分页查询 */ @Test public void testPage() { int page = 1; int size = 2; //1、创建Criteria对象,并设置查询条件 Criteria criteria = Criteria.where("age").lt(50); //is 相当于sql语句中的= //2、根据Criteria创建Query Query queryLimit = new Query(criteria) .skip((page -1) * size) //从第几条开始查询 .limit(size) //每页查询条数 .with(Sort.by(Sort.Order.desc("age"))); //3、查询 List<Person> list = mongoTemplate.find(queryLimit, Person.class); for (Person person : list) { System.out.println(person); } } /** * 更新 */ @Test public void testUpdate() { //1、构建Query对象 Query query = Query.query(Criteria.where("id").is("61275c3980f68e67ab4fdf25")); //2、设置需要更新的数据内容 Update update = new Update(); update.set("age", 10); update.set("myname", "lisi"); //3、调用方法 mongoTemplate.updateFirst(query, update, Person.class); } //删除 @Test public void testDelete() { //1、构建Query对象 Query query = Query.query(Criteria.where("id").is("5fe404c26a787e3b50d8d5ad")); mongoTemplate.remove(query, Person.class); } }
在用户登录成功后,就会进入首页,首页中有推荐相似 。
2.1 单机问题分析
单机Mongodb并不适用于企业场景,存在两个问题亟需解决
-
单点故障
单一MongoDB提供服务,在服务器宕机时造成整体应用崩溃
-
海量数据存储
单一MongoDB,并不能支持海量数据存储
2.2 集群概述
为了解决单点故障和海量数据存储问题,MongoDB提供了三种集群形式来支持
-
Master-Slaver(主从集群): 是一种主从副本的模式,目前已经不推荐使用
-
Replica Set (副本集群):模式取代了 Master-Slaver 模式,是一种互为主从的关系。可以解决单点故障问题
-
Sharding (分片集群):可以解决单点故障和海量数据存储问题
企业中一般由专业人员部署构建集群,故MongoDB集群不需要自行搭建,
学习集群需要理解各个集群的特点以及适用场景即可
2.3 副本集群
-
包括主节点和副本节点/从节点 // 1:50T 2:100T 3:300T
-
主节点只能有一个,可以完成数据读写操作
-
副本节点可以有多个,只能完成读操作
-
多节点间有心跳检测,并进行数据同步
-
主节点宕机后,副本节点选举新的主节点
spring:
date:
mongo:ip,ip,ip
Sharding (分片集群)该模式适合处理大量数据,它将数据分开存储,不同服务器保存不同的数据,所有服务器数据的总和即为整个数据集。
分片集群-内部结构
分片服务 存储数据
配置服务 包含所有服务的请求地址 和端口
路由服务 启动时加载配置服务的分片服务,是客户端请求的入口,路由服务根据一定的规则,选择最恰当的分片服务器。