闭关修炼180天----吐血整理MongoDB的学习笔记

MongoDB

一、MongoDB体系结构

1.1 mongoDB和NoSql

  • mongoDB是一种NoSql,是文档存储的代表。

  • mongoDB是一个基于分布式文件存储的数据库。为web应用提供可扩展,高性能,易部署的数据存储解决方案。

  • mongoDB是最像关系型数据库的,在高负载场景下,通过添加更多的节点,可以保证服务器的性能。

  • NoSql数据库的四大家族:列存储Hbase,键值存储redis,图像存储Neo4j,文件存储mongodb

1.2 mongoDB体系结构

MongoDB -> MongoDB-database -> Collection -> document(fileid1,fileid2)

Mysql->mysql.database->tables->row(column1,column2)

1.3 mongoDB和Rdbms(关系型数据库)对比

关系型数据库 MongoDB
database(数据库) database(数据库)
table(表) collection(集合)
row(行) document(bson文档)
column(列) field(字段)
index(唯一索引、主键索引) index (支持地理位置索引、全文索引 、哈希索引)
join(主外键关联) embedded Document (嵌套文档)
primary key(指定主键) primary key (指定_id field做为主键,不指定会自动生成)

1.4什么是Bson

  • Bson是一种类似于json的二进制存储格式(相对于json会浪费一些空间)
  • Bson存储结构:{key:value,key2:value2},其中key是字符串类型,value的类型是字符串,double,Array,ISODate等。
  • BSON有三个特点:轻量级、可遍历性,高效性。

1.5BSON在MongoDB中的使用

MongoDB使用了BSON这种结构来存储数据和网络数据交换。把这种格式转化成一文档这个概念
(Document),这里的一个Document也可以理解成关系数据库中的一条记录(Record),只是这里的
Document的变化更丰富一些,如Document可以嵌套。

1.6 MongoDB在linux中的安装

  • 登录网址:https://www.mongodb.com/下载社区版的mongodb4.1.3tgz包,上传到linux虚拟机中。

  • 解压:tar -zxvf MongoDB-linux-x86_64-4.1.3.tgz

  • 启动:./bin/mongod[可能会启动失败,通过创建mkdir -p /data/db,查看mongo进程:ps -ef | grep mongo]

  • 指定配置文件方式的启动:./bin/mongod -f mongo.conf [创建完conf文件后使用此命令启动可能还会失败,原因是没有创建数据库目录/data/mongo,需要先mkdir /data/mongo,然后再使用以上命令进行配置文件方式启动]

    # 配置文件的样例
    dbpath=/data/mongo/  # 数据库目录 
     port=27017 #端口号
     bind_ip=0.0.0.0 #设定可以绑定访问的ip,现在是任意都可以
     fork=true #开启后台启动
     logpath = /data/mongo/MongoDB.log # 日志文件路径
     logappend = true  # 是否以追加方式产生日志
     auth=false # 是否授权安全认证
    

1.7 mongo shell的启动

  • 启动mongo shell:./bin/mongo(默认连接的是27017端口的)
  • 指定主机和端口的方式启动:./bin/mongo --host=主机ip --port=端口

1.8 mongodb GUI工具

  • MongoDB Compass Community
  • NoSQLBooster(mongobooster) - [如果连接不上可能是端口没有开放]

开启指定端口的命令:

  • 开启防火墙:systemctl start firewalld
  • 开放指定的端口:firewall-cmd --zone=public --add-port=27017/tcp --permanent
    • --zone:作用域
    • --add-port=1935/tcp #添加端口,格式为:端口/通讯协议
    • --permanent #永久生效,没有此参数重启后失效
  • 重启防火墙:firewall-cmd --reload
  • 查看端口号:netstat -ntlp,查看27017端口的使用情况:netstat -ntulp | grep 1935

二、MongoDB命令

2.1 MongoDB的基本操作

  • 查看数据库:show dbs;
  • 切换/使用指定的数据库:usr 数据库名称;
  • 创建集合:db.createCollection("集合名");
  • 查看集合:show tables;或者show collections;
  • 删除集合:db,集合名.drop();
  • 删除当前数据库:db.dropDatabase();

2.2MongoDB集合数据操作(CURD)

2.2.1 数据添加

  1. 插入单条数据:db.collection-name.insert(document)。

    例子:db.lagou.insert({name:"zae",salary:12,birthday:new ISODate("1996-01-01")})

  2. 插入多条数据:db.collection-name.insert([document1,document2])

  3. 插入数据时,_id字段系统会自动生成,我们也可以指定,默认生成的格式为:

    • 前四个字节为时间戳,可以通过ObjectId("对象id字符串").getTimestamp()来获取
    • 接下来的3个字节是机器码标识
    • 紧接的两个字节由进程id组成-PID
    • 最后三个字节是随机数

2.2.2 数据查询

比较条件查询:db.collection-name.find(条件)

  • 全查:db.col-name.find()

  • 等值条件:db.col-name.find({key:value}).pretty()

  • 大于条件:db.col-name.find({key:{$gt:value}}).pretty()

  • 小于条件:db.col-name.find({key:{$lt:value}}).pretty()

  • 大于等于条件:db.col-name.find({key:{$gte:value}}).pretty()

  • 小于等于条件:db.col-name.find({key:{$lte:value}}).pretty()

  • 不等于条件:db.col-name.find({key:{$ne:value}}).pretty()

逻辑条件查询

  • and条件:db.col-name.find({key1:value1,key2:value2}).pretty()
  • or条件:db.col-name.find({$or:[{key1:value1},{key2:value2}]}).pretty()
  • not条件:db.col-name.find({key:{$not:{$操作符:value}}}).pretty()

