MongoDB 学习笔记

一. MongoDB 简介

1.1 简介

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

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

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

1.2 体系结构

MySQL和MongoDB对比

在这里插入图片描述
在这里插入图片描述

SQL术语/概念MongoDB术语/概念解释
databasedatabase数据库
tablecollection数据库表/集合
rowdocument数据记录行/文档
columnfield数据字段/域
indexindex索引
table joins/表连接,MongoDB不支持
/嵌入文档MongoDB通过嵌入式文档来替代多表连接
primary keyprimary 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中,除了基本的JSON类型:string,integer,boolean,double,null,array和object,mongo还使用了特殊的数据类型。这些类型包括date,object id,binary data,regular expression 和code。每一个驱动都以特定语言的方式实现了这些类型,查看你的驱动的文档来获取详细信息。

数据类型描述举例
String字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。{“x” : “foobar”}
Integer整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。
Boolean布尔值。用于存储布尔值(真/假)。{“x”:true}
Double双精度浮点值。用于存储浮点值。{“x”:3.14159,“y”:3}
Min/Max keys将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。
Array用于将数组或列表或多个值存储为一个键。{“x” : [“a”, “b”, “c”]}
Timestamp时间戳。记录文档修改或添加的具体时间。
Object用于内嵌文档。
Null用于创建空值。{“x”:null}
Symbol符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。
Date日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。
Object ID对象 ID。用于创建文档的 ID
Binary Data对象 ID。二进制数据。用于存储二进制数据。
Code对象 ID。代码类型。用于在文档中存储 JavaScript 代码。{“x” : function() { /* …… */ }}
Regular expression正则表达式类型。用于存储正则表达式。{“x” : /foobar/i}

1.4 特点

  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. 其他特点:

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

二. MongoDB安装

2.1 window系统安装

  1. 官网安装包下载

在这里插入图片描述
提示:版本的选择:
MongoDB的版本命名规范如:x.y.z;
y为奇数时表示当前版本为开发版,如:1.5.2、4.1.13;
y为偶数时表示当前版本为稳定版,如:1.6.3、4.0.10;
z是修正版本号,数字越大越好。

  1. 解压安装

  2. 创建数据目录

    MongoDB将数据目录存储在 db 目录下。但是这个数据目录不会主动创建,我们在安装完成后需要创建它。在解压目录中,手动建立一个目录用于存放数据文件,如 data/db

  3. 启动

    4.1 命令行下运行 MongoDB 服务器

    从 MongoDB 目录的 bin 目录中执行 mongod.exe 文件。

    H:\mongodb\bin\mongod --dbpath c:\data\db
    

    连接MongoDB: 直接在bin目录下执行mongo.exe即可
    或者执行命令 mongo --host=127.0.0.1 --port=27017

    4.2 配置文件方式启动服务

    在解压目录中新建 config 文件夹,该文件夹中新建配置文件 mongod.conf ,内如参考如下:

    systemLog:
        destination: file
        path: c:\data\log\mongod.log  #日志文件的目录
    storage:
        dbPath: c:\data\db  #数据库目录
    

    在bin目录下执行命令启动

    mongod -f ../config/mongod.conf
    
    net start MongoDB #启动MongoDB服务
    net stop MongoDB #关闭MongoDB服务
    H:\mongodb\bin\mongod.exe --remove  #移除MongoDB服务
    
  4. 图像化界面安装

    官网下载地址

    如果是下载安装版,则按照步骤安装;如果是下载压缩版,直接解压,执行里面的 MongoDBCompassCommunity.exe 文件即可。

2.2 Linux 系统中的安装启动和连接

  1. 官网下载安装包

  2. 解压压缩包

    在这里插入图片描述

  3. 新建几个目录,分别用来存储数据和日志:

    在这里插入图片描述

  4. 在conf中新建并修改配置文件

    #数据库路径
    dbpath=/root/mongodb/single/data/db
    #日志输出文件路径
    logpath=/root/mongodb/single/log/mongod.log
    #错误日志采用追加模式
    logappend=true
    #启用日志文件,默认启用
    journal=true
    #这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置false
    quiet=true
    #端口号 默认为27017
    port=27017
    #指访问ip,设置0.0.0.0就没有限制,都可以连接
    bind_ip=0.0.0.0
    #启用在后台运行mongos或mongod进程的守护进程模式
    fork=true
    
  5. 启动服务

    /root/mongodb/bin/mongod -f /root/mongodb/single/conf/mongod.conf
    

    在这里插入图片描述
    启动成功后,分别使用mongo命令和compass工具来连接测试,如果远程连接不上,需要配置防火墙放行,或直接关闭linux防火墙

    #查看防火墙状态
    systemctl status firewalld
    #临时关闭防火墙
    systemctl stop firewalld
    #开机禁止启动防火墙
    systemctl disable firewalld
    
  6. 停止关闭服务

    #客户端登录服务,注意,这里通过localhost登录,如果需要远程登录,必须先登录认证才行。
    mongo --port 27017
    #切换到admin库
    use admin
    #关闭服务
    db.shutdownServer()
    

三. 基本操作

3.1 数据库操作

选择和创建数据库的语法格式:

use 数据库名称 #如果数据库不存在则自动创建

查看有权限查看的所有的数据库命令

show dbs

注意 : 在 MongoDB 中,集合只有在内容插入后才会创建. 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。

查看当前正在使用的数据库命令

db

MongoDB 中默认的数据库为 test,如果你没有选择数据库,集合将存放在 test 数据库中。

mongo 默认自带3个库

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

数据库的删除

db.dropDatabase() #主要用来删除已经持久化的数据库

3.2 集合操作

集合,类似关系型数据库中的表。可以显示的创建,也可以隐式的创建。

集合的显式创建(了解)

db.createCollection("user") ##创建一个名为user的集合(表)

查看当前库中的表: show tables命令

集合的隐式创建

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

集合删除

db.user.drop()

MongoDB 固定集合

一般情况下我们创建的集合是没有大小的,可以一直往里边添加文档,这种集合可以动态增长,MongoDB 中还有一种集合叫做固定集合,这种集合的大小是固定的,我可以在创建的时候设置该集合中文档的数目,假设为 100 条,当集合中的文档数目达到 10 条时,如果再向集合中插入文档,则只会保留最新的 10 个文档,之前的文档则会被删除。

一般像日志信息我们就可以使用固定集合,其他一些需要定期删除的数据也可以使用固定集合,本文我们就来看看这个固定集合的使用。

固定集合的创建方式也比较简单,如下:

db.createCollection("comment",{capped:true,size:1000,max:10})

capped:true :表示该集合为一个固定大小集合
size 表示集合的大小,单位为 kb
max 则表示集合中文档的最大数量。

我们这里相当于给了固定集合两个限制条件,只要有任意一个限制条件满足,集合都会开始将更古老的数据删除。固定集合一旦创建成功就不能再修改(包括里面的文档),想修改只能删除重来。此时我们可以尝试向集合中添加 15条简单的数据,然后我们会发现最早的 5 条数据消失了。

除了直接创建一个固定集合外,我们也可以通过 convertToCapped 操作将一个普通集合转为一个固定集合,如下:

db.runCommand({convertToCapped:"author",size:10})

自然排序问题

