897 lines
29 KiB
Markdown
897 lines
29 KiB
Markdown
|
---
|
|||
|
title: MongoDB 副本集
|
|||
|
date: 2021-01-10
|
|||
|
author: ac
|
|||
|
tags:
|
|||
|
- MongoDB
|
|||
|
categories:
|
|||
|
- Database
|
|||
|
---
|
|||
|
|
|||
|
> `MongoDB`的副本集:操作、主要概念、故障转义、选举规则
|
|||
|
|
|||
|
<!-- more -->
|
|||
|
|
|||
|
## 副本集- Replica Sets
|
|||
|
|
|||
|
### 1.简介
|
|||
|
|
|||
|
`MongoDB`中的副本集(Replic Set)是一组维护相同数据集的`mongodb`服务。
|
|||
|
|
|||
|
副本集可提供冗余和高可用性,是所有生产部署的基础。也就是说,副本集类似于有自动故障恢复功能的主从集群。通俗的讲就是用多台机器进行同一数据的异步同步,从而使多台机器拥有同一数据的多个副本,并且当主库当掉时在不需要用户干预的情况下自动切换其他备份服务器做主库。而且还可以副本服务器做只读服务器,实现读写分离,提高负载。
|
|||
|
|
|||
|
- 冗余和数据可用性
|
|||
|
|
|||
|
复制提供冗余并提高数据可用性。通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别的容错功能,以防止丢失单个数据库服务器。
|
|||
|
|
|||
|
在某些情况下,复制可以提供增加的读取性能,因为客户端可以将读取操作发送到不同的服务上,在不同数据中心维护数据副本可以增加分布式应用程序的数据位置和可用性。还可以专用目的维护其他副本,例如灾难恢复,报告或备份。
|
|||
|
|
|||
|
- `MongoDB`中复制
|
|||
|
|
|||
|
副本集是一组维护相同数据集的`mongodb`实例。副本集包含多个数据承载节点和可选的一个仲裁节点。在承载数据的节点中,一个且仅一个成员被视为主节点,而其他节点被视为次要(从)节点。
|
|||
|
|
|||
|
主节点接收所有写操作。副本集只能有一个主要能够确认具有`{w:"most"}`写入关注的写入;虽然在某些情况下,另一个`mongod`实例可能暂时认为自己也是主要的。主要记录其他操作日志中的数据集的所有更改,即`oplog`。
|
|||
|
|
|||
|

|
|||
|
|
|||
|
辅助(副本)节点复制主节点的`oplog`并将操作应用于其它数据集,以使辅助节点的数据集反映主节点的数据集。如果主要人员不在,则符合条件的将举行选举以选出新的主要人员。
|
|||
|
|
|||
|
- 主从复制和副本集区别
|
|||
|
|
|||
|
主从集群和副本集最大的区别就是副本集没有固定的"主节点";整个集群会选出一个"主节点",当其挂掉后,又在剩下来的从节点中选中其他节点为"主节点",副本集总有一个活跃点(主、primary)和一个或多个备份节点(从、secondary)。
|
|||
|
|
|||
|
### 2.副本集的三个角色
|
|||
|
|
|||
|
副本集有两种类型三种角色
|
|||
|
|
|||
|
两种类型:
|
|||
|
|
|||
|
- 主节点(primary)类型:数据操作的主要连接点,可读写。
|
|||
|
- 次要(辅助、从)节点(Secondary)类型:数据冗余备份节点,可读或选举。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
三种角色:
|
|||
|
|
|||
|
主要成员(Primary):主要接收所有写操作。就是主节点。
|
|||
|
|
|||
|
副本节点(Replicate):从主节点通过复制操作以维护相同的数据集,即备份数据,不可写操作,但可以读操作(需要配置)。是默认的一种从节点类型。
|
|||
|
|
|||
|
仲裁者(Arbiter):不保留任何数据的副本,只具有投票选举作用。当然也可以将仲裁服务器维护为副本集的一部分,即副本成员也可以是仲裁者。也是一种从节点类型。
|
|||
|
|
|||
|