分页查询

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

2.2.3 数据更新 调用update

$set # 设置字段值
$unset #删除指定字段
$inc #对修改的值进行自增

# 语法结构
db.col_name.update(
<query>, # 查询条件
<update>, #更新操作
 {
    upsert:<boolean>, #如果更新的是不存在的update记录,是否新增进去,默认是false不新增
    multi:<boolean>,# 默认false,只更新找到的第一条数据,如果改为true,会将查询到的多条数据都更新
    writeConcern:<document># 指定为写的行为是否需要确认
    }
)

# 举例
db.col_name.update(
{条件},
{$set:{key:value}},
{
    multi:true
 }
)

2.2.4 数据删除

db.collection.remove(
 <query>,
 {
  justOne: <boolean>,
  writeConcern: <document>
 }
)
参数说明:
query :(可选)删除的文档的条件。
justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值
false,则删除所有匹配条件的文档。
writeConcern :(可选)用来指定mongod对写操作的回执行为。

2.2.5 CURD案例演示

# 插入单条数据
db.lg_resume_preview.insert({name:"zae",birthday:new ISODate("2000-09-21"),salary:15000,city:'bj'});
 
 # 插入多条数据
db.lg_resume_preview.insert([
{name:"许嵩",birthday:new ISODate("2000-09-21"),salary:18000,city:'bj'},
 {name:"徐良",birthday:new ISODate("2000-09-21"),salary:10000,city:'bj'}]);
 
 #查询所有数据
db.lg_resume_preview.find();

#等于
db.lg_resume_preview.find({name:"汪苏泷"}); 
db.lg_resume_preview.find({salary:{$eq: 15000}});

#大于
db.lg_resume_preview.find({salary:{$gt: 15000}});

#小于
db.lg_resume_preview.find({salary:{$lt: 15000}});

#不等于
db.lg_resume_preview.find({salary:{$ne: 15000}});

#and
db.lg_resume_preview.find({name:"zae",salary:15000});

#or
db.lg_resume_preview.find({$or:[{name:"许嵩"},{salary:15000}]});

#not
db.lg_resume_preview.find({name:{$not:{$eq:"薛之谦"}}});

# 在lg_resume_preview集合中查询出数据按照salary属性进行正序排序,跳过2条后,截取两条
db.lg_resume_preview.find().sort({ salary:1 }).skip(2).limit(2);

# 修改salary=18000的数据,salary为28000,只修改查询到的第一条
db.lg_resume_preview.update(
    {salary:18000}, 
    {$set:{salary:28000}}, 
    { multi: false, upsert: false}
);

# 查询出name=李白的数据,并将name设置为杜甫,salary设置为800。没有该条数据时执行插入新数据
db.lg_resume_preview.update(
    {name:"李白"}, 
    {$set:{name:"杜甫",salary:800}}, 
    { multi: false, upsert: true}
);

# 给name为杜甫的数据里面的salary值增加800
db.lg_resume_preview.update(
    {name:"杜甫"}, 
    {$inc:{salary:800}}, 
    { multi: false, upsert: false}
);

# 移除name = 杜甫的那条数据的salary字段
db.lg_resume_preview.update(
    {name:"杜甫"}, 
    {$unset:{salary:""}}, 
    { multi: false, upsert: false}
);

# 忘记操作符,会将那条数据其余字段全删掉,只剩下要更新的那个字段
db.lg_resume_preview.update(
    {name:"杜甫"}, 
    {salary:"19000"}, 
    { multi: false, upsert: false}
);

# 删除某个条件查询出的整条数据
db.lg_resume_preview.remove({salary:"19000"}, {justOne: true})

2.3 MongoDB聚合操作

2.3.1 聚合操作简介

聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档
里不存在的文档信息。一般都是将记录按条件分组之后进行一系列求最大值,最小值,平均值的简单操
作,也可以对记录进行复杂数据统计,数据挖掘的操作。聚合操作的输入是集中的文档,输出可以是一
个文档也可以是多个文档

2.3.2 mongoDB聚合操作分类

  • 单目的聚合操作
  • 聚合管道
  • MapReduce编程模型

2.3.3 单目的聚合操作

  • 统计个数:db.col_name.find({}).count()
  • 去重:db.col_name.distinct(field,query,options)

2.3.4 聚合管道

语法:db.col_name.aggregate([{},{}......])

mongoDB中聚合主要用于统计数据(诸如统计平均值,求和等),并返回计算的结果。

表达式 描述
$sum 计算总和
$avg 计算平均值
$min 获取集合中所有文档对应值的最小值
$max 获取集合中所有文档对应值的最大值
$push 在结果文档中插入值到一个数组中
$addToSet 在结果文档中插入值到一个数组中,但数据不重复
$first 根据资源文档的排序获取第一个文档数据
$last 根据资源文档的排序获取最后一个文档数据

聚合框架中管道常用的一些操作:(注:管道就是将上一步的操作交给下一步)

表达式 说明
$group 将集合中的文档分组,可用于统计结果
$project 修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
$match 用于过滤数据,只输出符合条件的文档。
$limit 用来限制MongoDB聚合管道返回的文档数。
$skip 在聚合管道中跳过指定数量的文档,并返回余下的文档
$sort 在输入文档排序后输出
$geoNear 输出接近某一地理位置的有序文档

演示案例:

