MongoDB集群模式详解与Docker搭建教程

1,111 阅读37分钟

MongoDB作为一款流行的非关系型数据库,由于其高性能、可扩展性好,被广泛使用。今天就来探讨一下MongoDB的主要集群模式,以及如何使用Docker搭建MongoDB集群。

在MongoDB中,官方给出了两种集群模式:副本集(Replica Set)模式和分片(Sharding)模式。这两种集群模式各有优劣,适用于不同的应用场景。

1,副本集模式概述

(1) 基本概念

副本集模式集群是由多个MongoDB节点(运行着mongod进程的节点)构成的集群,其中每一个节点都保存一份完整的数据,也就是说副本集模式是通过数据冗余的方式来提高可用性的。

(2) 节点角色构成

在副本集模式集群中,节点的角色类型分为下面几种:

  • 主节点(Primary):主节点是在整个集群中唯一的接收写操作(增、删、改)操作的节点,同时它也会接收读操作
  • 从节点(Secondaries):从节点在集群中仅仅接收读操作,并且它们会从主节点那里复制全部数据到自身来
  • 仲裁节点(Arbiter):仲裁节点是可选的节点,可以不需要,仲裁节点是不会存放任何数据的,它负责在主节点宕机时,在从节点中选举出一个新的可用的主节点出来,如果说仲裁节点不存在,那么主节点宕机时,从节点之间也会相互进行选举

可见,主节点和从节点是集群中必要的组成角色,客户端发起的写操作(增删改)全部由主节点处理,而读操作(查)可以是主节点处理,也可以是从节点处理。可见副本集模式的集群能够提升数据查询需求的抗压能力。

image.png

(3) 从节点的特殊类型

事实上,除了上述提到的普通的从节点,我们还可以将从节点设定为下列几种特殊的类型,适用于特殊场景:

  • 0优先级节点(Priority 0 Replica Set Member):该节点不会被选举成为主节点,通常用于作为数据备份节点使用
  • 隐藏节点(Hidden Replica Set Member):该节点对客户端是不可见的,也就是说客户端无法从隐藏节点那里进行查询操作,并且隐藏节点也不会被选举成为主节点
  • 延迟节点(Delay Replica Set Member):延迟节点会被配置一定的复制延迟,即它们会延迟一定时间再和主节点进行同步,这样可以防止一些人为错误或数据误删除的传播

在后续我们再来进一步地学习如何配置节点类型。

(4) 读取优先级

默认情况下,客户端进行查询操作时,也是从主节点读取数据的,不过事实上客户端可以设定多种读取数据优先级,比如说可以设定优先到从节点查询数据等等。

目前有下列读取优先级:

  • primary 默认设定,全部从主节点进行查询
  • primaryPreferred 如果主节点可用则从主节点查询数据,否则到从节点查询
  • secondary 全部到从节点进行查询操作
  • secondaryPreferred 查询操作主要到从节点进行,如果说集群中只有一个节点,并且该节点是主节点,那么就从这个主节点进行查询操作
  • nearest 根据集群中每个节点的连接延迟,挑选一个延迟最小的节点进行数据查询操作,无论该节点是主节点还是从节点

读取优先级通常是在MongoDB的驱动中进行设定的,例如在Java中集成MongoDB时进行配置,具体配置方式大家可以查看官方文档。

(5) 集群架构

一个集群的架构组成,也是会影响这个集群的可用性的,一个标准的副本集集群通常是三节点构成,这样可以避免集群过于复杂,也能够保证可用性,当然具体结构仍然需要依据我们的程序具体需求而定。

通常,三节点的副本集集群可以是如下构成:

  • PSS方式:集群由1个主节点和2个从节点构成
  • PSA方式:集群由1个主节点、1个从节点和1个仲裁节点构成

在副本集模式集群中,一个集群最多由50个节点组成,但是这其中只能有最多7个投票节点。

2,分片模式概述

(1) 基本概念

与副本集模式不同,分片模式是一种跨多机器的、分布式的数据储存方案,对于超大数据集以及高并发的读取需求,分片模式集群更好。

在分片模式下,全部数据集被划分成多个部分(片),每个片存储一部分数据,每个分片部署在独立的服务器上,这些服务器共同组成一个分片集群,也就是说分片模式集群中每一个分片保存一部分数据,那么整个集群的数据加起来就是全部数据。

(2) 节点角色构成

在分片模式集群中,节点由下列角色构成:

  • 分片节点(Shard):也就是存放数据的节点,每个分片包含一部分数据的子集,需要注意的是从MongoDB 3.6版本开始,分片必须部署为副本集的形式
  • 路由节点(Mongos):路由节点充当一个类似网关的角色,它接收由客户端发起的写入和查询请求,客户端通常连接路由节点进行写入或者查询,然后路由节点再将这些操作转发给分片节点
  • 配置节点(Config Server):配置节点是用于储存集群元数据的节点,并且用于管理一个集群的配置信息,需要注意的是从MongoDB 3.4版本开始,配置服务器必须部署为副本集的形式

image.png

可见在分片模式中,每一个分片保存一部分数据,而一个分片又是由一个副本集集群构成的,可见分片模式集群需要配合副本集集群一起使用。