自然排序就是按照文档在磁盘中的顺序来进行排列,在普通的集合中自然排序并没有多大的意义,因为文档的位置总是在变化,而固定集合中的文档是按照文档被插入的顺序保存的,自然顺序也就是文档的插入顺序,因此我们可以利用自然排序对文档从旧到新排序,如下:

db.comment.find().sort({$natural:1})

也可以从新到旧排序:

db.comment.find().sort({$natural:-1})

3.3 文档操作

3.3.1 文档插入

单条插入

db.comment.insert(
	{
	  "articleid":"100000",
	  "content":"今天天气真好,阳光明媚",
	  "userid":"1001","nickname":"Rose",
	  "createdatetime":new Date(),
	  "likenum":NumberInt(10),
	  "state":null
	}
)

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

在这里插入图片描述

  1. 文档中的键/值对是有序的。
  2. 文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。
  3. MongoDB区分类型和大小写。
  4. MongoDB的文档不能有重复的键。
  5. 文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。
  6. 以下划线 "_"开头的键是保留的(不是严格要求的)。

批量插入

db.comment.insertMany(
[
  {
  "articleid":"100000",
  "content":"今天天气真好,阳光明媚",
  "userid":"1001","nickname":"Rose",
  "createdatetime":new Date(),
  "likenum":NumberInt(10),
  "state":null
  },
  {
    "_id": "1",
    "articleid":"100000",
    "content":"今天天气真好,阳光明媚",
    "userid":"1001","nickname":"Rose",
    "createdatetime":new Date(),
    "likenum":NumberInt(10),
    "state":null
  },
  {
    "_id": "2",
    "articleid":"100000",
    "content":"今天天气真好,阳光明媚",
    "userid":"1001","nickname":"Rose",
    "createdatetime":new Date(),
    "likenum":NumberInt(10),
    "state":null
  },
  {
    "_id": "5",
    "articleid":"100000",
    "content":"今天天气真好,阳光明媚",
    "userid":"1001","nickname":"Rose",
    "createdatetime":new Date(),
    "likenum":NumberInt(10),
    "state":null
  }
]
)

在这里插入图片描述

插入时指定了 _id ,则主键就是该值。
如果某条数据插入失败,将会终止插入,但已经插入成功的数据不会回滚掉。

3.3.2 文档查询

查询所有

db.comment.find()

按条件查询

db.comment.find({_id:ObjectId("5f881a6a0abbc8007ae8f74c")})
db.comment.find({content:"今天天气真好,阳光明媚"})
##只最多返回符合条件的第一条记录
db.comment.findOne({content:"今天天气真好,阳光明媚"})

在这里插入图片描述
在这里插入图片描述
返回指定字段

db.comment.find({},{articleid:1,likenum:1,name:1,_id:0})
db.comment.find({"name": "test"},{articleid:1,likenum:1,name:1,_id:0})

在这里插入图片描述

3.3.3 文档修改

覆盖的修改

db.comment.update({userid:"1001"},{nickname:"jack"})

在这里插入图片描述

执行后,我们会发现,这条文档除了 nickname字段其它字段都不见了

局部修改

db.comment.update({userid:"1001"},{$set{nickname:"bob"}})

在这里插入图片描述
这样就不会出现上面的问题了 ,但是这样修改只会修改满足条件的第一条数据

批量修改

db.comment.update({nickname:"Rose"},{$set:{createdatetime:new Date()}},{multi:true})

更改所有满足nickname为Rose的数据 如果不加后面的multi参数,则只更新符合条件的第一条记录

列值增长的修改

 db.comment.update({userid:"1001"},{$inc:{likenum:NumberInt(1)}})

对userid 为1001的数据的linknum每次增加1

3.3.4 文档删除

条件删除

 db.comment.remove({_id:ObjectId("5f88fcaca12478cb298b3772")})

删除_id为ObjectId("5f88fcaca12478cb298b3772")的数据

全部删除 慎用

db.comment.remove({})
3.3.5 文档分页查询

统计查询

db.comment.count()   #统计所有记录数
db.comment.count({userid:"1001"})  #条件统计

分页查询

 db.comment.find().limit(2) #查询前2条数据
 db.comment.find().skip(2)  #跳过前2条查询
 #每页2个,第二页开始:跳过前两条数据
 //第一页
db.comment.find().skip(0).limit(2)
//第二页
db.comment.find().skip(2).limit(2)
//第三页
db.comment.find().skip(4).limit(2)

排序

db.comment.find().sort({userid:-1,likenum:1}) #对userid降序 对likenum升序

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

3.3.6 文档的复杂查询

正则查询

db.comment.find({content:/明媚/}) #查询所有content包含明媚的文档
db.comment.find({content:/^明媚/}) #查询所有content以明媚开头的文档

比较查询

db.comment.find({userid:{$gt:"1003"}}) #查询userid大于1003的文档
db.comment.find({userid:{$lt:"1003"}}) #查询userid小于1003的文档
db.comment.find({userid:{$gte:"1003"}}) #查询userid>=1003的文档
db.comment.find({userid:{$lte:"1003"}}) #查询userid<=1003的文档
db.comment.find({userid:{$ne:"1003"}}) #查询userid!=1003的文档

包含查询

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

条件连接查询

查询评论集合中 likenum大于等于10 并且小于16的文档

db.comment.find(
	{
		$and:[
			{
				likenum:{$gt:10}
			},
			{
				likenum:{$lt:16}
			}
		]
	}
)

查询评论集合中 userid为1001,或者点赞数小于16的文档记录

 db.comment.find({$or:[{userid:1001},{likenum:{$lt:16}}]})

四. 索引

4.1 索引概述

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

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

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

4.2 索引类型

4.2.1 单字段索引

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

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

4.2.2 复合索引

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

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

4.2.3 其他索引

地理空间索引(Geospatial Index)、文本索引(Text Indexes)、哈希索引(Hashed Indexes)。

地理空间索引(Geospatial Index)

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

文本索引

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

哈希索引

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

4.3 索引管理

查看索引

db.commet.getIndexes()

在这里插入图片描述

新建索引

db.comment.createIndex({userid:1}) #单字段索引
db.comment.createIndex({userid:1,likenum:-1})  #符合索引

createIndex() 接收可选参数,可选参数列表如下:

在这里插入图片描述
删除索引

#删除单个索引
db.comment.dropIndex({userid:1})
或
db.comment.dropIndex(索引名称)

#删除全部索引
db.comment.dropIndexes()

注意: _id 的字段的索引是无法删除的,只能删除非 _id 字段的索引。

索引使用

执行计划

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

那么,通常,我们想知道,建立的索引是否有效,效果如何,都需要通过执行计划查看。

db.comment.find({userid:'1003'}).explain()

在这里插入图片描述
在这里插入图片描述
表示全表扫描 未走索引

涵盖的查询

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

在这里插入图片描述

五. SpringBoot集成mongo

引入相关依赖

在这里插入图片描述

创建和表相对应的实体类

@Document(collection = "comment") //所对应库里的集合名称
//@CompoundIndex(def = "{'userid':1,'nickname':-1}")  //复合索引
public class Comment implements Serializable {

    /**
     * 主键标识,该属性的值会自动对应mongodb的主键字段"_id",
     * 如果该属性名就叫“id”,则该注解可以省略,否则必须写
     */
    @Id
    private String id;//主键