# 按照city进行分组,并统计每组城市出现的次数
db.lg_resume_preview.aggregate([{$group:{_id:"$city",city_count:{$sum:1}}}]);
#  按照city进行分组,并统计每组城市里面薪资的平均值
db.lg_resume_preview.aggregate([{$group:{_id:"$city",salary_avg:{$avg:"$salary"}}}]);
# 按照city进行分组,将结果放在一个数组中
db.lg_resume_preview.aggregate([{$group: { _id: "$city",city_name:{$push: "$city"}}}]);
# 按照city进行分组,将结果放在一个数组中,且结果数据不重复
db.lg_resume_preview.aggregate([{$group: { _id: "$city",city_name:{$addToSet: "$city"}}}]);
# 先进行分组,分组处理后的结果的avgSal名称修改为salary
db.lg_resume_preview.aggregate(
[{$group : {_id: "$city", avgSal:{$avg:"$salary"}}},
{$project : {city: "$city", salary : "$avgSal"}}
]);
#根据城市进行分组获取每个城市的个数,然后将个数大于3的记录统计出来
db.lg_resume_preview.aggregate([{$group: { _id: "$city",city_count:{$sum: 1}}},
{$match: {city_count:{$gt:3}}}]);

2.3.5 MapReduce编程模型

概念:MapReduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结
果合并成最终结果(REDUCE)

案例演示

db.lg_resume_preview.mapReduce(
    function(){emit(this.city,this.salary);}, #map模块,定义key - value,并传递给下一代码块
    function(key,value){              # reduce模块,接收到key,value,自动根据key分组,处理返回value的平均值
        return Array.avg(value)
    },
    {						#参数模块
        query:{salary:{$gt:15000}},  # query定义条件:例子中是只有salary大于15000的才能进入到此map中
        out:"avgSalary",                  #out存放结果集,结果集字段为avgSalary
        finalize:function(key,value){ # finalize对reduce输出的结果再一次的进行修改
            return value+100;
        },
        verbose:true #verbose结果信息中是否包含时间信息,默认为false
    
        #sort: 和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
	#limit: 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)
    })

三、MongoDB索引index

3.1 什么是索引

索引是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单,能大大提高查询效率。

3.2 索引类型

3.2.1单键索引

概念:单键索引就是指的在一个字段上加的索引。

语法:db.col_name.createIndex({"字段名":排序方式})(其中1是升序,-1是降序)

特殊的单键索引 ---- 过期索引TTL:TTL索引是mongodb中一种特殊的索引,可以支持文档在一定时间之后自动过期删除,TTL索引只能在单字段上建立,并且字段类型必须是日期类型。 语法:db.co_name.createIndex({"日期字段":排序方式},{expireAfterSeconds:秒数})

演示案例:

db.createCollection("lg_test_index");

db.lg_test_index.insert({name:"张三丰",city:"上海"});

db.lg_test_index.insert({name:"张无忌",city:"洛阳",birthday:new ISODate("2020-12-31")});
# 给name创建单键索引
db.lg_test_index.createIndex({name:1});
# 给birthday创建过期索引TTL,设置过期时间为15秒,15秒之后,张无忌的那条数据会自动删除
db.lg_test_index.createIndex({birthday:1},{expireAfterSeconds:15});
# 获取所有索引
db.lg_test_index.getIndexes();

db.lg_test_index.find();

# 创建唯一索引
db.lg_test_index.createIndex({name:1},{unique,true});

3.2.2 复合索引

概念:在多个字段添加的索引(注意字段的顺序和索引方向)

语法:db.col_name.createIndex({"字段名1":排序方式},{"字段名2":排序方式})

官方详细介绍:https://www.mongodb.com/ -> Docs -> Server -> indexes

注意:如果使用字段名1和字段名2作为条件查询,索引生效;如果只使用字段名1作为条件查询,索引会生效,原因是字段名1在最左侧;如果只使用字段名2作为条件查询,索引不生效。

3.2.3 多键索引

概念:针对一个数组字段创建的索引

语法:db.col_name.createIndex({loc:排序方式})

3.2.4 地理空间索引

概念:针对地理空间坐标创建索引

语法:db.col_name.createIndex({数组的字段名:"2dsphere"或者"2d"})

  • 2dsphere索引,用于存储和查找球面上的点
  • 2d索引,用于存储和查找平面上的点

演示案例

db.company.insert(
 {
 # 创建该条数据的坐标位置
  loc : { type: "Point", coordinates: [ 116.482451, 39.914176 ] },
  name: "大望路地铁",
  category : "Parks"
 }
)
# 给loc创建地理位置坐标索引
db.company.ensureIndex( { loc : "2dsphere" } )
#参数不是1或-1,为2dsphere 或者 2d。还可以建立组合索引。
#查询距离坐标[116.482451,39.914176]0.05公里以内的数据
db.company.find({
 "loc" : {
   "$geoWithin" : {
    "$center":[[116.482451,39.914176],0.05]
   }
 }
})

3.2.5 全文索引

概念:对文本内容进行查询。假设一个字段的内容是一段文本,可以给该字段建立全文索引,根据里面的文本内容进行关键字查询。一个集合仅支持建立一个全文索引。

语法:db.col_name.createIndex({field,"text"})

查询语法:db.col_name.find({"$text":{"$search":"条件值"}})

案例演示:

# 插入测试数据
db.content_test.insert({id:1,name:"z1",des:"hello zae"});
db.content_test.insert({id:2,name:"z2",des:"hello vae"});
db.content_test.insert({id:3,name:"z3",des:"but zae is not vae"});
# 查询下所有
db.content_test.find();
# 给des创建全文索引
db.content_test.createIndex({des:"text"});
# 根据des的value值的文本内容进行查询
db.content_test.find({"$text": {$search:"zae"}})

3.2.6 哈希索引

概念:针对属性的hash值进行索引查询,当使用hash索引时,程序能自动计算所需要的hash值。hash index只支持等于查询,不支持范围查询

语法:db.col_name.createIndex({"字段":"hashed"})

