MongoDB学习与总结

MongoDB学习与总结

作者:运维人在路上

个人博客https://www.cnblogs.com/hujinzhong

微信公众号:运维人在路上

Bilibili账号https://space.bilibili.com/409609392

个人简介:本人是一枚大型电商平台的运维工程师,对开发及运维有一定了解,现分享技术干货,欢迎大家交流!

一、MongoDB介绍

1.1、MongoDB简介

1)MongoDB是一个开源、高性能、无模式的文档型数据库,当初的设计就是用于简化开发和方便扩展,是NoSQL数据库产品中的一种。是最像关系型数据库(MySQL)的非关系型数据库

2)它支持的数据结构非常松散,是一种类似于 JSON 的 格式叫BSON,所以它既可以存储比较复杂的数据类型,又相当的灵活。

3)MongoDB中的记录是一个文档,它是一个由字段和值对(field:value)组成的数据结构。MongoDB文档类似于JSON对象,即一个文档认为就是一个对象。字段的数据类型是字符型,它的值除了使用基本的一些类型外,还可以包括其他文档、普通数组和文档数组。

1.2、体系结构

MySQL和MongoDB对比:

image-20210330095424678

MySQL MongoDB 说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins 表连接,MongoDB不支持
嵌入文档 MongoDB通过嵌入式文档来替代多表连接
primary key primary key 主键,MongoDB自动将_id字段设置为主键

1.3、数据类型

MongoDB的最小存储单位就是文档(document)对象。文档(document)对象对应于关系型数据库的行。数据在MongoDB中以BSON(Binary-JSON)文档的格式存储在磁盘上。

BSON(Binary Serialized Document Format)是一种类json的一种二进制形式的存储格式,简称Binary JSON。BSON和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和BinData类型。

BSON采用了类似于 C 语言结构体的名称、对表示方法,支持内嵌的文档对象和数组对象,具有轻量性、可遍历性、高效性的三个特点,可以有效描述非结构化数据和结构化数据。这种格式的优点是灵活性高,但它的缺点是空间利用率不是很理想。

Bson中,除了基本的JSON类型:string,integer,boolean,double,null,array和object,mongo还使用了特殊的数据类型。这些类型包括date,object id,binary data,regular expression 和code。每一个驱动都以特定语言的方式实现了这些类型,查看你的驱动的文档来获取详细信息。

BSON数据类型参考列表

数据类型 描述 举例
字符串 UTF-8字符串都可表示为字符串类型的数据
对象id 对象id是文档的12字节的唯一 ID
布尔值 真或者假:true或者false
数组 值的集合或者列表可以表示成数组
32位整数 类型不可用。JavaScript仅支持64位浮点数,所以32位整数会被自动转换 shell是不支持该类型的,shell中默认会转换成64位浮点数
64位整数 不支持这个类型。shell会使用一个特殊的内嵌文档来显示64位整数 shell是不支持该类型的,shell中默认会转换成64位浮点数
64位浮点数 shell中的数字就是这一种类型
null 表示空值或者未定义的对象
undefined 文档中也可以使用未定义类型
符号 shell不支持,shell会将数据库中的符号类型的数据自动转换成字符串
正则表达式 文档中可以包含正则表达式,采用JavaScript的正则表达式语法
代码 文档中还可以包含JavaScript代码 {"x" : function() { /* …… */ }}
二进制数据 二进制数据可以由任意字节的串组成,不过shell中无法使用
最大值/最小值 BSON包括一个特殊类型,表示可能的最大值。shell中没有这个类型

注意:shell默认使用64位浮点型数值。{“x”:3.14}或{“x”:3}。对于整型值,可以使用NumberInt(4字节符号整数)或NumberLong(8字节符号整数),{“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)}

1.4、MongoDB特点

MongoDB主要有如下特点:

1)高性能

MongoDB提供高性能的数据持久性。特别是对嵌入式数据模型的支持减少了数据库系统上的I/O活动。

索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。(文本索引解决搜索的需求、TTL索引解决历史数据自动过期的需求、地

理位置索引可用于构建各种 O2O 应用)

mmapv1、wiredtiger、mongorocks(rocksdb)、in-memory 等多引擎支持满足各种场景需求。

Gridfs解决文件存储的需求。

2)高可用性

MongoDB的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余。

3)高扩展性

MongoDB提供了水平可扩展性作为其核心功能的一部分。分片将数据分布在一组集群的机器上。(海量数据存储,服务能力水平扩展)

从3.4开始,MongoDB支持基于片键创建数据区域。在一个平衡的集群中,MongoDB将一个区域所覆盖的读写只定向到该区域内的那些

片。

4)丰富的查询支持

MongoDB支持丰富的查询语言,支持读和写操作(CRUD),比如数据聚合、文本搜索和地理空间查询等。

5)其他特点

如无模式(动态模式)、灵活的文档模型

1.5、业务场景应用

MongoDB可应对三高要求

  • High performance - 对数据库高并发读写的需求。
  • Huge Storage - 对海量数据的高效率存储和访问的需求。
  • High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求

具体的应用场景如

1)社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。

2)游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、高效率存储和访问。

3)物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将

订单所有的变更读取出来。

4)物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。

5)视频直播,使用 MongoDB 存储用户信息、点赞互动信息等。

数据操作的特点

  • 数据量大
  • 写入操作频繁(读写都很频繁)
  • 价值较低的数据,对事务性要求不高

二、MongoDB单机部署

2.1、Windows系统安装及启动

1)下载安装包

下载地址:https://www.mongodb.com/try/download/community

image-20210330103630844

版本选择:

MongoDB的版本命名规范如:x.y.z

y为奇数时表示当前版本为开发版,如:1.5.2、4.1.13

y为偶数时表示当前版本为稳定版,如:1.6.3、4.0.10

z是修正版本号,数字越大越好。

2)解压启动

将压缩包解压到一个目录中,在解压目录中,手动建立一个目录用于存放数据文件,如 data/db

image-20210330105522149

方式一:命令行参数启动服务

bin 目录中打开命令行提示符,输入如下命令:

# mongoDB的默认端口是27017,如果我们想改变默认的启动端口,可以通过--port来指定端口
mongod --dbpath=..\data\db

image-20210330110009268

方式二:配置文件方式启动服务

在解压目录中新建conf文件夹,该文件夹中新建配置文件 mongod.conf

配置项文档:https://docs.mongodb.com/manual/reference/configuration-options/

# mongod.conf内容 
storage: 
  #The directory where the mongod instance stores its data.Default Value is "\data\db" on Windows. 
  dbPath: D:\mongodb-win32-x86_64-2008plus-ssl-4.0.12\data\db

启动方式:

mongod -f ../conf/mongod.conf
# 或者
mongod --config ../conf/mongod.conf

更多参数配置示例:

systemLog:
  destination: file
  #The path of the log file to which mongod or mongos should send all diagnostic logging information 
  path: "D:\mongodb-win32-x86_64-2008plus-ssl-4.0.12/log/mongod.log"
  logAppend: true
storage:
  journal:
    enabled: true 
	#The directory where the mongod instance stores its data.Default Value is "/data/db". 
	dbPath: "D:\mongodb-win32-x86_64-2008plus-ssl-4.0.12/data" 
net:
  #bindIp: 127.0.0.1 
  port: 27017 
setParameter: 
  enableLocalhostAuthBypass: false

2.2、Linux系统安装启动

# 1、下载软件包 mongod-linux-x86_64-4.0.10.tgz

# 2、上传压缩包到Linux中,解压到指定目录
[root@mongo ~]# ll mongodb-linux-x86_64-4.0.10.tgz 
-rw-r--r-- 1 root root 84996443 Aug 10  2019 mongodb-linux-x86_64-4.0.10.tgz

# 3、移动解压后的文件夹到指定的目录中
[root@mongo ~]# tar xf mongodb-linux-x86_64-4.0.10.tgz -C /usr/local/
[root@mongo ~]# mv /usr/local/mongodb-linux-x86_64-4.0.10/ /usr/local/mongodb

# 4、新建几个目录,分别用来存储数据和日志,配置文件
[root@mongo ~]# mkdir -p /mongodb/data/db
[root@mongo ~]# mkdir -p /mongodb/log
[root@mongo ~]# mkdir -p /mongodb/conf

# 5、新建并修改配置文件
[root@mongo ~]# vim /mongodb/conf/mongod.conf
systemLog: 
   #MongoDB发送所有日志输出的目标指定为文件 
   destination: file
   #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径 
   path: "/mongodb/log/mongod.log" 
   #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾
   logAppend: true
storage: 
   #mongod实例存储其数据的目录
   dbPath: "/mongodb/data/db" 
   journal: 
      #启用或禁用持久性日志以确保数据文件保持有效和可恢复。 
      enabled: true 
processManagement: 
   #启用在后台运行mongos或mongod进程的守护进程模式。 
   fork: true 
net:
   #服务实例绑定的IP,默认是localhost 
   bindIp: localhost,10.0.0.104
   #绑定的端口,默认是27017 
   port: 27017
   
# 6、启动MongoDB服务
[root@mongo ~]# /usr/local/mongodb/bin/mongod -f /mongodb/conf/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 9252
child process started successfully, parent exiting

# 7、查看服务是否启动
[root@mongo ~]# ps -ef |grep mongod
root       9252      1  1 12:01 ?        00:00:00 /usr/local/mongodb/bin/mongod -f /mongodb/conf/mongod.conf
root       9474   3166  0 12:02 pts/0    00:00:00 grep --color=auto mongod

# 8、配置环境变量
[root@mongo ~]# vim /etc/profile
MONGO_HOME=/usr/local/mongodb/
export PATH=$PATH:$MONGO_HOME/bin
[root@mongo ~]# source /etc/profile

# 9、shell命令行连接
[root@mongo ~]# mongo
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB

mongod常用命令:

# 重启mongodb
mongod -f /mongodb/conf/mongod.conf --shutdown
mongod -f /mongodb/conf/mongod.conf

# mongodb关闭
#Kill模式
kill -2 PID
kill -4 PID
 
#内置模式
admin> db.shutdownServer()
或
admin> db.adminCommand({shutdown:1})
或
mongod -f /mongodb/conf/mongod.conf  --shutdown

2.3、图形化界面客户端

2.3.1、MongoDB Compass

下载地址:https://www.mongodb.com/try/download/compass?initial=true

image-20210330144228738

image-20210330144302076

2.3.2、robo3t

下载地址:https://robomongo.org/

image-20210330144536271

三、MongoDB常用基本命令

3.1、数据库操作

# 选择和创建数据库的语法格式
use 数据库名称

# 如果数据库不存在则自动创建,例如,以下语句创建 articledb数据库
# 注意: 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建
use articledb

# 查看有权限查看的所有的数据库命令
show dbs/databases

# 查看当前正在使用的数据库
db

# 数据库的删除(删除持久化的数据库)
db.dropDatabase()

系统默认数据库

  • admin: 从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
  • local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
  • config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。

3.2、集合操作

3.2.1、集合创建

1)显式创建集合

# 创建一个名为 mycollection 的普通集合
db.createCollection("mycollection")

# 查看当前库中的集合
show collections/tables

2)隐式创建集合

当向一个集合中插入一个文档的时候,如果集合不存在,则会自动创建集合。提示:通常我们使用隐式创建文档

3.2.2、集合删除

# 如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false
# 删除mycollection集合
db.mycollection.drop()

3.3、文档操作

文档(document)的数据结构和 JSON 基本一样。所有存储在集合中的数据都是 BSON 格式

3.3.1、文档插入

1)单个文档插入

# 向comment的集合(表)中插入一条测试数据
> db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光明 媚","userid":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null})
WriteResult({ "nInserted" : 1 })

# 查询
> db.comment.find()
{ "_id" : ObjectId("6062cececaa955dc12e4df30"), "articleid" : "100000", "content" : "今天天气真好,阳光明媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2021-03-30T07:10:06.248Z"), "likenum" : 10, "state" : null }

提示:

  • comment集合如果不存在,则会隐式创建
  • mongo中的数字,默认情况下是double类型,如果要存整型,必须使用函数NumberInt(整型数字),否则取出来就有问题了。
  • 插入当前日期使用 new Date()
  • 插入的数据没有指定 _id ,会自动生成主键值
  • 如果某字段没值,可以赋值为null,或不写该字段

2)文档批量插入

