MongoDB 不专业指北(十七):创建多个mongod副本集实现高可用服务 (上)

1,822 阅读6分钟

本文翻译自 MongoDB 官网文档:docs.mongodb.com/v4.4/replic…,略有修改并附加了示例。

概述

MongoDB 的副本集(replica set)是一组维护同一份数据集的 mongod 进程。副本集保障了冗余性和高可用,这基本是生产环境的基本要求。本篇将介绍副本集、组件及其架构。

冗余性和数据可用性

复本提供了冗余,并且增加了数据可用性。通过不同数据库服务器的多个数据副本,副本集相比单个数据库服务器容错性更高。

在某些情况下,因为客户度可以向不同的服务器发送读操作请求,副本可以提高读取数据的并发量。在不同的数据中心维护数据副本对分布式应用而言。能够实现数据异地备份和提高可用性。维护额外的副本也能满足如灾难恢复、报表或备份等目的。

MongoDB 的复制

副本集其实就是维护同个数据集的 mongod 进程组。一个副本集包括了多个数据容错节点和一个可选的仲裁节点。在数据容错节点中,有且仅有一个节点被视为主节点,而其他节点是从节点。

主节点接收全部写操作。一个副本集只能有一个主节点通过{w: "majority"}指令确认写操作。在某些情况下,另外的 mongod 进程可能在短时间会自认为是主节点(出现多节点竞争主节点的时候会存在两个节点有这种情况,但是只有完成写操作的才是真正当前的主节点)。主节点会在操作日志中记录数据集的所有操作。

image.png

从节点会复制主节点的操作日志并且在他们的数据集执行同样的操作,从而保持和主节点的数据集一致。如果主节点不可用,一个有资格的从节点将会举行选举将自己提升为新的主节点。

image.png

在某些情况下(例如你有一个主节点和一个从节点,但是因为成本原因不能再增加从节点),我们会选择一个 mongod 实例作为仲裁者。仲裁者仅参与选举而不保有数据。正常情况下。仲裁者角色不会改变。然而在主节点出现下线的时候会变为从节点参与投票(仅参与投票),从而影响投票结果,选举出另一个从节点为主节点。通常情况下用于示例数量为偶数时,这种情况可能出现选票一致的情况,这个时候需要仲裁者打破平衡。

image.png

异步复制

从节点使用异步的方式从主节点复制操作日志并将操作应用到自身的数据集。由于从节点保留了主节点相同的数据集,因此尽管一个或多个节点挂掉,副本集还可以继续正常运行。

从节点从主节点复制操作时需要耗费一定的时间,因此会导致复制时延。复制时延是指从节点从主节点复制写操作所需要的时间。复制时延如果小通常是可接受的,但是复制时延如果增加的话会导致很严重的紧急问题,包括增加主节点的缓存构建压力。

从 MongoDB 4.2版本开始,管理员可以限制主节点写操作的频率,以保持最大的提交时延。这个时延可以通过flowControlTargetLagSeconds 配置,这就是流控。流控默认是开启的。开启流控后,一旦时延接近到了flowControlTargetLagSeconds,主节点在获取到写操作锁之前必须获取到排队票。流控通过限制每秒产生的排队票数量这种机制来试图保持时延在设定的目标时延以下。

自动故障处理

当一个主节点在设定的时间范围(electionTimeoutMillis,默认10秒)内没有与其他成员进行通信,就会被认为挂了。此时一个有资格的节点会举行投票并提名自己为新的主节点。集群会试图选举新的主节点来恢复正常操作。 image.png 在选举过程中,副本集无法处理写操作,但是如果查询配置未运行在从节点上,那么读操作还是可以正常进行。通常,选举出新的主节点不应该超过12秒——这是副本集设置的默认时间。这包括了将主节点标记为不可用,以及发起和完成选择这个过程。可以通过修改 settings.electionTimeoutMillis来修改这个时间。而诸如网络延迟等因素也可能延长这一时间。由于这些影响,集群可能会在没有主节点的情况下工作。 ​

electionTimeoutMillis调低能够快速检测主节点失败,但是这样也可能导致更频繁的主节点选举——尤其是对于偶尔的网络延迟而言,即便是主节点没有宕机也可能发生这种情况。应用连接逻辑应当考虑容忍自动故障处理以及接下来的选举过程。从3.6版本以后,MongoDB 的驱动会检测主节点的失败,并且会自动尝试一次写操作,从而增加了额外的内置自动故障和选举处理。其中 4.2版本及以上会自动开启该特性,而3.6-4.0版本需要在连接字符属性中包括retryWrites=true

从4.4版本后,MongoDB 提供了镜像读的方式来预热参与选举从节点,以缓存最近访问的数据。预热从节点的缓存能够加快选举后的恢复速度。

创建副本集

创建副本集的格式如下:

mongod --replSet "rs0" --bind_ip localhost,<hostname(s)|ip address(es)> --port <port>

也可以使用配置文件启动:

replication:
   replSetName: "rs0"
net:
   bindIp: localhost,<hostname(s)|ip address(es)>

如果不指定 bind_ip,默认为本机,不指定 port 则默认是27017。例如在本机创建三个节点:

# 节点1(第一个节点会设置为主节点)
mongod --replSet s0 --dbpath mongodb --port 27017 --oplogSize 100
# 节点2
mongod --replSet s0 --dbpath mongodb1 --port 27018 --oplogSize 100
# 节点3
mongod --replSet s0 --dbpath mongodb2 --port 27018 --oplogSize 100

客户端连接

可以使用 mongo --port 连接具体的实例,连接后会显示当前连接的主节点还是从节点。

# 主节点客户端
s0: PRIMARY>
# 从节点客户端
s0:SECONDARY>

如果我们在从节点执行写操作会提示:NotWritablePrimary。注意,从节点默认是不可读的,读取时会提示错误:NotPrimaryNoSecondaryOk,如果临时开启读,需要在从节点的客户端执行(旧版本是 rs.slaveOk()):

rs.secondaryOk();
# 或下面的命令
db.getMongo().setSecondaryOk();

监测节点状态:

var config = {"_id":"s0", "members":[]};
config.members.push({"_id": 0, "host": "localhost:27017"});
config.members.push({"_id": 1, "host": "localhost:27018"});
config.members.push({"_id": 2, "host": "localhost:27019"});
rs.initiate(config);
# 查看节点状态
rs.status();

总结

本篇介绍了 MongoDB 副本集的基本概念和特性,并示例了如何创建副本和客户端连接多节点的方式。