注意:哈希索引仅仅支持单字段,不支持同时写入组合索引中。

3.3 索引和explain分析

3.3.1 索引管理

  • 创建索引并在后台运行:db.col_name.createIndex({"字段",排序方式},{background,true})
  • 获取针对某个集合的索引:db.col_name.getIndexes()
  • 索引的大小:db.col_name.totalIndexSize
  • 索引的重建:db.col_name.reIndex()
  • 索引的删除:(注意:_id对应的索引时删除不了的)
    • 删除单个索引:db.col_name.dropIndex("INDEX-NAME")
    • 删除全部的索引:db.col_name.dropIndexes()

3.3.2 explain分析

语法:db.col_name.find({}).explain()

explain()可以接收的参数:queryPlanner,executionStats,allPlansExecution

executionStats返回逐层分析:

第一层,executionTimeMillis最为直观explain返回值是executionTimeMillis值,指的是这条语句的执行时间,这个值当然是希望越少越好。
其中有3个executionTimeMillis,分别是:
executionStats.executionTimeMillis 该query的整体查询时间。
executionStats.executionStages.executionTimeMillisEstimate 该查询检索document获得数据的时间。
executionStats.executionStages.inputStage.executionTimeMillisEstimate 该查询扫描文档 index所用时间。

第二层,index与document扫描数与查询返回条目数 这个主要讨论3个返回项 nReturned、totalKeysExamined、totalDocsExamined,分别代表该条查询返回的条目、索引扫描条目、文档扫描条目。 这些都是直观地影响到executionTimeMillis,我们需要扫描的越少速度越快。 对于一个查询,
我们最理想的状态是:nReturned=totalKeysExamined=totalDocsExamined

第三层,stage状态分析 那么又是什么影响到了totalKeysExamined和totalDocsExamined?是stage的类型。
类型列举如下:
COLLSCAN:全表扫描
IXSCAN:索引扫描
FETCH:根据索引去检索指定document
SHARD_MERGE:将各个分片返回数据进行merge
SORT:表明在内存中进行了排序
LIMIT:使用limit限制返回数
SKIP:使用skip进行跳过
IDHACK:针对_id进行查询
SHARDING_FILTER:通过mongos对分片数据进行查询
COUNT:利用db.coll.explain().count()之类进行count运算
TEXT:使用全文索引进行查询时候的stage返回
PROJECTION:限定返回字段时候stage的返回

对于普通查询,我希望看到stage的组合(查询的时候尽可能用上索引):
Fetch+IDHACK
Fetch+IXSCAN
Limit+(Fetch+IXSCAN)
PROJECTION+IXSCAN
SHARDING_FITER+IXSCAN

不希望看到包含如下的stage:
COLLSCAN(全表扫描)
SORT(使用sort但是无index)
COUNT 不使用index进行count)

3.4慢查询分析

  • 开启内置的查询分析器,记录读写操作效率。db.setProfilingLevel(n,m),n的值可选0,1,2(一般情况设置为1)
    • 0表示不记录
    • 1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢查询时间的阈值。
    • 2表示记录所有的读写操作
  • 查询监控结果:db.system.profile.find().sort({millis:-1}).limit(3)[显示最慢的三个操作]
  • 分析慢速查询:
  • 解读explain结果,确定是否缺少索引。

3.5 MongoDB索引底层实现原理分析

MongoDB使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问,
单次查询从结构上来看要快于MySql。

B-树是一种自平衡的搜索树,形式很简单,B-数的特点:

  • 多路,非二叉树
  • 每个节点即保存数据,又保存索引
  • 搜索时,相当于二分查找

B+树是B-树的变种,特点如下:

  • 多路非二叉
  • 只有叶子结点保存数据
  • 搜索时,也相当于二分查找
  • 增加了相邻的叶子结点指针

四、MongoDB应用实战

4.1 MongoDB的适用场景

  • 网站数据
  • 缓存
  • 大尺寸、低价值的数据
  • 高伸缩性的场景
  • 用于对象以及JSON数据的存储

4.2 MongoDB的行业具体应用场景

  • 游戏场景
  • 物流场景
  • 社交场景
  • 物联网场景
  • 直播

4.3 如何选择是否使用MongoDB

  • 应用不需要事务及复杂的join支持(必须满足这条才能使用MongoDB)
  • 需求会变,数据模型无法确定
  • 应用需要TB甚至是PB的数据存储
  • 应用需要2000-3000以上的QPS
  • 应用发展迅速,需要能快速水平扩展
  • 应用要求存储的数据不丢失
  • 应用需要99.999%的高可用
  • 应用需要大量的地理位置查询、文本查询。

注意:第一条必须满足,剩余几条满足其一可以使用,满足其二的话使用mongoDB就是十分好的选择。

4.4 Java访问MongoDB

引入依赖

<dependency>
  <groupId>org.mongodb</groupId>
  <artifactId>mongo-java-driver</artifactId>
  <version>3.10.1</version>
</dependency>

案例演示:

// 演示插入数据

public class InsertDataTest {
    public static void main(String[] args) {
        // 获取mongodb连接的客户端
        MongoClient mongoClient = new MongoClient("192.168.1.130", 27017);
        // 获取数据库
        MongoDatabase lagouDataBase = mongoClient.getDatabase("lagou");
        // 获取集合
        MongoCollection<Document> collection = lagouDataBase.getCollection("lg_resume_preview");
        // 创建document对象
        Document document = Document.parse("{name:'苏东坡',city:'开封',salary:200,birthDay:new ISODate('1997-12-31')}");
        // 执行插入
        collection.insertOne(document);
    }
}
// 演示查询数据