db.comment.insertMany([ 
{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我 他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08- 05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"}, 
{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔 悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"}, {"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船 长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"}, {"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯 撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"}, 
{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫 嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08- 06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"} 
]);

插入时指定了 _id ,则主键就是该值。

如果某条数据插入失败,将会终止插入,但已经插入成功的数据不会回滚掉。

因为批量插入由于数据较多容易出现失败,因此,可以使用try catch进行异常捕捉处理,测试的时候可以不处理

try {
	db.comment.insertMany([ 
{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我 他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08- 05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"}, 
{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔 悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"}, {"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船 长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"}, {"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯 撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"}, 
{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08- 06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"} 
]);
}catch(e){
	print(e);
}

3.3.2、文档基本查询

查询语法:

db.collection.find(<query>, [projection])

1)查询所有

# 查询所有
> db.comment.find()
> db.comment.find({})

# 条件查询
> db.comment.find({userid:'1003'})

# 返回符合条件的第一条数据
> db.comment.findOne({userid:'1003'})
{
	"_id" : "4",
	"articleid" : "100001",
	"content" : "专家说不能空腹吃饭,影响健康。",
	"userid" : "1003",
	"nickname" : "凯 撒",
	"createdatetime" : ISODate("2019-08-06T08:18:35.288Z"),
	"likenum" : 2000,
	"state" : "1"
}

2)投影查询(Projection Query

如果要查询结果返回部分字段,则需要使用投影查询(不显示所有字段,只显示指定的字段)

# 查询结果只显示 _id、userid、nickname
> db.comment.find({userid:"1003"},{userid:1,nickname:1})
{ "_id" : "4", "userid" : "1003", "nickname" : "凯 撒" }
{ "_id" : "5", "userid" : "1003", "nickname" : "凯撒" }

# 查询结果只显示userid、nickname,不显示_id
> db.comment.find({userid:"1003"},{userid:1,nickname:1,_id:0})
{ "userid" : "1003", "nickname" : "凯 撒" }
{ "userid" : "1003", "nickname" : "凯撒" }

# 查询所有数据,但只显示 _id、userid、nickname
> db.comment.find({},{userid:1,nickname:1,_id:0})
{ "userid" : "1002", "nickname" : "相忘于江湖" }
{ "userid" : "1005", "nickname" : "伊人憔悴" }
{ "userid" : "1004", "nickname" : "杰克船长" }
{ "userid" : "1003", "nickname" : "凯撒" }
{ "userid" : "1003", "nickname" : "凯撒" }

3.3.3、文档更新

1)覆盖修改

# 修改_id为1的记录,点赞量为1001
> db.comment.find({_id:"1"})
{ "_id" : "1", "articleid" : "100001", "content" : "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。", "userid" : "1002", "nickname" : "相忘于江湖", "createdatetime" : ISODate("1970-01-01T00:00:00Z"), "likenum" : 1000, "state" : "1" }
> db.comment.update({_id:"1"},{likenum:NumberInt(1001)})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.comment.find({_id:"1"})
{ "_id" : "1", "likenum" : 1001 }

2)局部修改

需要使用修改器$set来实现

> db.comment.find({_id:"2"})
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬天喝温开水", "userid" : "1005", "nickname" : "伊人憔 悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 888, "state" : "1" }
> db.comment.update({_id:"2"},{$set:{likenum:NumberInt(1001)}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.comment.find({_id:"2"})
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬天喝温开水", "userid" : "1005", "nickname" : "伊人憔 悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 1001, "state" : "1" }

3)批量修改

# 默认只修改第一条数据
> db.comment.update({userid:"1003"},{$set:{nickname:"凯撒2"}}) 
# 修改所有符合条件的数据 
> db.comment.update({userid:"1003"},{$set:{nickname:"凯撒大帝"}},{multi:true})

4)列值增长修改

想实现对某列值在原有值的基础上进行增加或减少,可以使用 $inc 运算符来实现

# 对3号数据的点赞数,每次递增1
> db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(1)}})

3.3.4、文档删除

刪除语法:db.集合名称.remove(条件)

# 全部删除
> db.comment.remove({})

# 部分删除
> db.comment.remove({_id:"1"})

3.3.5、文档复杂查询

1)统计查询

# 统计comment集合的所有的记录数
> db.comment.count()
5

# 统计userid为1003的记录条数
> db.comment.count({userid:"1003"})
2

2)分页列表查询

可以使用limit()方法来读取指定数量的数据,使用skip()方法来跳过指定数量的数据

# 返回指定条数的记录,可以在find方法后调用limit来返回结果(TopN),默认值20
> db.comment.find().limit(3)
{ "_id" : "1", "articleid" : "100001", "content" : "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我 他。", "userid" : "1002", "nickname" : "相忘于江湖", "createdatetime" : ISODate("1970-01-01T00:00:00Z"), "likenum" : 1000, "state" : "1" }
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬天喝温开水", "userid" : "1005", "nickname" : "伊人憔 悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 888, "state" : "1" }
{ "_id" : "3", "articleid" : "100001", "content" : "我一直喝凉开水,冬天夏天都喝。", "userid" : "1004", "nickname" : "杰克船 长", "createdatetime" : ISODate("2019-08-06T01:05:06.321Z"), "likenum" : 666, "state" : "1" }

# skip方法同样接受一个数字参数作为跳过的记录条数。(前N个不要),默认值是0
> db.comment.find().skip(3)
{ "_id" : "4", "articleid" : "100001", "content" : "专家说不能空腹吃饭,影响健康。", "userid" : "1003", "nickname" : "凯 撒", "createdatetime" : ISODate("2019-08-06T08:18:35.288Z"), "likenum" : 2000, "state" : "1" }
{ "_id" : "5", "articleid" : "100001", "content" : "研究表明,刚烧开的水千万不能喝,因为烫嘴。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("1970-01-01T00:00:00Z"), "likenum" : 3000, "state" : "1" }

分页查询需求:每页2个,第二页开始:跳过前两条数据,接着值显示3和4条数据

# 第一页
> db.comment.find().skip(0).limit(2)
{ "_id" : "1", "articleid" : "100001", "content" : "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我 他。", "userid" : "1002", "nickname" : "相忘于江湖", "createdatetime" : ISODate("1970-01-01T00:00:00Z"), "likenum" : 1000, "state" : "1" }
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬天喝温开水", "userid" : "1005", "nickname" : "伊人憔 悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 888, "state" : "1" }
# 第二页
> db.comment.find().skip(2).limit(2)
{ "_id" : "3", "articleid" : "100001", "content" : "我一直喝凉开水,冬天夏天都喝。", "userid" : "1004", "nickname" : "杰克船 长", "createdatetime" : ISODate("2019-08-06T01:05:06.321Z"), "likenum" : 666, "state" : "1" }
{ "_id" : "4", "articleid" : "100001", "content" : "专家说不能空腹吃饭,影响健康。", "userid" : "1003", "nickname" : "凯 撒", "createdatetime" : ISODate("2019-08-06T08:18:35.288Z"), "likenum" : 2000, "state" : "1" }
# 第三页
> db.comment.find().skip(4).limit(2)
{ "_id" : "5", "articleid" : "100001", "content" : "研究表明,刚烧开的水千万不能喝,因为烫嘴。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("1970-01-01T00:00:00Z"), "likenum" : 3000, "state" : "1" }

3)排序查询

sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列

# 对userid降序排列,并对访问量进行升序排列
> db.comment.find({},{userid:1,likenum:1}).sort({userid:-1,likenum:1})
{ "_id" : "2", "userid" : "1005", "likenum" : 888 }
{ "_id" : "3", "userid" : "1004", "likenum" : 666 }
{ "_id" : "4", "userid" : "1003", "likenum" : 2000 }
{ "_id" : "5", "userid" : "1003", "likenum" : 3000 }
{ "_id" : "1", "userid" : "1002", "likenum" : 1000 }

注意skip(), limilt(), sort()三个放在一起执行的时候,执行的顺序是先 sort(), 然后是 skip(),最后是显示的 limit(),和命令编写顺序无关

4)正则查询

MongoDB的模糊查询是通过正则表达式的方式实现的,格式为:

db.collection.find({field:/正则表达式/})
db.集合.find({字段:/正则表达式/})

示例如下:

# 查询评论内容包含“开水”的所有文档
> db.comment.find({content:/开水/})

# 查询评论的内容中以“专家”开头的
> db.comment.find({content:/^专家/})

5)比较查询

格式如下:

# 大于: field > value 
db.集合名称.find({ "field" : { $gt: value }})
# 小于: field < value 
db.集合名称.find({ "field" : { $lt: value }}) 
# 大于等于: field >= value 
db.集合名称.find({ "field" : { $gte: value }}) 
# 小于等于: field <= value 
db.集合名称.find({ "field" : { $lte: value }}) 
# 不等于: field != value
db.集合名称.find({ "field" : { $ne: value }}) 

示例如下:

# 查询评论点赞数量大于700的记录
> db.comment.find({likenum:{$gt:NumberInt(700)}})

6)包含查询

使用$in操作符和用$nin操作符

# 查询评论的集合中userid字段包含1003或1004的文档
> db.comment.find({userid:{$in:["1003","1004"]}})

# 查询评论集合中userid字段不包含1003和1004的文档
> db.comment.find({userid:{$nin:["1003","1004"]}})

7)条件连接查询

语法格式如下:

# 需要查询同时满足两个以上条件,需要使用$and操作符将条件进行关联
$and:[ { },{ },{ } ]
# 如果两个以上条件之间是或者的关系,我们使用$or操作符进行关联
$or:[ { },{ },{ } ]

示例如下:

# 查询评论集合中likenum大于等于700 并且小于2000的文档
> db.comment.find({$and:[{likenum:{$gte:NumberInt(700)}},{likenum:{$lt:NumberInt(2000)}}]})

# 查询评论集合中userid为1003,或者点赞数小于1000的文档记录
> db.comment.find({$or:[ {userid:"1003"} ,{likenum:{$lt:1000} }]})

3.4、常用命令小结

# 选择切换数据库
use articledb
# 插入数据
db.comment.insert({bson数据}) 
# 查询所有数据
db.comment.find();
# 条件查询数据
db.comment.find({条件})
# 查询符合条件的第一条记录
db.comment.findOne({条件})
# 查询符合条件的前几条记录
db.comment.find({条件}).limit(条数)
# 查询符合条件的跳过的记录
db.comment.find({条件}).skip(条数)
# 修改数据
db.comment.update({条件},{修改后的数据}) 或db.comment.update({条件},{$set:{要修改部分的字段:数据})
# 修改数据并自增某字段值
db.comment.update({条件},{$inc:{自增的字段:步进值}})
# 删除数据
db.comment.remove({条件}) 
# 统计查询
db.comment.count({条件})
# 模糊查询
db.comment.find({字段名:/正则表达式/})
# 条件比较运算
db.comment.find({字段名:{$gt:值}})
# 包含查询
db.comment.find({字段名:{$in:[值1,值2]}})或db.comment.find({字段名:{$nin:[值1,值2]}}) 
# 条件连接查询
db.comment.find({$and:[{条件1},{条件2}]})或db.comment.find({$or:[{条件1},{条件2}]})

四、MongoDB索引

4.1、MongoDB索引概述

索引支持在MongoDB中高效地执行查询。如果没有索引,MongoDB必须执行全集合扫描,即扫描集合中的每个文档,以选择与查询语句匹配的文档。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。如果查询存在适当的索引,MongoDB可以使用该索引限制必须检查的文档数。

索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或一组字段的值,按字段值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB还可以使用索引中的排序返回排序结果。

官网文档:https://docs.mongodb.com/manual/indexes/

了解:MongoDB索引使用B树数据结构(确切的说是B-Tree,MySQL是B+Tree)

4.2、索引的类型

4.2.1、单字段索引

MongoDB支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引(Single Field Index)

对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以在任何方向上遍历索引

image-20210330174143435

4.2.2、复合索引

MongoDB还支持多个字段的用户定义索引,即复合索引(Compound Index)

复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由 { userid: 1, score: -1 } 组成,则索引首先按userid正序排序,然后在每个userid的值内,再在按score倒序排序

image-20210330174458951

4.2.3、其他索引

1)地理空间索引(Geospatial Index)

为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引:返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引。

2)文本索引(Text Indexes)

MongoDB提供了一种文本索引类型,支持在集合中搜索字符串内容。这些文本索引不存储特定于语言的停止词(例如“the”、“a”、“or”),而将集合中的词作为词干,只存储根词。

3)哈希索引(Hashed Indexes)

为了支持基于散列的分片,MongoDB提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询

4.3、索引的管理

4.3.1、索引的查看

返回一个集合中的所有索引的数组

>  db.comment.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_",
		"ns" : "articledb.comment"
	}
]

默认_id索引:MongoDB在创建集合的过程中,在 _id 字段上创建一个唯一的索引,默认名字为 _id_ ,该索引可防止客户端插入两个具有相同值的文

档,您不能在_id字段上删除此索引。

注意:该索引是唯一索引,因此值不能重复,即 _id 值不能重复的。在分片集群中,通常使用 _id 作为片键

4.3.2、索引的创建

1)创建单字段索引

# 单字段索引示例:对userid字段建立索引
>  db.comment.createIndex({userid:1})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}
> db.comment.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_",
		"ns" : "articledb.comment"
	},
	{
		"v" : 2,
		"key" : {
			"userid" : 1
		},
		"name" : "userid_1",
		"ns" : "articledb.comment"
	}
]

image-20210330194202164

2)创建复合索引

# 复合索引:对 userid 和 nickname 同时建立复合(Compound)索引
> db.comment.createIndex({userid:1,nickname:-1})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 2,
	"numIndexesAfter" : 3,
	"ok" : 1
}
> db.comment.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_",
		"ns" : "articledb.comment"
	},
	{
		"v" : 2,
		"key" : {
			"userid" : 1
		},
		"name" : "userid_1",
		"ns" : "articledb.comment"
	},
	{
		"v" : 2,
		"key" : {
			"userid" : 1,
			"nickname" : -1
		},
		"name" : "userid_1_nickname_-1",
		"ns" : "articledb.comment"
	}
]

image-20210330194451063

4.3.3、索引的移除

# 删除 comment 集合中 userid 字段上的升序索引
> db.comment.dropIndex({userid:1})
{ "nIndexesWas" : 3, "ok" : 1 }
> db.comment.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_",
		"ns" : "articledb.comment"
	},
	{
		"v" : 2,
		"key" : {
			"userid" : 1,
			"nickname" : -1
		},
		"name" : "userid_1_nickname_-1",
		"ns" : "articledb.comment"
	}
]

# 根据索引名称删除
> db.comment.dropIndex("userid_1_nickname_-1")
{ "nIndexesWas" : 2, "ok" : 1 }
> db.comment.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_",
		"ns" : "articledb.comment"
	}
]

# 删除所有的索引
> db.collection.dropIndexes()
{
	"ok" : 0,
	"errmsg" : "ns not found",
	"code" : 26,
	"codeName" : "NamespaceNotFound"
}
> db.comment.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_",
		"ns" : "articledb.comment"
	}
]

提示_id 的字段的索引是无法删除的,只能删除非 _id 字段的索引

4.3.4、索引执行计划

分析查询性能(Analyze Query Performance)通常使用执行计划来查看查询的情况,如查询耗费的时间、是否基于索引查询等

1)单字段索引执行计划