    /**
     * 该属性对应mongodb的字段的名字,如果一致,则无需该注解
     */
    @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;


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getPublishtime() {
        return publishtime;
    }

    public void setPublishtime(Date publishtime) {
        this.publishtime = publishtime;
    }

    public String getUserid() {
        return userid;
    }

    public void setUserid(String userid) {
        this.userid = userid;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public LocalDateTime getCreatedatetime() {
        return createdatetime;
    }

    public void setCreatedatetime(LocalDateTime createdatetime) {
        this.createdatetime = createdatetime;
    }

    public Integer getLikenum() {
        return likenum;
    }

    public void setLikenum(Integer likenum) {
        this.likenum = likenum;
    }

    public Integer getReplynum() {
        return replynum;
    }

    public void setReplynum(Integer replynum) {
        this.replynum = replynum;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getParentid() {
        return parentid;
    }

    public void setParentid(String parentid) {
        this.parentid = parentid;
    }

    public String getArticleid() {
        return articleid;
    }

    public void setArticleid(String articleid) {
        this.articleid = articleid;
    }

    @Override
    public String toString() {
        return "Comment{" +
            "id='" + id + '\'' +
            ", content='" + content + '\'' +
            ", publishtime=" + publishtime +
            ", userid='" + userid + '\'' +
            ", nickname='" + nickname + '\'' +
            ", createdatetime=" + createdatetime +
            ", likenum=" + likenum +
            ", replynum=" + replynum +
            ", state='" + state + '\'' +
            ", parentid='" + parentid + '\'' +
            ", articleid='" + articleid + '\'' +
            '}';
    }
}

新建MongoRep接口集成自MongoRepository

public interface MongoRep extends MongoRepository<Comment,String> {

    /**
     * 根据父Id分页查询
     * @param parentId
     * @param pageable
     * @return
     */
    Page<Comment> findByParentid(Integer parentId, Pageable pageable);
}

MongoRepository中定义了很多默认crud的方法直接调用即可

新建service

@Service
public class MongoService {

    @Autowired
    private MongoRep mongoRep;

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 保存一个评论
     *
     * @param comment
     */
    public void saveComment(Comment comment) {

        //如果需要自定义主键,可以在这里指定主键;如果不指定主键,MongoDB会自动生成主键
        mongoRep.save(comment);
    }

    /**
     * 更新评论
     *
     * @param comment
     */
    public void updateComment(Comment comment) {

        mongoRep.save(comment);
    }

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

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

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

    /**
     * 根据父ID分页查询
     * @param pid
     * @param page
     * @param size
     * @return
     */
    public List<Comment> findByParentid(Integer pid, Integer page, Integer size) {

        PageRequest request = PageRequest.of(page - 1, size);
        Page<Comment> rep = mongoRep.findByParentid(pid, request);
        return rep.getContent();

    }

    /**
     * 对点赞数进行递增操作
     * @param id
     */
    public void updateCommentThumbupToIncrementingOld(String id) {
        Query query = new Query(Criteria.where("_id").is(id));
        UpdateDefinition update = new Update();
        //局部更新,相当于$set
        //update.set(key,value)
        //递增$inc
        // update.inc("likenum",1);
        ((Update) update).inc("likenum", -5);
        mongoTemplate.updateFirst(query, update, Comment.class);
    }

}

六. MongoDB 集群和安全

6.1 副本集

6.1.1 简介

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

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

  1. 冗余和数据可用性

    复制提供冗余并提高数据可用性。 通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别的容错功能,以防止丢失单个数据库服务器。

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

  2. MongoDB中的复制

    副本集是一组维护相同数据集的mongod实例。 副本集包含多个数据承载节点和可选的一个仲裁节点。

    在承载数据的节点中,一个且仅一个成员被视为主节点,而其他节点被视为次要(从)节点。主节点接收所有写操作。 副本集只能有一个主要能够确认具有{w:“most”}写入关注的写入; 虽然在某些情况下,另一个mongod实例可能暂时认为自己也是主要的。主要记录其操作日志中的数据集的所有更改,即oplog。

    在这里插入图片描述

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

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

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

6.1.2 副本集的三个角色

副本集有两种类型三种角色

两种类型:
.
主节点( Primary)类型:数据操作的主要连接点,可读写。
次要(辅助、从)节点( Secondaries)类型:数据冗余备份节点,可以读或选举。

三种角色:

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

在这里插入图片描述
关于仲裁者的额外说明:

可以将额外的mongod实例添加到副本集作为仲裁者。 仲裁者不维护数据集。 仲裁者的目的是通过响应其他副本集成员的心跳和选举请求来维护副本集中的仲裁。 因为它们不存储数据集,所以仲裁器可以是提供副本集仲裁功能的好方法,其资源成本比具有数据集的全功能副本集成员更便宜。

如果您的副本集具有偶数个成员,请添加仲裁者以获得主要选举中的“大多数”投票。 仲裁者不需要专用硬件。

仲裁者将永远是仲裁者,而主要人员可能会退出并成为次要人员,而次要人员可能成为选举期间的主要人员。

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

6.1.3 副本集的创建
myrs_27017 主节点
myrs_27018 从节点
myrs_27019 仲裁者

创建主节点

建立存放数据和日志的目录

mkdir -p /root/mongodb/replica_sets/myrs_27017/log \ &
mkdir -p /root/mongodb/replica_sets/myrs_27017/data/db

新建或修改配置文件:

vi /root/mongodb/replica_sets/myrs_27017/mongod.conf
#数据库路径
dbpath=/root/mongodb/replica_sets/myrs_27017/data/db
  #日志输出文件路径
logpath=/root/mongodb/replica_sets/myrs_27017/log/mongod.log
  # PID File 的完整路径,如果没有设置,则没有PID文件
pidfilepath=/root/mongodb/replica_sets/myrs_27017/log/mongod.pid
  #错误日志采用追加模式
logappend=true
  #启用日志文件,默认启用
journal=true
  #这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置false
quiet=true
  #端口号 默认为27017
port=27017
bind_ip=0.0.0.0
#以后台方式运行进程
fork=true
#设置副本集名称
replSet=myrs

副本节点和仲裁节点以同样的方式创建并启动 用端口号区别

在这里插入图片描述
在这里插入图片描述
在bin目录下启动

./mongod -f /root/mongodb/replica_sets/myrs_27017/mongod.conf
./mongod -f /root/mongodb/replica_sets/myrs_27018/mongod.conf
./mongod -f /root/mongodb/replica_sets/myrs_27019/mongod.conf

在这里插入图片描述
初始化配置副本集和主节点

使用客户端命令连接任意一个节点,但这里尽量要连接主节点(27017节点):

./mongo -port=27017

结果,连接上之后,很多命令无法使用,,比如 show dbs 等,必须初始化副本集才行

在这里插入图片描述
使用默认的配置来初始化副本集:

rs.initiate()

在这里插入图片描述

  1. “ok”的值为1,说明创建成功。
  2. 命令行提示符发生变化,变成了一个从节点角色,此时默认不能读写。稍等片刻,回车,变成主节点。

查看副本集的配置内容

rs.config() 

在这里插入图片描述

  1. “_id” : “myrs” :副本集的配置数据存储的主键值,默认就是副本集的名字
  2. “members” :副本集成员数组,此时只有一个: “host” : “pinyoyougou-docker:27017” ,该成员不是仲裁节点: “arbiterOnly” : false ,优先级(权重值): “priority” : 1,
  3. “settings” :副本集的参数配置。

副本集配置的查看命令,本质是查询的是 system.replset 的表中的数据:

use local
show tables
db.system.replset.find()

在这里插入图片描述
查看副本集状态

myrs:PRIMARY> rs.status()

{
	"set" : "myrs",
	"date" : ISODate("2020-10-19T03:04:08.127Z"),
	"myState" : 1,
	"term" : NumberLong(1),
	"syncingTo" : "",
	"syncSourceHost" : "",
	"syncSourceId" : -1,
	"heartbeatIntervalMillis" : NumberLong(2000),
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1603076642, 1),
			"t" : NumberLong(1)
		},
		"readConcernMajorityOpTime" : {
			"ts" : Timestamp(1603076642, 1),
			"t" : NumberLong(1)
		},
		"appliedOpTime" : {
			"ts" : Timestamp(1603076642, 1),
			"t" : NumberLong(1)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1603076642, 1),
			"t" : NumberLong(1)
		}
	},
	"lastStableCheckpointTimestamp" : Timestamp(1603076622, 1),
	"members" : [
		{
			"_id" : 0,
			"name" : "pinyoyougou-docker:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 1676,
			"optime" : {
				"ts" : Timestamp(1603076642, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2020-10-19T03:04:02Z"),
			"syncingTo" : "",
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"electionTime" : Timestamp(1603075900, 2),
			"electionDate" : ISODate("2020-10-19T02:51:40Z"),
			"configVersion" : 1,
			"self" : true,
			"lastHeartbeatMessage" : ""
		}
	],
	"ok" : 1,
	"operationTime" : Timestamp(1603076642, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1603076642, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

  1. “set” : “myrs” :副本集的名字
  2. “myState” : 1:说明状态正常
  3. “members” :副本集成员数组,此时只有一个: “name” : “pinyoyougou-docker:27017” ,该成员的角色是 “stateStr” : “PRIMARY”, 该节点是健康的: “health” : 1 。

添加副本从节点

在主节点添加从节点,将其他成员加入到副本集

rs.add("192.168.83.129:27018")

在这里插入图片描述
“ok” : 1 :说明添加成功。

添加仲裁从节点

rs.addArb("192.168.83.129:27019")

在这里插入图片描述
此时查看副本集状态

myrs:PRIMARY> rs.status()
{
	"set" : "myrs",
	"date" : ISODate("2020-10-19T03:11:21.932Z"),
	"myState" : 1,
	"term" : NumberLong(1),
	"syncingTo" : "",
	"syncSourceHost" : "",
	"syncSourceId" : -1,
	"heartbeatIntervalMillis" : NumberLong(2000),
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1603077073, 2),
			"t" : NumberLong(1)
		},
		"readConcernMajorityOpTime" : {
			"ts" : Timestamp(1603077073, 2),
			"t" : NumberLong(1)
		},
		"appliedOpTime" : {
			"ts" : Timestamp(1603077073, 2),
			"t" : NumberLong(1)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1603077073, 2),
			"t" : NumberLong(1)
		}
	},
	"lastStableCheckpointTimestamp" : Timestamp(1603077042, 1),
	"members" : [
		{
			"_id" : 0,
			"name" : "pinyoyougou-docker:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 2109,
			"optime" : {
				"ts" : Timestamp(1603077073, 2),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2020-10-19T03:11:13Z"),
			"syncingTo" : "",
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"electionTime" : Timestamp(1603075900, 2),
			"electionDate" : ISODate("2020-10-19T02:51:40Z"),
			"configVersion" : 3,
			"self" : true,
			"lastHeartbeatMessage" : ""
		},
		{
			"_id" : 1,
			"name" : "192.168.83.129:27018",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 174,
			"optime" : {
				"ts" : Timestamp(1603077073, 2),
				"t" : NumberLong(1)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1603077073, 2),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2020-10-19T03:11:13Z"),
			"optimeDurableDate" : ISODate("2020-10-19T03:11:13Z"),
			"lastHeartbeat" : ISODate("2020-10-19T03:11:20.421Z"),
			"lastHeartbeatRecv" : ISODate("2020-10-19T03:11:21.827Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncingTo" : "pinyoyougou-docker:27017",
			"syncSourceHost" : "pinyoyougou-docker:27017",
			"syncSourceId" : 0,
			"infoMessage" : "",
			"configVersion" : 3
		},
		{
			"_id" : 2,
			"name" : "192.168.83.129:27019",
			"health" : 1,
			"state" : 7,
			"stateStr" : "ARBITER",
			"uptime" : 35,
			"lastHeartbeat" : ISODate("2020-10-19T03:11:20.435Z"),
			"lastHeartbeatRecv" : ISODate("2020-10-19T03:11:20.140Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncingTo" : "",
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"configVersion" : 3
		}
	],
	"ok" : 1,
	"operationTime" : Timestamp(1603077073, 2),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1603077073, 2),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

6.1.4 副本集的数据读写操作

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

首先登录主节点27017,写入和读取数据:

测试发现主节点的读写没有问题

登录从节点27018

./mongo -port=27018

在这里插入图片描述

发现,不能读取集合的数据。当前从节点只是一个备份,不是奴隶节点,无法读取数据,写当然更不行。
因为默认情况下,从节点是没有读写权限的,可以增加读的权限,但需要进行设置。

rs.slaveOk(true)

在这里插入图片描述

现在可实现了读写分离,让主插入数据,让从来读取数据。

如果要取消作为奴隶节点的读权限:

rs.slaveOk(false)

仲裁者节点,不存放任何业务数据的,可以登录查看

在这里插入图片描述
发现,只存放副本集配置等数据。

6.1.5 主节点的选举原则

MongoDB在副本集中,会自动进行主节点的选举,主节点选举的触发条件:

  1. 主节点故障
  2. 主节点网络不可达(默认心跳信息为10秒)
  3. 人工干预(rs.stepDown(600))

选举规则是根据票数来决定谁获胜:

  • 票数最高,且获得了 “大多数”成员的投票支持的节点获胜。

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

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

在获得票数的时候,优先级(priority)参数影响重大。

可以通过设置优先级(priority)来设置额外票数。优先级即权重,取值为0-1000,相当于可额外增加0-1000的票数,优先级的值越大,就越可能获得多数成员的投票(votes)数。指定较高的值可使成员更有资格成为主要成员,更低的值可使成员更不符合条件。

默认情况下,优先级的值是1

myrs:ARBITER> rs.conf()
{
	"_id" : "myrs",
	"version" : 3,
	"protocolVersion" : NumberLong(1),
	"writeConcernMajorityJournalDefault" : true,
	"members" : [
		{
			"_id" : 0,
			"host" : "pinyoyougou-docker:27017",
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 1,
			"tags" : {
				
			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		},
		{
			"_id" : 1,
			"host" : "192.168.83.129:27018",
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 1,
			"tags" : {
				
			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		},
		{
			"_id" : 2,
			"host" : "192.168.83.129:27019",
			"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("5f8cff395fb720d5b0455332")
	}
}

可以看出,主节点和副本节点的优先级各为 1,即,默认可以认为都已经有了一票。但选举节点,优先级是0,(要注意是,官方说了,选举节点的优先级必须是0,不能是别的值。即不具备选举权,但具有投票权

修改优先级

先将配置导入cfg变量 :cfg=rs.conf()
然后修改值(ID号默认从0开始):cfg.members[1].priority=2
重新加载配置:rs.reconfig(cfg)
在通过rs.conf()查看

在这里插入图片描述

6.2 副本集故障测试

6.2.1 副本节点故障测试

关闭27018副本节点:

发现,主节点和仲裁节点对27018的心跳失败。因为主节点还在,因此,没有触发投票选举。
如果此时,在主节点写入数据。再启动从节点,会发现,主节点写入的数据,会自动同步给从节点。

6.2.2 主节点故障测试

关闭27017节点

发现,从节点和仲裁节点对27017的心跳失败,当失败超过10秒,此时因为没有主节点了,会自动发起投票。

而副本节点只有27018,因此,候选人只有一个就是27018,开始投票。

27019向27018投了一票,27018本身自带一票,因此共两票,超过了“大多数”

27019是仲裁节点,没有选举权,27018不向其投票,其票数是0.
最终结果,27018成为主节点。具备读写功能。在27018写入数据查看。

再启动 27017节点,发现27017变成了从节点,27018仍保持主节点。

登录27017节点,发现是从节点了,数据自动从27018同步。
从而实现了高可用。

6.2.3 仲裁节点和主节点故障

先关掉仲裁节点27019,关掉现在的主节点27018

登录27017后,发现,27017仍然是从节点,副本集中没有主节点了,导致此时,副本集是只读状态,无法写入。

为啥不选举了?因为27017的票数,没有获得大多数,即没有大于等于2,它只有默认的一票(优先级是1)如果要触发选举,随便加入一个成员即可。

  • 如果只加入 27019仲裁节点成员,则主节点一定是27017,因为没得选了,仲裁节点不参与选举,但参与投票。
  • 如果只加入 27018节点,会发起选举。因为27017和27018都是两票,则按照谁数据新,谁当主节点。
6.2.4 仲裁节点和从节点故障

先关掉仲裁节点27019,关掉现在的副本节点27018

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

6.3 Compass 连接副本集

首先将27017的ip改的与其他2个节点ip一致

var config = rs.config();
config.members[0].host="192.168.83.129:27017";
rs.reconfig(config)

在这里插入图片描述
在这里插入图片描述

6.4 SpringDataMongoDB 连接副本集

spring.data.mongodb.uri=mongodb://192.168.83.129:27017,192.168.83.129:27018,192.168.83.129:27019/test?connect=replicaSet&slaveOk=true&replicaSet=myrs

其中:

  • slaveOk=true :开启副本节点读的功能,可实现读写分离。
  • connect=replicaSet :自动到副本集中选择读写的主机。如果slaveOK是打开的,则实现了读写分离

格式:

mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

标准的连接格式包含了多个选项(options),如下所示:

在这里插入图片描述

七.分片集群

7.1 分片概念

分片(sharding)是一种跨多台机器分布数据的方法, MongoDB使用分片来支持具有非常大的数据集和高吞吐量操作的部署。

换句话说:分片(sharding)是指将数据拆分,将其分散存在不同的机器上的过程。有时也用分区(partitioning)来表示这个概念。将数据分散到不同的机器上,不需要功能强大的大型计算机就可以储存
更多的数据,处理更多的负载。

具有大型数据集或高吞吐量应用程序的数据库系统可能会挑战单个服务器的容量。例如,高查询率会耗尽服务器的CPU容量。工作集大小大于系统的RAM会强调磁盘驱动器的I / O容量

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

垂直扩展

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

水平扩展

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

7.2 分片集群包含的组件

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

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

在这里插入图片描述
MongoDB在集合级别对数据进行分片,将集合数据分布在集群中的分片上。

7.3 分片集群架构目标

在这里插入图片描述
两个分片节点副本集(3+3)+一个配置节点副本集(3)+两个路由节点(2),共11个服务节点。

7.3.1 第一套副本集搭建

准备存放数据和日志的目录:

mkdir -p /root/mongodb/sharded_cluster/myshardrs01_27018/log \ &
mkdir -p /root/mongodb/sharded_cluster/myshardrs01_27018/data/db \ &
mkdir -p /root/mongodb/sharded_cluster/myshardrs01_27118/log \ &
mkdir -p /root/mongodb/sharded_cluster/myshardrs01_27118/data/db \ &
mkdir -p /root/mongodb/sharded_cluster/myshardrs01_27218/log \ &
mkdir -p /root/mongodb/sharded_cluster/myshardrs01_27218/data/db

新建配置文件

vi /root/mongodb/sharded_cluster/myshardrs01_27018/mongod.conf
#数据库路径
dbpath=/root/mongodb/sharded_cluster/myshardrs01_27018/data/db
  #日志输出文件路径
logpath=/root/mongodb/sharded_cluster/myshardrs01_27018/log/mongod.log
  # PID File 的完整路径,如果没有设置,则没有PID文件
pidfilepath=/root/mongodb/sharded_cluster/myshardrs01_27018/log/mongod.pid
  #错误日志采用追加模式
logappend=true
  #启用日志文件,默认启用
journal=true
  #这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置false
quiet=true
  #端口号 默认为27017
port=27018
bind_ip=0.0.0.0
#以后台方式运行进程
fork=true
#设置副本集名称
replSet=myshardrs01
  # 声明这是一个集群的分片,默认端口27018
shardsvr=true

以同样的方式 新建27118和27218的mongod.conf 区别于端口号
然后依次启动第一套副本集:一主一副本一仲裁

在这里插入图片描述
然后初始化副本集和创建主节点:

使用客户端命令连接任意一个节点,但这里尽量要连接主节点:

./mongo --port=27018  #连接主节点
rs.initiate()  #初始化副本集命令
rs.add("192.168.83.129:27118") #添加副本节点
rs.addArb("192.168.83.129:27218") #添加仲裁节点
7.3.2 第二套副本集搭建

准备存放数据和日志的目录:

mkdir -p /root/mongodb/sharded_cluster/myshardrs02_27318/log \ &
mkdir -p /root/mongodb/sharded_cluster/myshardrs02_27318/data/db \ &
mkdir -p /root/mongodb/sharded_cluster/myshardrs02_27418/log \ &
mkdir -p /root/mongodb/sharded_cluster/myshardrs02_27418/data/db \ &
mkdir -p /root/mongodb/sharded_cluster/myshardrs02_27518/log \ &
mkdir -p /root/mongodb/sharded_cluster/myshardrs02_27518/data/db

新建配置文件

vi /root/mongodb/sharded_cluster/myshardrs02_27318/mongod.conf
#数据库路径
dbpath=/root/mongodb/sharded_cluster/myshardrs02_27318/data/db
  #日志输出文件路径
logpath=/root/mongodb/sharded_cluster/myshardrs02_27318/log/mongod.log
  # PID File 的完整路径,如果没有设置,则没有PID文件
pidfilepath=/root/mongodb/sharded_cluster/myshardrs02_27318/log/mongod.pid
  #错误日志采用追加模式
logappend=true
  #启用日志文件,默认启用
journal=true
  #这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置false
quiet=true
  #端口号 默认为27017
port=27318
bind_ip=0.0.0.0
#设置副本集名称
replSet=myshardrs02
  # 声明这是一个集群的分片
shardsvr=true
  #以后台方式运行进程
fork=true

以同样的方式 新建27418和27518的mongod.conf 区别于端口号
然后依次启动第二套副本集:一主一副本一仲裁

然后初始化副本集和创建主节点:

使用客户端命令连接任意一个节点,但这里尽量要连接主节点:

./mongo --port=27318  #连接主节点
rs.initiate()  #初始化副本集命令
rs.add("192.168.83.129:27418") #添加副本节点
rs.addArb("192.168.83.129:27518") #添加仲裁节点
7.3.3 配置节点副本集的创建

准备存放数据和日志的目录:

mkdir -p /root/mongodb/sharded_cluster/myconfigrs_27019/log \ &
mkdir -p /root/mongodb/sharded_cluster/myconfigrs_27019/data/db \ &
mkdir -p /root/mongodb/sharded_cluster/myconfigrs_27119/log \ &
mkdir -p /root/mongodb/sharded_cluster/myconfigrs_27119/data/db \ &
mkdir -p /root/mongodb/sharded_cluster/myconfigrs_27219/log \ &
mkdir -p /root/mongodb/sharded_cluster/myconfigrs_27219/data/db

新建配置文件

vi /root/mongodb/sharded_cluster/myconfigrs_27019/mongod.conf
#数据库路径
dbpath=/root/mongodb/sharded_cluster/myconfigrs_27019/data/db
  #日志输出文件路径
logpath=/root/mongodb/sharded_cluster/myconfigrs_27019/log/mongod.log
  # PID File 的完整路径,如果没有设置,则没有PID文件
pidfilepath=/root/mongodb/sharded_cluster/myconfigrs_27019/log/mongod.pid
  #错误日志采用追加模式
logappend=true
  #启用日志文件,默认启用
journal=true
  #这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置false
quiet=true
  #端口号 默认为27017
port=27019
bind_ip=0.0.0.0
#设置副本集名称
replSet=myconfigrs
 # 声明这是一个集群的config服务
configsvr=true
  #以后台方式运行进程
fork=true

以同样的方式 新建27119和27219的mongod.conf 区别于端口号
启动配置副本集:一主两副本

./mongo --port=27019  #连接主节点
rs.initiate()  #初始化副本集命令
rs.add("192.168.83.129:27119") #添加副本节点
rs.add("192.168.83.129:27219") #添加副本节点
7.3.4 第一个路由节点的创建和操作

第一步:准备存放数据和日志的目录:

mkdir -p /root/mongodb/sharded_cluster/mymongos_27017/log

新建配置文件:

vi /root/mongodb/sharded_cluster/mymongos_27017/mongos.conf
  #日志输出文件路径
logpath=/root/mongodb/sharded_cluster/mymongos_27017/log/mongod.log
  # PID File 的完整路径,如果没有设置,则没有PID文件
pidfilepath=/root/mongodb/sharded_cluster/mymongos_27017/log/mongod.pid
  #错误日志采用追加模式
logappend=true
  #端口号 默认为27017
port=27017
bind_ip=0.0.0.0
  #指定配置节点副本集
configdb=myconfigrs/192.168.83.129:27019,192.168.83.129:27119,192.168.83.129:27219
  #以后台方式运行进程
fork=true

启动

./mongos -f ../sharded_cluster/mymongos_27017/mongos.conf

注意:启动路由节点用的是mongos命令

在这里插入图片描述

连接27017节点后 发现此时,写不进去数据,如果写数据会报错:

在这里插入图片描述
原因:

通过路由节点操作,现在只是连接了配置节点,还没有连接分片数据节点,因此无法写入业务数据。

7.3.5 在路由节点上进行分片配置操作

首先分别登录27018和27318节点,将副本集myshardrs01和myshardrs02的ip修改正确

通过如下命令修改

var config = rs.config();
config.members[0].host="192.168.83.129:27318";
rs.reconfig(config)

使用命令添加分片:

sh.addShard("myshardrs01/192.168.83.129:27018,192.168.83.129:27118,192.168.83.129:27218")
sh.addShard("myshardrs02/192.168.83.129:27318,192.168.83.129:27418,192.168.83.129:27518")

在这里插入图片描述

查看分片状态

mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
  	"_id" : 1,
  	"minCompatibleVersion" : 5,
  	"currentVersion" : 6,
  	"clusterId" : ObjectId("5f8e928f15635ca2d8d129a1")
  }
  shards:
        {  "_id" : "myshardrs01",  "host" : "myshardrs01/192.168.83.129:27018,192.168.83.129:27118",  "state" : 1 }
        {  "_id" : "myshardrs02",  "host" : "myshardrs02/192.168.83.129:27318,192.168.83.129: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 }

提示:如果添加分片失败,需要先手动移除分片,检查添加分片的信息的正确性后,再次添加分片。

移除分片参考(了解):

use admin
db.runCommand( { removeShard: "myshardrs02" } )

注意:如果只剩下最后一个 shard,是无法删除的,移除时会自动转移分片数据,需要一个时间过程。完成后,再次执行删除分片命令才能真正删除。

开启分片功能

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

mongos> sh.enableSharding("articledb")
{
	"ok" : 1,
	"operationTime" : Timestamp(1603184291, 5),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1603184291, 5),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

对集合分片

对集合分片,你必须使用 sh.shardCollection()方法指定集合和分片键。

语法:

sh.shardCollection(namespace, key, unique)

参数:

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

用什么字段当片键都可以,如:nickname作为片键,但一定是必填字段

分片规则一:哈希策略

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

使用nickname作为片键,根据其值的哈希值进行数据分片

mongos> sh.shardCollection("articledb.comment",{"nickname":"hashed"})
{
	"collectionsharded" : "articledb.comment",
	"collectionUUID" : UUID("8bf2d50d-7cb8-4063-b66d-aae8778eca11"),
	"ok" : 1,
	"operationTime" : Timestamp(1603185108, 29),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1603185108, 32),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

查看分片状态

mongos>sh.status()
--- Sharding Status --- 
  sharding version: {
  	"_id" : 1,
  	"minCompatibleVersion" : 5,
  	"currentVersion" : 6,
  	"clusterId" : ObjectId("5f8e928f15635ca2d8d129a1")
  }
  shards:
        {  "_id" : "myshardrs01",  "host" : "myshardrs01/192.168.83.129:27018,192.168.83.129:27118",  "state" : 1 }
        {  "_id" : "myshardrs02",  "host" : "myshardrs02/192.168.83.129:27318,192.168.83.129: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("9d058a1d-a85f-4756-a379-754539858ea2"),  "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) 

分片规则二:范围策略

对于 基于范围的分片 ,MongoDB按照片键的范围把数据分成不同部分.假设有一个数字的片键:想象一个从负无穷到正无穷的直线,每一个片键的值都在直线上画了一个点.MongoDB把这条直线划分为更短的不重叠的片段,并称之为 数据块 ,每个数据块包含了片键在一定范围内的数据.

在使用片键做范围划分的系统中,拥有”相近”片键的文档很可能存储在同一个数据块中,因此也会存储在同一个分片中.

如使用作者年龄字段作为片键分片:

sh.shardCollection("articledb.author",{"age":1})
{
	"collectionsharded" : "articledb.author",
	"collectionUUID" : UUID("35d4b1d1-6e1b-4993-a468-83a278b6d6a7"),
	"ok" : 1,
	"operationTime" : Timestamp(1603249056, 14),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1603249056, 14),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

注意的是:

  1. 一个集合只能指定一个片键,否则报错。
  2. 一旦对一个集合分片,分片键和分片值就不可改变。 如:不能给集合选择不同的分片键、不能更新分片键的值。
  3. 根据age索引进行分配数据。

基于范围的分片方式与基于哈希的分片方式性能对比:

基于范围的分片方式提供了更高效的范围查询,给定一个片键的范围,分发路由可以很简单地确定哪个数据块存储了请求需要的数据,并将请求转发到相应的分片中.

不过,基于范围的分片会导致数据在不同分片上的不均衡,有时候,带来的消极作用会大于查询性能的积极作用.比如,如果片键所在的字段是线性增长的,一定时间内的所有请求都会落到某个固定的数据块中,最终导致分布在同一个分片中.在这种情况下,一小部分分片承载了集群大部分的数据,系统并不能很好地进行扩展.

与此相比,基于哈希的分片方式以范围查询性能的损失为代价,保证了集群中数据的均衡.哈希值的随机性使数据随机分布在每个数据块中,因此也随机分布在不同分片中.但是也正由于随机性,一个范围查询很难确定应该请求哪些分片,通常为了返回需要的结果,需要请求所有分片.

如无特殊情况,一般推荐使用 Hash Sharding

而使用 _id 作为片键是一个不错的选择,因为它是必有的,你可以使用数据文档 _id 的哈希作为片键。

这个方案能够是的读和写都能够平均分布,并且它能够保证每个文档都有不同的片键所以数据块能够很精细。

似乎还是不够完美,因为这样的话对多个文档的查询必将命中所有的分片。虽说如此,这也是一种比较好的方案了。

理想化的 shard key 可以让 documents 均匀地在集群中分布:

在这里插入图片描述
显示集群的详细信息:

mongos> db.printShardingStatus()

查看均衡器是否工作(需要重新均衡时系统才会自动启动,不用管它):

mongos> sh.isBalancerRunning()
false

查看当前 Balancer状态:

mongos> sh.getBalancerState()
true

7.4 分片后插入数据测试

测试一(哈希规则):登录mongs后,向comment循环插入1000条数据做测试:

mongos> use articledb
mongos> for(var i=1;i<=1000;i++){db.comment.insert({_id:i+"",nickname:"BoBo"+i})}

提示: js的语法,因为mongo的shell是一个JavaScript的shell。

注意:从路由上插入的数据,必须包含片键,否则无法插入。

然后分别登陆两个片的主节点,统计文档数量

在这里插入图片描述
在这里插入图片描述
可以看到, 1000条数据近似均匀的分布到了2个shard上。是根据片键的哈希值分配的。

这种分配方式非常易于水平扩展:一旦数据存储需要更大空间,可以直接再增加分片即可,同时提升了性能。

使用db.comment.stats()查看单个集合的完整情况,mongos执行该命令可以查看该集合的数据分片的情况。

使用sh.status()查看本库内所有集合的分片信息。

测试二(范围规则):登录mongs后,向author循环插入20000条数据做测试:

这里我们为了方便测试将默认的数据块改小一点,数据块(chunk)没有填满,默认的数据块尺寸(chunksize)是64M,填满后才会考虑向其他片的数据块填充数据

这里改为1M

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

测试完记得改回来:

db.settings.save( { _id:"chunksize", value: 64 } )

向author中循环插入2w条数据做测试:

for(var i=1;i<=20000;i++){db.author.save({"name":"BoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBo"+i,"age":NumberInt(i%120)})}

然后分别查看2个分片里的数据

在这里插入图片描述

在这里插入图片描述
注意:要先将默认的数据块改小,再设置分片。为了测试,可以先删除集合,重新建立集合的分片策略,再插入数据测试即可。

7.5 再增加一个路由节点

第一步:准备存放数据和日志的目录:

mkdir -p /root/mongodb/sharded_cluster/mymongos_27117/log

新建配置文件:

vi /root/mongodb/sharded_cluster/mymongos_27117/mongos.conf
 #日志输出文件路径
logpath=/root/mongodb/sharded_cluster/mymongos_27117/log/mongod.log
  # PID File 的完整路径,如果没有设置,则没有PID文件
pidfilepath=/root/mongodb/sharded_cluster/mymongos_27117/log/mongod.pid
  #错误日志采用追加模式
logappend=true
  #端口号 默认为27017
port=27117
bind_ip=0.0.0.0
  #指定配置节点副本集
configdb=myconfigrs/192.168.83.129:27019,192.168.83.129:27119,192.168.83.129:27219
  #以后台方式运行进程
fork=true

启动后

使用mongo客户端登录27117,发现,第二个路由无需配置,因为分片配置都保存到了配置服务器中了

7.6 Compass 连接分片集群

在这里插入图片描述
和连接单机 mongod一样。

7.6 SpringDataMongDB 连接分片集群

#直接配置路由节点即可,多个路由节点之间用逗号隔开
spring.data.mongodb.uri=mongodb://192.168.83.129:27017,192.168.83.129:27117/articledb

通过日志发现,写入数据的时候,会选择一个路由写入

八. 安全认证

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

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

mongodb官网上说,为了能保障mongodb的安全可以做以下几个步骤:

  1. 使用新的端口,默认的27017端口如果一旦知道了ip就能连接上,不太安全。
  2. 设置mongodb的网络环境,最好将mongodb部署到公司服务器内网,这样外网是访问不到的。公司内部访问使用vpn等。
  3. 开启安全认证。认证要同时设置服务器之间的内部认证方式,同时要设置客户端连接到集群的账号密码认证方式。

相关概念:

  1. 启用访问控制:

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

  2. 角色:

    在MongoDB中通过角色对用户授予相应数据库资源的操作权限,每个角色当中的权限可以显式指定,也可以通过继承其他角色的权限,或者两都都存在的权限。

  3. 权限:

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

    • 资源(resource)包括:数据库、集合、部分集合和集群;
    • 操作(action)包括:对资源进行的增、删、改、查(CRUD)操作。在角色定义时可以包含一个或多个已存在的角色,新创建的角色会继承包含的角色所有的权限。在同一
      个数据库中,新创建角色可以继承其他角色的权限,在 admin 数据库中创建的角色可以继承在其它任意数据库中角色的权限。

关于角色权限的查看,可以通过如下命令查询:

// 查询所有角色权限(仅用户自定义角色)
> db.runCommand({ rolesInfo: 1 })
// 查询所有角色权限(包含内置角色)
> db.runCommand({ rolesInfo: 1, showBuiltinRoles: true })
 // 查询当前数据库中的某角色的权限
> db.runCommand({ rolesInfo: "<rolename>" })
// 查询其它数据库中指定的角色权限
> db.runCommand({ rolesInfo: { role: "<rolename>", db: "<database>" } }
// 查询多个角色权限
> db.runCommand(
 {
    rolesInfo: [
      "<rolename>",
     { role: "<rolename>", db: "<database>" },
      ...
   ]  
 }
)

常用的内置角色:

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

在这里插入图片描述

8.2 单实例环境

对单实例的MongoDB服务开启安全认证,这里的单实例指的是未开启副本集或分片的MongoDB实例。

8.2.1 添加用户和权限

这里创建两个管理员用户,一个是系统的超级管理员 myroot ,一个是admin库的管理用户myadmin

//切换到admin库
> use admin

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

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

//查看已经创建了的用户的情况:
> db.system.users.find()

//删除用户
> db.dropUser("myadmin")

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

1)本案例创建了两个用户,分别对应超管和专门用来管理用户的角色,事实上,你只需要一个用户即可。如果你对安全要求很高,防止超管泄漏,则不要创建超管用户。
2)和其它数据库(MySQL)一样,权限的管理都差不多一样,也是将用户和权限信息保存到数据库对应的表中。Mongodb存储所有的用户信息在admin 数据库的集合system.users中,保存用户名、密码和数据库信息。
3)如果不指定数据库,则创建的指定的权限的用户在所有的数据库上有效,如 {role:“userAdminAnyDatabase”, db:""}

认证测试

//切换到admin
> use admin
//密码输错
> db.auth("myroot","12345")
Error: Authentication failed.
0
//密码正确
> db.auth("myroot","123456")
1

创建普通用户

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

//创建(切换)将来要操作的数据库test,
> use test

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

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

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

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

首先关闭mongo服务

use admin
db.shutdownServer()

需要几个条件:

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

修改配置文件

#数据库路径
dbpath=/root/mongodb/single/data/db
#日志输出文件路径
logpath=/root/mongodb/single/log/mongod.log
#错误日志采用追加模式
logappend=true
#启用日志文件,默认启用
journal=true
#这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置false
quiet=true
#端口号 默认为27017
port=27017
bind_ip=0.0.0.0
#后台启动
fork=true
#开启认证
auth=true

然后重新启动mongo服务

./mongod -f ../single/conf/mongod.conf

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

  1. 先登录在认证

    ./mongo --port=27017
    

    这时登录进去是进行不了任何读写操作的

    在这里插入图片描述
    需要进行相关认证操作后 才能进行操作

    > use test
    switched to db test
    > db.auth("ljh","123456")
    1
    > show tables
    comment
    
  2. 连接时直接认证

    ./mongo --host 192.168.83.129 --port 27017 --authenticationDatabase admin -u myroot -p 123456
    

    - u :用户名
    - p :密码
    -- authenticationDatabase :指定连接到哪个库。当登录指定用户名密码时,必须指定对应的数据库

8.2.3 Compass 连接

在这里插入图片描述
认证连接时必须要指定此用户有权限操作对应的数据库名称

8.2.4 SpringDataMongoDB连接认证
spring.data.mongodb.uri=mongodb://ljh:123456@192.168.83.129:27017/test

8.3 副本集环境

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

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

2)使用客户端连接到mongodb集群时,开启访问授权。对于集群外部的访问。如通过可视化客户端,或者通过代码连接的时候,需要开启授权。在keyfile身份验证中,副本集中的每个mongod实例都使用keyfile的内容作为共享密码,只有具有正确密钥文件的mongod或者mongos实例可以连接到副本集。密钥文件的内容必须在6到1024个字符之间,并且在unix/linux系统中文件所有者必须有对文件至少有读的权限。

8.3.1 通过主节点添加一个管理员帐号

首先分别启动副本集3个节点

./mongod -f /root/mongodb/replica_sets/myrs_27017/mongod.conf
./mongod -f /root/mongodb/replica_sets/myrs_27018/mongod.conf
./mongod -f /root/mongodb/replica_sets/myrs_27019/mongod.conf

只需要在主节点上添加用户,副本集会自动同步。

开启认证之前,创建超管用户: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.2 创建副本集认证的key文件

第一步:生成一个key文件到当前文件夹中。

可以使用任何方法生成密钥文件。例如,以下操作使用openssl生成密码文件,然后使用chmod来更改文件权限,仅为文件所有者提供读取权限

openssl rand -base64 90 -out ./mongo.keyfile
chmod 400 ./mongo.keyfile

所有副本集节点都必须要用同一份keyfile,一般是在一台机器上生成,然后拷贝到其他机器上,且必须有读的权限,否则将来会报错

cp mongo.keyfile /root/mongodb/replica_sets/myrs_27017/
cp mongo.keyfile /root/mongodb/replica_sets/myrs_27018/
cp mongo.keyfile /root/mongodb/replica_sets/myrs_27019/

分别修改3个节点的配置文件添加如下配置

#数据库路径
dbpath=/root/mongodb/replica_sets/myrs_27019/data/db
  #日志输出文件路径
logpath=/root/mongodb/replica_sets/myrs_27019/log/mongod.log
  # PID File 的完整路径,如果没有设置,则没有PID文件
pidfilepath=/root/mongodb/replica_sets/myrs_27019/log/mongod.pid
  #错误日志采用追加模式
logappend=true
  #启用日志文件,默认启用
journal=true
  #这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置false
quiet=true
  #端口号 默认为27017
port=27019
bind_ip=0.0.0.0
#以后台方式运行进程
fork=true
#设置副本集名称
replSet=myrs
#开启认证
auth=true
#鉴权文件位置
keyFile=/root/mongodb/replica_sets/myrs_27019/mongo.keyfile

然后重新启动3个节点

登录进主节点添加普通用户

#先用管理员账号登录
#切换到admin库
use admin
#管理员账号认证
db.auth("myroot","123456")
#切换到要认证的库
use articledb
#添加普通用户
db.createUser({user: "bobo", pwd: "123456", roles: ["readWrite"]})

重新连接,使用普通用户 bobo重新登录,查看数据。这里用户建好后从节点后自动同步

8.3.3 SpringDataMongoDB 连接副本集
spring.data.mongodb.uri=mongodb://bobo:123456@192.168.83.129:27017,192.168.83.129:27018,192.168.83.129:27019/test?connect=replicaSet&slaveOk=true&replicaSet=myrs

8.4 分片集群环境

分片集群环境下的安全认证和副本集环境下基本上一样。

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

如果之前有分片集群启动着,停止分片集群的顺序如下:

依次杀死 mongos路由、配置副本集服务,分片副本集服务,从次节点开始。直到所有成员都离线。副本集杀的时候,建议先杀仲裁者,再杀副本节点,最后是主节点,以避免潜在的回滚。杀完要检查一下,避免有的没有杀掉。

其他的操作和副本集环境一样

在配置路由节点的conf时只用配置keyFile,不用配置auth,原因是,副本集加分片的安全认证需要配置两方面的,副本集各个节点之间使用内部身份验证,用于内部各个mongo实例的通信,只有相同keyfile才能相互访问。所以都要开启 keyFile

然而对于所有的mongod,才是真正的保存数据的分片。mongos只做路由,不保存数据。所以所有的mongod开启访问数据的授权auth=true。这样用户只有账号密码正确才能访问到数据

配置好后 重新启动节点

注意:
这里有个非常特别的情况,就是启动顺序。先启动配置节点,再启动分片节点,最后启动路由节点。如果先启动分片节点,会卡住,提示:

about to fork child process, waiting until server is ready for connections

然后登录任意一路由节点,创建相关的帐号和角色即可 通过mongos添加的账号信息,只会保存到配置节点的服务中,具体的数据节点不保存账号信息,因此,分片中的账号信息不涉及到同步问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值