• 首页

  • 分类

  • 归档

  • 随笔

  • 关于
h a s a k i
h a s a k i

阿波

我单手握着方向盘没跟谁比赛

05月
13

MongoDB进阶

发表于 2021-05-13 • 字数统计 13672 • 被 146 人看爆

1. 副本集

1.1 概念

  MongoDB副本集是一组维护相同数据的mongod服务,类似于mysql的主从复制,当主节点挂掉时副本自动升级为主节点,提高容灾性。并且可以利用副本节点做只读服务,实现读写分离,提高负载。

1.2 角色

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

  • 主节点:数据操作的主要节点,可读写。
  • 从节点:数据冗余备份节点,可以读和选举。

三种角色:

  • 主要成员(Primary):主节点
  • 副本成员(Replicate):从主节点复制维护相同数据的数据集,仅提供写的操作(需配置)
  • 仲裁者(Arbiter):不存数据,只用于选举

关系如图:
image.png

创建主副和仲裁节点的方式与单机并无太大区别,只需在配置文件中指定同一副本集成员的replSetName属性为相同即可。
配置文件示例:

systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/replica_sets/myrs_27018/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。	
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/replica_sets/myrs_27018/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/replica_sets/myrs_27018/log/mongod.pid"
net:
  #服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.0.2
  #bindIp
  #绑定的端口
  port: 27018
replication:
  #副本集的名称
  replSetName: myrs

创建主副和仲裁节点后(配置参见1.4),需要初始化副本集:

rs.initiate(configuration)

在主节点添加其他成员:

  1. 添加副本节点

语法:

rs.add(host, arbiterOnly)

示例:

rs.add("180.71.128,122:27017", false)

arbiterOnly用于指定是否为仲裁者,这里可以写false或省略不写

  1. 添加仲裁者

语法

rs.add(host, arbiterOnly)
or
rs.addArb(host)

可以用rs.add(host, true)指定arbiterOnly为真也可以直接addArb。

1.3 选举规则

在副本集中,会自动进行主节点的选举,触发条件包含以下情况:

  • 主节点故障
  • 主节点网络不可达(默认心跳10s)
  • 人工干预(rs.stepDown(600))

一旦触发选举,就要根据规则选主节点。
选举规则根据票数决定谁获胜:

  • 票数最高,并且获得大多数成员的投票,当节点获得N/2+1(N为副本集成员数)及以上的票数时,该节点选举成功。(成员数量不足则无法选举主节点,复制集会处于只读状态。)
  • 票数相同,且都获得了大多数成员投票时,数据最新的节点获胜,新旧数据通过oplog进行对比(操作日志)。

1.4 配置

查看副本集配置的语法:

rs.conf(configuration)

configuration:可选,如果没有配置,则使用默认主节点配置。

ps:rs.config()是该方法的别名。

配置查询示例:

myrs:PRIMARY> rs.conf()
{
    "_id":"myrs",
    "version":1,
    "protocolVersion":NumberLong(1),
    "writeConcernMajorityJournalDefault":true,
    "members":[
        {
            "_id":0,
            "host":"180.76.159.126: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("5d539bdcd6a308e600d126bb")
    }
}

说明:

  1. "_id" : "myrs" :副本集的配置数据存储的主键值,默认就是副本集的名字

  2. "members" :副本集成员数组,此时只有一个: "host" : "180.76.159.126:27017",该成员不是仲裁节点: "arbiterOnly" : false,优先级(权重值): "priority" : 1

  3. "settings" :副本集的参数配置。

事实上这个命令也是查询的表的内容,也可以通过查询local.system.replset表查看副本集配置。

2. 分片集群

2.1 概念

分片(sharding)是一种跨多机器分布数据的方法,MongoDB使用分片来支持具有非常大的数据集和高吞吐量操作的部署。分片将数据拆分,将其分散到不同机器上,不需要强大的计算机就可以存储更多的数据,处理更多的负载。

扩展知识:

具有大型数据集和高吞吐量应用程序的数据库系统会挑战单个服务器的容量,例如高查询率会耗尽服务器的cpu,工作集大小大于系统运行内存会强调磁盘i/o容量。

基于这个问题,有两种解决方案,垂直扩展和水平扩展。

垂直扩展意味着增加单个服务器的容量,例如使用更强大的cpu,增加内存或磁盘,但可用技术的局限性会限制单个机器的最高可扩展上限。

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

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

2.2 分片集群架构

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

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

下图描述了分片集群的组件交互关系:

分片集群组件

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

2.3 搭建

2.3.1 目标

搭建一套架构如下的服务
架构
两个分片存储数据,一个副本集作为配置中心,两个mongos作为路由。

2.3.2配置

  1. 分片配置
sharding:
  #分片角色
  clusterRole: shardsvr
  1. config server配置
sharding:
   clusterRole: configsvr
  1. mongos配置

使用mongos.conf配置

sharding:
   #指定配置节点副本集
   configDB: myconfigrs/180.76.159.126:27019,180.76.159.126:27119,180.76.159.126:27219

正常启动mongodb,此时用mongos查询数据会报错,因为只连接了config server,但未关联到分片

2.3.3 配置分片

  1. 在路由节点上进行分片配置操作:
sh.addShard("ip:port")
  1. 查看分片情况:
sh.status()
  1. 为集合开启分片功能:
sh.enableSharding("库名")
sh.shardCollection("库名.集合名",{"key":1})

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

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

2.3.4 数据流向

用户通过路由存储或获取数据的流程为:

  1. config servers保存集群元数据,元数据包含集群里所有数据和组建的状态以及每个碎片的chunks list和chunks 定义的范围。
  2. mongos会缓存config server的状态,当元数据发生变化时mongos会同步更新。
  3. 用户通过mongos的路由地址查询数据。
  4. mongos根据缓存的元数据识别用户请求的数据存储所在碎片。
  5. 分片将正确结果返回给用户。

3. 安全认证

3.1 概述

默认情况下,MongoDB实例启动运行时是没有启用用户访问权限控制的,也就是说,只要知道了MongoDB的服务ip及端口,就可以随意连接操作数据,这是非常危险的!

官方给出的安全保障方案有三点:

  • 使用新的端口,默认端口27017一旦知道ip就能连接上,很不安全
  • 设置网络环境,将mongodb置于内网环境,这样外网是无法访问的
  • 开启安全认证,认证要同时设置服务器之间的内部认证方式,同时要设置客户端连接到集群的账号密码认证方式。

3.2 启用安全认证

启用安全认证有两种方式:

1. 参数方式

/usr/local/mongodb/bin/mongod -f /mongodb/single/mongod.conf --auth

在启动命令中加入 --auth启用安全认证

2. 配置文件方式(推荐)

在mongod.conf配置文件中加入

security:
   #开启授权认证
   authorization: enabled

3.3 添加用户与权限

3.3.1 概述

MongoDB使用的是基于角色的访问控制来管理用户对实例的访问,通过对用户授予一个或多个角色来控制用户访问数据库资源的权限和数据库操作的权限,在对用户分配角色前,用户无法访问实例。

3.3.2 角色管理

在mongodb中已经内置了许多角色,角色及角色权限查看可通过如下命令:

//查询所有自定义角色权限
db.runCommand({roleInfo:1})
//查询包含内置角色所有角色权限
db.runCommand({roleInfo:1, showBuiltinRoles:true})
//查询当前数据库的某个角色的权限
db.runCommand({roleInfo:"<roleName>"})
//查询其他数据库中指定的角色权限
db.runCommand({roleInfo:{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

角色说明:

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

3.3.3 用户&权限管理

创建用户并赋予角色

  1. 语法
use admin
db.createUser({user:"name",pwd:"123456",roles:[{role:"roleName","db":"dbName"},{...}]})
  1. 认证验证
db.auth("userName","pwd")

这里要注意的是,如果用户只有某一个库的权限,则需要switch到该库再进行认证。

3.3.4 副本集认证方式

副本集与单实例认证方式无二,并且成员之间需要通过keyfile的方式进行验证。

3.3.4.1 生成keyfile文件

可以使用任何方法生成密钥文件,例如openssl:

openssl rand -base64 90 -out ./mongo.keyfile
chmod 400 ./mongo.keyfile
3.3.4.2 修改配置文件制定keyfile

编辑所有组件的mongod.conf文件,添加内容:

security:
   #KeyFile鉴权文件
   keyFile: /mongodb/replica_sets/myrs_27017/mongo.keyfile
   #开启认证方式运行
   authorization: enabled

3.3.4 分片集群认证方式

分片集群认证方式与副本集基本一样,所有组件成员都通过同一keyfile进行验证。

4. Spring Data实战

4.1 搭建项目

使用Spring Initializr搭建默认springboot项目

4.2 依赖

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

4.3 配置连接

# MongoDB 连接配置
spring:
  data:
    mongodb:
      database: repodb
      host: 10.12.21.40
      port: 27017

4.4 代码&测试

entity注意@Document(collection = "face_meta")指明集合,@Field("person_id")指明字段名,如果字段名相同可以不指定。

@Data
@Document(collection = "face_meta")
public class FaceVo implements Serializable {
    private String id;
    private String name;
    @Field("person_id")
    private String personId;
    @Field("face_image_uri")
    private String faceImageUri;
    private BsonTimestamp timestamp;

    @Override
    public String toString() {
        return "FaceVo{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", personId='" + personId + '\'' +
                ", faceImageUri='" + faceImageUri + '\'' +