概述
Replica Set,一般翻译为副本集,RS,是MongoDB官方提供的一个数据及应用高可用解决方案。
RS由2个或更多个节点组成,其中一个用于处理写操作的是主节点(Primary),还有多个用于保存主节点的数据副本的从节点(Secondary)。如果主节点崩溃,则从节点会从其中选取出一个新的主节点。从节点定时通过oplog从主节点复制数据,实现自动failover和recovery。客户端可以从主节点或从节点读取数据,但只能往主节点写数据。
MongoDB支持两种复制的模式:
- Master Slave,主从复制,角色包括master和slave
- Replica Set,复制集复制,角色包括primary和secondary
副本集架构图
RS适用场景:
- 数据冗余
- 自动failover,提供高可用性的服务
- 分散读的负载
- 简化维护(相对于master-slave来说)
- 灾难恢复
节点
副本集中的节点主要分为三种:
- 主节点:Primary,包含所有的写操作的日志。副本服务器集群包含有所有的主服务数据,因此当主服务器挂掉时,就会在副本服务器上重新选取一个成为主服务器。
- 从节点:Seconary,也叫副本节点,正常情况下,副本集的从节点会参与主节点选举,并从主节点同步最新写入的数据,以保证与主节点存储相同的数据。通常,从节点提供读服务,增加从节点可以提供副本集的读服务能力,同时提升副本集的可用性。
副本节点可以通过配置指定其具体的属性,比如选举、隐藏、延迟同步等,最多可以有50个副本节点,但只能有7个副本节点能参与选举。 - 仲裁节点:Arbiter,只参与投票,不能被选举为主节点,并且不从主节点同步数据。当副本集成员为偶数时,最好加入一个仲裁节点,以提升副本集的可用性。当然,如果可以的话,最好使用没有仲裁者的部署。添加额外的仲裁者并不能加快选举速度,也不能提供更好的数据安全性,仅仅能使得副本集成员数为奇数防止选举出现平票。
成员属性
RS的成员具备以下常见属性:
- priority:对于副本节点,可以通过该属性来增大或减小该节点被选举成为主节点的可能性,取值范围为0-1000(如果是arbiters,则取值只有0或1),数据越大,成为主节点的可能性越大,如果被配置为0,就不能被选举成为主节点,也不能主动发起选举。
这种特性一般会被用在有多个数据中心的情况下,如一个主数据中心,一个备份数据中心,主数据中心速度会更快,如果主节点挂掉,希望新主节点也在主数据中心产生,就可以设置在备份数据中心的副本节点优先级为0 - hidden:隐藏节点会从主节点同步数据,但对客户端不可见,在mongo shell 执行
db.isMaster()
方法也不会展示该节点,隐藏节点Priority必须为0,即不可以被选举成为主节点。但是如果有配置选举权限的话,可以参与选举。
隐藏节点对客户端不可见,跟客户端不会互相影响,可以用来备份数据或者跑一些后端定时任务之类的操作 - slaveDelay:延迟同步即延迟从主节点同步数据,如延迟时间配置1小时,现在时间是 09:52,那么延迟节点中只同步到主节点 08:52 之前的数据。延迟节点必须是隐藏节点,且Priority为0。
为了防止数据库误操作,比如更新服务前,一般会先执行数据库更新脚本,如果脚本有问题,且操作前未做备份,那数据可能就找不回。但如果说配置延迟节点,那误操作完,还有该节点可以兜底。 - tags:支持对副本集成员打标签,在查询数据时会用到,如找到对应标签的副本节点,然后从该节点读取数据,可以根据标签对节点分类,查询数据时不同服务的客户端指定其对应的标签的节点,对某个标签的节点数量进行增加或减少,也不怕会影响到使用其他标签的服务。
- votes:表示节点是否有权限参与选举,最大可以配置7个副本节点参与选举。
成员类型
包括被动成员和隐藏成员:
- 被动成员
给从节点设置 priority 可以指定其成为主节点的优先级,取值范围是 0 到 100,默认是1。
优先级为 0 的从节点不参与选举,这样的从节点被称为被动成员。
拥有最高优先级的成员总是会被选举为主节点(只要它能连接到副本集中的大多数成员,并且拥有最新的数据)。 - 隐藏成员
给从节点设置 hidden 为 true 可以将其作为隐藏成员,隐藏成员只对 isMaster 不可见。
客户端不会向隐藏成员发送请求,隐藏成员也不会优先作为副本集的数据源(尽管当其他复制源不可用时隐藏成员也会被使用)。
通常会将性能较弱的服务器或者备份服务器隐藏起来,因此,隐藏成员适合做数据备份、离线计算的任务。
成员状态
成员之间通过心跳来传达自己的状态。最常见的状态就是“主节点”和“从节点”状态,其他状态:
- STARTUP: 成员在第一次启动时的状态,正在尝试加载副本集配置
- STARTUP2: 配置被加载后进入这个状态,初始化同步过程会持续处于这个状态
- RECOVERING: 成员运行正常,但不能处理读请求
- ARBITER: 仲裁节点独有的特殊状态
- DOWN: 一个成员被正常启动,但后来变为不可访问
- UNKNOWN: 如果一个成员未能访问到另一个成员,那么就不知道它处于什么状态
- REMOVED: 此成员已被从副本集中移除
- ROLLBACK: 成员正在回滚数据中会处于此状态
实战
部署
通过k8s部署副本集
该配置中描述的资源:
◈ 从核心开始,有一个名为 mongo-node1 的容器。 mongo-node1 包含一个名为 mongo 的镜像,这是一个在 Docker Hub [5] 上托管的一个公开可用的 MongoDB 容器镜像。容器在集群中暴露端口 27107。
◈ Kubernetes 的数据卷功能用于将连接器中的 /data/db 目录映射到名为 mongo-persistent-storage1 的永久存储上,这又被映射到在 Google Cloud 中创建的名为 mongodb-disk1 的磁盘中。这是 MongoDB 存储其数据的地方,这样它可以在容器重新编排后保留。
◈ 容器保存在一个 pod 中,该 pod 中有标签命名为 mongo-node,并提供一个名为 rod 的(任意)示例。
◈ 配置 mongo-node1 复制控制器以确保 mongo-node1 pod 的单个实例始终运行。
◈ 名为 mongo-svc-a 的 负载均衡 服务给外部开放了一个 IP 地址以及 27017 端口,它被映射到容器相同的端口号上。该服务使用选择器来匹配 pod 标签来确定正确的 pod。外部 IP 地址和端口将用于应用程序以及副本集成员之间的通信。每个容器也有本地 IP 地址,但是当容器移动或重新启动时,这些 IP 地址会变化,因此不会用于副本集。
添加节点
副本集数据量比较大时如何添加次节点
当副本集的数据量比较大时,添加新节点时,如果使用初始化同步的话,会给主节点造成比较大的压力。有以下选择:
- 暂停当前副本集中的一个次节点,然后将 次节点的数据(data)复制到要新添加的节点的数据目录。然后再将两个节点启动。(建议先测试)
如果要复制数据文件,请确保您的副本包含local数据库的内容。 - 指定同步节点 rs.syncFrom(hostportstr),指定同步节点为次节点,默认的是同步节点 是主节点。
但是该设置在重新启动节点,或者同步指定的新节点的连接被关闭了,是会失效的。(建议先测试)
选举
触发时机
当出现以下情况时,会触发选举机制:
- 初始化副本集时
- 往副本集中新加入节点
- 对副本集进行维护时,如执行
rs.stepDown()
或rs.reconfig()
操作时 - 从节点失联时,如超时(默认是10秒)
以下因素会影响到选举结果:
- 副本集的选举协议
- 心跳
- 成员权重
- 数据中心失联
- 网络分区
- 镜像读取
故障转移回滚
回滚指的是,当成员在故障转移后重新加入其副本集时,将还原之前主节点上的写操作,并恢复成现在主节点的状态数据。
仅当节点接收到主节点降级前未成功复制的写操作后,重新加入副本集群之后发现与现有主节点的数据不一致时,才需要回滚。
当节点重新加入到副本集群时,它会还原或“回滚”其不一致的写操作,以保持与其他成员的一致性。
写关注
Write concern,指写入一条数据,主节点处理完成后,需要其他承载数据的副本节点也确认写成功后,才能给客户端返回写入数据成功。
这个功能主要是解决主节点挂掉后,数据还未来得及同步到副本节点,而导致数据丢失的问题。
可以配置节点个数,默认配置{"w": 1}
,表示主节点写入数据成功,即可给客户端返回成功;{"w": 2}
,表示除主节点,还需要收到其中一个副本节点返回写入成功,才能给客户端返回成功;{"w": majority}
,表示需要集群中大多数承载数据且有选举权限的节点返回写入成功。
在写请求中指定 writeConcern 相关参数:
db.products.insert(
{ item: "envelopes", qty: 100, type: "Clasp" },
{ writeConcern: { w: "majority", wtimeout: 5000 } }
)
修改副本集 getLastErrorDefaults 配置:
cfg = rs.conf()
cfg.settings.getLastErrorDefaults = { w: "majority", wtimeout: 5000 }
rs.reconfig(cfg)
读偏好
Read preference,为了保持一致性,写只能通过主节点,但读可以选择主节点,也可以选择副本节点,区别是主节点数据最新,副本节点因为同步问题可能会有延迟,但从副本节点读取数据可以分散对主节点的压力。
读写模式
默认情况下,副本集的所有读请求都发送到主节点,Driver 可通过设置 Read Preference 来将请求路由到其他节点,规则如下:
- primary:默认规则,所有读请求发送到主节点
- primaryPreferred:主节点优先,如果主节点不可达,请求从节点
- secondary:所有读请求发送到从节点
- secondaryPreferred:从节点优先,当所有从节点不可达时请求主节点
- nearest:读请求发送到最近的可达节点上(通过 ping 探测得出最近的节点)
3个条件,条件是在符合模式的基础上,再根据条件删选具体的节点
- Tag Sets(标签)
这个可以给节点加上标签,然后查找数据时,可以根据标签选择对应的节点,然后在该节点查找数据。可通过mongo shell 使用rs.conf()
查看当前每个节点下面的 tags, 修改或添加tags 过程同上面修改 getLastErrorDefaults 配置 ,如:cfg.members[n].tags = { "region": "South", "datacenter": "A" }
- maxStalenessSeconds (可容忍的最大同步延迟)
这个值是指副本节点同步主节点写入的时间 跟 主节点实际最近写入时间的对比值,如果主节点挂掉了,那就跟副本集中最新写入的时间做对比。
建议设置,避免因为部分副本节点网络原因导致比较长时间未同步主节点数据,然后读到比较老的数据。该值需要设置 90s 以上,因为客户端是定时去校验副本节点的同步延迟时间,数据不会特别准确,设置比 90s 小,会抛出异常。 - Hedged Read (对冲读取)
MongoDB 4.4 版本后才支持,指 mongos 实例路由读取请求时会同时发给两个符合条件的副本集节点,然后那个先返回结果就返回这个结果给客户端。
使用connection string uri时,可加上以下三个参数:
- readPreference,枚举值:primary、 primaryPreferred、secondary、secondaryPreferred、nearest
- maxStalenessSeconds,最大同步延时秒数,取值0 - 90会报错, -1 表示没有最大值
- readPreferenceTags 标签,如果标签是
{ "dc": "ny", "rack": "r1" }
,则在uri 为readPreferenceTags=dc:ny,rack:r1
在mogo shell 中,可使用cursor.readPref()
或Mongo.setReadPref()
,cursor.readPref()
参数分别为:mode、tag set、hedge options,例如:
db.collection.find({}).readPref(
"secondary", // mode
[ { "datacenter": "B" }, { } ], // tag set
{ enabled: true } // hedge options
)
Mongo.setReadPref()
类似,预先设置请求条件,这样就不用每个请求后面带上 readPref 条件。
命令
MongoDB提供RS方法:
rs.help()
:查看帮助rs.initiate()
:初始化rs.conf()
:查看当前的配置rs.reconfig()
:需找到primary主机,在该主节点服务器上才有权限修改配置rs.add('ip:port')
:添加节点rs.addArb('ip:port')
:添加仲裁节点rs.remove('ip:port')
:移除节点rs.status()
:查看各个节点状态和身份rs.slaveOk()
:次节点执行,表示允许次节点读取数据rs.freeze(secs)
:设定某个节点多少秒不可成为主节点rs.isMaster()
:判断当前节点是否是主节点rs.syncFrom(hostportstr)
:设置次节点从指定节点同步数据rs.stepDown([stepdownSecs, catchUpSecs])
:降低主节点为次节点,只能在主节点上运行,进而触发选举rs.printReplicationInfo()
:以主节点的视角打印RS集群信息rs.printSecondaryReplicationInfo()
:以从节点的视角打印RS集群信息rs.printSlaveReplicationInfo()
:自版本4.4.1后已废弃,使用rs.printSecondaryReplicationInfo()
rs.reconfigForPSASet()
:
MongoDB提供的与RS相关的DB方法db.printSlaveReplicationInfo()
:查看副本集的次节点与主节点延迟