public class FindDataTest {
    public static void main(String[] args) {
        // 获取mongodb连接的客户端
        MongoClient mongoClient = new MongoClient("192.168.1.130", 27017);
        // 获取数据库
        MongoDatabase lagouDataBase = mongoClient.getDatabase("lagou");
        // 获取集合
        MongoCollection<Document> collection = lagouDataBase.getCollection("lg_resume_preview");
       // 指定按照salary倒叙排序
        Document sortDocument = Document.parse("{salary:-1}");
        // 获取全部数据
        FindIterable<Document> documents = collection.find().sort(sortDocument);
        for (Document document : documents) {
            System.out.println(document);
        }

    }
}
// 演示过滤数据

public class FilterDataTest {

    public static void main(String[] args) {
        // 获取mongodb连接的客户端
        MongoClient mongoClient = new MongoClient("192.168.1.130", 27017);
        // 获取数据库
        MongoDatabase lagouDataBase = mongoClient.getDatabase("lagou");
        // 获取集合
        MongoCollection<Document> collection = lagouDataBase.getCollection("lg_resume_preview");
        // 指定按照salary倒叙排序
        Document sortDocument = Document.parse("{salary:-1}");
        // 使用filters指定过滤条件进行查询
        FindIterable<Document> documents = collection.find(Filters.gt("salary",10000)).sort(sortDocument);
        for (Document document : documents) {
            System.out.println(document);
        }

    }
}

4.5 Spring访问MongoDB

引入依赖

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-mongodb</artifactId>
  <version>2.0.9.RELEASE</version>
</dependency>

配置文件编写:applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mongo="http://www.springframework.org/schema/data/mongo"
       xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd">

    <mongo:db-factory id="mongoDbFactory" client-uri="mongodb://192.168.1.130:27017/lagou"></mongo:db-factory>
    <bean id="mongoTemplate"  class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg index="0" ref="mongoDbFactory"></constructor-arg>
    </bean>
    <context:component-scan base-package="com.lagou"></context:component-scan>
</beans>

MongoTemplate的运用

@Repository("resumeDao")
public class ResumeDaoImpl implements ResumeDao {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 插入一条数据
     * @param resume
     */
    public void insertResume(Resume resume) {
        mongoTemplate.insert(resume,"lg_resume");
    }

    /**
     * 单条件进行查询
     * @param name
     * @return
     */
    public Resume findResumeByName(String name) {
        Query query = new Query();
        query.addCriteria(Criteria.where("name").is(name));
        List<Resume> lgResume = mongoTemplate.find(query, Resume.class, "lg_resume");
        return lgResume.isEmpty()?null: lgResume.get(0);
    }

    /**
     * 多条件进行查询
     * @param name
     * @param salary
     * @return
     */
    public List<Resume> findByNameAndSalary(String name, Double salary) {
        Query query = new Query();
        query.addCriteria(Criteria.where("name").is(name).andOperator(Criteria.where("salary").gt(salary)));
        List<Resume> resumeList = mongoTemplate.find(query, Resume.class, "lg_resume");
        return resumeList;
    }

    /**
     * 更新数据
     * @param resume
     */
    public void updateResume(Resume resume) {
        // 确定更新哪一条数据
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(resume.getId()));

        // 更新内容
        Update update = new Update();
        update.set("name",resume.getName());
        update.set("city",resume.getCity());
        update.set("salary",resume.getSalary());
        update.set("birthday",resume.getBirthday());

        UpdateResult updateResult = mongoTemplate.updateFirst(query, update, "lg_resume");
    }

    /**
     * 删除数据
     * @param id
     */
    public void deleteResume(String id) {
        // 确定删除哪一条数据
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(id));
        DeleteResult deleteResult = mongoTemplate.remove(query, "lg_resume");
    }
}

4.6 Spring Boot访问MongoDB

4.6.1 MongoTemplate 的方式

添加依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

配置文件:application.properties

spring.data.mongodb.host=192.168.1.130
spring.data.mongodb.port=27017
spring.data.mongodb.database=lagou

其余代码编写参考spring访问mongodb中的MongoTemplate的运用

4.6.2 MongoRepository 的方式

五、MongoDB架构

5.1 MongoDB逻辑结构

层次 描述
最上层 Client(客户端)
第二层 MongoDB Query Language(MongoDB的执行语言)
第三层 MongoDB Data Model(MongoDB的数据模型)
最底层 MongoDB的存储引擎(WriedTiger,InMemory,MMAPv1)

5.2 MongoDB的数据模型

5.2.1 描述数据模型

  • 内嵌:把相关联的数据保存在一个文档结构中,mongodb的文档结构允许一个字段或者一个数组内的值作为一个嵌套的文档。
  • 引用:通过存储数据的引用信息实现两个文档之间的关联,通过解析这些数据引用来访问相关数据。

5.22 如何选择数据模型

  • 选择内嵌:
    • 数据对象之间有包含关系,数据对象之间有一对多或者一对一的关系
    • 数据需要经常一起读取
    • 有 map-reduce/aggregation 需求的数据放在一起
  • 选择引用
    • 数据重复比较多
    • 表达比较复杂的多对多的关系
    • 大型层次结果数据集,嵌套不要太深

5.3 MongoDB存储引擎

5.3.1 存储引擎概述

5.3.2 WriedTiger存储引擎优势

5.3.3 WriedTiger存储包含的文件和作用

5.3.4 WriedTiger存储引擎实现原理

  • 写请求
  • checkpoint流程
  • Journaling

六、MongoDB集群高可用

6.1 MongoDB主从复制架构原理和缺陷