# 查看根据userid查询数据的情况
>  db.comment.find({userid:"1003"}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "articledb.comment",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"userid" : {
				"$eq" : "1003"
			}
		},
		"winningPlan" : {
			"stage" : "COLLSCAN", # 表示全集合扫描
			"filter" : {
				"userid" : {
					"$eq" : "1003"
				}
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "mongo",
		"port" : 27017,
		"version" : "4.0.10",
		"gitVersion" : "c389e7f69f637f7a1ac3cc9fae843b635f20b766"
	},
	"ok" : 1
}

compass中执行查看:

image-20210330195544178

下面对userid建立索引,再次查看执行计划

> db.comment.createIndex({userid:1})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}
> db.comment.find({userid:"1003"}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "articledb.comment",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"userid" : {
				"$eq" : "1003"
			}
		},
		"winningPlan" : {
			"stage" : "FETCH",
			"inputStage" : {
				"stage" : "IXSCAN",	# 基于索引的扫描
				"keyPattern" : {
					"userid" : 1
				},
				"indexName" : "userid_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"userid" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"userid" : [
						"[\"1003\", \"1003\"]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "mongo",
		"port" : 27017,
		"version" : "4.0.10",
		"gitVersion" : "c389e7f69f637f7a1ac3cc9fae843b635f20b766"
	},
	"ok" : 1
}

image-20210330195822720

4.3.5、覆盖的查询

文档:https://docs.mongodb.com/manual/core/query-optimization/#read-operations-covered-query

当查询条件和查询的投影仅包含索引字段时,MongoDB直接从索引返回结果,而不扫描任何文档或将文档带入内存。 这些覆盖的查询可以非常有效

image-20210330200419297

> db.comment.find({userid:"1003"},{userid:1,_id:0})
{ "userid" : "1003" }
{ "userid" : "1003" }
> db.comment.find({userid:"1003"},{userid:1,_id:0}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "articledb.comment",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"userid" : {
				"$eq" : "1003"
			}
		},
		"winningPlan" : {
			"stage" : "PROJECTION",
			"transformBy" : {
				"userid" : 1,
				"_id" : 0
			},
			"inputStage" : {
				"stage" : "IXSCAN",
				"keyPattern" : {
					"userid" : 1
				},
				"indexName" : "userid_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"userid" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"userid" : [
						"[\"1003\", \"1003\"]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "mongo",
		"port" : 27017,
		"version" : "4.0.10",
		"gitVersion" : "c389e7f69f637f7a1ac3cc9fae843b635f20b766"
	},
	"ok" : 1
}

image-20210330200808169

五、Java编码

1)搭建项目工程article,pom.xml引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>co,.dianchou</groupId>
    <artifactId>demo_mongo_article</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

2)编写application.yml

spring:
  data:
    mongodb:
      host: 10.0.0.104
      database: articledb
      port: 27017
      #也可以使用uri连接
      #uri: mongodb://10.0.0.104:27017/articledb

3)启动类

package com.dianchou.article;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class ArticleApplication {
    public static void main(String[] args) {
        SpringApplication.run(ArticleApplication.class, args);
    }
}

4)实体类**

package com.dianchou.article.pojo;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;

/**
 * 文章评论实体类
 */
@Data
@Document(collection="comment")
@CompoundIndex( def = "{'userid': 1, 'nickname': -1}")
public class Comment implements Serializable {
    @Id
    private String id;//主键
    @Field("content")
    private String content;     //吐槽内容
    private Date publishtime;   //发布日期
    @Indexed    //单字段索引注解
    private String userid;      //发布人ID
    private String nickname;    //昵称
    private LocalDateTime createdatetime;   //评论的日期时间
    private Integer likenum;    //点赞数
    private Integer replynum;   //回复数
    private String state;   //状态
    private String parentid;    //上级ID
    private String articleid;

}

5)Dao层

package com.dianchou.article.dao;

import com.dianchou.article.pojo.Comment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;


//评论的持久层接口
public interface CommentRepository extends MongoRepository<Comment,String> {

    //根据父id,查询子评论的分页列表
    Page<Comment> findByParentid(String parentid, Pageable pageable);
}

6)service层

package com.dianchou.article.service;

import com.dianchou.article.dao.CommentRepository;
import com.dianchou.article.pojo.Comment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
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.stereotype.Service;

import java.util.List;

@Service
public class CommentService {

    @Autowired
    private CommentRepository commentRepository;
    @Autowired
    private MongoTemplate mongoTemplate;


    /**
     * 保存一个评论
     */
    public void saveComment(Comment comment){
        commentRepository.save(comment);
    }

    /**
     * 更新评论
     */
    public void updateComment(Comment comment){
        commentRepository.save(comment);
    }

    /**
     * 根据id删除评论
     */
    public void deleteCommentById(String id){
        commentRepository.deleteById(id);
    }

    /**
     * 查询所有评论
     */
    public List<Comment> findCommentList(){
        return commentRepository.findAll();
    }

    /**
     * 根据id查询评论
     */
    public Comment findCommentById(String id){
        return commentRepository.findById(id).get();
    }

    /**
     * 根据父id查询
     */
    public Page<Comment> findCommentListByParentid(String parentid,int page,int size) {
        return commentRepository.findByParentid(parentid,PageRequest.of(page-1,size));
    }

    /**
     * 更新点赞数
     */
    public void updateCommentLikenum(String id){

        //  查询条件
        Query query = Query.query(Criteria.where("_id").is(id));
        //  更新条件
        Update update = new Update();
        update.inc("likenum");
        mongoTemplate.updateFirst(query,update,Comment.class);
    }

}

7)测试类

package com.diancou.article.service;

import com.dianchou.article.ArticleApplication;
import com.dianchou.article.pojo.Comment;
import com.dianchou.article.service.CommentService;
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.Page;
import org.springframework.test.context.junit4.SpringRunner;

import java.time.LocalDateTime;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ArticleApplication.class)
public class CommentServiceTest {

    @Autowired
    private CommentService commentService;


    @Test
    public void testFindCommentList() {
        List<Comment> commentList = commentService.findCommentList();
        System.out.println(commentList);
    }

    @Test
    public void testFindCommentById() {
        Comment commentById = commentService.findCommentById("1");
        System.out.println(commentById);
    }

    @Test
    public void testSaveComment(){
        Comment comment=new Comment();
        comment.setArticleid("100000");
        comment.setContent("测试添加的数据");
        comment.setCreatedatetime(LocalDateTime.now());
        comment.setUserid("1003");
        comment.setNickname("凯撒大帝");
        comment.setState("1");
        comment.setLikenum(0);
        comment.setReplynum(0);

        commentService.saveComment(comment);

    }

    @Test
    public void testFindCommentListByParentid() {
        Page<Comment> page = commentService.findCommentListByParentid("3", 1, 2);
        System.out.println(page.getTotalElements());
        System.out.println(page.getContent());
    }

    @Test
    public void testUpdateCommentLikenum() {
        commentService.updateCommentLikenum("1");
    }

}

六、MongoDB副本集

6.1、副本集介绍

官方文档:https://docs.mongodb.com/manual/replication/

MongoDB中的副本集(Replica Set)是一组维护相同数据集的mongod服务。 副本集可提供冗余和高可用性,是所有生产部署的基础

副本集类似于有自动故障恢复功能的主从集群。通俗的讲就是用多台机器进行同一数据的异步同步,从而使多台机器拥有同一数据的多个副本,并且当主库当掉时在不需要用户干预的情况下自动切换其他备份服务器做主库。而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载

1)冗余和数据可用性

通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别的容错功能,以防止丢失数据。在某些情况下,复制可以增加读取性能,因为客户端可以将读取操作发送到不同的服务上, 在不同数据中心维护数据副本可以增加分布式应用程序的数据位置和可用性。 您还可以为专用目的维护其他副本,例如灾难恢复,报告或备份

2)MongoDB中的复制

副本集是一组维护相同数据集的mongod实例。 副本集包含多个数据承载节点和可选的一个仲裁节点。在承载数据的节点中,一个且仅一个成员被视为主节点,而其他节点被视为次要(从)节点。主节点接收所有写操作

辅助(副本)节点复制主节点的oplog并将操作应用于其数据集,以使辅助节点的数据集反映主节点的数据集。 如果primary不在,则符合条件的secondary将举行选举以选出新的主节点

image-20210331105344243

image-20210331105420430

3)主从复制和副本集区别

主从集群和副本集最大的区别就是副本集没有固定的“主节点”;整个集群会选出一个“主节点”,当其挂掉后,又在剩下的从节点中选中其他节点为“主节点”,副本集总有一个活跃点(主、primary)和一个或多个备份节点(从、secondary)

6.2、副本集角色

副本集主要有三种角色:

  • primary(主节点):主要接收所有写操作,主节点
  • secondary(从节点):从主节点通过复制操作以维护相同的数据集,即备份数据,不可写操作,但可以读操作(但需要配置),是默认的一种从节点类型
  • arbiter(仲裁者):不保留任何数据的副本,只具有投票选举作用,当然也可以将仲裁服务器维护为副本集的一部分,即副本成员同时也可以是仲裁者,也是一种从节点类型

image-20210331111534455

关于仲裁者的说明:

仲裁者不维护数据集。 仲裁者的目的是通过响应其他副本集成员的心跳和选举请求来维护副本集中的仲裁

如果你的副本+主节点的个数是偶数,建议加一个仲裁者,形成奇数,容易满足大多数的投票。

如果你的副本+主节点的个数是奇数,可以不加仲裁者

6.3、副本集搭建

6.3.1、服务器规划

IP地址 角色
10.0.0.103 primary
10.0.0.104 secondary
10.0.0.105 arbiter

6.3.2、搭建步骤

1)三台都安装mongoDB并配置

三台都要做的操作:

[root@mongo103 ~]# ll mongodb-linux-x86_64-4.0.10.tgz 
-rw-r--r-- 1 root root 84996443 Aug 10  2019 mongodb-linux-x86_64-4.0.10.tgz
[root@mongo103 ~]# mkdir /data/
[root@mongo103 ~]# tar xf mongodb-linux-x86_64-4.0.10.tgz -C /data/
[root@mongo103 ~]# mv /data/mongodb-linux-x86_64-4.0.10/ /data/mongodb
[root@mongo103 ~]# mkdir -p /data/mongodb/{log,data,conf}
[root@mongo103 ~]# ll /data/mongodb/
total 196
drwxr-xr-x 2 root root   231 Mar 31 14:30 bin
drwxr-xr-x 2 root root     6 Mar 31 14:30 conf
drwxr-xr-x 2 root root     6 Mar 31 14:30 data
-rw-r--r-- 1 root root 30608 May 29  2019 LICENSE-Community.txt
drwxr-xr-x 2 root root     6 Mar 31 14:30 log
-rw-r--r-- 1 root root 16726 May 29  2019 MPL-2
-rw-r--r-- 1 root root  2601 May 29  2019 README
-rw-r--r-- 1 root root 60005 May 29  2019 THIRD-PARTY-NOTICES
-rw-r--r-- 1 root root 81355 May 29  2019 THIRD-PARTY-NOTICES.gotools

主节点-10.0.0.103

# 编写配置文件
[root@mongo103 ~]# vim /data/mongodb/conf/mongod.conf
systemLog:
   destination: file
   path: "/data/mongodb/log/mongod.log"
   logAppend: true
storage: 
   dbPath: "/data/mongodb/data"
   journal: 
      enabled: true 
processManagement: 
   fork: true 
   pidFilePath: "/data/mongodb/mongod.pid"                       
net:
   bindIp: 10.0.0.103
   port: 27017
replication:
   replSetName: myrs
   
# 启动
[root@mongo103 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf 
about to fork child process, waiting until server is ready for connections.
forked process: 1073
child process started successfully, parent exiting
[root@mongo103 ~]# ps -ef|grep mongo
root       1073      1 10 14:37 ?        00:00:00 /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
root       1102    953  0 14:37 pts/0    00:00:00 grep --color=auto mongo

副本节点-10.0.0.104

# 修改配置文件
[root@mongo104 ~]# vim /data/mongodb/conf/mongod.conf
systemLog:
   destination: file
   path: "/data/mongodb/log/mongod.log"
   logAppend: true
storage: 
   dbPath: "/data/mongodb/data"
   journal: 
      enabled: true 
processManagement: 
   fork: true 
   pidFilePath: "/data/mongodb/mongod.pid"                       
net:
   bindIp: 10.0.0.104
   port: 27017
replication:
   replSetName: myrs
   
# 启动
[root@mongo104 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 1444
child process started successfully, parent exiting
[root@mongo104 ~]# ps -ef|grep mongo
root       1444      1  7 14:42 ?        00:00:00 /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
root       1473   1410  0 14:42 pts/0    00:00:00 grep --color=auto mongo

仲裁者节点-10.0.0.105

# 创建配置文件
[root@mongo105 ~]# vim /data/mongodb/conf/mongod.conf
systemLog:
   destination: file
   path: "/data/mongodb/log/mongod.log"
   logAppend: true
storage: 
   dbPath: "/data/mongodb/data"
   journal: 
      enabled: true 
processManagement: 
   fork: true 
   pidFilePath: "/data/mongodb/mongod.pid"                       
net:
   bindIp: 10.0.0.105
   port: 27017
replication:
   replSetName: myrs
   
# 启动
[root@mongo105 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 1040
child process started successfully, parent exiting
[root@mongo105 ~]# ps -ef|grep mongo
root       1040      1 14 14:47 ?        00:00:00 /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
root       1069   1003  0 14:47 pts/0    00:00:00 grep --color=auto mongo

2)主节点初始化

# 连接主节点
[root@mongo103 ~]# /data/mongodb/bin/mongo --host=10.0.0.103 --port=27017

# 初始化
> rs.initiate()
{
	"info2" : "no configuration specified. Using a default configuration for the set",
	"me" : "10.0.0.103:27017",
	"ok" : 1,
	"operationTime" : Timestamp(1617173842, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617173842, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
# 提示符发生变化,变成一个从节点角色,此时默认不能读写,稍等片刻,回车变成主节点
myrs:SECONDARY> 
myrs:PRIMARY>

此时查看副本集的配置:

myrs:PRIMARY> rs.conf()
{
	"_id" : "myrs",	# 副本集的配置数据存储的主键值,默认就是副本集的名字
	"version" : 1,
	"protocolVersion" : NumberLong(1),
	"writeConcernMajorityJournalDefault" : true,
	"members" : [	# 副本集成员数组,此时只有一个
		{
			"_id" : 0,
			"host" : "10.0.0.103:27017",
			"arbiterOnly" : false,	# 非arbiter
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 1,	# 优先级
			"tags" : {
				
			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		}
	],
	"settings" : {	# 副本集的参数配置
		"chainingAllowed" : true,
		"heartbeatIntervalMillis" : 2000,
		"heartbeatTimeoutSecs" : 10,
		"electionTimeoutMillis" : 10000,
		"catchUpTimeoutMillis" : -1,
		"catchUpTakeoverDelayMillis" : 30000,
		"getLastErrorModes" : {
			
		},
		"getLastErrorDefaults" : {
			"w" : 1,
			"wtimeout" : 0
		},
		"replicaSetId" : ObjectId("60641d52945ca702779c6f1f")
	}
}

# 提示:副本集配置的查看命令,本质是查询的是 system.replset 的表中的数据
myrs:PRIMARY> use local
switched to db local
myrs:PRIMARY> show collections
oplog.rs
replset.election
replset.minvalid
replset.oplogTruncateAfterPoint
startup_log
system.replset
system.rollback.id
myrs:PRIMARY> db.system.replset.find()
{ "_id" : "myrs", "version" : 1, "protocolVersion" : NumberLong(1), "writeConcernMajorityJournalDefault" : true, "members" : [ { "_id" : 0, "host" : "10.0.0.103:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : {  }, "slaveDelay" : NumberLong(0), "votes" : 1 } ], "settings" : { "chainingAllowed" : true, "heartbeatIntervalMillis" : 2000, "heartbeatTimeoutSecs" : 10, "electionTimeoutMillis" : 10000, "catchUpTimeoutMillis" : -1, "catchUpTakeoverDelayMillis" : 30000, "getLastErrorModes" : {  }, "getLastErrorDefaults" : { "w" : 1, "wtimeout" : 0 }, "replicaSetId" : ObjectId("60641d52945ca702779c6f1f") } }

查看副本集的状态:

myrs:PRIMARY> rs.status()
{
	"set" : "myrs", # 副本集的名字
	"date" : ISODate("2021-03-31T07:04:36.213Z"),
	"myState" : 1,	# 说明状态正常
	"term" : NumberLong(1),
	"syncingTo" : "",
	"syncSourceHost" : "",
	"syncSourceId" : -1,
	"heartbeatIntervalMillis" : NumberLong(2000),
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1617174274, 1),
			"t" : NumberLong(1)
		},
		"readConcernMajorityOpTime" : {
			"ts" : Timestamp(1617174274, 1),
			"t" : NumberLong(1)
		},
		"appliedOpTime" : {
			"ts" : Timestamp(1617174274, 1),
			"t" : NumberLong(1)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1617174274, 1),
			"t" : NumberLong(1)
		}
	},
	"lastStableCheckpointTimestamp" : Timestamp(1617174254, 1),
	"members" : [	# 副本集成员数组
		{
			"_id" : 0,
			"name" : "10.0.0.103:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 1628,
			"optime" : {
				"ts" : Timestamp(1617174274, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2021-03-31T07:04:34Z"),
			"syncingTo" : "",
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"electionTime" : Timestamp(1617173842, 2),
			"electionDate" : ISODate("2021-03-31T06:57:22Z"),
			"configVersion" : 1,
			"self" : true,
			"lastHeartbeatMessage" : ""
		}
	],
	"ok" : 1,
	"operationTime" : Timestamp(1617174274, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617174274, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

3)添加副本从节点

myrs:PRIMARY> rs.add("10.0.0.104:27017")
{
	"ok" : 1,
	"operationTime" : Timestamp(1617174408, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617174408, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

# 查看副本集状态
myrs:PRIMARY> rs.status()
{
	"set" : "myrs",
	"date" : ISODate("2021-03-31T07:07:28.765Z"),
	"myState" : 1,
	"term" : NumberLong(1),
	"syncingTo" : "",
	"syncSourceHost" : "",
	"syncSourceId" : -1,
	"heartbeatIntervalMillis" : NumberLong(2000),
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1617174444, 1),
			"t" : NumberLong(1)
		},
		"readConcernMajorityOpTime" : {
			"ts" : Timestamp(1617174444, 1),
			"t" : NumberLong(1)
		},
		"appliedOpTime" : {
			"ts" : Timestamp(1617174444, 1),
			"t" : NumberLong(1)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1617174444, 1),
			"t" : NumberLong(1)
		}
	},
	"lastStableCheckpointTimestamp" : Timestamp(1617174434, 1),
	"members" : [
		{
			"_id" : 0,
			"name" : "10.0.0.103:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 1800,
			"optime" : {
				"ts" : Timestamp(1617174444, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2021-03-31T07:07:24Z"),
			"syncingTo" : "",
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"electionTime" : Timestamp(1617173842, 2),
			"electionDate" : ISODate("2021-03-31T06:57:22Z"),
			"configVersion" : 2,
			"self" : true,
			"lastHeartbeatMessage" : ""
		},
		{
			"_id" : 1,
			"name" : "10.0.0.104:27017",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY", # 副本节点
			"uptime" : 39,
			"optime" : {
				"ts" : Timestamp(1617174444, 1),
				"t" : NumberLong(1)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1617174444, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2021-03-31T07:07:24Z"),
			"optimeDurableDate" : ISODate("2021-03-31T07:07:24Z"),
			"lastHeartbeat" : ISODate("2021-03-31T07:07:27.015Z"),
			"lastHeartbeatRecv" : ISODate("2021-03-31T07:07:28.134Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncingTo" : "10.0.0.103:27017",
			"syncSourceHost" : "10.0.0.103:27017",
			"syncSourceId" : 0,
			"infoMessage" : "",
			"configVersion" : 2
		}
	],
	"ok" : 1,
	"operationTime" : Timestamp(1617174444, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617174444, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

4)添加仲裁节点

myrs:PRIMARY> rs.addArb("10.0.0.105:27017")
{
	"ok" : 1,
	"operationTime" : Timestamp(1617174550, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617174550, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

# 查看副本集状态
myrs:PRIMARY> rs.status()
{
	"set" : "myrs",
	"date" : ISODate("2021-03-31T07:09:37.704Z"),
	"myState" : 1,
	"term" : NumberLong(1),
	"syncingTo" : "",
	"syncSourceHost" : "",
	"syncSourceId" : -1,
	"heartbeatIntervalMillis" : NumberLong(2000),
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1617174575, 1),
			"t" : NumberLong(1)
		},
		"readConcernMajorityOpTime" : {
			"ts" : Timestamp(1617174575, 1),
			"t" : NumberLong(1)
		},
		"appliedOpTime" : {
			"ts" : Timestamp(1617174575, 1),
			"t" : NumberLong(1)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1617174575, 1),
			"t" : NumberLong(1)
		}
	},
	"lastStableCheckpointTimestamp" : Timestamp(1617174550, 1),
	"members" : [
		{
			"_id" : 0,
			"name" : "10.0.0.103:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 1929,
			"optime" : {
				"ts" : Timestamp(1617174575, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2021-03-31T07:09:35Z"),
			"syncingTo" : "",
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"electionTime" : Timestamp(1617173842, 2),
			"electionDate" : ISODate("2021-03-31T06:57:22Z"),
			"configVersion" : 3,
			"self" : true,
			"lastHeartbeatMessage" : ""
		},
		{
			"_id" : 1,
			"name" : "10.0.0.104:27017",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 168,
			"optime" : {
				"ts" : Timestamp(1617174575, 1),
				"t" : NumberLong(1)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1617174575, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2021-03-31T07:09:35Z"),
			"optimeDurableDate" : ISODate("2021-03-31T07:09:35Z"),
			"lastHeartbeat" : ISODate("2021-03-31T07:09:36.282Z"),
			"lastHeartbeatRecv" : ISODate("2021-03-31T07:09:36.326Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncingTo" : "10.0.0.103:27017",
			"syncSourceHost" : "10.0.0.103:27017",
			"syncSourceId" : 0,
			"infoMessage" : "",
			"configVersion" : 3
		},
		{
			"_id" : 2,
			"name" : "10.0.0.105:27017",
			"health" : 1,
			"state" : 7,
			"stateStr" : "ARBITER",	# 仲裁节点
			"uptime" : 27,
			"lastHeartbeat" : ISODate("2021-03-31T07:09:36.282Z"),
			"lastHeartbeatRecv" : ISODate("2021-03-31T07:09:36.295Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncingTo" : "",
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"configVersion" : 3
		}
	],
	"ok" : 1,
	"operationTime" : Timestamp(1617174575, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617174575, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

6.4、副本集读写

目标:测试三个不同角色的节点的数据读写情况

6.4.1、主节点读写

# 登录主节点
[root@mongo103 ~]# /data/mongodb/bin/mongo --host=10.0.0.103 --port=27017

# 测试读写
myrs:PRIMARY> use articledb
switched to db articledb
myrs:PRIMARY> db
articledb
myrs:PRIMARY> db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光 明媚","userid":"1001","nickname":"Rose","createdatetime":new Date()})
WriteResult({ "nInserted" : 1 })
myrs:PRIMARY> db.comment.find()
{ "_id" : ObjectId("606421ddcc657819556bfd1c"), "articleid" : "100000", "content" : "今天天气真好,阳光 明媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2021-03-31T07:16:45.483Z") }

6.4.2、副本节点读写

# 登录副本节点
[root@mongo104 ~]# /data/mongodb/bin/mongo --host=10.0.0.104 --port=27017

# 测试读写
myrs:SECONDARY> show dbs
2021-03-31T15:18:22.443+0800 E QUERY    [js] Error: listDatabases failed:{
	"operationTime" : Timestamp(1617175095, 1),
	"ok" : 0,
	"errmsg" : "not master and slaveOk=false",
	"code" : 13435,
	"codeName" : "NotMasterNoSlaveOk", # 此时不是master或者slave
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617175095, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:139:1
shellHelper.show@src/mongo/shell/utils.js:882:13
shellHelper@src/mongo/shell/utils.js:766:15
@(shellhelp2):1:1
myrs:SECONDARY> 

# 设置作为奴隶节点权限,具备读权限,实现了读写分离,让主插入数据,让从来读取数据
myrs:SECONDARY> rs.slaveOk()  # 或者rs.slaveOk(true)
myrs:SECONDARY> show dbs
admin      0.000GB
articledb  0.000GB
config     0.000GB
local      0.000GB
myrs:SECONDARY> use articledb
switched to db articledb
myrs:SECONDARY> show collections
comment
myrs:SECONDARY> db.comment.find()	# 具有读的权限,但无法写入
{ "_id" : ObjectId("606421ddcc657819556bfd1c"), "articleid" : "100000", "content" : "今天天气真好,阳光 明媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2021-03-31T07:16:45.483Z") }
myrs:SECONDARY> db.comment.insert({"_id":"1","articleid":"100001","content":"我们 不应该把清晨浪费在手机上,健康很重要,k一杯温水幸福你我 他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08- 05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"})
WriteCommandError({
	"operationTime" : Timestamp(1617175275, 1),
	"ok" : 0,
	"errmsg" : "not master", # 非master
	"code" : 10107,
	"codeName" : "NotMaster",
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617175275, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
})

# 取消作为奴隶节点的读权限
myrs:SECONDARY> rs.slaveOk(false)
myrs:SECONDARY> db.comment.find()
Error: error: {
	"operationTime" : Timestamp(1617175405, 1),
	"ok" : 0,
	"errmsg" : "not master and slaveOk=false",
	"code" : 13435,
	"codeName" : "NotMasterNoSlaveOk",
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617175405, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

6.4.3、仲裁节点读写

仲裁者节点,不存放任何业务数据的,可以登录查看,只存放副本集配置等数据

# 登录仲裁节点
[root@mongo105 ~]# /data/mongodb/bin/mongo --host=10.0.0.105 --port=27017

myrs:ARBITER> rs.slaveOk()
myrs:ARBITER> show dbs
local  0.000GB
myrs:ARBITER> use local
switched to db local
myrs:ARBITER> show collections
replset.minvalid
replset.oplogTruncateAfterPoint
startup_log
system.replset
system.rollback.id

6.5、副本集选举原则

6.5.1、触发选举条件

1)主节点故障

2)主节点网络不可达(默认心跳信息为10秒)

3)人工干预(rs.stepDown(600)

6.5.2、选举规则

1)票数最高,且获得了“大多数”成员的投票支持的节点获胜。"大多数"的定义为:假设复制集内投票成员数量为N,则大多数为 N/2 + 1。例如:3个投票成员,则大多数的值是2。当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。

2)若票数相同,且都获得了“大多数”成员的投票支持的,数据新的节点获胜。数据的新旧是通过操作日志oplog来对比的

优先级

在获得票数的时候,优先级(priority)参数影响重大。可以通过设置优先级(priority)来设置额外票数。优先级即权重,取值为0-1000,相当于可额外增加0-1000的票数,优先级的值越大,就越可能获得多数成员的投票(votes)数。指定较高的值可使成员更有资格成为primary,更低的值可使成员更不符合条件。默认情况下,优先级的值是1

优先级修改触发选举测试:提升从节点的优先级

# 注意:需要在primary上操作
# 1、先将配置导入cfg变量
myrs:PRIMARY> cfg=rs.conf()

# 2、修改值(ID号默认从0开始)
myrs:PRIMARY> cfg.members[1].priority=2
2

# 3、重新加载配置
myrs:PRIMARY> rs.reconfig(cfg)
{
	"ok" : 1,
	"operationTime" : Timestamp(1617176744, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617176744, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

# 4、稍等便会触发重新选举
myrs:PRIMARY> 
2021-03-31T15:45:54.497+0800 I NETWORK  [js] trying reconnect to 10.0.0.103:27017 failed
2021-03-31T15:45:54.498+0800 I NETWORK  [js] reconnect 10.0.0.103:27017 ok
myrs:SECONDARY> 

6.6、故障测试

6.6.1、副本节点故障测试

#关闭副本节点
[root@mongo103 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf --shutdown
killing process with pid: 1073

# 因为主节点还在,没有触发投票选举,在主节点写入数据
myrs:PRIMARY> db.comment.insert({"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在 手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江 湖","createdatetime":new Date("2019-08- 05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"})

# 启动副本节点,会发现,主节点写入的数据,会自动同步给副本节点
[root@mongo103 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
[root@mongo103 ~]# /data/mongodb/bin/mongo --host=10.0.0.103 --port=27017

6.6.2、主节点故障测试

关闭主节点,副本节点和仲裁节点对主节点的心跳失败,当失败超过10秒,此时因为没有主节点会自动触发选举。候选人只有一个就是副本节点,开始投票:

仲裁节点向副本节点投了一票,副本节点本身自带一票,因此共两票,超过了“大多数”

仲裁节点,没有选举权,副本节点不向其投票,其票数是0.

最终结果,副本节点成为主节点,具备读写功能

# 关闭主节点
[root@mongo104 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf --shutdown
killing process with pid: 1444

# 副本节点通过选举成为主节点
[root@mongo103 ~]# /data/mongodb/bin/mongo --host=10.0.0.103 --port=27017
myrs:SECONDARY> 
myrs:PRIMARY>

# 插入数据测试
myrs:PRIMARY> db.comment.insert({"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬 天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019- 08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"})

# 启动原来宕机的主节点,发现变成副本节点
[root@mongo104 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
[root@mongo104 ~]# /data/mongodb/bin/mongo --host=10.0.0.104 --port=27017
myrs:SECONDARY>

6.6.3、仲裁节点和主节点故障

仲裁节点和主节点故障会导致副本集是只读状态,无法写入

6.6.4、仲裁节点和从节点故障

主节点自动降级为副本节点(服务降级),副本集不可写数据了,已经故障了

6.7、Compass连接副本集

image-20210331162230008

image-20210331162320727

6.8、SpringDataMongoDB连接副本集

6.8.1、修改application.yml文件

spring:
  data:
    mongodb:
      # host: 10.0.0.104
      # database: articledb
      # port: 27017
      #副本集的连接字符串
      uri: mongodb://10.0.0.103:27017,10.0.0.104:27017,10.0.0.105:27017/articledb?connect=replicaSet&slaveOk=true&replicaSet=myrs
      
# slaveOk=true:开启副本节点读的功能,可实现读写分离
# connect=replicaSet:自动到副本集中选择读写的主机。如果slaveOK是打开的,则实现了读写分离

6.8.2、测试写操作

写数据:primary写入

image-20210331164238185

6.8.3、测试读操作

读操作是,同时打开主节点和从节点连接,但使用从节点获取数据

image-20210331164841961

七、分片集群

7.1、分片介绍

分片(sharding)是一种跨多台机器分布数据的方法, MongoDB使用分片来支持具有非常大的数据集和高吞吐量操作的部署。换句话说:分片(sharding)是指将数据拆分,将其分散存在不同的机器上的过程。有时也用分区(partitioning)来表示这个概念。将数据分散到不同的机器上,不需要功能强大的大型计算机就可以储存更多的数据,处理更多的负载。

两种解决系统增长的方法:垂直扩展水平扩展

垂直扩展意味着增加单个服务器的容量,例如使用更强大的CPU,添加更多RAM或增加存储空间量。可用技术的局限性可能会限制单个机器对于给定工作负载而言足够强大。此外,基于云的提供商基于可用的硬件配置具有硬性上限。结果,垂直缩放有实际的最大值。

水平扩展意味着划分系统数据集并加载多个服务器,添加其他服务器以根据需要增加容量。虽然单个机器的总体速度或容量可能不高,但每台机器处理整个工作负载的子集,可能提供比单个高速大容量服务器更高的效率。扩展部署容量只需要根据需要添加额外的服务器,这可能比单个机器的高端硬件的总体成本更低。权衡是基础架构和部署维护的复杂性增加。

MongoDB支持通过分片进行水平扩展

7.2、分片集群组件

MongoDB分片群集包含以下组件:

  • 分片(存储):每个分片包含分片数据的子集。 每个分片都可以部署为副本集。
  • mongos(路由):mongos充当查询路由器,在客户端应用程序和分片集群之间提供接口。
  • config servers("调度"的配置):配置服务器存储群集的元数据和配置设置。 从MongoDB 3.4开始,必须将配置服务器部署为副本集(CSRS)

MongoDB在集合级别对数据进行分片,将集合数据分布在集群中的分片上

image-20210401093525168

7.3、分片集群规划

两个分片节点副本集(3+3)+一个配置节点副本集(3)+两个路由节点(2),共11个服务节点

image-20210401093736942

角色 ip地址 服务
myshardrs01 10.0.0.103 primary:27018,secondary:27118,arbiter:27218
myshardrs02 10.0.0.104 primary:27318,secondary:27418,arbiter:27518
config server 10.0.0.105 primary:27019,secondary:27119,secondary:27219
mongos01 10.0.0.101 27017
mongos02 10.0.0.101 27117

7.4、分片集群部署

7.4.1、myshardrs01副本集群部署

1)解压mongoDB

[root@mongo103 ~]# tar xf mongodb-linux-x86_64-4.0.10.tgz
[root@mongo103 ~]# ls
anaconda-ks.cfg  mongodb-linux-x86_64-4.0.10  mongodb-linux-x86_64-4.0.10.tgz
[root@mongo103 ~]# mkdir -p /mongodb/sharded_cluster/
[root@mongo103 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/myshardrs01_27018
[root@mongo103 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/myshardrs01_27118
[root@mongo103 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/myshardrs01_27218

2)创建存放数据和日志的目录

mkdir -p /mongodb/sharded_cluster/myshardrs01_27018/{log,data,conf}
mkdir -p /mongodb/sharded_cluster/myshardrs01_27118/{log,data,conf}
mkdir -p /mongodb/sharded_cluster/myshardrs01_27218/{log,data,conf}

3)myshardrs01_27018 配置文件

[root@mongo103 sharded_cluster]# vim /mongodb/sharded_cluster/myshardrs01_27018/conf/mongod.conf
systemLog: 
   destination: file
   path: "/mongodb/sharded_cluster/myshardrs01_27018/log/mongod.log" 
   logAppend: true
storage: 
   dbPath: "/mongodb/sharded_cluster/myshardrs01_27018/data" 
   journal: 
      enabled: true 
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/myshardrs01_27018/mongod.pid" 
net:
   bindIp: 10.0.0.103
   port: 27018
replication:
   replSetName: myshardrs01
sharding: 
   #分片角色 
   clusterRole: shardsvr

4)myshardrs01_27118 配置文件

[root@mongo103 sharded_cluster]# vim /mongodb/sharded_cluster/myshardrs01_27118/conf/mongod.conf
systemLog: 
   destination: file
   path: "/mongodb/sharded_cluster/myshardrs01_27118/log/mongod.log" 
   logAppend: true
storage: 
   dbPath: "/mongodb/sharded_cluster/myshardrs01_27118/data" 
   journal: 
      enabled: true 
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/myshardrs01_27118/mongod.pid" 
net:
   bindIp: 10.0.0.103
   port: 27118
replication:
   replSetName: myshardrs01
sharding: 
   #分片角色 
   clusterRole: shardsvr

5)myshardrs01_27218 配置文件

[root@mongo103 sharded_cluster]# vim /mongodb/sharded_cluster/myshardrs01_27218/conf/mongod.conf
systemLog: 
   destination: file
   path: "/mongodb/sharded_cluster/myshardrs01_27218/log/mongod.log" 
   logAppend: true
storage: 
   dbPath: "/mongodb/sharded_cluster/myshardrs01_27218/data" 
   journal: 
      enabled: true 
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/myshardrs01_27218/mongod.pid" 
net:
   bindIp: 10.0.0.103
   port: 27218
replication:
   replSetName: myshardrs01
sharding: 
   #分片角色 
   clusterRole: shardsvr

6)启动第一套副本集:一主一副本一仲裁

[root@mongo103 ~]# cd /mongodb/sharded_cluster/
[root@mongo103 sharded_cluster]# ./myshardrs01_27018/bin/mongod -f ./myshardrs01_27018/conf/mongod.conf 
[root@mongo103 sharded_cluster]# ./myshardrs01_27118/bin/mongod -f ./myshardrs01_27118/conf/mongod.conf 
[root@mongo103 sharded_cluster]# ./myshardrs01_27218/bin/mongod -f ./myshardrs01_27218/conf/mongod.conf 
[root@mongo103 sharded_cluster]# ps -ef|grep mongo
root       1302      1  2 10:26 ?        00:00:00 ./myshardrs01_27018/bin/mongod -f ./myshardrs01_27018/conf/mongod.conf
root       1331      1  5 10:26 ?        00:00:00 ./myshardrs01_27118/bin/mongod -f ./myshardrs01_27118/conf/mongod.conf
root       1360      1 18 10:26 ?        00:00:00 ./myshardrs01_27218/bin/mongod -f ./myshardrs01_27218/conf/mongod.conf
root       1388    985  0 10:26 pts/0    00:00:00 grep --color=auto mongo

7)登录mongo并初始化主节点

# 登录
[root@mongo103 sharded_cluster]# ./myshardrs01_27018/bin/mongo --host=10.0.0.103 --port=27018

# 初始化
> rs.initiate()
{
	"info2" : "no configuration specified. Using a default configuration for the set",
	"me" : "10.0.0.103:27018",
	"ok" : 1,
	"operationTime" : Timestamp(1617244788, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617244788, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
myshardrs01:SECONDARY> 
myshardrs01:PRIMARY>

8)添加副本节点

myshardrs01:PRIMARY> rs.add("10.0.0.103:27118")
{
	"ok" : 1,
	"operationTime" : Timestamp(1617244904, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617244904, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

9)添加仲裁节点

myshardrs01:PRIMARY> rs.addArb("10.0.0.103:27218")
{
	"ok" : 1,
	"operationTime" : Timestamp(1617244961, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617244961, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

10)查看集群状态

# 集群状态信息
myshardrs01:PRIMARY> rs.status()
{
	"set" : "myshardrs01",
	"date" : ISODate("2021-04-01T02:43:47.751Z"),
	"myState" : 1,
	"term" : NumberLong(1),
	"syncingTo" : "",
	"syncSourceHost" : "",
	"syncSourceId" : -1,
	"heartbeatIntervalMillis" : NumberLong(2000),
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1617245020, 1),
			"t" : NumberLong(1)
		},
		"readConcernMajorityOpTime" : {
			"ts" : Timestamp(1617245020, 1),
			"t" : NumberLong(1)
		},
		"appliedOpTime" : {
			"ts" : Timestamp(1617245020, 1),
			"t" : NumberLong(1)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1617245020, 1),
			"t" : NumberLong(1)
		}
	},
	"lastStableCheckpointTimestamp" : Timestamp(1617244961, 1),
	"members" : [
		{
			"_id" : 0,
			"name" : "10.0.0.103:27018",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 1061,
			"optime" : {
				"ts" : Timestamp(1617245020, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2021-04-01T02:43:40Z"),
			"syncingTo" : "",
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"electionTime" : Timestamp(1617244788, 2),
			"electionDate" : ISODate("2021-04-01T02:39:48Z"),
			"configVersion" : 3,
			"self" : true,
			"lastHeartbeatMessage" : ""
		},
		{
			"_id" : 1,
			"name" : "10.0.0.103:27118",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 123,
			"optime" : {
				"ts" : Timestamp(1617245020, 1),
				"t" : NumberLong(1)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1617245020, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2021-04-01T02:43:40Z"),
			"optimeDurableDate" : ISODate("2021-04-01T02:43:40Z"),
			"lastHeartbeat" : ISODate("2021-04-01T02:43:47.112Z"),
			"lastHeartbeatRecv" : ISODate("2021-04-01T02:43:46.148Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncingTo" : "10.0.0.103:27018",
			"syncSourceHost" : "10.0.0.103:27018",
			"syncSourceId" : 0,
			"infoMessage" : "",
			"configVersion" : 3
		},
		{
			"_id" : 2,
			"name" : "10.0.0.103:27218",
			"health" : 1,
			"state" : 7,
			"stateStr" : "ARBITER",
			"uptime" : 66,
			"lastHeartbeat" : ISODate("2021-04-01T02:43:47.111Z"),
			"lastHeartbeatRecv" : ISODate("2021-04-01T02:43:47.121Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncingTo" : "",
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"configVersion" : 3
		}
	],
	"ok" : 1,
	"operationTime" : Timestamp(1617245020, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617245020, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
myshardrs01:PRIMARY>

# 集群配置信息
myshardrs01:PRIMARY> rs.conf()
{
	"_id" : "myshardrs01",
	"version" : 3,
	"protocolVersion" : NumberLong(1),
	"writeConcernMajorityJournalDefault" : true,
	"members" : [
		{
			"_id" : 0,
			"host" : "10.0.0.103:27018",
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 1,
			"tags" : {
				
			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		},
		{
			"_id" : 1,
			"host" : "10.0.0.103:27118",
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 1,
			"tags" : {
				
			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		},
		{
			"_id" : 2,
			"host" : "10.0.0.103:27218",
			"arbiterOnly" : true,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 0,
			"tags" : {
				
			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		}
	],
	"settings" : {
		"chainingAllowed" : true,
		"heartbeatIntervalMillis" : 2000,
		"heartbeatTimeoutSecs" : 10,
		"electionTimeoutMillis" : 10000,
		"catchUpTimeoutMillis" : -1,
		"catchUpTakeoverDelayMillis" : 30000,
		"getLastErrorModes" : {
			
		},
		"getLastErrorDefaults" : {
			"w" : 1,
			"wtimeout" : 0
		},
		"replicaSetId" : ObjectId("606532747f096245c4860c0b")
	}
}

7.4.2、myshardrs02 副本集群部署

1)解压mongoDB

[root@mongo104 ~]# tar xf mongodb-linux-x86_64-4.0.10.tgz
[root@mongo104 ~]# ls
anaconda-ks.cfg  mongodb-linux-x86_64-4.0.10  mongodb-linux-x86_64-4.0.10.tgz
[root@mongo104 ~]# mkdir -p /mongodb/sharded_cluster/
[root@mongo104 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/myshardrs02_27318
[root@mongo104 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/myshardrs02_27418
[root@mongo104 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/myshardrs02_27518

2)创建存放数据和日志的目录

mkdir -p /mongodb/sharded_cluster/myshardrs02_27318/{log,data,conf}
mkdir -p /mongodb/sharded_cluster/myshardrs02_27418/{log,data,conf}
mkdir -p /mongodb/sharded_cluster/myshardrs02_27518/{log,data,conf}

3)myshardrs02_27318 配置文件

[root@mongo104 sharded_cluster]# vim /mongodb/sharded_cluster/myshardrs02_27318/conf/mongod.conf
systemLog: 
   destination: file
   path: "/mongodb/sharded_cluster/myshardrs02_27318/log/mongod.log" 
   logAppend: true
storage: 
   dbPath: "/mongodb/sharded_cluster/myshardrs02_27318/data" 
   journal: 
      enabled: true 
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/myshardrs02_27318/mongod.pid" 
net:
   bindIp: 10.0.0.104
   port: 27318
replication:
   replSetName: myshardrs02
sharding: 
   #分片角色 
   clusterRole: shardsvr

4)myshardrs02_27418 配置文件

[root@mongo104 sharded_cluster]# vim /mongodb/sharded_cluster/myshardrs02_27418/conf/mongod.conf
systemLog: 
   destination: file
   path: "/mongodb/sharded_cluster/myshardrs02_27418/log/mongod.log" 
   logAppend: true
storage: 
   dbPath: "/mongodb/sharded_cluster/myshardrs02_27418/data" 
   journal: 
      enabled: true 
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/myshardrs02_27418/mongod.pid" 
net:
   bindIp: 10.0.0.104
   port: 27418
replication:
   replSetName: myshardrs02
sharding: 
   #分片角色 
   clusterRole: shardsvr

5)myshardrs012_27518 配置文件

[root@mongo104 sharded_cluster]# vim /mongodb/sharded_cluster/myshardrs02_27518/conf/mongod.conf
systemLog: 
   destination: file
   path: "/mongodb/sharded_cluster/myshardrs02_27518/log/mongod.log" 
   logAppend: true
storage: 
   dbPath: "/mongodb/sharded_cluster/myshardrs02_27518/data" 
   journal: 
      enabled: true 
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/myshardrs02_27518/mongod.pid" 
net:
   bindIp: 10.0.0.104
   port: 27518
replication:
   replSetName: myshardrs02
sharding: 
   #分片角色 
   clusterRole: shardsvr

6)启动第二套副本集:一主一副本一仲裁

[root@mongo104 ~]# cd /mongodb/sharded_cluster/
[root@mongo104 sharded_cluster]# ./myshardrs02_27318/bin/mongod -f ./myshardrs02_27318/conf/mongod.conf 
[root@mongo104 sharded_cluster]# ./myshardrs02_27418/bin/mongod -f ./myshardrs02_27418/conf/mongod.conf 
[root@mongo104 sharded_cluster]# ./myshardrs02_27518/bin/mongod -f ./myshardrs02_27518/conf/mongod.conf 

7)登录mongo并初始化主节点

# 登录
[root@mongo104 sharded_cluster]# ./myshardrs02_27318/bin/mongo --host=10.0.0.104 --port=27318

# 初始化
> rs.initiate()
myshardrs01:SECONDARY> 
myshardrs01:PRIMARY>

8)添加副本节点

myshardrs02:PRIMARY> rs.add("10.0.0.104:27418")

9)添加仲裁节点

myshardrs02:PRIMARY> rs.addArb("10.0.0.104:27518")

10)查看集群状态

# 集群状态信息
myshardrs02:PRIMARY> rs.status()
# 集群配置信息
myshardrs02:PRIMARY> rs.conf()

7.4.3、配置节点副本集部署

1)解压mongoDB

[root@mongo105 ~]# tar xf mongodb-linux-x86_64-4.0.10.tgz
[root@mongo105 ~]# ls
anaconda-ks.cfg  mongodb-linux-x86_64-4.0.10  mongodb-linux-x86_64-4.0.10.tgz
[root@mongo105 ~]# mkdir -p /mongodb/sharded_cluster/
[root@mongo105 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/myconfigrs_27019
[root@mongo105 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/myconfigrs_27119
[root@mongo105 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/myconfigrs_27219

2)创建存放数据和日志的目录

mkdir -p /mongodb/sharded_cluster/myconfigrs_27019/{log,data,conf}
mkdir -p /mongodb/sharded_cluster/myconfigrs_27119/{log,data,conf}
mkdir -p /mongodb/sharded_cluster/myconfigrs_27219/{log,data,conf}

3)myconfigrs_27019配置文件

[root@mongo105 sharded_cluster]# vim myconfigrs_27019/conf/mongod.conf
systemLog:
   destination: file
   path: "/mongodb/sharded_cluster/myconfigrs_27019/log/mongod.log"  
   logAppend: true
storage: 
   dbPath: "/mongodb/sharded_cluster/myconfigrs_27019/data"  
   journal: 
      enabled: true 
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/myconfigrs_27019/mongod.pid"  
net:
   bindIp: 10.0.0.105
   port: 27019
replication:
   replSetName: myconfigrs
sharding: 
   #分片角色 
   clusterRole: configsvr

4)myconfigrs_27119配置文件

[root@mongo105 sharded_cluster]# vim myconfigrs_27119/conf/mongod.conf
systemLog:
   destination: file
   path: "/mongodb/sharded_cluster/myconfigrs_27119/log/mongod.log"  
   logAppend: true
storage: 
   dbPath: "/mongodb/sharded_cluster/myconfigrs_27119/data"  
   journal: 
      enabled: true 
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/myconfigrs_27119/mongod.pid"  
net:
   bindIp: 10.0.0.105
   port: 27119
replication:
   replSetName: myconfigrs
sharding: 
   #分片角色
   clusterRole: configsvr

4)myconfigrs_27219配置文件

[root@mongo105 sharded_cluster]# vim myconfigrs_27219/conf/mongod.conf
systemLog:
   destination: file
   path: "/mongodb/sharded_cluster/myconfigrs_27219/log/mongod.log"  
   logAppend: true
storage: 
   dbPath: "/mongodb/sharded_cluster/myconfigrs_27219/data"  
   journal: 
      enabled: true 
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/myconfigrs_27219/mongod.pid"  
net:
   bindIp: 10.0.0.105
   port: 27219
replication:
   replSetName: myconfigrs
sharding: 
   #分片角色
   clusterRole: configsvr

5)启动配置副本集:一主两副本

[root@mongo105 sharded_cluster]# ./myconfigrs_27019/bin/mongod -f ./myconfigrs_27019/conf/mongod.conf 
[root@mongo105 sharded_cluster]# ./myconfigrs_27119/bin/mongod -f ./myconfigrs_27119/conf/mongod.conf 
[root@mongo105 sharded_cluster]# ./myconfigrs_27219/bin/mongod -f ./myconfigrs_27219/conf/mongod.conf

6)登录并初始化主节点

[root@mongo105 sharded_cluster]# ./myconfigrs_27019/bin/mongo --host=10.0.0.105 --port=27019
> rs.initiate()

7)加入副本

myconfigrs:PRIMARY> rs.add("10.0.0.105:27119")
myconfigrs:PRIMARY> rs.add("10.0.0.105:27219")

8)查看状态

myconfigrs:PRIMARY> rs.status()

7.4.4、第一个路由节点创建及分片

1)解压mongoDB

[root@mongos01 ~]# tar xf mongodb-linux-x86_64-4.0.10.tgz
[root@mongos01 ~]# mkdir -p /mongodb/sharded_cluster/
[root@mongos01 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/mymongos_27017

2)创建存放日志的目录(不需要创建数据目录)

[root@mongos01 ~]# mkdir -p /mongodb/sharded_cluster/mymongos_27017/{log,conf}

3)mymongos_27017节点配置文件

[root@mongos01 ~]# vim /mongodb/sharded_cluster/mymongos_27017/conf/mongos.conf
systemLog:
   destination: file
   path: "/mongodb/sharded_cluster/mymongos_27017/log/mongod.log"    
   logAppend: true
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/mymongos_27017/mongod.pid"    
net:
   bindIp: 10.0.0.101
   port: 27017
sharding: 
   #指定配置副本集
   configDB: myconfigrs/10.0.0.105:27019,10.0.0.105:27119,10.0.0.105:27219

4)启动mongos

[root@mongos01 ~]# cd /mongodb/sharded_cluster/mymongos_27017/
[root@mongos01 mymongos_27017]# ./bin/mongos -f ./conf/mongos.conf
[root@mongos01 mymongos_27017]# ps -ef|grep mongos
root       3901      1  0 15:39 ?        00:00:00 ./bin/mongos -f ./conf/mongos.conf
root       3924   3833  0 15:39 pts/0    00:00:00 grep --color=auto mongos

5)在路由节点记性分片配置操作

# 登录路由节点
[root@mongos01 ~]# /mongodb/sharded_cluster/mymongos_27017/bin/mongo --host=10.0.0.101 --port=27017

# 通过路由节点操作,现在只是连接了配置节点,还没有连接分片数据节点,因此无法写入业务数据
mongos> use aadb
switched to db aadb
mongos> db.aa.insert({aa:"aa"})
WriteCommandError({
	"ok" : 0,
	"errmsg" : "unable to initialize targeter for write op for collection aadb.aa :: caused by :: Database aadb not found :: caused by :: No shards found",
	"code" : 70,
	"codeName" : "ShardNotFound",
	"operationTime" : Timestamp(1617263114, 2),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617263114, 2),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
})

6)添加第一套分片副本集

mongos> sh.addShard("myshardrs01/10.0.0.103:27018,10.0.0.103:27118,10.0.0.103:27218")
{
	"shardAdded" : "myshardrs01",
	"ok" : 1,
	"operationTime" : Timestamp(1617263228, 5),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617263228, 5),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

# 查看分片状态情况
mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
  	"_id" : 1,
  	"minCompatibleVersion" : 5,
  	"currentVersion" : 6,
  	"clusterId" : ObjectId("6065716fe6ba7160184c718e")
  }
  shards:
        {  "_id" : "myshardrs01",  "host" : "myshardrs01/10.0.0.103:27018,10.0.0.103:27118",  "state" : 1 }
  active mongoses:
        "4.0.10" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  0
        Migration Results for the last 24 hours: 
                No recent migrations
  databases:
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }

7)添加第二套分片副本集

mongos> sh.addShard("myshardrs02/10.0.0.104:27318,10.0.0.104:27418,10.0.0.104:27518")
{
	"shardAdded" : "myshardrs02",
	"ok" : 1,
	"operationTime" : Timestamp(1617263436, 5),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617263436, 5),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

# 查看状态
mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
  	"_id" : 1,
  	"minCompatibleVersion" : 5,
  	"currentVersion" : 6,
  	"clusterId" : ObjectId("6065716fe6ba7160184c718e")
  }
  shards:	# 默认不显示arbiter
        {  "_id" : "myshardrs01",  "host" : "myshardrs01/10.0.0.103:27018,10.0.0.103:27118",  "state" : 1 }
        {  "_id" : "myshardrs02",  "host" : "myshardrs02/10.0.0.104:27318,10.0.0.104:27418",  "state" : 1 }
  active mongoses:
        "4.0.10" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  0
        Migration Results for the last 24 hours: 
                No recent migrations
  databases:
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        shard key: { "_id" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                myshardrs01	1
                        { "_id" : { "$minKey" : 1 } } -->> { "_id" : { "$maxKey" : 1 } } on : myshardrs01 Timestamp(1, 0)

提示:如果添加分片失败,需要先手动移除分片,检查添加分片的信息的正确性后,再次添加分片,如果只剩下最后一个shard,是无法删除的

# 移除分片
use admin
db.runCommand( { removeShard: "myshardrs02" } )

8)指定库开启分片功能