而此时客户端需要连接到路由节点进行增删改查操作,路由节点可以把客户端的请求转发给对应的分片节点。

(3) 分片键的概念和类型

既然一个Shard节点保存一部分数据,那么这些数据是怎么分布的呢?或者说如何决定一个节点保存哪一部分的数据呢?这就需要用到分片键了!

在MongoDB的分片集群中,分片键(Shard Key)是用于将数据水平划分到不同分片上的字段,这个字段的值确定了数据属于哪个分片,选择一个合适的分片键对于分片集群的性能和可伸缩性至关重要。

image.png

就例如上图中,我们指定了一个集合的分片键是字段x,那么MongoDB就根据字段x的值为依据,将数据划分到不同的对应的分片节点上去。

需要注意的是,分片键必须是一个建立了索引的字段,当我们指定分片键时:

  • 如果集合为空(数据库中该Collection没数据),那么设定分片键时就会自动给该字段建立索引
  • 如果集合非空(数据库中该Collection已有数据),那么在将某个字段设定为分片键之前必须先将其建立索引

在一个集合中,我们必须要指定一个字段作为分片键,那么应当选择哪个字段作为分片键呢?可以遵循下列规则:

  • 单字段或复合字段: 分片键可以是单个字段,也可以是多个字段的组合,复合字段的选择应该考虑到查询的模式以及如何最好地满足系统需求
  • 选择范围: 分片键的值应该在业务上有意义,并且在查询中有很好的选择性,以便将数据均匀地分布在各个分片上,通常选择容量大的字段(如日期或范围字段)作为分片键是一个好的策略
  • 不可更改: 分片键的值最好是不可更改的,频繁更改分片键的值可能会导致数据在不同分片之间移动,影响性能
  • 数据分布: 在选择分片键时,了解数据的访问模式和分布是非常重要的,这可以通过分析查询日志、监控和性能测试来实现

假设有一个存储用户数据的分片集群,可以选择用户id作为分片键。这样,不同用户的数据将分布在不同的分片上,实现了数据的均匀分布和负载均衡。

那么MongoDB是怎么按照分片键字段的值,将数据划分到不同的分片节点上的呢?

事实上分片键有两种类型:哈希分片键(Hashed Sharding)范围型分片键(Ranged Sharding),这两种类型的分片键使用不同的方式对数据进行划分,我们可以在指定分片键字段时指定其类型。

哈希分片键可以用在单字段或者多字段上,在使用哈希分片键进行数据划分时,MongoDB会将分片键字段的值传入一个哈希函数进行计算,得到一个哈希值,然后,根据这个哈希值的范围或特定规则,MongoDB决定将该数据存储在哪个分片上

image.png

哈希分片键能够保证数据分布在分片上是相对均匀和随机的,这很适用于需要随机读写以及负载均衡的场景,因为数据的位置是不可预测的。

在使用范围分片键进行数据划分时,MongoDB会将分片键字段的全部值划分为多个连续的范围,然后将不同范围内的数据存放在不同的分片上

image.png

可见上图中,指定了分片键为集合中的x字段,那么x字段值在-75 ~ 25这个范围内的数据都会被存放在第2个分片节点中。

可见与哈希分片键划分方式不同,范围分片键划分的数据是整体有序的,这更加适用于需要按照范围查询或者是按照顺序访问的场景。

3,副本集集群的搭建

在搭建集群之前,首先确保我们的电脑或者服务器上已经安装完成了Docker容器引擎,这里就不再赘述怎么安装配置Docker了!然后先拉取MongoDB最新镜像:

docker pull mongo

除此之外,我们还需要在自己的电脑上安装Mongo Shell以连接数据库并配置集群,这是一个命令行工具,首先去官网下载,下载完成后,得到一个压缩文件,解压到一个目录,然后将其中bin文件夹所在的路径,添加到环境变量Path中即可。

打开cmd或者其它命令行终端,输入下列命令检测Mongo Shell是否配置成功:

mongosh --version

输出版本号即为配置成功。

接下来我们就在本地搭建一个三节点的MongoDB副本集集群为例,开始进行配置。我们在本地搭建的三个节点集群信息如下:

节点名地址容器内MongoDB端口容器内MongoDB端口映射至宿主机的端口(外部访问)
node-1node-12701725000
node-2node-22701725001
node-3node-32701725002

由于我们是本地搭建集群,因此我们需要创建一个Docker的网络,并使得这三个容器都位于这个网络下,使得它们可以相互访问,因此上述地址使用容器名表示。

而在公网服务器部署时,请将地址替换为服务器外网地址,并确保服务器之间可以两两访问对应的端口。

单个MongoDB节点也是可以构成一个副本集集群的。

(1) 为节点创建数据卷

我们需要为每个节点先创建数据卷,使得我们可以通过数据卷持久化其数据,以及修改其配置文件:

# 节点1的数据卷
docker volume create config-1
docker volume create data-1
docker volume create cluster-1

# 节点2的数据卷
docker volume create config-2
docker volume create data-2
docker volume create cluster-2