原理:在主从结构中,主节点的操作记录成为oplog(operation log)。oplog存储在系统数据库local的oplog.$main集合中,这个集合的每个文档都代表主节点上执行的一个操作。从服务器会定期从主服务器中获取oplog记录,然后在本机上执行!对于存储oplog的集合,MongoDB采用的是固定集合,也就是说随着操作过多,新的操作会覆盖旧的操作!

缺陷:主从结构没有自动故障转移功能,需要指定master和slave端,不推荐在生产中使用。

6.2 复制集replica sets(为了解决主从复制的缺陷)

6.2.1 什么是复制集

复制集是一个集群,提供了数据的冗余备份。

复制集是一个改进后的主从。

不会去指定谁是主谁是从,主从会自动切换,保证高可用

6.2.2 为什么要使用复制集

  • 高可用
  • 灾难恢复:当发生故障时,可以从其他节点恢复,用于备份、
  • 功能隔离

6.2.3 复制集集群架构原理

6.2.4 复制集搭建

1.在虚拟机192.168.1.130下新建一个文件夹mongo_cluster,并执行命令tar -zxvf mongodb-linux-x86_64-rhel70-4.2.14.tgz -C mongo_cluster

2.进入到解压后的文件里(和bin同级目录),准备三个conf文件放在这,文件内容如下:

  • 主节点配置mongo_37017.conf
# 主节点配置 
dbpath=/data/mongo/data/server1
bind_ip=0.0.0.0
port=37017
fork=true
logpath=/data/mongo/logs/server1.log
replSet=lagouCluster
  • 从节点1配置mongo_37018.conf
dbpath=/data/mongo/data/server2
bind_ip=0.0.0.0
port=37018
fork=true
logpath=/data/mongo/logs/server2.log
replSet=lagouCluster
  • 从节点1配置mongo_37019.conf
dbpath=/data/mongo/data/server3
bind_ip=0.0.0.0
port=37019
fork=true
logpath=/data/mongo/logs/server3.log
replSet=lagouCluster

3.创建好配置文件中涉及到的几个目录,防止启动报错。

  • mkdir -p /data/mongo/data/server1
  • mkdir -p /data/mongo/data/server2
  • mkdir -p /data/mongo/data/server3
  • mkdir -p /data/mongo/logs

4.初始化节点配置

启动三个节点:./bin/mongod -f mongo_37017.conf;./bin/mongod -f mongo_37018.conf;./bin/mongod -f mongo_37018.conf

然后进入任意一个节点(我这里进入的是37017:./bin/mongo --port=37017),运行以下命令

# 配置相关信息
var cfg ={"_id":"lagouCluster",
     "protocolVersion" : 1,
"members":[
{"_id":1,"host":"192.168.1.130:37017","priority":10},
{"_id":2,"host":"192.168.1.130:37018"}
]
}

# 初始化配置
rs.initiate(cfg)
# 查看状态
rs.status()

5.节点的动态增删

#增加节点
rs.add("192.168.1.130:37019")
#删除slave 节点
rs.remove("192.168.1.130:37019")

6.复制集操作演示

  • 由于在配置相关信息时将37017的优先级提高了,因此PRIMARY主节点为37017,进入到37017下的shell操作页面,依次执行use lagou_db,db.createCollection("lagou_tb1"),db.lagou_tb1.insert({name:"张三",city:"beijing"})
  • 进入到37018下,执行use lagou_db,db.lagou_tb1.find(),此时会发现查询报错,原因是没有开启slaveOk,在37018下执行:rs.slaveOk(),再次查询就可以了。

7.主库切换演示

  • 新打开一个命令窗口,输入ps -ef | grep mongo,查看正在启动的mongo进程,通过命令kill PID,将37017服务结束,此时会发现37018自动变成了主节点。

6.2.5 复制集成员的配置参数

参数字段 类型说明 取值 说明
_id 复制集标识
host 节点的主机名:ip:port
arbiterOnly 是否为仲裁节点
priority(权重) 默认1,是否有资格变成主节点,取值范围0-1000,0永远不会变成主节点。权重越大越可能成为主节点
hidden 当权重为0的时候才可以设置
votes 是否为投票节点:0不投票 1投票
slaveDelay 从库的延迟多少秒
buildIndexes 主库的索引,从库也创建,_id索引无效

案例演示:

var cfg ={"_id":"lagouCluster",
 "protocolVersion" : 1,
 "members":[
{"_id":1,"host":"192.168.1.130:37017","priority":10},
{"_id":2,"host":"192.168.1.130:37018","priority":0},
{"_id":3,"host":"192.168.1.130:37019","priority":5},
 {"_id":4,"host":"192.168.1.130:37020","arbiterOnly":true}
]
};
// 重新装载配置,并重新生成集群节点。注意重新装载配置时,需要在当时的主节点下进行配置设置
rs.reconfig(cfg)
//重新查看集群状态
rs.status()

6.2.6 有仲裁节点复制集搭建

  • 新建一个37020的conf,重复上面搭建复制集的步骤,启动起来。
  • 配置仲裁节点方案一:在当时复制集的主节点下按照6.2.5的案例演示进行重新装载配置。
  • 配置仲裁节点方案二:rs.addArb("IP:端口");rs.addArb("192.168.1.130:37020")

6.3 分片集群Shard Cluster

6.3.1 什么是分片

6.3.2 为什么要分片

6.3.3 分片的原理

6.3.4 分片集群的搭建过程