sh.enableSharding("库名")
sh.shardCollection("库名.集合名",{"key":1})

在mongos上的articledb数据库配置sharding

mongos> sh.enableSharding("articledb")
{
	"ok" : 1,
	"operationTime" : Timestamp(1617263805, 5),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617263805, 5),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
  	"_id" : 1,
  	"minCompatibleVersion" : 5,
  	"currentVersion" : 6,
  	"clusterId" : ObjectId("6065716fe6ba7160184c718e")
  }
  shards:
        {  "_id" : "myshardrs01",  "host" : "myshardrs01/10.0.0.103:27018,10.0.0.103:27118",  "state" : 1 }
        {  "_id" : "myshardrs02",  "host" : "myshardrs02/10.0.0.104:27318,10.0.0.104:27418",  "state" : 1 }
  active mongoses:
        "4.0.10" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  0
        Migration Results for the last 24 hours: 
                No recent migrations
  databases:
        {  "_id" : "articledb",  "primary" : "myshardrs02",  "partitioned" : true,  "version" : {  "uuid" : UUID("ce97812b-fdf0-444a-98d9-f64a2f6cafbf"),  "lastMod" : 1 } }
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        shard key: { "_id" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                myshardrs01	1
                        { "_id" : { "$minKey" : 1 } } -->> { "_id" : { "$maxKey" : 1 } } on : myshardrs01 Timestamp(1, 0)

9)具体集合进行分片