# 节点3的数据卷
docker volume create config-3
docker volume create data-3
docker volume create cluster-3

(2) 创建集群配置文件

在每个节点对应的配置数据卷(上述config-1config-2config-3)的文件夹中创建一个配置文件config.yaml,内容如下:

replication:
  replSetName: "my-cluster-0"
net:
  bindIpAll: true

上述配置中:

  • replication.replSetName 表示集群名称,一个集群中所有节点应当配置为同一个集群名称
  • net.bindIpAll 设定为true使得MongoDB监听并接受所有的IP地址

需要注意的是,默认情况下MongoDB是只会监听并接受来自本机localhost的地址的,所以上述配置了net.bindIpAlltrue使得集群节点之间可以相互交换数据,如果你只是想使得MongoDB监听并接受指定的IP地址,那么就使用下列配置:

net:
  bindIp: 地址1, 地址2...

多个IP地址之间使用英文逗号隔开。

为什么在平时在Docker容器中部署MongoDB时,即使不设定net.bindIpAll也能够接受来自任何来源的请求呢?这是由于Docker网络和MongoDB的默认配置相互作用的结果,我们通常会把MongoDB的Docker容器的27017端口映射至宿主机的某个端口上,那么宿主机该端口接收到请求后会由Docker容器引擎转发给MongoDB容器,此时在容器内部的MongoDB看来,所有的请求都是从本地(容器内部)发起的,所以如果说在使用Docker部署MongoDB时,可能需要在宿主机上的防火墙做配置才能实现限制来源访问。

而在一个Docker网络中配置集群时,MongoDB还需要接收除了外部客户端的连接之外,还需要接收网络内其它MongoDB容器的请求以交换数据。

(3) 启动每个节点

按照上述表格的节点信息,我们分别启动这三个节点:

# 先创建一个网络
docker network create mongo-cluster

# 启动node-1
docker run -id --name=node-1 \
--network=mongo-cluster \
-p 25000:27017 \
-v config-1:/etc/mongo \
-v data-1:/data/db \
-v cluster-1:/data/configdb \
-e LANG=C.UTF-8 \
mongo mongod -f /etc/mongo/config.yaml

# 启动node-2
docker run -id --name=node-2 \
--network=mongo-cluster \
-p 25001:27017 \
-v config-2:/etc/mongo \
-v data-2:/data/db \
-v cluster-2:/data/configdb \
-e LANG=C.UTF-8 \
mongo mongod -f /etc/mongo/config.yaml

# 启动node-3
docker run -id --name=node-3 \
--network=mongo-cluster \
-p 25002:27017 \
-v config-3:/etc/mongo \
-v data-3:/data/db \
-v cluster-3:/data/configdb \
-e LANG=C.UTF-8 \
mongo mongod -f /etc/mongo/config.yaml

上述我们创建了三个容器,并分别设定了它们的数据卷,以及映射到不同的宿主机端口。

如果说在Windows系统上使用Git Bash终端启动容器失败,报错找不到文件,则先执行下列命令禁止Git Bash自动转换路径

export MSYS_NO_PATHCONV=1

然后再执行上述创建容器命令。

(4) 将节点连结为集群

使用mongosh命令连接第一个节点:

mongosh --host 127.0.0.1 --port 25000

如下图连接成功:

image.png

然后执行rs.initiate函数初始化集群:

rs.initiate({
	_id: 'my-cluster-0',
	members: [{
			_id: 0,
			host: 'node-1:27017'
		},
		{
			_id: 1,
			host: 'node-2:27017'
		},
		{
			_id: 2,
			host: 'node-3:27017'
		}
	]
});

出现下列消息则为成功:

image.png

到此,集群就创建成功了!执行rs.conf()可以查看当前集群信息,使用rs.status()命令即可查看集群状态,以及查看哪个是主节点。

可见在rs.initiate传入的参数是一个大的对象,其中_id属性表示集群名称,需要和前面配置文件中的集群名称一致!然后members是一个数组,里面每个对象表示集群的每个节点的地址和idid0开始自增就行,而这个地址是表示集群节点之间相互访问的地址,在本示例中,我们将三个容器全部放在了一个Docker网络中,因此地址配置为容器名和MongoDB本身端口。

为什么不能配置为127.0.0.1:映射到宿主机端口号呢?因为容器内的MongoDB在和其它节点通信时,如果去访问127.0.0.1则是访问的容器内部的自己,而并非是宿主机

如果说我们是在外网的三台服务器上分别部署的集群呢?那就需要设定其中的地址为每个服务器的外网地址了,端口就是对应容器映射到宿主机上的端口了

比如说现有三台位于外网的服务器,三台服务器上分别使用Docker部署的MongoDB:

节点名地址容器内MongoDB端口容器内MongoDB端口映射至宿主机的端口(外部访问)
node-1node-1.swsk33-mcs.top2701725000
node-2node-2.swsk33-mcs.top2701725001
node-3node-3.swsk33-mcs.top2701725002

那么创建集群时,就需要在Mongo Shell中执行下列指令:

rs.initiate({
	_id: 'my-cluster-0',
	members: [{
			_id: 0,
			host: 'node-1.swsk33-mcs.top:25000'
		},
		{
			_id: 1,
			host: 'node-2.swsk33-mcs.top:25001'
		},
		{
			_id: 2,
			host: 'node-3.swsk33-mcs.top:25002'
		}
	]
});

可见,在指令中指定的集群的地址时,只要知道这个地址是用于集群节点之间相互交互的即可,也就是说集群节点之间需要能够通过该地址相互访问的到。

在外网服务器上使用Docker部署MongoDB集群时,无需配置net.bindIpAlltrue,只需要将27017映射至宿主机的某个端口且外网可以访问就行。

4,分片集群搭建

我们同样还是在本地搭建一个分片集群,其中包含如下部分:

节点/集群类型节点个数
分片12
分片22
配置集群2
路由节点1

总的来说表示如下图:

image.png

分片1集群中的节点信息如下:

节点名节点地址容器内MongoDB端口容器内MongoDB端口映射至宿主机的端口(外部访问)
shard-1-1shard-1-12701825001
shard-1-2shard-1-22701825002

分片2集群中的节点信息如下:

节点名节点地址容器内MongoDB端口容器内MongoDB端口映射至宿主机的端口(外部访问)
shard-2-1shard-2-12701825003
shard-2-2shard-2-22701825004

配置集群中节点信息如下:

节点名节点地址容器内MongoDB端口容器内MongoDB端口映射至宿主机的端口(外部访问)
config-1config-12701926001
config-2config-22701926002

需要注意的是:

  • 当节点以分片模式启动,作为分片节点时,默认端口号就变成了27018,除非你手动配置指定了端口号
  • 当节点以分片模式启动,作为配置节点时,默认端口号就变成了27019,除非你手动配置指定了端口号
  • 上述全部节点仍然是全部位于同一个Docker网络mongo-cluster下,如果是在公网部署,那么必须保证全部节点能够相互之间两两访问

下面,我们将创建每个集群,并使得它们构成整个分片集群,其中对于分片模式集群来说,分片节点配置节点也是以一个副本集集群的形式存在的,对于单个副本集集群创建上面已经提及,这里只简单讲解其过程。

(1) 搭建配置集群

首先创建每个节点对应的数据卷:

# config-1的数据卷
docker volume create c1-config
docker volume create c1-data
docker volume create c1-cluster

# config-2的数据卷
docker volume create c2-config
docker volume create c2-data
docker volume create c2-cluster

然后在每个节点对应的配置文件数据卷(上述c1-configc2-config)文件夹中创建配置文件config.yaml,内容如下:

sharding:
  clusterRole: "configsvr"
replication:
  replSetName: "config-cluster-0"
net:
  bindIpAll: true

可见这里多了个配置sharding.clusterRole,我们设定为configsvr表示该集群中的节点作为分片模式集群中的配置节点

然后创建并启动这两个容器:

# 启动config-1
docker run -id --name=config-1 \
--network=mongo-cluster \
-p 26001:27019 \
-v c1-config:/etc/mongo \
-v c1-data:/data/db \
-v c1-cluster:/data/configdb \
-e LANG=C.UTF-8 \
mongo mongod -f /etc/mongo/config.yaml

# 启动config-2
docker run -id --name=config-2 \
--network=mongo-cluster \
-p 26002:27019 \
-v c2-config:/etc/mongo \
-v c2-data:/data/db \
-v c2-cluster:/data/configdb \
-e LANG=C.UTF-8 \
mongo mongod -f /etc/mongo/config.yaml

然后使用Mongo Shell连接一个节点:

mongosh --host 127.0.0.1 --port 26001

然后执行下列语句初始化集群:

rs.initiate({
	_id: 'config-cluster-0',
	members: [{
			_id: 0,
			host: 'config-1:27019'
		},
		{
			_id: 1,
			host: 'config-2:27019'
		}
	]
});

到此,配置节点集群搭建完毕!

可见这里搭建一个配置节点副本集集群和上述单纯搭建副本集集群步骤是一样的,注意事项也是一样的,只不过默认端口号不一样而已。

(2) 搭建两个分片集群

同样地,先创建所有节点的数据卷:

# 分片1集群
# shard-1-1的数据卷
docker volume create s11-config
docker volume create s11-data
docker volume create s11-cluster

# shard-1-2的数据卷
docker volume create s12-config
docker volume create s12-data
docker volume create s12-cluster

# 分片2集群
# shard-2-1的数据卷
docker volume create s21-config
docker volume create s21-data
docker volume create s21-cluster

# shard-2-2的数据卷
docker volume create s22-config
docker volume create s22-data
docker volume create s22-cluster

然后对于分片1中两个节点对应的数据卷(s11-configs12-config)文件夹中,创建下列配置文件:

sharding:
  clusterRole: "shardsvr"
replication:
  replSetName: "shard-cluster-1"
net:
  bindIpAll: true

然后对于分片2中两个节点对应的数据卷(s21-configs22-config)文件夹中,创建下列配置文件:

sharding:
  clusterRole: "shardsvr"
replication:
  replSetName: "shard-cluster-2"
net:
  bindIpAll: true

可见对于分片集群中节点类型就要设定为shardsvr了!

