前言
随着云计算和大数据技术的兴起,MongoDB 成为了一个备受关注的 NoSQL 数据库。而 Docker 技术则提供了一种快速部署和管理应用程序的方式。本文将介绍如何使用 Docker 快速搭建 MongoDB 集群,并给出其实际应用的案例。
环境准备
在开始之前,确保你已经安装好了 Docker 和 Docker Compose。
Docker Compose 文件结构
我们可以使用 Docker Compose 来定义和运行多个 Docker 容器。Docker Compose 文件是一个 YAML 文件,用于定义 Docker 容器和服务。
下面是一个 MongoDB 的 Docker Compose 文件的例子:
// www.javascriptcn.com code example
version: '2'
services:
# Replica set primary node
mongodb_rs1a:
image: mongo
container_name: rs1a
command: mongod --replSet rs1 --bind_ip_all
ports:
- "27017:27017"
volumes:
- rs1_data:/data/db
# Replica set secondary node
mongodb_rs1b:
image: mongo
container_name: rs1b
command: mongod --replSet rs1 --bind_ip_all --port 27018
ports:
- "27018:27018"
volumes:
- rs1_data:/data/db
# Replica set arbiter node
mongodb_rs1arb:
image: mongo
container_name: rs1arb
command: mongod --replSet rs1 --bind_ip_all --port 27019 --noprealloc --smallfiles --nojournal
ports:
- "27019:27019"
volumes:
- rs1_data:/data/db
volumes:
rs1_data:在这个文件中,我们定义了三个 MongoDB 容器,分别是一个 primary、一个 secondary 和一个 arbiter。我们还定义了一个 Docker 卷,用于持久化存储数据。
MongoDB 集群搭建
1. 创建 Docker Compose 文件
在一个空目录下创建一个名为 docker-compose.yml 的文件,并复制上面的 Docker Compose 配置。
2. 启动 MongoDB 集群
在命令行进入该目录,运行以下命令:
docker-compose up -d
这将启动容器并在后台运行。
3. 初始化 Replica Set
接下来,我们需要初始化我们的 MongoDB Replica Set。进入 primary 节点的容器,执行以下命令:
docker exec -it rs1a mongo --eval "rs.initiate({_id: 'rs1', members: [{_id: 0, host: 'rs1a:27017'}, {_id: 1, host: 'rs1b:27018'}, {_id: 2, host: 'rs1arb:27019', arbiterOnly: true}]})"这个命令将初始化一个名为 rs1 的 Replica Set,其中 rs1a 为 primary 节点,rs1b 为 secondary 节点,rs1arb 为 arbiter 节点。
4. 验证 Replica Set
运行以下命令验证 Replica Set 是否正常工作:
docker exec -it rs1a mongo rs.status()
如果一切正常,你应该会看到输出类似这样:
// www.javascriptcn.com code example
{
"set" : "rs1",
"date" : ISODate("2022-10-17T09:24:45.819Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1666046681, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2022-10-17T09:24:41.800Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1666046681, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2022-10-17T09:24:41.800Z"),
"appliedOpTime" : {
"ts" : Timestamp(1666046681, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1666046681, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2022-10-17T09:24:41.800Z"),
"lastDurableWallTime" : ISODate("2022-10-17T09:24:41.800Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1666046651, 1),
"members" : [
{
"_id" : 0,
"name" : "rs1a:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 1851,
"optime" : {
"ts" : Timestamp(1666046681, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1666046681, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-10-17T09:24:41Z"),
"optimeDurableDate" : ISODate("2022-10-17T09:24:41Z"),
"lastHeartbeat" : ISODate("2022-10-17T09:24:44.395Z"),
"lastHeartbeatRecv" : ISODate("2022-10-17T09:24:45.332Z"),
"pingMs" : NumberLong(0),
"electionTime" : Timestamp(1666046626, 1),
"electionDate" : ISODate("2022-10-17T09:23:46Z"),
"configVersion" : 2,
"self" : true,
"votes" : 1,
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"tagSet" : {
}
},
{
"_id" : 1,
"name" : "rs1b:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 1844,
"optime" : {
"ts" : Timestamp(1666046681, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1666046681, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-10-17T09:24:41Z"),
"optimeDurableDate" : ISODate("2022-10-17T09:24:41Z"),
"lastHeartbeat" : ISODate("2022-10-17T09:24:44.419Z"),
"lastHeartbeatRecv" : ISODate("2022-10-17T09:24:45.302Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "rs1a:27017",
"configVersion" : 2,
"votes" : 1,
"tagSet" : {
}
},
{
"_id" : 2,
"name" : "rs1arb:27019",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 1822,
"lastHeartbeat" : ISODate("2022-10-17T09:24:44.418Z"),
"lastHeartbeatRecv" : ISODate("2022-10-17T09:24:45.200Z"),
"pingMs" : NumberLong(0),
"configVersion" : 2,
"votes" : 0,
"lastHeartbeatMessage" : "could not find member to sync from",
"tagSet" : {
}
}
],
"ok" : 1,
"operationTime" : Timestamp(1666046681, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1666046681, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}如果你看到了类似输出,那么你已经成功地搭建了一个 MongoDB Replica Set 集群。
实际应用
在上面的例子中,我们已经成功地搭建了一个 MongoDB Replica Set 集群。那么这个集群除了可以存储数据之外,还可以有什么实际应用呢?接下来我们将展示如何使用 MongoDB Replica Set 集群来实现数据的读写分离、故障转移等应用场景。
数据读写分离
在一个高负载的系统中,我们希望将读操作和写操作分开处理,这样可以减轻 primary 节点的压力,并提高系统的吞吐量。我们可以将写操作指向 primary 节点,将读操作均匀地分布到 secondary 节点中,从而实现读写分离。
为了实现这个目的,我们需要使用 MongoDB 客户端驱动程序提供的读写分离功能。下面是一个 Python 的例子:
// www.javascriptcn.com code example
from pymongo import MongoClient, ReadPreference
from pymongo.errors import AutoReconnect
client = MongoClient(
"mongodb://rs1a:27017,rs1b:27018/?replicaSet=rs1",
read_preference=ReadPreference.SECONDARY_PREFERRED
)
db = client.test
try:
db.insert_one({"name": "Alice", "age": 20})
except AutoReconnect:
pass
print("Primary node:")
print(db.find_one({"name": "Alice"}))
print()
print("Secondary node:")
secondary = client.secondary_acceptable_latency_ms
db.read_preference = ReadPreference.SECONDARY
print(db.find_one({"name": "Alice"}))在这个例子中,我们使用了 Python 的 pymongo 库来访问 MongoDB。我们向 primary 节点插入了一条数据,并将查询的 read_preference 设置为 SECONDARY_PREFERRED。然后我们打印了查询结果,分别查看了 primary 和 secondary 节点的数据。
故障转移
在一个分布式系统中,由于网络延迟、软件错误等原因,节点可能会出现故障。为了避免系统的崩溃,我们需要在出现故障时进行故障转移。在 MongoDB Replica Set 集群中,当 primary 节点出现故障时,MongoDB 会自动选举一个 secondary 节点为新的 primary 节点,从而实现故障转移。
下面我们将模拟 primary 节点的故障:
docker stop rs1a
然后再次运行以下命令验证 Replica Set 是否正常工作:
docker exec -it rs1b mongo rs.status()
输出类似这样:
// www.javascriptcn.com code example
{
"set" : "rs1",
"date" : ISODate("2022-10-17T09:46:22.063Z"),
"myState" : 1,
"term" : NumberLong(2),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1666049182, 1),
"t" : NumberLong(2)
},
"lastCommittedWallTime" : ISODate("2022-10-17T09:46:22.020Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1666049182, 1),
"t" : NumberLong(2)
},
"readConcernMajorityWallTime" : ISODate("2022-10-17T09:46:22.020Z"),
"appliedOpTime" : {
"ts" : Timestamp(1666049182, 1),
"t" : NumberLong(2)
},
"durableOpTime" : {
"ts" : Timestamp(1666049182, 1),
"t" : NumberLong(2)
},
"lastAppliedWallTime" : ISODate("2022-10-17T09:46:22.020Z"),
"lastDurableWallTime" : ISODate("2022-10-17T09:46:22.020Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1666049148, 1),
"members" : [
{
"_id" : 0,
"name" : "rs1a:27017",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDurable" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2022-10-17T09:46:35.725Z"),
"lastHeartbeatRecv" : ISODate("2022-10-17T09:46:36.360Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "Error getting heartbeat info from rs1a:27017: network error",
"syncingTo" : "",
"configVersion" : 2,
"self" : true,
"votes" : 1,
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"tagSet" : {
}
},
{
"_id" : 1,
"name" : "rs1b:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 494,
"optime" : {
"ts" : Timestamp(1666049182, 1),
"t" : NumberLong(2)
},
"optimeDurable" : {
"ts" : Timestamp(1666049182, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2022-10-17T09:46:22Z"),
"optimeDurableDate" : ISODate("2022-10-17T09:46:22Z"),
"lastHeartbeat" : ISODate("2022-10-17T09:46:34.079Z"),
"lastHeartbeatRecv" : ISODate("2022-10-17T09:46:34.120Z"),
"pingMs" : NumberLong(0),
"electionTime" : Timestamp(1666049143, 1),
"electionDate" : ISODate("2022-10-17T09:45:43Z"),
"configVersion" : 2,
"self" : true,
"votes" : 1,
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"tagSet" : {
}
},
{
"_id" : 2,
"name" : "rs1arb:27019",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 1052,
"lastHeartbeat" : ISODate("2022-10-17T09:46:34.079Z"),
"lastHeartbeatRecv" : ISODate("2022-10-17T09:46:36.324Z"),
"pingMs" : NumberLong(0),
"configVersion" : 2,
"votes" : 0,
"lastHeartbeatMessage" : "could not find member to sync from",
"tagSet" : {
}
}
],
"ok" : 1,
"operationTime" : Timestamp(1666049182, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1666049182, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}从输出可以看出,rs1a 节点已经无法工作,而 rs1b 节点已经成为了新的 primary 节点。这个过程是自动完成的,无需人工干预,基本上不会有数据丢失。
真实场景案例
华为分布式数据库存储引擎 GaussDB NoSQL 采用了 MongoDB 的分布式数据库架构。为了支持多节点复制和容灾,GaussDB NoSQL 采用了 MongoDB Replica Set 集群,实现了高可用、故障转移和数据备份等功能。同时,GaussDB NoSQL 还支持基于分片的横向扩展,并提供了快速查询、索引、聚合等高级查询功能。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d5d31ea941bf7134b1ab4c