对集合进行分片时,你需要选择一个 片键(Shard Key) , shard key 是每条记录都必须包含的,且建立了索引的单个字段或复合字段,MongoDB按照片键将数据划分到不同的 数据块 中,并将 数据块均衡地分布到所有分片中.为了按照片键划分数据块,MongoDB使用 基于哈希的分片方式(随机平均分配)或者基于范围的分片方式(数值大小分配)

分片规则一:哈希策略

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

mongos> sh.shardCollection("articledb.comment",{"nickname":"hashed"})
{
	"collectionsharded" : "articledb.comment",
	"collectionUUID" : UUID("3ce480fb-7cb7-4d5c-ab96-8fb818ce3c20"),
	"ok" : 1,
	"operationTime" : Timestamp(1617265055, 28),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617265055, 28),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

# 查看状态
mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
  	"_id" : 1,
  	"minCompatibleVersion" : 5,
  	"currentVersion" : 6,
  	"clusterId" : ObjectId("6065716fe6ba7160184c718e")
  }
  shards:
        {  "_id" : "myshardrs01",  "host" : "myshardrs01/10.0.0.103:27018,10.0.0.103:27118",  "state" : 1 }
        {  "_id" : "myshardrs02",  "host" : "myshardrs02/10.0.0.104:27318,10.0.0.104:27418",  "state" : 1 }
  active mongoses:
        "4.0.10" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  0
        Migration Results for the last 24 hours: 
                No recent migrations
  databases:
        {  "_id" : "articledb",  "primary" : "myshardrs02",  "partitioned" : true,  "version" : {  "uuid" : UUID("ce97812b-fdf0-444a-98d9-f64a2f6cafbf"),  "lastMod" : 1 } }
                articledb.comment
                        shard key: { "nickname" : "hashed" }
                        unique: false
                        balancing: true
                        chunks:
                                myshardrs01	2
                                myshardrs02	2
                        { "nickname" : { "$minKey" : 1 } } -->> { "nickname" : NumberLong("-4611686018427387902") } on : myshardrs01 Timestamp(1, 0) 
                        { "nickname" : NumberLong("-4611686018427387902") } -->> { "nickname" : NumberLong(0) } on : myshardrs01 Timestamp(1, 1) 
                        { "nickname" : NumberLong(0) } -->> { "nickname" : NumberLong("4611686018427387902") } on : myshardrs02 Timestamp(1, 2) 
                        { "nickname" : NumberLong("4611686018427387902") } -->> { "nickname" : { "$maxKey" : 1 } } on : myshardrs02 Timestamp(1, 3) 
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        shard key: { "_id" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                myshardrs01	1
                        { "_id" : { "$minKey" : 1 } } -->> { "_id" : { "$maxKey" : 1 } } on : myshardrs01 Timestamp(1, 0) 

image-20210401162302537

分片规则二:范围策略

image-20210401163014376

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

如使用作者年龄字段作为片键,按照点赞数的值进行分片:

mongos> sh.shardCollection("articledb.author",{"age":1})
{
	"collectionsharded" : "articledb.author",
	"collectionUUID" : UUID("89405c61-7746-4ef6-9f66-de9cd0b6be33"),
	"ok" : 1,
	"operationTime" : Timestamp(1617265574, 13),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1617265574, 13),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

image-20210401162724808

7.4.5、哈希分片测试

1)登录mongs后,向comment循环插入1000条数据做测试