然后启动上述全部节点:

# 分片1集群的节点
# 节点shard-1-1
docker run -id --name=shard-1-1 \
--network=mongo-cluster \
-p 25001:27018 \
-v s11-config:/etc/mongo \
-v s11-data:/data/db \
-v s11-cluster:/data/configdb \
-e LANG=C.UTF-8 \
mongo mongod -f /etc/mongo/config.yaml

# 节点shard-1-2
docker run -id --name=shard-1-2 \
--network=mongo-cluster \
-p 25002:27018 \
-v s12-config:/etc/mongo \
-v s12-data:/data/db \
-v s12-cluster:/data/configdb \
-e LANG=C.UTF-8 \
mongo mongod -f /etc/mongo/config.yaml

# 分片2集群的节点
# 节点shard-2-1
docker run -id --name=shard-2-1 \
--network=mongo-cluster \
-p 25003:27018 \
-v s21-config:/etc/mongo \
-v s21-data:/data/db \
-v s21-cluster:/data/configdb \
-e LANG=C.UTF-8 \
mongo mongod -f /etc/mongo/config.yaml

# 节点shard-2-2
docker run -id --name=shard-2-2 \
--network=mongo-cluster \
-p 25004:27018 \
-v s22-config:/etc/mongo \
-v s22-data:/data/db \
-v s22-cluster:/data/configdb \
-e LANG=C.UTF-8 \
mongo mongod -f /etc/mongo/config.yaml

然后先使用Mongo Shell连接分片1中的任意一个节点,初始化分片1集群:

mongosh --host 127.0.0.1 --port 25001

执行下列指令:

rs.initiate({
	_id: 'shard-cluster-1',
	members: [{
			_id: 0,
			host: 'shard-1-1:27018'
		},
		{
			_id: 1,
			host: 'shard-1-2:27018'
		}
	]
});

初始化完成输入exit退出,然后连接分片2中任意一个节点,初始化分片2集群:

mongosh --host 127.0.0.1 --port 25003

执行下列指令:

rs.initiate({
	_id: 'shard-cluster-2',
	members: [{
			_id: 0,
			host: 'shard-2-1:27018'
		},
		{
			_id: 1,
			host: 'shard-2-2:27018'
		}
	]
});

到此,两个分片集群搭建完毕!

(3) 搭建路由节点

我们还是先创建一个数据卷,存放路由节点的配置文件:

docker volume create router-config

在数据卷对应文件夹中,创建配置文件config.yaml,内容如下:

sharding:
  configDB: "config-cluster-0/config-1:27019,config-2:27019"
net:
  bindIpAll: true

可见这里需要配置sharding.configDB选项指定你的配置节点集群名称及其其中节点地址,其配置值形式为:配置集群名称/配置节点地址1:端口1,配置节点地址2:端口2,...

然后创建容器:

docker run -id --name=router \
--network=mongo-cluster \
-p 25000:27017 \
-v router-config:/etc/mongo \
-e LANG=C.UTF-8 \
mongo mongos -f /etc/mongo/config.yaml

可见启动路由节点时,使用的是mongos命令而非mongod命令,并且路由节点的端口号默认也是27017,我们把它映射到了25000端口。

现在,使用Mongo Shell连接路由节点,将之前的两个分片集群加入到路由列表:

mongosh --host 127.0.0.1 --port 25000

执行下列指令:

sh.addShard('shard-cluster-1/shard-1-1:27018,shard-1-2:27018');
sh.addShard('shard-cluster-2/shard-2-1:27018,shard-2-2:27018');

可见我们使用addShard方法添加分片集群到路由列表中,其中分片集群表示方式如下:分片集群名/分片集群节点地址1:端口1,分片集群节点地址2:端口2...

需要注意的是,这里无论是在配置文件中写的配置集群节点地址和端口,还是这里添加分片时设定的分片集群节点地址端口,都是用于节点之间互相交流用的。因此将节点部署在公网服务器上时,我们需要指定其中的地址为对应的服务器外网IP以及容器映射到的宿主机的端口。

到此,一个分片集群搭建完成!需要注意的是,在分片集群中客户端需要连接到路由节点进行增删改查以及数据库管理操作!如果直接连接到分片节点进行操作,会导致数据混乱。

需要注意的是,虽然分片节点和配置节点都是以(副本集)集群形式存在,但是路由节点(mongos节点)通常则不是以路由形式存在,每个mongos实例通常视为独立的路由节点,而不是一个整体集群,这是因为mongos节点的主要责任是路由客户端请求到正确的分片上,而不涉及像数据存储和分片平衡等任务。

虽然mongos节点本身通常不以集群形式存在,但可以在架构中部署多个mongos节点以实现负载均衡和高可用性。客户端可以通过连接到不同的mongos节点来分发负载,并且如果一个mongos节点不可用,客户端可以切换到另一个可用的mongos节点,这种部署方式有助于提高整个分片集群的可用性和性能。

5,副本集模式集群的维护

在部署完副本集模式的集群之后,我们还可以对集群中节点进行修改、移除、添加等操作。

假设现有一个现成的副本集集群:

节点名地址容器内MongoDB端口容器内MongoDB端口映射至宿主机的端口(外部访问)类型
node-1node-12701727001
node-2node-22701727002
node-3node-32701727003

我们先连接至主节点即可:

mongosh --host 127.0.0.1 --port 27001

可以使用rs.status()查看每个节点是否是主节点:

image.png

(1) 增加、移除节点

假设现在需要增加一个节点作为副本集中节点,那么只需要按照上述搭建副本集集群中的步骤,再创建一个MongoDB容器,并完成配置文件编写,然后启动即可,注意配置文件中集群名称和待加入的集群名称一致。

假设我们创建了一个新的MongoDB容器,其地址为node-4,端口默认,那么连接原有集群的主节点后,执行下列指令加入新节点:

rs.add({
	host: 'node-4:27017'
});

可见使用rs.add方法即可。

除此之外,使用rs.remove还可以移除某个节点:

rs.remove('node-3:27017');

rs.remove方法中传入要移除的节点地址和端口即可。

(2) 添加一个仲裁节点

同样地我们按照上述搭建副本集集群中的步骤,再创建一个新的MongoDB容器,配置文件中也需要完成集群名称配置,然后启动,假设该仲裁节点地址为node-a,端口默认,那么连接原有集群主节点后执行下列指令:

rs.addArb('node-a:27017');

这样即可将这个节点添加进集群并作为仲裁节点。

如果说遇到报错:MongoServerError: Reconfig attempted to install a config that would change the implicit default write concern. Use the setDefaultRWConcern command to set a cluster-wide write concern and try the reconfig again.,那么就先执行下列指令:

db.adminCommand({
	"setDefaultRWConcern": 1,
	"defaultWriteConcern": {
		"w": 2
	}
});

然后再尝试执行rs.addArb指令。

执行rs.config(),查看节点的arbiterOnly属性,如果是true说明这个节点就是仲裁节点:

image.png

(3) 设定节点为0优先级节点、隐藏节点或者延迟节点

首先执行rs.conf()获取配置,并修改配置中的memberspriority属性为0即可实现修改某个节点为0优先级节点:

// 获取配置
config = rs.conf();
// 修改节点列表中第2个节点为0优先级节点
config.members[1].priority = 0;
// 刷新配置
rs.reconfig(config);

直接执行rs.conf可以查看全部配置及其中节点信息,可见MongoDB连接后的操作指令就是JavaScript语法,如果大家掌握了JavaScript事实上这些语法是非常好理解的。

如果说要设定为隐藏节点,设定节点hidden属性为true即可:

// 获取配置
config = rs.conf();
// 修改节点列表中第2个节点为隐藏节点
config.members[1].priority = 0;
config.members[1].hidden = true;
// 刷新配置
rs.reconfig(config);

需要注意的是,隐藏节点的优先级也必须是0,所以我们先设定其优先级为0然后才能设定为隐藏节点。

如果说要设定为延迟节点:

// 获取配置
config = rs.conf();
// 修改节点列表中第2个节点为隐藏节点
config.members[1].priority = 0;
config.members[1].hidden = true;
// 设定每次延迟1小时(3600秒)复制备份数据
config.members[1].secondaryDelaySecs = 3600;
// 刷新配置
rs.reconfig(config);

可见延迟节点也必须是隐藏节点。

(4) 开启认证(密码)

前面我们搭建的副本集集群是没有密码的,这在生产环境或者外网搭建时是绝对不允许的。

对于一个带认证的副本集集群,我们要配置两个主要的项目:

  • 配置副本集节点之间内部相互交换数据时的认证方式,通常使用证书密钥认证
  • 配置客户端和集群节点之间的认证方式,通常使用用户名和密码认证(RBAC认证)

那么现在,我们就将上述的集群更改为带密码认证的集群。

1. 创建超级管理员和集群管理员

首先使用Mongo Shell连接主节点,创建一个超级管理员账户和集群管理员账户:

// 切换至admin库
use admin

// 创建超级管理员
db.createUser({
	// 用户名
	user: 'root',
	// 密码
	pwd: 'wocaoOp',
	// 定义用户的角色,一个用户可以有多个角色,不同角色有不同权限
	roles: [
		// role表示角色,db表示授权的数据库,即指定的角色权限仅在授权的数据库内生效
		{
			role: 'userAdminAnyDatabase',
			db: 'admin'
		},
		{
			role: 'readWriteAnyDatabase',
			db: 'admin'
		}
	]
});

// 创建集群管理员
db.createUser({
	// 用户名
	user: 'cluster-admin',
	// 密码
	pwd: 'WanYuanShenWanDe',
	// 定义用户的角色,一个用户可以有多个角色,不同角色有不同权限
	roles: [{
		role: 'clusterAdmin',
		db: 'admin'
	}]
});

超级管理员拥有对整个数据库的库、集合等操作的全部权限,而集群管理员是拥有对集群配置的全部权限。

2. 生成证书并设置权限

现在我们需要停止整个集群全部容器,然后生成证书并重新修改配置文件。

我们使用openssl命令生成一个证书文件:

openssl rand -base64 756 > keyfile.key

这样,我们就在当前目录下生成了一个证书文件。

