MongoDB 监控(十)MongoDB 副本集集群搭建及获取副本集状态
这是 MongoDB 监控系列文章的第十篇,前面几篇文章的链接如下:
- MongoDB 监控(一)
- MongoDB 监控(二)
- MongoDB 监控(三)
- MongoDB 监控(四)
- MongoDB 监控(五)
- MongoDB 监控(六)
- MongoDB 监控(七)
- MongoDB 监控(八)
- MongoDB 监控(九)
之前的章节我们重点研究的是 MongoDB 单节点模式,这一节我们探索一下 MongoDB 的副本集集群以及相关的监控命令。MongoDB 的副本集集群有点像是 MySQL 的一主多从的方式,具备高可用能力,但是不具备水平扩展能力,如果想要水平扩展能力,则需要使用分片集群。多个副本集集群组成一套分片集群。
MongoDB 副本集集群搭建
参考:https://www.cnblogs.com/studyjobs/p/17643808.html
我们使用 docker compose 快速启动一个 MongoDB 副本集集群,首先创建三个目录,存放三个 MongoDB 实例的数据:
mkdir -p /Users/ulric/works/mongos/mongo1
mkdir -p /Users/ulric/works/mongos/mongo2
mkdir -p /Users/ulric/works/mongos/mongo3
mongodb 使用 keyFile 进行认证,副本集群中的每个节点的 mongodb 使用 keyFile 的内容作为认证其他成员的共享密码。mongodb 实例只有拥有正确的 keyFile 才可以加入副本集群,集群中所有成员的 keyFile 内容必须相同。
cd /Users/ulric/works/mongos
openssl rand -base64 666 > mongodb.key
mongodb 的集群其实有 3 种角色:主节点、从节点、仲裁节点(可选,可有可无)
- 主节点(Primary):主要负责数据的写操作,当然也可以读取数据,大部分命令需要在主节点才能运行。
- 从节点(Secondary):从主节点实时同步数据,只能读取数据,不能进行写操作。
- 仲裁节点(Arbiter):不保留任何数据的副本,只能用来投票选举使用。
本篇博客的演示中,没有去设置仲裁节点,3 个节点都保留数据副本。在 /Users/ulric/works/mongos 目录下创建 docker-compose.yml 文件:
services:
# 服务名称
mongodb1:
# 使用最新的 mongodb 镜像
image: mongo:latest
# docker 服务启动时,自动启动 mongo 容器
restart: always
# 容器的名称
container_name: mongo1
# 宿主机中的目录和文件,映射容器内部的目录和文件
volumes:
- /Users/ulric/works/mongos/mongo1:/data/db
- /Users/ulric/works/mongos/mongodb.key:/data/mongodb.key
ports:
# 宿主机的端口映射容器内的端口
- 27017:27017
environment:
# 初始化一个 root 角色的用户 jobs 密码是 123456
- MONGO_INITDB_ROOT_USERNAME=jobs
- MONGO_INITDB_ROOT_PASSWORD=123456
# 使用创建的桥接网络,把各个 mongodb 容器连接在一起
networks:
- mongoNetwork
# 启动容器时,在容器内部额外执行的命令
# 其中 --replSet 参数后面的 mongos 是集群名称,这个很重要
command: mongod --replSet mongos --keyFile /data/mongodb.key
entrypoint:
- bash
- -c
- |
chmod 400 /data/mongodb.key
chown 999:999 /data/mongodb.key
exec docker-entrypoint.sh $$@
mongodb2:
image: mongo:latest
restart: always
container_name: mongo2
volumes:
- /Users/ulric/works/mongos/mongo2:/data/db
- /Users/ulric/works/mongos/mongodb.key:/data/mongodb.key
ports:
- 27018:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=jobs
- MONGO_INITDB_ROOT_PASSWORD=123456
networks:
- mongoNetwork
command: mongod --replSet mongos --keyFile /data/mongodb.key
entrypoint:
- bash
- -c
- |
chmod 400 /data/mongodb.key
chown 999:999 /data/mongodb.key
exec docker-entrypoint.sh $$@
mongodb3:
image: mongo:latest
restart: always
container_name: mongo3
volumes:
- /Users/ulric/works/mongos/mongo3:/data/db
- /Users/ulric/works/mongos/mongodb.key:/data/mongodb.key
ports:
- 27019:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=jobs
- MONGO_INITDB_ROOT_PASSWORD=123456
networks:
- mongoNetwork
command: mongod --replSet mongos --keyFile /data/mongodb.key
entrypoint:
- bash
- -c
- |
chmod 400 /data/mongodb.key
chown 999:999 /data/mongodb.key
exec docker-entrypoint.sh $$@
# 创建一个桥接网络,把各个 mongodb 实例连接在一起,该网络适用于单机
# 如果在不同的宿主机上,使用 docker swarm 需要创建 overlay 网络
networks:
mongoNetwork:
driver: bridge
然后执行 docker compose up -d
启动 MongoDB 副本集集群,启动后可以通过 docker ps
查看容器是否启动成功。
随便进入其中一个容器,比如进入 mongo1 docker exec -it mongo1 bash
,然后进入 mongo 客户端 mongosh -u jobs -p 123456
,执行以下命令配置将 3 个节点初始化为一个副本集群:
rs.initiate({
_id: "mongos",
members: [
{ _id : 0, host : "10.211.55.2:27017" },
{ _id : 1, host : "10.211.55.2:27018" },
{ _id : 2, host : "10.211.55.2:27019" }
]
});
初始化完成之后,通过 rs.status()
命令可以看到副本集集群的状态:
mongos [direct: primary] test> use admin
switched to db admin
mongos [direct: primary] admin> rs.status()
{
set: 'mongos',
date: ISODate('2024-11-18T09:39:48.651Z'),
myState: 1,
term: Long('1'),
syncSourceHost: '',
syncSourceId: -1,
heartbeatIntervalMillis: Long('2000'),
majorityVoteCount: 2,
writeMajorityCount: 2,
votingMembersCount: 3,
writableVotingMembersCount: 3,
optimes: {
lastCommittedOpTime: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
lastCommittedWallTime: ISODate('2024-11-18T09:39:40.165Z'),
readConcernMajorityOpTime: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
appliedOpTime: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
durableOpTime: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
writtenOpTime: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
lastAppliedWallTime: ISODate('2024-11-18T09:39:40.165Z'),
lastDurableWallTime: ISODate('2024-11-18T09:39:40.165Z'),
lastWrittenWallTime: ISODate('2024-11-18T09:39:40.165Z')
},
lastStableRecoveryTimestamp: Timestamp({ t: 1731922730, i: 1 }),
electionCandidateMetrics: {
lastElectionReason: 'electionTimeout',
lastElectionDate: ISODate('2024-11-18T08:21:08.285Z'),
electionTerm: Long('1'),
lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1731918057, i: 1 }), t: Long('-1') },
lastSeenWrittenOpTimeAtElection: { ts: Timestamp({ t: 1731918057, i: 1 }), t: Long('-1') },
lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1731918057, i: 1 }), t: Long('-1') },
numVotesNeeded: 2,
priorityAtElection: 1,
electionTimeoutMillis: Long('10000'),
numCatchUpOps: Long('0'),
newTermStartDate: ISODate('2024-11-18T08:21:08.329Z'),
wMajorityWriteAvailabilityDate: ISODate('2024-11-18T08:21:08.820Z')
},
members: [
{
_id: 0,
name: '10.211.55.2:27017',
health: 1,
state: 1,
stateStr: 'PRIMARY',
uptime: 5067,
optime: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
optimeDate: ISODate('2024-11-18T09:39:40.000Z'),
optimeWritten: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
optimeWrittenDate: ISODate('2024-11-18T09:39:40.000Z'),
lastAppliedWallTime: ISODate('2024-11-18T09:39:40.165Z'),
lastDurableWallTime: ISODate('2024-11-18T09:39:40.165Z'),
lastWrittenWallTime: ISODate('2024-11-18T09:39:40.165Z'),
syncSourceHost: '',
syncSourceId: -1,
infoMessage: '',
electionTime: Timestamp({ t: 1731918068, i: 1 }),
electionDate: ISODate('2024-11-18T08:21:08.000Z'),
configVersion: 1,
configTerm: 1,
self: true,
lastHeartbeatMessage: ''
},
{
_id: 1,
name: '10.211.55.2:27018',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 4731,
optime: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
optimeDurable: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
optimeWritten: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
optimeDate: ISODate('2024-11-18T09:39:40.000Z'),
optimeDurableDate: ISODate('2024-11-18T09:39:40.000Z'),
optimeWrittenDate: ISODate('2024-11-18T09:39:40.000Z'),
lastAppliedWallTime: ISODate('2024-11-18T09:39:40.165Z'),
lastDurableWallTime: ISODate('2024-11-18T09:39:40.165Z'),
lastWrittenWallTime: ISODate('2024-11-18T09:39:40.165Z'),
lastHeartbeat: ISODate('2024-11-18T09:39:47.018Z'),
lastHeartbeatRecv: ISODate('2024-11-18T09:39:47.014Z'),
pingMs: Long('0'),
lastHeartbeatMessage: '',
syncSourceHost: '10.211.55.2:27017',
syncSourceId: 0,
infoMessage: '',
configVersion: 1,
configTerm: 1
},
{
_id: 2,
name: '10.211.55.2:27019',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 4731,
optime: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
optimeDurable: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
optimeWritten: { ts: Timestamp({ t: 1731922780, i: 1 }), t: Long('1') },
optimeDate: ISODate('2024-11-18T09:39:40.000Z'),
optimeDurableDate: ISODate('2024-11-18T09:39:40.000Z'),
optimeWrittenDate: ISODate('2024-11-18T09:39:40.000Z'),
lastAppliedWallTime: ISODate('2024-11-18T09:39:40.165Z'),
lastDurableWallTime: ISODate('2024-11-18T09:39:40.165Z'),
lastWrittenWallTime: ISODate('2024-11-18T09:39:40.165Z'),
lastHeartbeat: ISODate('2024-11-18T09:39:47.018Z'),
lastHeartbeatRecv: ISODate('2024-11-18T09:39:47.013Z'),
pingMs: Long('0'),
lastHeartbeatMessage: '',
syncSourceHost: '10.211.55.2:27017',
syncSourceId: 0,
infoMessage: '',
configVersion: 1,
configTerm: 1
}
],
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1731922780, i: 1 }),
signature: {
hash: Binary.createFromBase64('pGZHCVLcTS6roDiQL8DsOUTkKD0=', 0),
keyId: Long('7438531461411504133')
}
},
operationTime: Timestamp({ t: 1731922780, i: 1 })
}
通过上面的输出可以看到,三个实例,其中一个是主节点,两个是从节点,这样就搭建了一个 MongoDB 副本集集群。每个实例都有很多字段,比如 health、state、uptime、optime、lastHeartbeat 等等,正常的集群,三个节点的 optimeDate 应该是类似的。一般监控数据采集器 agent 会采集 MongoDB 的副本集信息,通常执行的是 db.runCommand({replSetGetStatus: 1})
命令,和 rs.status()
命令效果一样。