mongos> use articledb
switched to db articledb
mongos> for(var i=1;i<=1000;i++) {db.comment.insert({_id:i+"",nickname:"ABC"+i})}
WriteResult({ "nInserted" : 1 })
mongos> db.comment.count()
1000

2)登录第一个分片集群

[root@mongo103 sharded_cluster]# ./myshardrs01_27018/bin/mongo --host=10.0.0.103 --port=27018
myshardrs01:PRIMARY> use articledb
switched to db articledb
myshardrs01:PRIMARY> db.comment.count()
504

3)登录第二个分片集群

[root@mongo104 sharded_cluster]# ./myshardrs02_27318/bin/mongo --host=10.0.0.104 --port=27318
myshardrs02:PRIMARY> use articledb
switched to db articledb
myshardrs02:PRIMARY> db.comment.count()
496

可以看到,1000条数据近似均匀的分布到了2个shard上。是根据片键的哈希值分配的。这种分配方式非常易于水平扩展:一旦数据存储需要更大空间,可以直接再增加分片即可,同时提升了性能

7.4.6、范围分片测试

范围分片默认的数据库尺寸(chunksize)是64M,填满后才会考虑向其他片的数据块填充数据,因此,为了测试,可以将其改小,这里改为1M

use config 
db.settings.save( { _id:"chunksize", value: 1 } )

# 测试完改回来
db.settings.save( { _id:"chunksize", value: 64 } )

1)登录mongs后,向comment循环插入20000条数据做测试

mongos> use articledb
switched to db articledb
mongos> for(var i=1;i<=200000;i++) {db.author.save({"name":"ABC"+i,"age":NumberInt(i%120)})}
WriteResult({ "nInserted" : 1 })
mongos> db.author.count()
200000

7.4.7、第二个路由节点添加

1)创建日志文件夹

[root@mongos01 ~]# cp -rp mongodb-linux-x86_64-4.0.10 /mongodb/sharded_cluster/mymongos_27117
[root@mongos01 ~]# mkdir -p /mongodb/sharded_cluster/mymongos_27117/{log,conf}

2)创建配置文件

[root@mongos01 ~]# vim /mongodb/sharded_cluster/mymongos_27117/conf/mongos.conf
systemLog:
   destination: file
   path: "/mongodb/sharded_cluster/mymongos_27117/log/mongod.log"  
   logAppend: true
processManagement: 
   fork: true
   pidFilePath: "/mongodb/sharded_cluster/mymongos_27117/mongod.pid"  
net:
   bindIp: 10.0.0.101
   port: 27117
sharding: 
   #指定配置副本集
   configDB: myconfigrs/10.0.0.105:27019,10.0.0.105:27119,10.0.0.105:27219

3)启动mongos2

[root@mongos01 ~]# cd /mongodb/sharded_cluster/
[root@mongos01 sharded_cluster]# ./mymongos_27117/bin/mongos -f ./mymongos_27117/conf/mongos.conf

4)使用mongo客户端登录27117

第二个路由无需配置,因为分片配置都保存到了配置服务器中了

7.4.8、Compass连接分片集群

和连接单机mongod一样

image-20210401191433517

image-20210401191925146

7.4.9、SpringDataMongoDB连接分片集群

Java客户端常用的是SpringDataMongoDB,其连接的是mongs路由,配置和单机mongod的配置是一样的。多个路由的时候的SpringDataMongoDB的客户端配置(application.yml)参考如下:

spring:
  data:
    mongodb:
      # host: 10.0.0.104
      # database: articledb
      # port: 27017
      #副本集的连接字符串
      #uri: mongodb://10.0.0.103:27017,10.0.0.104:27017,10.0.0.105:27017/articledb?connect=replicaSet&slaveOk=true&replicaSet=myrs
      #连接路由字符串
      uri: mongodb://10.0.0.101:27017,10.0.0.101:27117/articledb

八、MongoDB安全认证

8.1、MongoDB用户和角色权限简介

默认情况下,MongoDB实例启动运行时是没有启用用户访问权限控制的,也就是说,在实例本机服务器上都可以随意连接到实例进行各种操作,MongoDB不会对连接客户端进行用户验证,这是非常危险的。

1)保障mongodb的安全可以做以下几个步骤:

  • 使用新的端口:默认的27017端口如果一旦知道了ip就能连接上,不太安全。

  • 设置mongodb的网络环境,最好将mongodb部署到公司服务器内网,这样外网是访问不到的。公司内部访问使用vpn等。

  • 开启安全认证。认证要同时设置服务器之间的内部认证方式,同时要设置客户端连接到集群的账号密码认证方式。

2)相关概念

启用访问控制:MongoDB使用的是基于角色的访问控制(Role-Based Access Control,RBAC)来管理用户对实例的访问。通过对用户授予一个或多个角色来控制用户访问数据库资源的权限和数据库操作的权限,在对用户分配角色之前,用户无法访问实例。在实例启动时添加选项 --auth 或指定启动配置文件中添加选项 auth=true

角色:在MongoDB中通过角色对用户授予相应数据库资源的操作权限,每个角色当中的权限可以显式指定,也可以通过继承其他角色的权限,或者两都都存在的权限。在角色定义时可以包含一个或多个已存在的角色,新创建的角色会继承包含的角色所有的权限。在同一个数据库中,新创建角色可以继承其他角色的权限,在 admin 数据库中创建的角色可以继承在其它任意数据库中角色的权限。

权限:权限由指定的数据库资源(resource)以及允许在指定资源上进行的操作(action)组成

  • 资源(resource):数据库、集合、部分集合和集群;
  • 操作(action):对资源进行的增、删、改、查(CRUD)操作

3)权限命令

# 查询所有角色权限(仅用户自定义角色)
> db.runCommand({ rolesInfo: 1 })

# 查询其它数据库中指定的角色权限
> db.runCommand({ rolesInfo: { role: "<rolename>", db: "<database>" } }

# 查询多个角色权限
> db.runCommand( 
	{ 
		rolesInfo: [ 
			"<rolename>", { role: "<rolename>", 
			db: "<database>" }, 
			... 
			] 
	} 
)

4)角色说明

# 查看所有内置角色
> db.runCommand({ rolesInfo: 1, showBuiltinRoles: true })
{
	"roles" : [
		{
			"role" : "__queryableBackup",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "__system",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "backup",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "clusterAdmin",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "clusterManager",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "clusterMonitor",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "dbAdmin",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "dbAdminAnyDatabase",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "dbOwner",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "enableSharding",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "hostManager",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "read",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "readAnyDatabase",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "readWrite",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "readWriteAnyDatabase",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "restore",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "root",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "userAdmin",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		},
		{
			"role" : "userAdminAnyDatabase",
			"db" : "admin",
			"isBuiltin" : true,
			"roles" : [ ],
			"inheritedRoles" : [ ]
		}
	],
	"ok" : 1
}

常用的内置角色

  • 数据库用户角色:read、readWrite;
  • 所有数据库用户角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
  • 数据库管理角色:dbAdmin、dbOwner、userAdmin
  • 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
  • 备份恢复角色:backup、restore;
  • 超级用户角色:root
  • 内部角色:system

角色说明

角色 权限描述
read 可以读取指定数据库中任何数据
readWrite 可以读写指定数据库中任何数据,包括创建、重命名、删除集合
readAnyDatabase 可以读取所有数据库中任何数据(除了数据库config和local之外)。
readWriteAnyDatabase 可以读写所有数据库中任何数据(除了数据库config和local之外)。
userAdminAnyDatabase 可以在指定数据库创建和修改用户(除了数据库config和local之外)。
dbAdminAnyDatabase 可以读取任何数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作(除了数据库config和local之外)。
dbAdmin 可以读取指定数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作。
userAdmin 可以在指定数据库创建和修改用户。
clusterAdmin 可以对整个集群或数据库系统进行管理操作
backup 备份MongoDB数据最小的权限。
restore 从备份文件中还原恢复MongoDB数据(除了system.profile集合)的权限
root 超级账号,超级权限

8.2、单实例权限控制

8.2.1、添加用户和权限

1)按照普通无授权认证的配置先启动mongoDB

# 原来的配置
[root@mongo ~]# cat /mongodb/conf/mongod.conf 
systemLog: 
   destination: file
   path: "/mongodb/log/mongod.log" 
   logAppend: true
storage: 
   dbPath: "/mongodb/data/db" 
   journal: 
      enabled: true 
processManagement: 
   fork: true 
net:
   bindIp: localhost,10.0.0.104
   port: 27017

# 按之前未开启认证的方式(不添加 --auth 参数)来启动MongoDB服务
[root@mongo ~]# /usr/local/mongodb/bin/mongod -f /mongodb/conf/mongod.conf

2)登录mongoDB,创建两个管理员用户,一个是系统的超级管理员 myroot ,一个是admin库的管理用户myadmin

# 登录mongoDB
[root@mongo ~]# /usr/local/mongodb/bin/mongo --host 10.0.0.104 --port 27017

# 切换到admin库
> use admin
switched to db admin

# 创建系统超级用户 myroot,设置密码123456,设置角色root
> db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
Successfully added user: { "user" : "myroot", "roles" : [ "root" ] }

# 创建专门用来管理admin库的账号myadmin,只用来作为用户权限的管理
> db.createUser({user:"myadmin",pwd:"123456",roles: [{role:"userAdminAnyDatabase",db:"admin"}]})
Successfully added user: {
	"user" : "myadmin",
	"roles" : [
		{
			"role" : "userAdminAnyDatabase",
			"db" : "admin"
		}
	]
}