然后我们把这个证书复制到每个MongoDB节点容器对应的配置文件数据卷文件夹中去,注意每个都要复制,复制之后先不要修改配置,我们需要先启动容器,并使用docker exec命令进入容器修改证书权限和所属,否则会导致后续启动失败。

node-1为例,我们先启动node-1容器并进入容器:

docker start node-1
docker exec -it node-1 bash
# 此时已经进入容器终端
chown mongodb:mongodb /etc/mongo/keyfile.key
chmod 400 /etc/mongo/keyfile.key
# 退出容器
exit
# 关闭容器
docker stop node-1

可见由于我们将证书文件keyfile.key复制到了配置文件数据卷文件夹中,而上述我们的配置文件数据卷都是映射至容器内部的/etc/mongo目录,因此这里在容器中修改证书文件权限时,位置时需要是对应的证书文件在容器内部的绝对路径。

对于其它节点node-2node-3同理。

3. 修改配置文件

现在就可以修改每个节点对应的配置文件,最终如下:

replication:
  replSetName: "cluster"
net:
  bindIpAll: true
security:
  keyFile: "/etc/mongo/keyfile.key"
  authorization: enabled

可见上述security配置项两项是我们后面加上的,其中:

  • security.keyFile 表示证书文件位置,可见也要指定为证书在容器中的位置
  • security.authorization 表示打开用户名和密码认证

保证全部节点配置完成,再次重启全部节点容器即可,到此,一个带有认证的副本集集群搭建完毕!我们可以使用mongosh连接主节点后,然后通过下列命令进行认证:

use admin
db.auth('用户名', '密码');

事实上,如果你一开始就需要搭建一个带有认证机制的副本集集群,也是这个步骤,总的来说就是先创建一个不带认证的集群,然后在主节点创建超级管理员账户以及集群管理员账户,然后关闭集群配置认证,最后再启动集群。

后续你可以使用超级管理员登录主节点,创建其它用户,并分配具体数据库的权限,然后使用你的应用程序连接。我们在主节点创建账户后,账户信息也会同步到从节点上面去,毕竟账户也是存在数据库内容中的。

6,分片模式集群维护

(1) 开启认证(密码)

对于分片模式集群开启认证,事实上也就是将其中的全部副本集(分片副本集集群和配置节点副本集集群)开启证书认证即可。

1. 创建超级管理员

首先我们启动整个分片模式集群,在先开始不带认证的情况下,使用mongosh连接mongos节点并创建超级管理员和集群管理员,步骤同副本集开启认证的步骤。

使用mongosh连接路由节点(mongos节点)后,执行下列指令:

// 切换至admin库
use admin

// 创建超级管理员
db.createUser({
	// 用户名
	user: 'root',
	// 密码
	pwd: 'wocaoOp',
	// 定义用户的角色,一个用户可以有多个角色,不同角色有不同权限
	roles: [
		// role表示角色,db表示授权的数据库,即指定的角色权限仅在授权的数据库内生效
		{
			role: 'userAdminAnyDatabase',
			db: 'admin'
		},
		{
			role: 'readWriteAnyDatabase',
			db: 'admin'
		}
	]
});

// 创建集群管理员
db.createUser({
	// 用户名
	user: 'cluster-admin',
	// 密码
	pwd: 'WanYuanShenWanDe',
	// 定义用户的角色,一个用户可以有多个角色,不同角色有不同权限
	roles: [{
		role: 'clusterAdmin',
		db: 'admin'
	}]
});

这部分和之前创建账户一样。

2. 为每个分片集群节点和配置节点配置证书并修改配置

对于分片集群中的每个分片节点集群和配置节点集群,这一点和上面副本集模式开启认证的生成证书设定证书权限以及修改配置文件的操作是一模一样的,大家可以参考上面副本集的配置,这里就不再赘述了。

需要注意的是,即使是不同的分片节点集群,包括配置节点集群在内,也最好是使用同一个证书文件。

3. 为路由节点配置证书

分片节点和配置节点都配置了证书并启用了认证,因此mongos路由节点也需要和它们配置同一个证书才能够转发请求。

mongos节点配置证书的方式和上述副本集中节点配置证书的方式几乎一样,都是使用相同的方式复制证书、设定证书的权限、修改配置文件。

只不过修改配置文件时,只需要配置证书文件位置即可,即在mongos节点配置中增加如下配置:

security:
  keyFile: "/etc/mongo/keyfile.key"

也就是说不需要配置security.authorization了!

启动全部节点,最好是先启动分片集群和配置集群,最后启动路由节点。到此,整个分片集群的认证开启成功!

分片模式集群开启认证后,只需连接mongos节点,像往常一样进行认证操作即可。

(2) 设定分片键

前面我们了解过分片键,也提到了分片集群中分片键是必须的,因此在写入数据时,我们应当先创建集合并指定分片键。

首先使用mongosh命令连接mongos路由节点,完成认证后,创建数据库和集合,例如:

// 认证
use admin
db.auth('root', 'wocaoOp');

// 创建数据库和集合
use my-db
db.createCollection('User');