|
|||
|
|
|||
|
|
|||
|
|
|||
|
### 3.副本集的创建
|
|||
|
|
|||
|
根据上图配置:一主一副本一仲裁(同一个副本集名称:`myrs`)
|
|||
|
|
|||
|
在同一台机主机上搭建,可以通过端口号来区分一下。
|
|||
|
|
|||
|
#### 第一步:创建主节点
|
|||
|
|
|||
|
建立存放数据和日志的目录
|
|||
|
|
|||
|
```shell
|
|||
|
#主节点
|
|||
|
$ mkdir -p /home/qiusj/.local/mongodb/replica_sets/myrs_27017/log
|
|||
|
$ mkdir -p /home/qiusj/.local/mongodb/replica_sets/myrs_27017/data/db
|
|||
|
```
|
|||
|
|
|||
|
新建或修改配置文件:
|
|||
|
|
|||
|
```shell
|
|||
|
$ vim /home/qiusj/.local/mongodb/replica_sets/myrs_27017/mongod.conf
|
|||
|
```
|
|||
|
|
|||
|
配置信息:
|
|||
|
|
|||
|
```yml
|
|||
|
systemLog:
|
|||
|
destination: file
|
|||
|
path: "/home/qiusj/.local/mongodb/replica_sets/myrs_27017/log/mongod.log"
|
|||
|
logAppend: true
|
|||
|
storage:
|
|||
|
#mongod实例存储其数据目录,storage.dbPath设置仅适用于mongod
|
|||
|
dbPath: "/home/qiusj/.local/mongodb/replica_sets/myrs_27017/data/db"
|
|||
|
journal:
|
|||
|
#启用或禁用持久性日志以确保数据文件保持有效和可恢复
|
|||
|
enabled: true
|
|||
|
net:
|
|||
|
#添加本机在局域网内的ip,如果是云服务器,也是云服务器所在的局域网ip,不是公网ip
|
|||
|
bindIp: 127.0.0.1,192.168.0.152
|
|||
|
port: 27017
|
|||
|
setParameter:
|
|||
|
enableLocalhostAuthBypass: false
|
|||
|
processManagement:
|
|||
|
#启动在后台运行mongos或mongod进程的守护进程模式
|
|||
|
fork: true
|
|||
|
replication:
|
|||
|
#副本集名称
|
|||
|
replSetName: myrs
|
|||
|
```
|
|||
|
|
|||
|
启动节点服务:
|
|||
|
|
|||
|
```shell
|
|||
|
$ /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/replica_sets/myrs_27017/mongod.conf
|
|||
|
about to fork child process, waiting until server is ready for connections.
|
|||
|
forked process: 1731129
|
|||
|
child process started successfully, parent exiting
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#### 第二步:创建副本节点
|
|||
|
|
|||
|
建立存放数据和日志的目录
|
|||
|
|
|||
|
```shell
|
|||
|
#副本节点
|
|||
|
$ mkdir -p /home/qiusj/.local/mongodb/replica_sets/myrs_27018/log
|
|||
|
$ mkdir -p /home/qiusj/.local/mongodb/replica_sets/myrs_27018/data/db
|
|||
|
```
|
|||
|
|
|||
|
新建或修改配置文件:
|
|||
|
|
|||
|
```shell
|
|||
|
$ vim /home/qiusj/.local/mongodb/replica_sets/myrs_27018/mongod.conf
|
|||
|
```
|
|||
|
|
|||
|
配置信息:
|
|||
|
|
|||
|
```yml
|
|||
|
systemLog:
|
|||
|
destination: file
|
|||
|
path: "/home/qiusj/.local/mongodb/replica_sets/myrs_27018/log/mongod.log"
|
|||
|
logAppend: true
|
|||
|
storage:
|
|||
|
#mongod实例存储其数据目录,storage.dbPath设置仅适用于mongod
|
|||
|
dbPath: "/home/qiusj/.local/mongodb/replica_sets/myrs_27018/data/db"
|
|||
|
journal:
|
|||
|
#启用或禁用持久性日志以确保数据文件保持有效和可恢复
|
|||
|
enabled: true
|
|||
|
net:
|
|||
|
#添加本机在局域网内的ip,如果是云服务器,也是云服务器所在的局域网ip,不是公网ip
|
|||
|
bindIp: 127.0.0.1,192.168.0.152
|
|||
|
port: 27018
|
|||
|
setParameter:
|
|||
|
enableLocalhostAuthBypass: false
|
|||
|
processManagement:
|
|||
|
#启动在后台运行mongos或mongod进程的守护进程模式
|
|||
|
fork: true
|
|||
|
replication:
|
|||
|
#副本集名称
|
|||
|
replSetName: myrs
|
|||
|
```
|
|||
|
|
|||
|
启动节点服务:
|
|||
|
|
|||
|
```shell
|
|||
|
$ /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/replica_sets/myrs_27018/mongod.conf
|
|||
|
about to fork child process, waiting until server is ready for connections.
|
|||
|
forked process: 1731833
|
|||
|
child process started successfully, parent exiting
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#### 第三步:创建仲裁节点
|
|||
|
|
|||
|
建立存放数据和日志的目录
|
|||
|
|
|||
|
```shell
|
|||
|
#创建仲裁节点
|
|||
|
$ mkdir -p /home/qiusj/.local/mongodb/replica_sets/myrs_27019/log
|
|||
|
$ mkdir -p /home/qiusj/.local/mongodb/replica_sets/myrs_27019/data/db
|
|||
|
```
|
|||
|
|
|||
|
新建或修改配置文件:
|
|||
|
|
|||
|
```shell
|
|||
|
$ vim /home/qiusj/.local/mongodb/replica_sets/myrs_27019/mongod.conf
|
|||
|
```
|
|||
|
|
|||
|
配置信息:
|
|||
|
|
|||
|
```yaml
|
|||
|
systemLog:
|
|||
|
destination: file
|
|||
|
path: "/home/qiusj/.local/mongodb/replica_sets/myrs_27019/log/mongod.log"
|
|||
|
logAppend: true
|
|||
|
storage:
|
|||
|
#mongod实例存储其数据目录,storage.dbPath设置仅适用于mongod
|
|||
|
dbPath: "/home/qiusj/.local/mongodb/replica_sets/myrs_27019/data/db"
|
|||
|
journal:
|
|||
|
#启用或禁用持久性日志以确保数据文件保持有效和可恢复
|
|||
|
enabled: true
|
|||
|
net:
|
|||
|
#添加本机在局域网内的ip,如果是云服务器,也是云服务器所在的局域网ip,不是公网ip
|
|||
|
bindIp: 127.0.0.1,192.168.0.152
|
|||
|
port: 27019
|
|||
|
setParameter:
|
|||
|
enableLocalhostAuthBypass: false
|
|||
|
processManagement:
|
|||
|
#启动在后台运行mongos或mongod进程的守护进程模式
|
|||
|
fork: true
|
|||
|
replication:
|
|||
|
#副本集名称
|
|||
|
replSetName: myrs
|
|||
|
```
|
|||
|
|
|||
|
启动节点服务:
|
|||
|
|
|||
|
```shell
|
|||
|
$ /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/replica_sets/myrs_27019/mongod.conf
|
|||
|
about to fork child process, waiting until server is ready for connections.
|
|||
|
forked process: 12081
|
|||
|
child process started successfully, parent exiting
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#### 第四步:初始化配置副本集和主节点
|
|||
|
|
|||
|
使用客户端命令连接任意个节点
|
|||
|
|
|||
|
```shell
|
|||
|
$ /usr/local/mongodb/bin/mongo --host=localhost --port=27017
|
|||
|
> show db
|
|||
|
uncaught exception: Error: don't know how to show [db] :
|
|||
|
shellHelper.show@src/mongo/shell/utils.js:1191:11
|
|||
|
shellHelper@src/mongo/shell/utils.js:819:15
|
|||
|
@(shellhelp2):1:1
|
|||
|
```
|
|||
|
|
|||
|
连接上后,很多命令无法使用,必须初始化副本集才可以。
|
|||
|
|
|||
|
初始化新的副本集,语法:
|
|||
|
|
|||
|
```shell
|
|||
|
rs.initiate(configuration)
|
|||
|
```
|
|||
|
|
|||
|
`configuration`配置项参数:
|
|||
|
|
|||
|
| param | type | description |
|
|||
|
| ------------- | -------- | ----------- |
|
|||
|
| configuration | document | |
|
|||
|
|
|||
|
示例:
|
|||
|
|
|||
|
```shell
|
|||
|
> rs.initiate()
|
|||
|
{
|
|||
|
"info2" : "no configuration specified. Using a default configuration for the set",
|
|||
|
"me" : "192.168.0.152:27017",
|
|||
|
"ok" : 1,
|
|||
|
"$clusterTime" : {
|
|||
|
"clusterTime" : Timestamp(1609990491, 1),
|
|||
|
"signature" : {
|
|||
|
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
|
|||
|
"keyId" : NumberLong(0)
|
|||
|
}
|
|||
|
},
|
|||
|
"operationTime" : Timestamp(1609990491, 1)
|
|||
|
}
|
|||
|
myrs:SECONDARY>
|
|||
|
myrs:PRIMARY>
|
|||
|
```
|
|||
|
|
|||
|
开始显示是`SECONDARY`副本的状态,再按回车键后过渡到`PRIMARY`主节点的状态。
|
|||
|
|
|||
|
> “ok”的值为1,说明创建成功。
|
|||
|
>
|
|||
|
> 命令行提示符发生变化,编程一个从节点角色,此时默认不能读写。稍等片刻,回车,变成主节点。
|
|||
|
|
|||
|
#### 第五步:查看副本集的配置内容
|
|||
|
|
|||
|
`rs.conf(configuration)`:返回包含当前副本集配置的文档。
|
|||
|
|
|||
|
> `rs.config()`是该方法的别名,`configuration`为可选值,没有配置,默认使用主节点配置
|
|||
|
|
|||
|
示例:
|
|||
|
|
|||
|
```shell
|
|||
|
myrs:PRIMARY> rs.conf()
|
|||
|
{
|
|||
|
"_id" : "myrs",
|
|||
|
"version" : 1,
|
|||
|
"term" : 1,
|
|||
|
"protocolVersion" : NumberLong(1),
|
|||
|
"writeConcernMajorityJournalDefault" : true,
|
|||
|
"members" : [
|
|||
|
{
|
|||
|
"_id" : 0,
|
|||
|
"host" : "192.168.0.152: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("5ff6815b7ec759b53e444211")
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
说明:
|
|||
|
|
|||
|
1. `_id`、`myrs`是副本集的配置数据存储的主键值,默认是副本集的名称。
|
|||
|
2. `members`是副本集成员数组,此时只有一个:`"host" : "192.168.0.152:27017"`,该节点不是仲裁节点:` "arbiterOnly" : false`,优先级(权重值):` "priority" : 1`。
|
|||
|
3. `settings`:副本集的参数配置。
|
|||
|
|
|||
|
> 副本集配置的查看命令,本质是查询的是`system.replset`表中的数据。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#### 第六步:添加副本从节点
|
|||
|
|
|||
|
从主节点添加从节点,其它成员加入到副本集。
|
|||
|
|
|||
|
语法:
|
|||
|
|
|||
|
```shel
|
|||
|
rs.add(host,arbiterOnly)
|
|||
|
```
|
|||
|
|
|||
|
参数:
|
|||
|
|
|||
|
| param | type | description |
|
|||
|
| ------------- | ------------------ | ------------------------------------------------------------ |
|
|||
|
| `host` | string or document | 要添加到副本集的新成员。指定为字符串或配置文档:<br>1.如果是字符串,则需要指定新成员的主机名和可选的端口号;<br>2.如果是文档,请指定`members`数组中找到的副本集成员配置文档。必须在成员配置文档中指定主机字段。有关文档配置字段的说明,想见下方文档:“主机成员的配置文档”。 |
|
|||
|
| `arbiterOnly` | boolean | 可选的。仅在`<host>`值为字符串时适用。如果为true,则添加的主机是仲裁者,但与设置仲裁节点的`rs.addArb()`方法冲突,常选择不使用该配置项,而是使用`rs.addArb()`方法来设置仲裁节点。 |
|
|||
|
|
|||
|
主机成员的配置文档:
|
|||
|
|
|||
|
```shell
|
|||
|
{
|
|||
|
"_id" : <int>,
|
|||
|
"host" : <string>, //required
|
|||
|
"arbiterOnly" : <boolean>,
|
|||
|
"buildIndexes" : <boolean>,
|
|||
|
"hidden" : <boolean>,
|
|||
|
"priority" : <number>,
|
|||
|
"tags" : <document>,
|
|||
|
"slaveDelay" : <int>,
|
|||
|
"votes" : <int>
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
示例:
|
|||
|
|
|||
|
将27018的副本节点添加到副本集中:
|
|||
|
|
|||
|
```shell
|
|||
|
myrs:PRIMARY> rs.add("192.168.0.152:27018")
|
|||
|
{
|
|||
|
"ok" : 1,
|
|||
|
"$clusterTime" : {
|
|||
|
"clusterTime" : Timestamp(1610001544, 1),
|
|||
|
"signature" : {
|
|||
|
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
|
|||
|
"keyId" : NumberLong(0)
|
|||
|
}
|
|||
|
},
|
|||
|
"operationTime" : Timestamp(1610001544, 1)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
看到`"ok":1`说明添加成功,再次查看副本集配置:
|
|||
|
|
|||
|
```shell
|
|||
|
myrs:PRIMARY> rs.conf()
|
|||
|
{
|
|||
|
"_id" : "myrs",
|
|||
|
"version" : 2,
|
|||
|
"term" : 1,
|
|||
|
"protocolVersion" : NumberLong(1),
|
|||
|
"writeConcernMajorityJournalDefault" : true,
|
|||
|
"members" : [
|
|||
|
{
|
|||
|
"_id" : 0,
|
|||
|
"host" : "192.168.0.152:27017",
|
|||
|
"arbiterOnly" : false,
|
|||
|
"buildIndexes" : true,
|
|||
|
"hidden" : false,
|
|||
|
"priority" : 1,
|
|||
|
"tags" : {
|
|||
|
|
|||
|
},
|
|||
|
"slaveDelay" : NumberLong(0),
|
|||
|
"votes" : 1
|
|||
|
},
|
|||
|
{
|
|||
|
"_id" : 1,
|
|||
|
"host" : "192.168.0.152:27018",
|
|||
|
"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("5ff6815b7ec759b53e444211")
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### 第七步:添加仲裁节点
|
|||
|
|
|||
|
添加仲裁节点发到副本集
|
|||
|
|
|||
|
语法:
|
|||
|
|
|||
|
```shell
|
|||
|
rs.addArb(host)
|
|||
|
```
|
|||
|
|
|||
|
将27019的仲裁节点添加到副本集中:
|
|||
|
|
|||
|
```shell
|
|||
|
myrs:PRIMARY> rs.addArb("192.168.0.152:27019")
|
|||
|
{
|
|||
|
"ok" : 1,
|
|||
|
"$clusterTime" : {
|
|||
|
"clusterTime" : Timestamp(1610001879, 1),
|
|||
|
"signature" : {
|
|||
|
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
|
|||
|
"keyId" : NumberLong(0)
|
|||
|
}
|
|||
|
},
|
|||
|
"operationTime" : Timestamp(1610001879, 1)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
添加完成后,副本集的搭建完成了,再次查看副本集的状态:
|
|||
|
|
|||
|
```shell
|
|||
|
myrs:PRIMARY> rs.conf()
|
|||
|
{
|
|||
|
"_id" : "myrs",
|
|||
|
"version" : 3,
|
|||
|
"term" : 1,
|
|||
|
"protocolVersion" : NumberLong(1),
|
|||
|
"writeConcernMajorityJournalDefault" : true,
|
|||
|
"members" : [
|
|||
|
{
|
|||
|
"_id" : 0,
|
|||
|
"host" : "192.168.0.152:27017",
|
|||
|
"arbiterOnly" : false,
|
|||
|
"buildIndexes" : true,
|
|||
|
"hidden" : false,
|
|||
|
"priority" : 1,
|
|||
|
"tags" : {
|
|||
|
|
|||
|
},
|
|||
|
"slaveDelay" : NumberLong(0),
|
|||
|
"votes" : 1
|
|||
|
},
|
|||
|
{
|
|||
|
"_id" : 1,
|
|||
|
"host" : "192.168.0.152:27018",
|
|||
|
"arbiterOnly" : false,
|
|||
|
"buildIndexes" : true,
|
|||
|
"hidden" : false,
|
|||
|
"priority" : 1,
|
|||
|
"tags" : {
|
|||
|
|
|||
|
},
|
|||
|
"slaveDelay" : NumberLong(0),
|
|||
|
"votes" : 1
|
|||
|
},
|
|||
|
{
|
|||
|
"_id" : 2,
|
|||
|
"host" : "192.168.0.152: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("5ff6815b7ec759b53e444211")
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
### 4.副本集的数据读写
|
|||
|
|
|||
|
目标:测试三个不同角色的节点的数据读写情况。
|
|||
|
|
|||
|
先登陆主节点,插入数据:
|
|||
|
|
|||
|
```shell
|
|||
|
myrs:PRIMARY> db
|
|||
|
test
|
|||
|
myrs:PRIMARY> use article
|
|||
|
switched to db article
|
|||
|
myrs:PRIMARY> db.article.insert({
|
|||
|
... "articleid": "100000",
|
|||
|
... "content": "今天天气真好,阳光明 媚",
|
|||
|
... "userid": "1001",
|
|||
|
... "nickname": "Rose",
|
|||
|
... "createdatetime": new Date(),
|
|||
|
... "likenum": NumberInt(10),
|
|||
|
... "state": null
|
|||
|
... })
|
|||
|
WriteResult({ "nInserted" : 1 })
|
|||
|
myrs:PRIMARY> db.article.find()
|
|||
|
{ "_id" : ObjectId("5ff6b6b0d52bc37ba2bfe1fc"), "articleid" : "100000", "content" : "今天天气真好,阳光明 媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2021-01-07T07:22:24.298Z"), "likenum" : 10, "state" : null }
|
|||
|
```
|
|||
|
|
|||
|
再登陆从节点27018
|
|||
|
|
|||
|
```shell
|
|||
|
$ /usr/local/mongodb/bin/mongo --host=localhost --port=27018
|
|||
|
|
|||
|
myrs:SECONDARY> show dbs
|
|||
|
uncaught exception: Error: listDatabases failed:{
|
|||
|
"topologyVersion" : {
|
|||
|
"processId" : ObjectId("5ff67ae9c8665616a37a2392"),
|
|||
|
"counter" : NumberLong(4)
|
|||
|
},
|
|||
|
"operationTime" : Timestamp(1610004361, 1),
|
|||
|
"ok" : 0,
|
|||
|
"errmsg" : "not master and slaveOk=false",
|
|||
|
"code" : 13435,
|
|||
|
"codeName" : "NotPrimaryNoSecondaryOk",
|
|||
|
"$clusterTime" : {
|
|||
|
"clusterTime" : Timestamp(1610004361, 1),
|
|||
|
"signature" : {
|
|||
|
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
|
|||
|
"keyId" : NumberLong(0)
|
|||
|
}
|
|||
|
}
|
|||
|
} :
|
|||
|
```
|
|||
|
|
|||
|
查看存在的数据库发现`"not master and slaveOk=false"`说明该节点现在既不是主节点可不是"奴隶"节点,不能读取集合中的数据。当前节点只是一个备份,不是努力借点,无法读取数据,写当然也不可以。
|
|||
|
|
|||
|
> 默认情况下,从节点是没有读写权限的,可以增加读的权限,但需要进行设置。
|
|||
|
|
|||
|
设置读操作权限,语法:
|
|||
|
|
|||
|
```shell
|
|||
|
rs.slaveOk()
|
|||
|
或
|
|||
|
rs.slaveOk(true)
|
|||
|
```
|
|||
|
|
|||
|
> 该命令是`db.getMongo().setSlaveOk()`的简化命令。
|
|||
|
|
|||
|
示例:
|
|||
|
|
|||
|
```shell
|
|||
|
myrs:SECONDARY> rs.slaveOk()
|
|||
|
WARNING: slaveOk() is deprecated and may be removed in the next major release. Please use secondaryOk() instead.
|
|||
|
myrs:SECONDARY> show dbs
|
|||
|
admin 0.000GB
|
|||
|
article 0.000GB
|
|||
|
config 0.000GB
|
|||
|
local 0.000GB
|
|||
|
myrs:SECONDARY> db
|
|||
|
test
|
|||
|
myrs:SECONDARY> use article
|
|||
|
switched to db article
|
|||
|
myrs:SECONDARY> db.article.find()
|
|||
|
{ "_id" : ObjectId("5ff6b6b0d52bc37ba2bfe1fc"), "articleid" : "100000", "content" : "今天天气真好,阳光明 媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2021-01-07T07:22:24.298Z"), "likenum" : 10, "state" : null }
|
|||
|
myrs:SECONDARY> db.article.insert({
|
|||
|
... "articleid": "100000",
|
|||
|
... "content": "今天天气真好,阳光明 媚",
|
|||
|
... "userid": "1001",
|
|||
|
... "nickname": "Rose",
|
|||
|
... "createdatetime": new Date(),
|
|||
|
... "likenum": NumberInt(10),
|
|||
|
... "state": null
|
|||
|
... })
|
|||
|
WriteCommandError({
|
|||
|
"topologyVersion" : {
|
|||
|
"processId" : ObjectId("5ff67ae9c8665616a37a2392"),
|
|||
|
"counter" : NumberLong(4)
|
|||
|
},
|
|||
|
"operationTime" : Timestamp(1610005151, 1),
|
|||
|
"ok" : 0,
|
|||
|
"errmsg" : "not master",
|
|||
|
"code" : 10107,
|
|||
|
"codeName" : "NotWritablePrimary",
|
|||
|
"$clusterTime" : {
|
|||
|
"clusterTime" : Timestamp(1610005151, 1),
|
|||
|
"signature" : {
|
|||
|
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
|
|||
|
"keyId" : NumberLong(0)
|
|||
|
}
|
|||
|
}
|
|||
|
})
|
|||
|
```
|
|||
|
|
|||
|
> `slaveOk()`方法已经被弃用了,可能在下一个主要版本中移除,可以使用`secondaryOk()`代替。
|
|||
|
|
|||
|
可以看到,从节点现在有了读操作的权限,但不能写。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
仲裁者节点不存放业务数据的,放的只是一些配置信息,可以登陆查看:
|
|||
|
|
|||
|
```shell
|
|||
|
$ /usr/local/mongodb/bin/mongo --host=localhost --port=27019
|
|||
|
|
|||
|
myrs:ARBITER> show dbs
|
|||
|
uncaught exception: Error: listDatabases failed:{
|
|||
|
"topologyVersion" : {
|
|||
|
"processId" : ObjectId("5ff67be8a2f0db14fa3b8857"),
|
|||
|
"counter" : NumberLong(1)
|
|||
|
},
|
|||
|
"ok" : 0,
|
|||
|
"errmsg" : "node is not in primary or recovering state",
|
|||
|
"code" : 13436,
|
|||
|
"codeName" : "NotPrimaryOrSecondary"
|
|||
|
} :
|
|||
|
_getErrorWithCode@src/mongo/shell/utils.js:25:13
|
|||
|
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:147:19
|
|||
|
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:99:12
|
|||
|
shellHelper.show@src/mongo/shell/utils.js:937:13
|
|||
|
shellHelper@src/mongo/shell/utils.js:819:15
|
|||
|
@(shellhelp2):1:1
|
|||
|
|
|||
|
myrs:ARBITER> use local
|
|||
|
switched to db local
|
|||
|
|
|||
|
myrs:ARBITER> show collections
|
|||
|
replset.election
|
|||
|
replset.minvalid
|
|||
|
replset.oplogTruncateAfterPoint
|
|||
|
startup_log
|
|||
|
system.replset
|
|||
|
system.rollback.id
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
### 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
|
|||
|
|
|||
|
```shell
|
|||
|
myrs:PRIMARY> rs.conf()
|
|||
|
{
|
|||
|
"_id" : "myrs",
|
|||
|
"version" : 3,
|
|||
|
"term" : 1,
|
|||
|
"protocolVersion" : NumberLong(1),
|
|||
|
"writeConcernMajorityJournalDefault" : true,
|
|||
|
"members" : [
|
|||
|
{
|
|||
|
"_id" : 0,
|
|||
|
"host" : "192.168.0.152:27017",
|
|||
|
"arbiterOnly" : false,
|
|||
|
"buildIndexes" : true,
|
|||
|
"hidden" : false,
|
|||
|
"priority" : 1,
|
|||
|
"tags" : {
|
|||
|
|
|||
|
},
|
|||
|
"slaveDelay" : NumberLong(0),
|
|||
|
"votes" : 1
|
|||
|
},
|
|||
|
{
|
|||
|
"_id" : 1,
|
|||
|
"host" : "192.168.0.152:27018",
|
|||
|
"arbiterOnly" : false,
|
|||
|
"buildIndexes" : true,
|
|||
|
"hidden" : false,
|
|||
|
"priority" : 1,
|
|||
|
"tags" : {
|
|||
|
|
|||
|
},
|
|||
|
"slaveDelay" : NumberLong(0),
|
|||
|
"votes" : 1
|
|||
|
},
|
|||
|
{
|
|||
|
"_id" : 2,
|
|||
|
"host" : "192.168.0.152: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("5ff6815b7ec759b53e444211")
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
### 6.故障测试
|
|||
|
|
|||
|

|
|||
|
|
|||
|
#### 副本节点故障
|
|||
|
|
|||
|
关闭27018节点。
|
|||
|
|
|||
|
发现主节点和仲裁节点对27018的心跳失败。但因为主节点还在,因此,没有触发投票选举。
|
|||
|
|
|||
|
如果从节点故障后,在主节点插入数据,当启动从节点后,从节点会自动同步数据。
|
|||
|
|
|||
|
#### 主节点故障
|
|||
|
|
|||
|
关闭27017节点。
|
|||
|
|
|||
|
发现从节点和仲裁者节点对27017的心跳失败,当失败超过10秒,此时因为没有主节点,会自动发起投票。
|
|||
|
|
|||
|
而符本节点只有27018,因此,候选人只有一个27018,开始投票。
|
|||
|
|
|||
|
27019向27018投了一票,27018本身自带一票,因此共两票,超过了“大多数”。
|
|||
|
|
|||
|
27019是仲裁者节点,没有选举权,27018不向其投票,其票数是0.
|
|||
|
|
|||
|
最终结果,27018成为主节点,具备读写功能。
|
|||
|
|
|||
|
可以在27018 上写入数据查看。
|
|||
|
|
|||
|
27017节点再启动起来后是从节点。
|
|||
|
|
|||
|
#### 仲裁节点和主节点故障
|
|||
|
|
|||
|
先关掉仲裁节点27109,再关掉现在主节点27018。
|
|||
|
|
|||
|
登陆27017后,发现27017仍然是从节点,副本集中没有主节点,导致此时,副本集是只读状态,无法写入。
|
|||
|
|
|||
|
为啥不选举呢?因为27017的票数,没有获得大多数,即没有大于2,它只有默认的一票(优先级是1)
|
|||
|
|
|||
|
如果要触发选举,随便加入一个成员即可。
|
|||
|
|
|||
|
- 如果只加入27019仲裁借点成员,则主节点一定是27017。
|
|||
|
- 如果只加入27018节点,会发起选举。但因为27017和27018 都是两票,则按照为数据新,为当主节点。
|
|||
|
|
|||
|
#### 仲裁节点和从节点故障
|
|||
|
|
|||
|
先关掉仲裁节点27019,再关掉现在的副本节点27018.
|
|||
|
|
|||
|
10秒后27017主节点自动降级为副本节点。(服务降级)
|
|||
|
|
|||
|
副本集不可写数据,已经故障(只读状态)。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
### 7.Compass连接副本集
|
|||
|
|
|||
|
示例:
|
|||
|
|
|||
|
```javascript
|
|||
|
mongodb://mongodb0.example.com:27017,mongodb1.example.com:27017,mongodb2.example.com:27017/?replicaSet=myRepl
|
|||
|
mongodb://192.168.0.152:27017,192.168.0.152:27018,192.168.0.152:27019/?replicaSet=myrs
|
|||
|
```
|
|||
|
|
|||
|

|
|||
|
|
|||
|
### 8.使用SpringData连接副本集
|
|||
|
|
|||
|
```yaml
|
|||
|
spring:
|
|||
|
#数据源配置
|
|||
|
data:
|
|||
|
mongodb:
|
|||
|
#连接分片集群的路由节点,多个路由用逗号分隔,mongodb会有其负载均衡的策略
|
|||
|
# uri: mongodb://192.168.0.152:27017,192.168.0.152:27117/meface
|
|||
|
# host: 192.168.0.152
|
|||
|
# database: meface
|
|||
|
# port: 27017
|
|||
|
#使用uri的方式
|
|||
|
uri:mongodb://192.168.0.152:27017,192.168.0.152:27018,192.168.0.152:27019/meface?connect=replicaSet&slaveOk=true&replicaSet=myrs
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
## 参考文章
|
|||
|
|
|||
|
[1] `Replication` https://docs.mongodb.com/manual/replication/
|
|||
|
|
|||
|
[2] `Sharding` https://docs.mongodb.com/manual/sharding/
|
|||
|
|
|||
|
[3] `Sharding reference` https://docs.mongodb.com/manual/reference/sharding/
|
|||
|
|
|||
|
[4] `Security Reference` https://docs.mongodb.com/manual/reference/security/
|