# 查看创建的用户情况
> db.system.users.find()
{ "_id" : "admin.myroot", "userId" : UUID("6847a67e-83f4-4814-8788-fcaf2f762213"), "user" : "myroot", "db" : "admin", "credentials" : { "SCRAM-SHA-1" : { "iterationCount" : 10000, "salt" : "WC4RWaRK/X2EPpwTBsWZ/w==", "storedKey" : "GOZ8vYQg5+OyDy6lAxpSDwy4P3E=", "serverKey" : "/JwZiE8wuaUBdTTvt7DKk9xCfO0=" }, "SCRAM-SHA-256" : { "iterationCount" : 15000, "salt" : "DoRJ6cUdM6fgBZP2f9Tt4LXYfXG9KZPdud8P/w==", "storedKey" : "6EuV7DL7BUb3Jryhy1/IEa2tea4m2VXyVWZGVL1ixqA=", "serverKey" : "Yxyb075ydXKTNw4n7u1gE2RNySRCSLnUDrDo+AWklYo=" } }, "roles" : [ { "role" : "root", "db" : "admin" } ] }
{ "_id" : "admin.myadmin", "userId" : UUID("b51f3539-1330-45e3-a900-427a7288e59c"), "user" : "myadmin", "db" : "admin", "credentials" : { "SCRAM-SHA-1" : { "iterationCount" : 10000, "salt" : "hDj8Ut45FrpCtC/FplL6VA==", "storedKey" : "ntc0mhpVMLcACL2ilsXM0ICXcN0=", "serverKey" : "76lqeGWwcpU+xH5pT0EVDYe0fxI=" }, "SCRAM-SHA-256" : { "iterationCount" : 15000, "salt" : "E/Z7PRNRC96MPTC0P4FM+zveDk3FzZJVt7DAeQ==", "storedKey" : "xlsvkIBeHbCaoAm+FwacosJ6Qj1/mz+On4NWKe2OTZM=", "serverKey" : "xEDF21/TV6ubfV2Oak5aTNnoCK8FWbsq9oNVf9mJ2SQ=" } }, "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" } ] }

# 删除用户
> db.dropUser("myadmin")
true
> db.system.users.find()
{ "_id" : "admin.myroot", "userId" : UUID("6847a67e-83f4-4814-8788-fcaf2f762213"), "user" : "myroot", "db" : "admin", "credentials" : { "SCRAM-SHA-1" : { "iterationCount" : 10000, "salt" : "WC4RWaRK/X2EPpwTBsWZ/w==", "storedKey" : "GOZ8vYQg5+OyDy6lAxpSDwy4P3E=", "serverKey" : "/JwZiE8wuaUBdTTvt7DKk9xCfO0=" }, "SCRAM-SHA-256" : { "iterationCount" : 15000, "salt" : "DoRJ6cUdM6fgBZP2f9Tt4LXYfXG9KZPdud8P/w==", "storedKey" : "6EuV7DL7BUb3Jryhy1/IEa2tea4m2VXyVWZGVL1ixqA=", "serverKey" : "Yxyb075ydXKTNw4n7u1gE2RNySRCSLnUDrDo+AWklYo=" } }, "roles" : [ { "role" : "root", "db" : "admin" } ] }

# 修改密码
> db.changeUserPassword("myroot", "123456")

注意

(1)本案例创建了两个用户,分别对应超管和专门用来管理用户的角色,事实上,你只需要一个用户即可。如果你对安全要求很高,防止超管泄漏,则不要创建超管用户。

(2)和其它数据库(MySQL)一样,权限的管理都差不多一样,也是将用户和权限信息保存到数据库对应的表中。Mongodb存储所有的用户信息在admin 数据库的集合system.users中,保存用户名、密码和数据库信息。

(3)如果不指定数据库,则创建的指定的权限的用户在所有的数据库上有效,如 {role: "userAdminAnyDatabase", db:""}

3)认证测试

> use admin
switched to db admin
> db.auth("myroot","12345")
Error: Authentication failed.
0
> db.auth("myroot","123456")
1

4)创建普通用户

创建普通用户可以在没有开启认证的时候添加,也可以在开启认证之后添加,但开启认证之后,必须使用有操作admin库的用户登录认证后才能操作。底层都是将用户信息保存在了admin数据库的集合system.users

如果开启了认证后,登录的客户端的用户必须使用admin库的角色,如拥有root角色的myadmin用户,再通过myadmin用户去创建其他角色的用户

# 创建(切换)将来要操作的数据库articledb
> use articledb
switched to db articledb

# 创建用户,拥有articledb数据库的读写权限readWrite,密码是123456
> db.createUser({user: "lawrence", pwd: "123456", roles: [{ role: "readWrite", db: "articledb" }]})
Successfully added user: {
	"user" : "lawrence",
	"roles" : [
		{
			"role" : "readWrite",
			"db" : "articledb"
		}
	]
}

# 测试是否可用
> db.auth("lawrence","123456")
1

8.2.2、服务端开启认证和客户端连接登录

1)关闭启动的服务

# 方式一:杀死进程
[root@mongo ~]# ps -ef|grep mongo
root       6498      1  0 09:48 ?        00:00:18 mongod -f /mongodb/conf/mongod.conf
root      39619   1529  0 10:46 pts/0    00:00:00 grep --color=auto mongo
[root@mongo ~]# kill -2 6498

# 方式二:localhost登录并db.shutdownServer()
> db.shutdownServer()
shutdown command only works with the admin database; try 'use admin'
> use admin
switched to db admin
> db.shutdownServer()
2021-04-02T10:47:02.996+0800 E QUERY    [js] Error: shutdownServer failed: {
	"ok" : 0,
	"errmsg" : "shutdown must run from localhost when running db without auth",
	"code" : 13,
	"codeName" : "Unauthorized"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
DB.prototype.shutdownServer@src/mongo/shell/db.js:453:1
@(shell):1:1
> 
[root@mongo ~]# /usr/local/mongodb/bin/mongo --host 127.0.0.1 --port 27017
> use admin
switched to db admin
> db.shutdownServer()
server should be down...
2021-04-02T10:47:48.479+0800 I NETWORK  [js] trying reconnect to 127.0.0.1:27017 failed
2021-04-02T10:47:48.479+0800 I NETWORK  [js] reconnect 127.0.0.1:27017 failed failed

主要需要几个条件:

  • 必须是在admin库下执行该关闭服务命令。
  • 如果没有开启认证,必须是从localhost登陆的,才能执行关闭服务命令。
  • 非localhost、通过远程登录的,必须有登录且必须登录用户有对admin操作权限才以。

2)开启认证的方式启动服务

有两种方式开启权限认证启动服务:一种是参数方式,一种是配置文件方式。

方式一:参数方式

# 在启动时指定参数 --auth,不推荐
[root@mongo ~]# /usr/local/mongodb/bin/mongod -f /mongodb/conf/mongod.conf --auth

方式二:配置文件方法

# 在配置文件中开启认证
[root@mongo ~]# vim /mongodb/conf/mongod.conf
systemLog:
   destination: file
   path: "/mongodb/log/mongod.log"
   logAppend: true
storage:
   dbPath: "/mongodb/data/db"
   journal:
      enabled: true
processManagement:
   fork: true
net:
   bindIp: localhost,10.0.0.104
   port: 27017
security: 
   #开启授权认证 
   authorization: enabled
   
# 启动
[root@mongo ~]# /usr/local/mongodb/bin/mongod -f /mongodb/conf/mongod.conf

3)客户端连接

有两种认证方式,一种是先登录,在mongo shell中认证;一种是登录时直接认证。

方式一:在mongoDB shell中认证

# 先登录在mongoDB shell中认证
> use admin
switched to db admin
> db.system.users.find()
Error: error: {
	"ok" : 0,
	"errmsg" : "command find requires authentication",
	"code" : 13,
	"codeName" : "Unauthorized"
}
> db.auth("myroot","123456")
1
> db.system.users.find()

# 退出shell,在登录,否者报错:too many users are authenticated
> use articledb
switched to db articledb
> db.comment.find()
Error: error: {
	"ok" : 0,
	"errmsg" : "command find requires authentication",
	"code" : 13,
	"codeName" : "Unauthorized"
}
> db.auth("lawrence","123456")
1
> db.comment.find()

方式二:连接时直接认证

# 对admin数据库进行登录认证和相关操作
[root@mongo ~]# /usr/local/mongodb/bin/mongo --host 10.0.0.104 --port 27017 --authenticationDatabase admin -u myroot -p 123456
> show dbs
admin      0.000GB
articledb  0.000GB
config     0.000GB
local      0.000GB
> use admin
switched to db admin
> db.system.users.find()

# 对articledb数据库进行登录认证和相关操作
[root@mongo ~]# /usr/local/mongodb/bin/mongo --host 10.0.0.104 --port 27017 --authenticationDatabase articledb -u lawrence -p 123456
> db
test
> use articledb
switched to db articledb
> db.comment.find()

8.2.3、SpringDataMongoDB连接认证

application.yml配置文件:

spring:
  data:
    mongodb:
      uri: mongodb://lawrence:123456@10.0.0.104:27017/articledb

8.2.4、Compass连接

image-20210402113559919

image-20210402113625092

8.3、副本集权限控制

image-20210331111534455

对副本集执行访问控制需要配置两个方面:

1)副本集和共享集群的各个节点成员之间使用内部身份验证,可以使用密钥文件x.509证书。密钥文件比较简单,本文使用密钥文件,官方推荐如果是测试环境可以使用密钥文件,但是正式环境,官方推荐x.509证书。原理就是,集群中每一个实例彼此连接的时候都检验彼此使用的证书的内容是否相同。只有证书相同的实例彼此才可以访问

2)使用客户端连接到mongodb集群时,开启访问授权。对于集群外部的访问。如通过可视化客户端,或者通过代码连接的时候,需要开启授权。

3)在keyfile身份验证中,副本集中的每个mongod实例都使用keyfile的内容作为共享密码,只有具有正确密钥文件的mongod或者mongos实例可以连接到副本集。密钥文件的内容必须在6到1024个字符之间,并且在unix/linux系统中文件所有者必须有对文件至少有读的权限

8.3.1、启动副本集群

[root@mongo103 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
[root@mongo104 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
[root@mongo105 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf

8.3.2、主节点添加管理员账号

# 在主节点上添加用户,副本集会自动同步,开启认证之前,创建超管用户:myroot,密码:123456
myrs:PRIMARY> use admin
switched to db admin
myrs:PRIMARY> db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
Successfully added user: { "user" : "myroot", "roles" : [ "root" ] }

8.3.3、创建副本集认证key文件

# 生成key文件
[root@mongo104 ~]# openssl rand -base64 90 -out /data/mongodb/mongo.keyfile

# 更改权限
[root@mongo104 ~]# chmod 400 /data/mongodb/mongo.keyfile
[root@mongo104 ~]# ll /data/mongodb/mongo.keyfile
-r-------- 1 root root 122 Apr  2 14:06 /data/mongodb/mongo.keyfile

# 拷贝key文件至其他副本集节点
[root@mongo104 ~]# scp -r /data/mongodb/mongo.keyfile root@10.0.0.103:/data/mongodb/
[root@mongo104 ~]# scp -r /data/mongodb/mongo.keyfile root@10.0.0.105:/data/mongodb/

8.3.4、修改配置文件制定key文件

# 104主机修改
[root@mongo104 ~]# vim /data/mongodb/conf/mongod.conf
...
security:
   #KeyFile鉴权文件
   keyFile: /data/mongodb/mongo.keyfile
   #开启认证方式运行
   authorization: enabled
   
# 103主机修改
[root@mongo103 ~]# vim /data/mongodb/conf/mongod.conf
...
security:
   #KeyFile鉴权文件
   keyFile: /data/mongodb/mongo.keyfile
   #开启认证方式运行
   authorization: enabled
   
# 105主机修改
[root@mongo105 ~]# vim /data/mongodb/conf/mongod.conf
...
security:
   #KeyFile鉴权文件
   keyFile: /data/mongodb/mongo.keyfile
   #开启认证方式运行
   authorization: enabled

8.3.5、重新启动副本集

# 先关闭后启动
kill -2 `ps -ef|grep mongo|grep -v grep|awk '{print $2}'`

[root@mongo103 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
[root@mongo104 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf
[root@mongo105 ~]# /data/mongodb/bin/mongod -f /data/mongodb/conf/mongod.conf

8.3.6、主节点上创建普通账号

myrs:PRIMARY> use admin
switched to db admin
myrs:PRIMARY> db.auth("myroot","123456")
1
myrs:PRIMARY> use articledb
switched to db articledb
myrs:PRIMARY> db.createUser({user: "lawrence", pwd: "123456", roles: ["readWrite"]})
Successfully added user: { "user" : "lawrence", "roles" : [ "readWrite" ] }

8.3.7、账号登录测试

# 对admin数据库进行登录认证和相关操作
[root@mongo104 ~]# /data/mongodb/bin/mongo --host 10.0.0.104 --port 27017 --authenticationDatabase admin -u myroot -p 123456
myrs:PRIMARY> use admin
switched to db admin
myrs:PRIMARY> db.system.users.find()

# 对articledb数据库进行登录认证和相关操作
[root@mongo104 ~]# /data/mongodb/bin/mongo --host 10.0.0.104 --port 27017 --authenticationDatabase articledb -u lawrence -p 123456
myrs:PRIMARY> show dbs
articledb  0.000GB
myrs:PRIMARY> use articledb
switched to db articledb
myrs:PRIMARY> db.comment.find()

8.3.8、SpringDataMongoDB连接副本集

application.yml文件配置:

spring:
  data:
    mongodb:
      uri: mongodb://lawrence:123456@10.0.0.103:27017,10.0.0.104:27017,10.0.0.105:27017/articledb?connect=replicaSet&slaveOk=true&replicaSet=myrs

8.3.9、Compass连接

image-20210402142959407

8.4、分片集群权限控制

分片集群环境下的安全认证和副本集环境下基本上一样,所有的节点都要保持相同的keyfile。

但分片集群的服务器环境和架构较为复杂,建议在搭建分片集群的时候,直接加入安全认证和服务器间的鉴权,如果之前有数据,可先将之前的数据备份出来,再还原回去。

posted @ 2021-04-02 14:45  运维人在路上  阅读(1132)  评论(0编辑  收藏  举报