上述在my-db中创建了User集合,然后指定该集合的_id字段为分片键即可:

// 创建分片键需要是集群管理员进行操作
// 所以要先切换认证到集群管理员
use admin
db.auth('cluster-admin', 'WanYuanShenWanDe');

// 切换至my-db
use my-db
// 对my-db开启分片功能
sh.enableSharding('my-db');
// 指定my-db中的User集合的_id字段为分片键,类型为哈希分片键
sh.shardCollection('my-db.User', {
	'_id': 'hashed'
});

到此,就完成了对数据的分片操作。

如果说你想指定上述id字段为范围型分片键,则:

sh.shardCollection('my-db.User', {
	'_id': 1
});

如果说数据库中已存在数据,则先要给对应的字段建立索引,再进行分片操作。

(3) 扩展集群

对于分片集群扩展,事实上也并不是很难,分片集群中的每个副本集集群扩展的方式可以参考上文的副本集增加节点的方式。如果说:

  • 你在配置集群中新增了配置节点,或者是新增了一个配置副本集集群,那么你需要修改mongos节点的配置文件,在其中sharding.configDB配置添加新的配置节点的地址,并重启该节点
  • 你新增了分片副本集集群,那么就需要连接mongos节点,执行sh.addShard方法增加新的集群

7,Spring Boot连接MongoDB集群

在使用Spring Boot连接MongoDB集群时,我们通常使用Spring Data MongoDB即可。

首先加入依赖:

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

(1) 连接副本集集群

我们可以指定多个uri来实现使得Spring Boot连接到副本集集群中每个节点,在配置文件application.yml中,编写配置如下:

spring:
  data:
    mongodb:
      uri: "mongodb://用户名:密码@副本集集群节点1地址:节点1端口,副本集集群节点2地址:节点2端口,.../数据库名"

例如连接我上述配置的本地副本集:

spring:
  data:
    mongodb:
      uri: "mongodb://root:wocaoOp@127.0.0.1:27001,127.0.0.1:27002,127.0.0.1:27003/my-db"

当然,还可以配置为如下形式:

spring:
  data:
    mongodb:
      host: "节点1地址"
      port: 节点1端口
      additional-hosts:
      - "节点2地址:节点2端口"
      - "节点3地址:节点3端口"
      - ...
      database: "数据库"
      username: "用户名"
      password: "密码"

可见,按照上述形式把副本集集群中每个节点地址、端口都配置进去就行。

(2) 连接分片集群

直接配置地址为mongos节点的地址即可,如果有多个mongos节点,也可以像上面配置副本集集群一样,将全部的mongos节点地址端口都配置进去即可。

(3) 连接本地MongoDB Docker集群失败

本地连接MongoDB的Docker集群时可能会连接失败:com.mongodb.MongoSocketException: 不知道这样的主机,这是因为之前在初始化集群时,我们使用的是Docker网络中的容器名,代表每个容器的地址的,这样虽然MongoDB集群节点之前可以相互访问,但是Spring Boot以及客户端无法通过该容器名作为地址进行访问

Spring Boot在连接MongoDB集群时,会先根据我们配置的地址获取集群信息(即rs.conf()得到的信息),然后再通过集群信息中的节点地址进行连接,因此最终Spring Boot是使用的我们配置连结集群时的IP地址

解决这个问题,我们可以在连结集群时,使用host.docker.internal这个特殊的域名代替宿主机的本地地址127.0.0.1,该域名事实上指向的是Docker的桥接网关,它可以:

  • 在容器内访问host.docker.internal时,相当于访问到了宿主机(我们的电脑)
  • 在宿主机(我们的电脑)上访问host.docker.internal:映射到宿主机的端口,也可以访问到对应容器服务

建议在创建副本集集群后,连结(初始化)集群时使用host.docker.internal:映射到宿主机的端口这样的地址表示容器节点地址:

// 初始化集群
rs.initiate({
	_id: 'my-cluster',
	members: [{
			_id: 0,
			host: 'host.docker.internal:25000'
		},
		{
			_id: 1,
			host: 'host.docker.internal:25001'
		},
		{
			_id: 2,
			host: 'host.docker.internal:25002'
		}
	]
});

如果是已经创建的集群,也可以通过修改集群信息的方式修改每个节点的信息:

// 修改每个节点的IP地址
conf = rs.conf();
conf.members[0].host = 'host.docker.internal:25000';
conf.members[1].host = 'host.docker.internal:25001';
conf.members[2].host = 'host.docker.internal:25002';
rs.reconfig(conf);

如果你没有使用Docker部署MongoDB集群,或者说直接在公网部署的MongoDB集群并且使用的公网地址和Docker映射到宿主机的暴露的端口作为节点地址的话,就不会存在该问题。

8,参考文档

  • MongoDB副本集集群概述:传送门
  • MongoDB副本集集群节点类型介绍:传送门
  • MongoDB副本集集群搭建:传送门
  • 带认证机制的MongoDB副本集集群搭建:传送门
  • MongoDB分片集群概述:传送门
  • MongoDB分片集群分片键概述:传送门
  • MongoDB分片集群搭建:传送门
  • Spring Data MongoDB Starter配置文档:传送门