Loading...
墨滴

岛上码农@公众号同名

2021/09/12  阅读:23  主题:默认主题

创建多个副本进程实现高可用服务

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

概述

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

冗余性和数据可用性

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

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

MongoDB 的复制

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

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

image.png
image.png

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

image.png
image.png

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

image.png
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();

读偏好选(Read Preference)择

默认情况下,客户端从主节点读取数据。但是就像我们上一篇演示的那样,可以指定客户端访问从节点读取。

mongo --port <port> --host <host | IP>
image.png
image.png

但是,由于存在主从异步复制数据,客户端在从节点读取的数据可能会和主节点的不一样。对于多文档的事务操作中,如果包含了读操作,那么必须要从主节点读取数据。对于同一个事务来说,不可以跨节点操作。读偏好操作有三种模式: 主节点:所有读操作都使用当前的主节点,这是默认的模式。如果主节点宕机,读操作讲抛出异常。主节点模式与使用标签集(tag sets)或最大有效延迟时间(maxStalenessSeconds)的读操作不兼容。

主节点优先:这种模式下,在大多数情况下,都是从主节点读取数据。然而,如果主节点不可用的时候,读操作会在满足最大有效延迟时间及标签集后切换到从节点读取数据。当设置为主节点优先模式且设置了最大有效延迟时间,当发生主节点不可用时,客户端会通过最后一次写入数据的时间评估各个从节点的时效性。然后选择一个低于或等于最大有效延迟时间的从节点读取数据。

当读偏好设置包含了标签集,主节点不可用时,客户端视图查找匹配标签的从节点(基于标签规则按需匹配直到找到一个)。如果找到了匹配的从节点,客户端则从匹配的节点中最临近的组中随机选择一个从节点读取数据。但如果没有找到的话,那就会产生一个错误。

当同时包含了最大有效延迟时间和标签集时,客户端会优先通过时效性来选择哪个从节点。使用主节点优先的读操作可能会返回滞后的数据。因此使用最大有效延迟时间选项可以避免读取过旧的数据。

从节点:客户端只从从节点读取数据。如果没有可用的从节点,读操作会产生错误或异常。大部分副本集会至少有一个从节点,但是也可能存在不可用的从节点(全部从节点宕机)。如果这种模式设置了最大有效延迟时间,客户端会评估主节点写入从节点的时效性,然后选择延时低于或等于最大有效延迟时间的节点。如果没有主节点,将会选择从写入时间最近的从节点读取数据。

当从节点模式包括了标签集时,客户端会视图找到匹配标签的从节点读取数据。如果找到了匹配的从节点,客户端则从匹配的节点中最临近的组中随机选择一个从节点读取数据。但如果没有找到的话,那就会产生一个错误。同样的,从节点模式也可能读取到滞后的数据,可以设置最大有效时间延迟来避免读取过旧的数据。

从节点优先:这种模式下,大多数情况下都是从从节点读取数据。但如果这个复制集只有一个主节点,就会从主节点读取数据。如果有多个可用的从节点,那选择哪个从节点和从节点模式一样。

最近模式(nearest):这种模式下,客户端不区分主从节点,而是会从网络延迟落在可接受的延迟范围内的节点读取数据。这种模式可以以最低的网络延迟中选择节点,因此默认情况下不会对数据的时效性做出偏好选择。

如果设置了最大有效延迟时间,客户端会评估主节点写入从节点的时效性,存在主节点的时候,客户端会评估会主节点写入各个从节点的时效性;如果没有主节点,则调出最近写入的节点。然后从这些节点中过滤掉那些超过最大有效延迟时间的节点,再从剩下的节点中(不区分主从节点)挑选网络时延低于可接受范围的节点读取数据。

如果指定了标签集,客户端会试图找到匹配标签集的节点,然而从最临近的组中选举一个节点读取数据。如果同时指定了最大有效延迟时间和标签集,客户端会先使用时效性过滤节点,接着使用标签集过滤。对于剩下的 mongod 实例,客户端随机地从满足网络时延的节点中挑选一个读取数据。

同样的,使用这种模式也可能读取到过期数据,可以通过设置最大有效延迟时间来降低这种情况发生的机率。

配置读偏好

如果使用了 MongoDB 的驱动,可以从驱动的 API 中配置读偏好模式。当使用 mongo 客户端连接时,可以使用 cursor.readPref()和 Mongo.setReadRef()来设置。

db.getMongo().setReadPref(
   "secondary",  //从节点模式
   [ 
      { "datacenter""B" },    // 先匹配数据中心标签
      { "region""West"},      // 如果没找到,就从区域标签匹配
      { }                       // 如果没找到,就使用空文档匹配所有候选节点
   ]
)
db.collection.find({ }).readPref(
   "secondary",
   [
      { "datacenter""B" },    // 先匹配数据中心标签
      { "region""West"},      
      { }
   ]
)

总结

可以看到,MongoDB 的复制集针对读操作配置了多种模式。其中标签模式更是适用于分布式存储的情况。这也是 MongoDB 相对其他数据库的一些优点,在集群部署上提供了很多可选的方式进行配置,以提高可用性和降低数据延迟。

关注岛上码农
关注岛上码农

岛上码农@公众号同名

2021/09/12  阅读:23  主题:默认主题

作者介绍

岛上码农@公众号同名