共计 9054 个字符,预计需要花费 23 分钟才能阅读完成。
MongoDB 副本集
一、副本集概念
单节点的 MongoDB
在数据的安全和冗余方面是比较低的,在生产环境中,我们为 MongoDB
配置副本集,这样可以提高数据的高可用性和安全性。
副本集:是一组 Mongod
维护相同数据集的实例。副本集可以包含多个数据承载点和多个仲裁点。在承载数据的节点中,仅有一个节点被视为主节点,其他节点称为次节点。
副本集的节点角色:
- Primary 主节点,用于承担
- Secondary 次节点
- Arbiter 仲裁节点,也是属于次节点
主节点接收所有的数据写入操作,主节点记录数据的所有更改,即oplog
。
副本集架构图
- 一个 主节点 两个次节点(Secondary)
- 一个主节点两个次节点(一个 Secondary 节点,一个 Arbiter 节点)
将一个额外的 mongod
实例添加到副本集作为 仲裁节点。仲裁节点不维护数据集。仲裁节点的目的是通过响应其他副本集成员的心跳和选举请求来维护副本集中的选举。因为它们不存储数据集,所以仲裁节点可以是提供副本集仲裁功能的好方法,其资源成本比具有数据集的全功能副本集成员更低。如果您的副本集具有偶数个成员,请添加仲裁者以避免脑裂出现。
- 主节点故障后重新选举主节点
在主节点未与配置中的其它成员通信超过 10s(默认为 10s)的话,则符合条件的次节点将推选自己为主节点。
在选举成功完成之前,副本集无法处理写入操作。
electionTimeoutMillis
默认值为 10000(10s),我们可以根据自己的项目情况来升高或者降低该值,我们在更改该值的时候需要考虑到网络延迟等因素。
默认情况下,副本集在选取新的主节点的等待时间不超过 12 秒(主要用于将原有主节点标记为不可用,并选举出新的主节点),
副本集数据同步
为了保持次节点与主节点的数据同步,MongoDB 使用两种方式进行数据的同步:
-
初始同步,用于同步主节点的所有数据
初始同步将所有的数据从副本集的一个成员复制到另外一个成员
-
增量同步,在初始同步后不断复制新的数据
在初始同步后不断复制数据,次节点从主节点中同步复制 Oplog,并在异步过程中应用这些操作
Oplog 详解
MongoDB-Oplog 详解
注意
副本集在部署前需要确定成员数据,副本集最多能有 50 个节点,但是只能有 7 个节点拥有被选举权,副本集需要具有奇数个投票成员,如果有偶数个的话,可以添加一个 仲裁者,来保证有奇数个成员,避免脑裂情况发生,
尽量使用 主机名 来寻找对应的节点,而不是使用 ip 地址,避免 ip 改变导致配置需要更改。
二、副本集部署
部署准备
部署需要更改 /etc/hosts 文件,将主机名和 ip 地址对应好,不应该使用 ip。
使用统一的端口。
创建数据储存的位置和配置文件的位置。
确定好副本集的名称
搭建架构选择
-
三节点,一个主节点,一个次节点,一个仲裁节点
副本集部署
基本环境:系统 CentOS 7 | |
MongoDB 版本 4.0.8 | |
3 个节点 | |
fymongodb001 内网 IP:172.18.186.161 公网 ip: 47.112.129.2 # 主节点 | |
fymongodb002 内网 IP: 172.18.186.162 公网 ip: 47.112.131.231 # 次节点 | |
fymongodb003 内网 IP: 172.18.186.163 公网 ip: 47.112.98.64 # 仲裁节点 | |
副本集名称:fymongodb |
三个节点都需要执行
# 下载 mongodb | |
cat <<EOF >>/etc/hosts | |
172.18.186.161 fymongodb001 | |
172.18.186.162 fymongodb002 | |
172.18.186.163 fymongodb003 | |
EOF | |
cd /tmp && wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.8.tgz | |
tar -zxf mongodb-linux-x86_64-4.0.8.tgz | |
mv mongodb-linux-x86_64-4.0.8 /opt/mongodb | |
mkdir /opt/mongodb/{data,logs} | |
useradd mongodb | |
# 我们这里使用的配置文件 是 YAML 格式的 | |
wget https://raw.githubusercontent.com/tobewithyou1996/LinuxGuide/master/MongoDB/mongodb_rep.yaml -P /opt/mongodb/ | |
chown -R mongodb:mongodb /opt/mongodb/ | |
cat <<EOF >>/usr/lib/systemd/system/mongodb.service | |
[Unit] | |
Description= mongodb service manager | |
[Service] | |
# Other directives omitted | |
# (file size) | |
LimitFSIZE=infinity | |
# (cpu time) | |
LimitCPU=infinity | |
# (virtual memory size) | |
LimitAS=infinity | |
# (locked-in-memory size) | |
LimitMEMLOCK=infinity | |
# (open files) | |
LimitNOFILE=64000 | |
# (processes/threads) | |
LimitNPROC=64000 | |
Type=forking | |
User=mongodb | |
Group=mongodb | |
PIDFile=/opt/mongodb/logs/mongod.pid | |
ExecStart= /opt/mongodb/bin/mongod -f /opt/mongodb/mongodb_rep.yaml | |
ExecStop= /opt/mongodb/bin/mongod --shutdown --dbpath /opt/mongodb/data | |
Restart=always | |
[Install] | |
WantedBy=multi-user.target | |
EOF | |
# 添加环境变量 | |
echo "export PATH=$PATH:/opt/mongodb/bin" >>/etc/profile | |
source /etc/profile | |
# 启动 | |
systemctl start mongodb |
主节点操作
# 主节点操作 | |
[root@fymongodb001 tmp]# mongo | |
MongoDB shell version v4.0.8 | |
connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb | |
Implicit session: session {"id" : UUID("77f60167-1d37-4796-bf2a-60cfdc4b0526") } | |
MongoDB server version: 4.0.8 | |
> use admin # 切换到 admin 数据库 | |
switched to db admin | |
# 初始化副本集,副本集名称为 fymongodb,第一个成员为自己本身。> rs.initiate({_id:'fymongodb',members: [{ _id: 0 , host: "fymongodb001:27017"}]}) | |
{"ok" : 1} | |
fymongodb:SECONDARY> # 接着回车,直到显示这个节点为 Primary 主节点 | |
fymongodb:PRIMARY> # 接着添加 次节点 fymongodb002 | |
# 添加次节点 | |
fymongodb:PRIMARY> rs.add('fymongodb002:27017') | |
{ | |
"ok" : 1, | |
"operationTime" : Timestamp(1555663440, 1), | |
"$clusterTime" : {"clusterTime" : Timestamp(1555663440, 1), | |
"signature" : {"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), | |
"keyId" : NumberLong(0) | |
} | |
} | |
} | |
# 添加仲裁节点 | |
fymongodb:PRIMARY> rs.addArb("fymongodb003:27017") | |
{ | |
"ok" : 1, | |
"operationTime" : Timestamp(1555663631, 1), | |
"$clusterTime" : {"clusterTime" : Timestamp(1555663631, 1), | |
"signature" : {"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), | |
"keyId" : NumberLong(0) | |
} | |
} | |
} | |
# 查看当前配置 | |
fymongodb:PRIMARY> rs.conf() | |
{ | |
"_id" : "fymongodb", | |
"version" : 3, | |
"protocolVersion" : NumberLong(1), | |
"writeConcernMajorityJournalDefault" : true, | |
"members" : [ | |
{ | |
"_id" : 0, | |
"host" : "fymongodb001:27017", | |
"arbiterOnly" : false, | |
"buildIndexes" : true, | |
"hidden" : false, | |
"priority" : 1, | |
"tags" : { }, | |
"slaveDelay" : NumberLong(0), | |
"votes" : 1 | |
}, | |
{ | |
"_id" : 1, | |
"host" : "fymongodb002:27017", | |
"arbiterOnly" : false, | |
"buildIndexes" : true, | |
"hidden" : false, | |
"priority" : 1, | |
"tags" : { }, | |
"slaveDelay" : NumberLong(0), | |
"votes" : 1 | |
}, | |
{ | |
"_id" : 2, | |
"host" : "fymongodb003:27017", | |
"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("5cb98534cd9d16ff3f4fffdc") | |
} | |
} | |
# rs.status() 查看各个节点的身份 |
当我们完成上面的操作的时,我们主节点更改的数据已经是会自动同步到次节点的。
次节点操作
fymongodb002
次节点 (Secondary) 设置允许读写操作。
[root@fymongodb002 ~]# mongo | |
MongoDB shell version v4.0.8 | |
connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb | |
Implicit session: session {"id" : UUID("f353edd5-d79a-4947-9844-7f9bde98a949") } | |
MongoDB server version: 4.0.8 | |
fymongodb:SECONDARY> fymongodb:SECONDARY> show dbs; # 我们发现无法读写 | |
2019-04-19T17:04:40.852+0800 E QUERY [js] Error: listDatabases failed:{"operationTime" : Timestamp(1555664675, 1), | |
"ok" : 0, | |
"errmsg" : "not master and slaveOk=false", | |
"code" : 13435, | |
"codeName" : "NotMasterNoSlaveOk", | |
"$clusterTime" : {"clusterTime" : Timestamp(1555664675, 1), | |
"signature" : {"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), | |
"keyId" : NumberLong(0) | |
} | |
} | |
} : | |
fymongodb:SECONDARY> rs.slaveOk() # 允许 次节点 进行读取 | |
fymongodb:SECONDARY> show dbs; # 我们就可以查看到次节点的数据了。admin 0.000GB | |
config 0.000GB | |
djx 0.000GB | |
local 0.000GB | |
fymongodb:SECONDARY> use djx; | |
switched to db djx | |
fymongodb:SECONDARY> show collections; # 从主节点同步过来的数据。djx | |
lsp |
数据同步测试
测试数据的同步,我们在主节点创建一个集合test
并添加一条数据 'age':38
, 我们可以看到次节点也同步了该数据。
故障模拟测试
测试一
模拟主节点 fymongodb001
宕机了,然后查看次节点 fymongodb002
是否会被选举成为主节点。
我们可以看到 fymongodb002
选举为主节点.
测试二
当 fymongodb002
选举为主节点后,fymongodb001
恢复了,fymongodb001
会作为次节点加入。
测试三
测试在次节点 (Secondary) 进行数据删除。是删除不了的。
fymongodb:SECONDARY> db.test.drop() | |
2019-04-19T21:48:38.140+0800 E QUERY [js] Error: drop failed: {"operationTime" : Timestamp(1555681711, 1), | |
"ok" : 0, | |
"errmsg" : "not master", | |
"code" : 10107, | |
"codeName" : "NotMaster", | |
"$clusterTime" : {"clusterTime" : Timestamp(1555681711, 1), | |
"signature" : {"hash" : BinData(0,"I+dFkOHcdqW+La7xvy8JFij+5CY="), | |
"keyId" : NumberLong("6681517989155569665") | |
} | |
} | |
} : | |
_getErrorWithCode@src/mongo/shell/utils.js:25:13 | |
DBCollection.prototype.drop@src/mongo/shell/collection.js:707:1 | |
@(shell):1:1 |
配置副本集登录验证
副本集配置用户和密码,登录主节点, 添加admin
用户。
> use admin | |
switched to db admin | |
> db.createUser({user: "admin", pwd: "9toc7tpji8", roles: [{ role: "root", db: "admin"}] }) | |
Successfully added user: { | |
"user" : "admin", | |
"roles" : [ | |
{ | |
"role" : "root", | |
"db" : "admin" | |
} | |
] | |
} |
在主节点生成 keyflie
并复制到其它两个节点
# 生成 keyFile | |
openssl rand -base64 90 -out /opt/mongodb/keyfile | |
#并复制到其它两个节点 | |
scp /opt/mongodb/keyfile fymongodb002:/opt/mongodb/ | |
scp /opt/mongodb/keyfile fymongodb003:/opt/mongodb/ |
更改三个节点的 keyFile
文件权限和所有者
chmod 600 /opt/mongodb/keyfile # 一定要更改成 600 权限,否正会报错 | |
chown mongodb:mongodb /opt/mongodb/keyfile # 更改文件所有者 |
更改三个节点的 mongodb_rep.yaml
配置文件, 将 security
的参数 authorization
设置为 enabled
,并配置
keyFile
的路径。
security: | |
authorization: "enabled" | |
keyFile: '/opt/mongodb/keyfile' | |
clusterAuthMode: "keyFile" |
然后依次重启 fymongodb001
、fymongodb002
、fymongodb003
。
# fymongodb001 | |
systemctl restart mongodb | |
# fymongodb002 | |
systemctl restart mongodb | |
# fymongodb003 | |
systemctl restart mongodb |
登陆 fymongodb001
, 我们使用 db.auth()
进行登陆验证。
fymongodb:PRIMARY> show dbs; | |
fymongodb:PRIMARY> use admin | |
switched to db admin | |
fymongodb:PRIMARY> db.auth('admin','9toc7tpji8') | |
1 | |
fymongodb:PRIMARY> show dbs; | |
admin 0.000GB | |
config 0.000GB | |
local 0.000GB |
三、副本集维护
副本集成员如何以单节点启动
副本集当做单节点启动需要更改配置文件,主要的配置文件有以下几点:
- 注释副本集名称设置,
replSet=fymongodb
- 更改端口
- 如果是分片储存的,那么还需要注释分片配置,添加 skipShardingConfigurationChecks = true。
然后我们执行维护完后,关闭节点
use admin | |
db.shutdownServer() |
然后还原原来的配置,然后作为副本集成员加入副本集。
副本集设置节点的优先级
如果我们想让某个节点成为主节点,或者是当主节点 down 了后,你想指定某个节点 优先级更高地成为 次节点。
cfg = rs.conf() | |
cfg.members[0].priority = 0.5 | |
cfg.members[1].priority = 0.5 | |
cfg.members[2].priority = 1 | |
rs.reconfig(cfg) |
副本集使用备份数据进行启动
# 使用备份数据进行启动 | |
mongod --dbpath /data/db | |
# 删除 local 数据库 | |
use local | |
db.dropDatabase() | |
# 指定副本集名称并以备份数据 启动 | |
mongod --dbpath /data/db --replSet <replName> | |
# 启动副本集 | |
rs.initiate( { | |
_id : <replName>, | |
members: [{ _id : 0, host : <host:port>} ] | |
}) |
副本集数据量比较大时如何添加次节点
当副本集的数据量比较大的时候,我们添加新的节点的时候,如果使用初始化同步的话,会给主节点造成比较大的压力。我们有以下选择:
-
暂停当前副本集中的一个次节点,然后将 次节点的数据 (data) 复制到要新添加的节点的数据目录。然后再将两个节点启动。(建议先测试)
如果要复制数据文件,请确保您的副本包含
local
数据库的内容。 -
指定同步节点
rs.syncFrom(hostportstr)
,指定同步节点为次节点,默认的是同步节点 是主节点。但是该设置在重新启动节点,或者同步指定的新节点的连接被关闭了,是会失效的。(建议先测试)
-
如果我们决定还是从主节点进行同步数据。
副本集延迟节点
延迟节点在此未记录,详细见官方文档。
副本集常用命令
# 副本集初始化 | |
rs.initiate( { | |
_id : "rs0", | |
members: [{ _id: 0, host: "mongodb0.example.net:27017"}, | |
{_id: 1, host: "mongodb1.example.net:27017"}, | |
{_id: 2, host: "mongodb2.example.net:27017"} | |
] | |
}) | |
# 副本集添加成员 | |
rs.add('mongodb3.example.net:27017') | |
# 副本集添加仲裁节点 | |
rs.addArb('mongodb4.example.net:27017') | |
# 移除节点 | |
rs.remove('hostportstr') | |
# 查看当前的配置 | |
rs.conf() | |
# 查看各个节点状态和身份 | |
rs.status() | |
# 设定某个节点多少秒不可成为主节点 | |
rs.freeze(secs) | |
# 设置次节点从指定节点同步数据 | |
rs.syncFrom(hostportstr) | |
# 降低主节点为次节点,只能在主节点上运行 | |
rs.stepDown([stepdownSecs, catchUpSecs]) | |
# 查看帮助 | |
rs.help() | |
# 次节点执行,表示允许次节点读取数据 | |
rs.slaveOk() | |
# 判断当前节点是否是主节点 | |
rs.isMaster() | |
# 查看 Oplog 信息 | |
rs.printReplicationInfo() | |
# 查看副本集的次节点与主节点延迟 | |
db.printSlaveReplicationInfo() | |
# 移除原有副本集命令 | |
use local | |
db.system.replset.remove({}) | |
# 关闭 mongodb 进程服务 | |
use admin | |
db.shutdownServer() |
四、注意事项
建议将副本集至少部署在 3 个可用区。
配置副本集成员,使用的是主机名而不是 ip,因为 ip 可能变动。
副本集包含奇数个投票成员。
错误问题
报错内容:
Failed global initialization: BadValue: replication.replSetName is not allowed when storage.indexBuildRetry is specified
storage
参数 indexBuildRetry
不能与 副本集共存,当开启 副本集的时候,就需要将indexBuildRetry
参数注释。官网链接
Changed in version 4.0: The setting
storage.indexBuildRetry
cannot be used in conjunction withreplication.replSetName
.
:
