1. 副本集概述
某些情况下,副本可以提供更高的读取容量,就像客户端可以发送读操作到不同的服务器。在不同数据中心维护数据副本可以增加分布式应用的数据局部性和可用性。还可以因为其它目的保存额外的副本,比如灾难恢复、报告或备份。
MongoDB 中的副本
一个副本集就是一组维护相同数据集的 mongod 实例。
一个数据集包含一些数据承载节点和一个可选的仲裁节点。数据承载节点中,有且只能有一个被认为是主承载节点,而其它节点被认为是次要节点。主节点接收所有写入操作。主节点将对其数据集所做的所有更改记录到其 oplog。
次要节点复制主节点的 oplog 并将操作应用到其数据集,就跟次要数据集反映了主数据集一样。如果主节点不可用,一个合格的次要节点将被选为新的主节点。
可以添加一个额外的 mongod 实例到副本集中来作为仲裁节点。仲裁节点不维护数据集。仲裁节点通过响应副本集其它成员的心跳和选举请求来达到维护副本集中法定成员数量的目的。因为它不需要存储数据集,比带有数据集的全功能副本集成员消耗更少的资源,所以它是一种提供副本集仲裁功能比较好的方式。如果你的副本集有偶数个成员,添加一个仲裁节点在主节点选举中来获得一个主节点的选票。仲裁节点不需要专用硬件。
仲裁节点将永远是仲裁节点,但主节点可能变为次要节点,次要节点也可能通过选举成为主要节点。
异步复制
- 次要节点异步的应用来自主节点的操作。通过在主节点之后应用操作,副本集可以不管一个或多个成员的失败而继续实现其功能。
自动故障切换
- 当主节点超过十秒没有与副本集的其它成员通信,一个合格的次要节点将举行选举来将它自己选举为新的主节点。第一个次要节点举行选举,并在接收多数成员的投票后成为主节点。
- MongoDB 3.2 版本后引入了版本 1 的复制协议,它减少了副本集的故障切换时间并加速了多个同时存在的主节点的检测。
- 虽然时间不同,故障转移过程一般在一分钟之内完成。
读操作
- 默认的,客户端从主节点进行读操作,然而可以通过指定读偏好来将读操作发送到次要节点。次要节点的异步复制意味着从次要节点进行的读操作可能返回没有反映主节点上数据状态的数据。
- MongoDB 中,客户端可以在写操作持久化之前看到其结果。
额外的特性
副本集提供多种支持应用需求的选项。可以部署成员在多个数据中心的副本集,或通过调整一些成员的 members[n].priority 来控制选举主节点的结果。副本集也支持专用于报告、灾难恢复或备份功能的成员。
副本集成员
- 主节点接收所有写操作。
- 次要节点从主节点复制操作来保持与主节点相同的数据集。次要节点可以有特殊配置文件的附加配置。比如,次要节点可以对选举弃权或优先级为 0。
- 副本集的推荐最低配置是带有三个数据承载点三成员副本集:一个主节点和两个次要节点。也可以部署为两个数据承载点的三成员副本集:一个主节点,一个次要节点和一个仲裁节点。但这种模式不如三数据承载点的模式冗余性好。
- 3.0 版本的变化:副本集最多可以有 50 名成员,但只有 7 名投票成员。
主节点
- 主节点是副本集中接收写操作的唯一成员。MongoDB 在主节点上应用写操作,然后将操作记录在主节点的 oplog 上。次要节点复制 oplog 并将操作应用到它们的数据集上。
次要节点
- 次要节点维护主节点数据集的一个副本。复制数据时,次要节点在异步过程中从主节点的 oplog 应用操作到它自己的数据集。副本集可以有一个或多个次要节点。
- 次要节点可以通过配置来达到实现一些效果:
- 防止被选举为主节点,以便允许它留在次要节点数据中心或作为冷备用。
- 防止应用从中读取,以便允许它运行需要与正常流量分离的应用。
- 保存一个运行的历史快照,以便从某些错误中恢复,如误删数据库。
仲裁节点
- 仲裁节点没有数据集的副本,并且不能变成主节点。副本集可以有仲裁节点来在选举主节点中投票。仲裁节点永远有一张选票,这样就允许副本集的投票成员数量不均等,而无需承担一个复制数据的成员的额外开销。
- 重要:不要在还承载副本集的主节点或次要节点的系统上运行仲裁节点。
副本集 oplog
- oplog:操作日志,一个特殊的固定集合,保持所有修改数据库上数据的操作的滚动记录。
- 副本集的所有成员都包含一份 oplog 的副本,在
local.oplog.rs
中,这允许它们维护数据库的当前状态。 - 为了减轻复制的困难,所有的副本集成员成员都发送心跳(ping)到所有其它成员。
- 任何成员都可以从其它成员那里导入 oplog。
- oplog 中的每个操作都是幂等(idempotent)的。即,oplog 对目标资料应用不管一次或多次操作,都产生相同的结果。
oplog 大小
- 当第一次开始一个副本集成员时,MongoDB 以默认大小创建一个 oplog。
- 如果可以预料副本集工作量的大小,可以将 oplog 设置为比默认值大些。相反,如果应用主要用来执行读操作和少量写操作,一个小的 oplog 可能就足够了。
- 以下情况可能需要较大的 oplog:
- 一次更新多个文档
- 删除与插入量大致相同的数据时
- 大量的就地更新
oplog 状态
- 使用
rs.printReplicationInfo()
方法来查看 oplog 状态,包括其大小和操作的时间范围。
2. 在 docker 中创建副本集
准备工作
安装 docker
-
下载 mongo 镜像,如有需求可加上版本号
docker pull mongo
概览
- 我们要从 mongo 镜像创建三个容器,都处在它们的 docker 容器网络内。
- 这三个容器将被命名为 mongo1、mongo2 和 mongo3。它们将作为副本集的三个 mongo 实例。
- 暴露它们的端口到本地机器,以便可以从本地机器的 mongo shell 来访问它们中的任意一个。
- 这三个 mongo 容器中的每一个都应该能与这个网络中的所有其它容器通信。
建立网络
-
创建一个名为 my-mongo-cluster 的网络:
docker network create my-mongo-cluster
-
查看当前系统中的网络:
docker network ls
创建容器
运行以下命令启动第一个容器:
docker run \
-p 30001:27017 \
--name mongo1 \
--net my-mongo-cluster \
mongo mongod --replSet my-mongo-set
- docker run: 从镜像启动容器
- -p 30001:27017:暴露容器中的 27017 端口,映射到本机的 30001 端口
- --name mongo1:给这个容器命名为 mongo1
- --net my-mongo-cluster:将此容器添加到 my-mongo-cluster 网络
- mongo:用来生成此容器的镜像名称
- mongod --replSet my-mongo-set:执行 mongod 命令,以将此 mongo 实例添加到名称为 my-mongo-se 的副本集。
启动其余两个容器:
docker run \
-p 30002:27017 \
--name mongo2 \
--net my-mongo-cluster \
mongo mongod --replSet my-mongo-set
docker run \
-p 30003:27017 \
--name mongo3 \
--net my-mongo-cluster \
mongo mongod --replSet my-mongo-set
配置副本集
现在我们需要的 mongo 实例已经运行起来了,现在来把它们编程副本集。
-
这条命令将在运行的容器 mongo1 中打开 mongo shell:
docker exec -it mongo1 mongo
-
在 mongo shell 中进行配置:
> db = (new Mongo('localhost:27017')).getDB('test') test > config = { "_id" : "my-mongo-set", "members" : [ { "_id" : 0, "host" : "mongo1:27017" }, { "_id" : 1, "host" : "mongo2:27017" }, { "_id" : 2, "host" : "mongo3:27017" } ] }
- 第一个 _id 键应当和 —replSet 标签为 mongo 实例设置的值一样,这个例子中是 my-mongo-set。
- 接下来列出了所有想放到副本集中的成员。
- 将所有 mongo 实例添加到 docker 网络。在 my-mongo-cluster 网络中根据每个容器的名称各自分配到 ip 地址。
-
通过以下命令启动副本集:
rs.initiate(config)
如果一切顺利,提示符将变成这样:
my-mongo-set:PRIMARY>
这意味着 shell 现在与 my-mongo-set 集群中的 PRIMARY 数据库进行了关联。
-
下面测试副本集是否工作,先在 primary 数据库中插入数据:
> db.mycollection.insert({name : 'sample'}) WriteResult({ "nInserted" : 1 }) > db.mycollection.find() { "_id" : ObjectId("57761827767433de37ff95ee"), "name" : "sample" }
-
然后新建一个与 secondary 数据库的连接,并测试文档是否在那里复制:
> db2 = (new Mongo('mongo2:27017')).getDB('test') test > db2.setSlaveOk() > db2.mycollection.find() { "_id" : ObjectId("57761827767433de37ff95ee"), "name" : "sample" }
执行 db2.setSlaveOk() 命令来让 shell 知道我们故意在查询非 primary 的数据库。
3. 使用 Dockerfile 构建副本集镜像
mongo 副本集基础镜像
-
Dockerfile
FROM ubuntu:14.04 MAINTAINER Jia chenhui ENV REFRESHED_AT 2017-0726 RUN sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6 RUN echo "deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list RUN sudo apt-get update RUN sudo apt-get install -y mongodb EXPOSE 27017 CMD []
-
构建基础镜像
docker build -t mongo_set_base .
mongo 副本集镜像
-
Dockerfile
FROM mongo_set_base MAINTAINER Jia chenhui ENV REFRESHED_AT 2017-07-26 RUN mkdir -p /data/db ENTRYPOINT [ "usr/bin/mongod", "--replSet", "my-mongo-set" ]
-
构建副本集镜像
docker build -t mongod .
-
从镜像 mongod 启动容器 mongo1
docker run \ -p 30001:27017 \ --name mongo1 \ --net my_mongo_cluster \ mongod
-
顺利的话会看到提示
[rsStart] replSet can't get local.system.replset config from self or any seed (EMPTYCONFIG)
这是因为还没有配置副本集,继续往下执行
mongo 副本集次要节点
从镜像 mongod 启动容器,这里启动两个容器 mongo2 和 mongo3,作为副本集次要节点
docker run \
-p 30002:27017 \
--name mongo2 \
--net my_mongo_cluster \
mongod
docker run \
-p 30003:27017 \
--name mongo3 \
--net my_mongo_cluster \
mongod
配置副本集 config
-
以交互模式进入主节点的 shell(可以连接任一容器)
docker exec -it mongo1 mongo
-
执行以下命令
db = (new Mongo('localhost:27017')).getDB('test') config = { "_id" : "my-mongo-set", "members" : [ { "_id" : 0, "host" : "mongo1:27017" }, { "_id" : 1, "host" : "mongo2:27017" }, { "_id" : 2, "host" : "mongo3:27017" } ] } rs.initiate(config)
-
如果一切正常,会出现以下提示,表示副本集已创建成功,目前在主节点中
my-mongo-set:PRIMARY>
测试副本集
-
插入一条文档
db.mycollection.insert({name : 'sample'})
-
查看是否成功插入
db.mycollection.find()
-
另开一个 terminal 连接次要节点,查询文档是否与主节点插入的文档一致
docker exec -it mongo2 mongo
db2 = (new Mongo('mongo2:27017')).getDB('test') db2.setSlaveOk() db2.mycollection.find()
到这里就完成了一个主节点两个次要节点副本集的创建。
4. 使用 docker-compose 构建副本集服务
Dockerfile
FROM mongo:3.4.1
MAINTAINER Jia Chenhui
ENV REFRESHED_AT 2017-07-27
ADD . /replicaset
WORKDIR /replicaset
RUN mkdir -p /replicaset/data/db
EXPOSE 27017
ENTRYPOINT [ "mongod", "--replSet", "my-mongo-set" ]
compose.yml
version: '2'
services:
primary:
build: .
image: mongoset:primary
ports:
- "30001:27017"
volumes:
- .:/replicaset
networks:
- my_mongo_cluster
replica1:
build: .
image: mongoset:replica1
ports:
- "30002:27017"
volumes:
- .:/replicaset
networks:
- my_mongo_cluster
replica2:
build: .
image: mongoset:replica2
ports:
- "30003:27017"
volumes:
- .:/replicaset
networks:
- my_mongo_cluster
networks:
my_mongo_cluster:
创建服务
docker-compose up
进入 mongo shell 配置并初始化副本集
config = {
"_id" : "my-mongo-set",
"members" : [
{
"_id" : 0,
"host" : "compose_primary_1:27017"
},
{
"_id" : 1,
"host" : "compose_replica1_1:27017"
},
{
"_id" : 2,
"host" : "compose_replica2_1:27017"
}
]
}
rs.initiate(config)