1.配置 并启动config 节点集群

  • 解压tar -zxvf mongodb-linux-x86_64-rhel70-4.2.14.tgz,然后将解压的文件重命名为mongo-shard:mv mongodb-linux-x86_64-rhel70-4.2.14 mongo-shard cd进入mongo-shard中

  • 在bin的同级目录下新建文件夹config,进入config中,新建三个配置文件:config-17017.conf,config-17018.conf,config-17019.conf,将配置文件里面的内容分别配置为以下内容:

    • # 数据库文件位置
      dbpath=config/config1
      #日志文件位置
      logpath=config/logs/config1.log
      # 以追加方式写入日志
      logappend=true
      # 是否以守护进程方式运行
      fork = true
      bind_ip=0.0.0.0
      port = 17017
      # 表示是一个配置服务器
      configsvr=true
      #配置服务器副本集名称
      replSet=configsvr
      
    • # 数据库文件位置
      bpath=config/config2
      #日志文件位置
      logpath=config/logs/config.log
      # 以追加方式写入日志
      logappend=true
      # 是否以守护进程方式运行
      fork = true
      bind_ip=0.0.0.0
      port = 17018
      # 表示是一个配置服务器
      configsvr=true
      #配置服务器副本集名称
      replSet=configsvr
      
    • # 数据库文件位置
      dbpath=config/config3
      #日志文件位置
      logpath=config/logs/config3.log
      # 以追加方式写入日志
      logappend=true
      # 是否以守护进程方式运行
      fork = true
      bind_ip=0.0.0.0
      port = 17019
      # 表示是一个配置服务器
      configsvr=true
      #配置服务器副本集名称
      replSet=configsvr
      
  • 在config文件夹下创建出配置文件所指定的文件夹:mkdir config1 config2 config3 logs

  • 启动配置节点:

    ./bin/mongod -f config/config-17017.conf
    ./bin/mongod -f config/config-17018.conf
    ./bin/mongod -f config/config-17019.conf
    
  • 进入任意节点,依次执行以下命令(我这里进入的是17017)

    # 进入17017的shell终端
    ./bin/mongo  --port 17017
    
    #使用admin数据库
    use admin
    
    # 配置config的配置信息
    var cfg ={"_id":"configsvr",
    "members":[
    {"_id":1,"host":"192.168.1.130:17017"},
    {"_id":2,"host":"192.168.1.130:17018"},
    {"_id":3,"host":"192.168.1.130:17019"}]
    };
    
    #初始化下配置
    rs.initiate(cfg)
    

2.配置shard集群

  • 在bin的同级目录下新建shard文件夹,并在shard文件夹下新建两个文件夹:shard1和shard2,这两个文件夹里面将会分别搭建各自的shard复制集群,接下来我只演示shard1的搭建过程,shard2同样,仅仅需要将一些信息修改为sard2即可。其中shard1的复制集端口为:37017到37019,shard2的复制集端口为:47017到47019

  • 在shard1文件夹下新建三个配置文件:shard-37017.conf,shard-37018.conf,shard-37019.conf.配置文件的内容分别如下:

    #shard-37017.conf的内容
    dbpath=shard/shard1/shard1-37017
    bind_ip=0.0.0.0
    port=37017
    fork=true
    logpath=shard/shard1/shard1-37017.log
    replSet=shard1
    shardsvr=true
    #shard-37018.conf的内容
    dbpath=shard/shard1/shard1-37018
    bind_ip=0.0.0.0
    port=37018
    fork=true
    logpath=shard/shard1/logs/shard1-37018.log
    replSet=shard1
    shardsvr=true 
    #shard-37019.conf的内容
    dbpath=shard/shard1/shard1-37019
    bind_ip=0.0.0.0
    port=37019
    fork=true
    logpath=shard/shard1/logs/shard1-37019.log
    replSet=shard1
    shardsvr=true
    
  • 在shard1文件夹下创建出配置文件中涉及到的几个文件夹:mkdir shard1-37017 shard1-37018 shard1-37019 logs

  • 启动每个mongod,然后进入到其中一个(我进入的是37017),执行以下命令。

    # 配置
    var cfg ={"_id":"shard1",
    "protocolVersion" : 1,
    "members":[
    {"_id":1,"host":"192.168.1.130:37017"},
    {"_id":2,"host":"192.168.1.130:37018"},
    {"_id":3,"host":"192.168.1.130:37019"}
    ]
    };
    # 初始化配置
    rs.initiate(cfg)
    
  • 重复以上操作,配置shard2,参考的conf内容以下命令如下:

    # 三个conf文件内容
    dbpath=shard/shard2/shard2-47017
    bind_ip=0.0.0.0
    port=47017
    fork=true
    logpath=shard/shard2/logs/shard2-47017.log
    replSet=shard2
    shardsvr=true 
    
    dbpath=shard/shard2/shard2-47018
    bind_ip=0.0.0.0
    port=47018
    fork=true
    logpath=shard/shard2/logs/shard2-47018.log
    replSet=shard2
    shardsvr=true 
    
    dbpath=shard/shard2/shard2-47019
    bind_ip=0.0.0.0
    port=47019
    fork=true
    logpath=shard/shard2/logs/shard2-47019.log
    replSet=shard2
    shardsvr=true
    
    # 集群配置命令
    var cfg ={"_id":"shard2",
    "protocolVersion" : 1,
    "members":[
    {"_id":1,"host":"192.168.1.130:47017"},
    {"_id":2,"host":"192.168.1.130:47018"},
    {"_id":3,"host":"192.168.1.130:47019"}
    ]
    };
    

3.配置路由

  • 在bin的同级目录位置,创建文件:mkdir -p route/logs

  • 进入到route文件夹下,创建配置文件route-27017.conf,内容为:

    port=27017
    bind_ip=0.0.0.0
    fork=true
    logpath=route/logs/route.log
    configdb=configsvr/192.168.1.130:17017,192.168.1.130:17018,192.168.1.130:17019
    
  • 启动路由节点:./bin/mongos -f route/route-27017.conf(注意这里是mongos而不是mongod)

4.在mongo路由中添加分片节点

#进入shell操作页面
./bin/mongo --port 27017

#查看下分片状态
sh.status()

#添加分片节点
sh.addShard("shard1/192.168.1.130:37017,192.168.1.130:37018,192.168.1.130:37019");
sh.addShard("shard2/192.168.1.130:47017,192.168.1.130:47018,192.168.1.130:47019");

5.开启数据库和集合分片(指定片键)

#为数据库开启分片功能
sh.enableSharding("数据库名")
sh.enableSharding("lagou_resume")

#为指定集合开启分片功能
sh.shardCollection("数据库名.集合名",{"片键字段名如 name":索引说明})
sh.shardCollection("lagou_resume.lagou_resume_datas",{"name":"hashed"})

6.向集合中插入数据测试

use  lagou_resume;

for(var i=1;i<= 100;i++){
  db.lagou_resume_datas.insert({"name":"test"+i,
    salary:(Math.random()*20000).toFixed(2)});
}

7.验证

分别去shard1复制群和shard2复制群中查看是否有数据

七、MongoDB安全认证

7.1 用户相关操作

  • 创建用户

    db.createUser(
    {
     user: "账号",
     pwd: "密码",
     roles: [
     { role: "角色", db: "安全认证的数据库" },
     { role: "角色", db: "安全认证的数据库" }
     ]
    }
    )
    
  • 修改密码

    db.changeUserPassword( 'root' , 'rootNew' );
    
  • 用户添加角色

    db.grantRolesToUser( '用户名' , [{ role:  '角色名' , db: '数据库名'}])
    
  • 验证用户

    db.auth("账号","密码")
    
  • 删除用户

    db.dropUser("用户名")
    

7.2 角色介绍

  • 数据库内置角色

    read:允许用户读取指定数据库
    readWrite:允许用户读写指定数据库
    dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问
    system.profile
    userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
    clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限
    readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
    readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
    userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
    dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限
    root:只在admin数据库中可用。超级账号,超级权限
    dbOwner:库拥有者权限,即readWrite、dbAdmin、userAdmin角色的合体
    
    
  • 各个类型用户对应的角色

    数据库用户角色:read、readWrite
    数据库管理角色:dbAdmin、dbOwner、userAdmin
    集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager
    备份恢复角色:backup、restore;
    所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、
     dbAdminAnyDatabase
    超级用户角色:root
    这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、
    userAdminAnyDatabase)
    

7.3.单机安全认证

  • 创建管理员用户

    # 使用admin管理员数据库
    use admin
    
    # 创建root用户
    db.createUser(
     {
     user:"root",
     pwd:"123456",
     roles:[{role:"root",db:"admin"}]
     })
    
  • 给普通数据库创建普通用户

    use mydb1
    switched to db mydb1
    > db
    mydb1
    > db.createUser({
    ... user:"zhangsan",
    ... pwd:"123456",
    ... roles:[{role:"readWrite",db:"mydb1"}]
    ... })
    > db.createUser({
    ... user:"lisi",
    ... pwd:"123456",
    ... roles:[{role:"read",db:"mydb1"}]
    ... })
    
  • 将数据库进程停止,修改conf配置文件,将auth=flase修改为auth=true

  • 重新启动mongodb,开始进行安全验证。

    # 验证管理员
    use admin
    
    db.auth("root","root")
    
    #验证普通用户
    use mydb1
    
    db.auth("zae","123456")
    

7.4.集群安全认证

  • 进入路由27017创建管理员用户和普通成员用户

  • 关闭所有的节点

    #安装psmisc 
    yum install psmisc
    #安装完之后可以使用killall 命令 快速关闭多个进程
    killall mongod
    
  • 生成秘钥文件,并赋予秘钥文件权限

    # 在data/mongodb/文件夹在生成秘钥文件testKeyFile.file
    openssl rand -base64 756 > data/mongodb/testKeyFile.file
    
    # 赋予秘钥文件权限
    chmod 600 data/mongodb/testKeyFile.file
    
  • .配置节点集群和分片节点集群开启安全认证和指定密钥文件

    #开启安全认证
    auth=true
    keyFile=data/mongodb/testKeyFile.file
    
  • 在路由配置文件中,设置密钥文件

    keyFile=data/mongodb/testKeyFile.file
    
  • 启动所有的配置节点,分片节点和路由节点,使用路由进行权限验证。

    • 写shell脚本,批量启动

    • vi startup.sh

      ./bin/mongod -f config/config-17017.conf
      ./bin/mongod -f config/config-17018.conf
      ./bin/mongod -f config/config-17019.conf
      ./bin/mongod -f shard/shard1/shard1-37017.conf
      ./bin/mongod -f shard/shard1/shard1-37018.conf
      ./bin/mongod -f shard/shard1/shard1-37019.conf
      ./bin/mongod -f shard/shard2/shard2-47017.conf
      ./bin/mongod -f shard/shard2/shard2-47018.conf
      ./bin/mongod -f shard/shard2/shard2-47019.conf
      ./bin/mongos -f route/route-27017.conf
      
    • 给脚本加执行权限:chmod +x startup.sh

    • 启动脚本:./shartup.sh

  • springboot连接安全认证

    spring.data.mongodb.host=192.168.1.130
    spring.data.mongodb.port=27017
    spring.data.mongodb.database=数据库名
    # 注意,这里连接到具体数据库时,需要使用给这个数据库配置的普通成员,不能使用admin的root用户连接
    spring.data.mongodb.username=账号
    spring.data.mongodb.password=密码
    
上一篇:如何在Android 或Linux 下,做Suspend /Resume 的Debug【转】


下一篇:利用multiprocessing.managers开发跨进程生产者消费者模型