简介

最近一个 MongoDB 集群环境中的某节点异常下电了,导致业务出现了中断,随即又恢复了正常。
通过ELK 告警也监测到了业务报错日志。

运维部对于节点下电的原因进行了排查,发现仅仅是资源分配上的一个失误导致。 在解决了问题之后,大家也对这次中断的也提出了一些问题:

>”当前的 MongoDB集群 采用了分片副本集的架构,其中主节点发生故障会产生多大的影响?”
>”MongoDB 副本集不是能自动倒换吗,这个是不是秒级的?”

带着这些问题,下面针对副本集的自动Failover机制做一些分析。

日志分析

首先可以确认的是,这次掉电的是一个副本集上的主节点,在掉电的时候,主备关系发生了切换。
从另外的两个备节点找到了对应的日志:

备节点1的日志

2019-05-06T16:51:11.766+0800 I REPL     [ReplicationExecutor] Starting an election, since we've seen no PRIMARY in the past 10000ms
2019-05-06T16:51:11.766+0800 I REPL     [ReplicationExecutor] conducting a dry run election to see if we could be elected
2019-05-06T16:51:11.766+0800 I ASIO     [NetworkInterfaceASIO-Replication-0] Connecting to 172.30.129.78:30071
2019-05-06T16:51:11.767+0800 I REPL     [ReplicationExecutor] VoteRequester(term 3 dry run) received a yes vote from 172.30.129.7:30071; response message: { term: 3, voteGranted: true, reason: "", ok: 1.0 }
2019-05-06T16:51:11.767+0800 I REPL     [ReplicationExecutor] dry election run succeeded, running for election
2019-05-06T16:51:11.768+0800 I ASIO     [NetworkInterfaceASIO-Replication-0] Connecting to 172.30.129.78:30071
2019-05-06T16:51:11.771+0800 I REPL     [ReplicationExecutor] VoteRequester(term 4) received a yes vote from 172.30.129.7:30071; response message: { term: 4, voteGranted: true, reason: "", ok: 1.0 }
2019-05-06T16:51:11.771+0800 I REPL     [ReplicationExecutor] election succeeded, assuming primary role in term 4
2019-05-06T16:51:11.771+0800 I REPL     [ReplicationExecutor] transition to PRIMARY
2019-05-06T16:51:11.771+0800 I REPL     [ReplicationExecutor] Entering primary catch-up mode.
2019-05-06T16:51:11.771+0800 I ASIO     [NetworkInterfaceASIO-Replication-0] Ending connection to host 172.30.129.78:30071 due to bad connection status; 2 connections to that host remain open
2019-05-06T16:51:11.771+0800 I ASIO     [NetworkInterfaceASIO-Replication-0] Connecting to 172.30.129.78:30071
2019-05-06T16:51:13.350+0800 I REPL     [ReplicationExecutor] Error in heartbeat request to 172.30.129.78:30071; ExceededTimeLimit: Couldn't get a connection within the time limit

备节点2的日志

2019-05-06T16:51:12.816+0800 I ASIO     [NetworkInterfaceASIO-Replication-0] Ending connection to host 172.30.129.78:30071 due to bad connection status; 0 connections to that host remain open
2019-05-06T16:51:12.816+0800 I REPL     [ReplicationExecutor] Error in heartbeat request to 172.30.129.78:30071; ExceededTimeLimit: Operation timed out, request was RemoteCommand 72553 -- target:172.30.129.78:30071 db:admin expDate:2019-05-06T16:51:12.816+0800 cmd:{ replSetHeartbeat: "shard0", configVersion: 96911, from: "172.30.129.7:30071", fromId: 1, term: 3 }
2019-05-06T16:51:12.821+0800 I REPL     [ReplicationExecutor] Member 172.30.129.160:30071 is now in state PRIMARY

可以看到,备节点1在 16:51:11 时主动发起了选举,并成为了新的主节点,随即备节点2在 16:51:12 获知了最新的主节点信息,因此可以确认此时主备切换已经完成。
同时在日志中出现的,还有对于原主节点(172.30.129.78:30071)大量心跳失败的信息。

那么,备节点具体是怎么感知到主节点已经 Down 掉的,主备节点之间的心跳是如何运作的,这对数据的同步复制又有什么影响?
下面,我们挖掘一下 ** 副本集的故障转移(Failover)** 机制

副本集 如何实现 Failover

如下是一个PSS(一主两备)架构的副本集,主节点除了与两个备节点执行数据复制之外,三个节点之间还会通过心跳感知彼此的存活。

一旦主节点发生故障以后,备节点将在某个周期内检测到主节点处于不可达的状态,此后将由其中一个备节点事先发起选举并最终成为新的主节点。 这个检测周期 由electionTimeoutMillis 参数确定,默认是10s。

接下来,我们通过一些源码看看该机制是如何实现的:

<>

db/repl/replication_coordinator_impl_heartbeat.cpp
相关方法
- ReplicationCoordinatorImpl::_startHeartbeats_inlock 启动各成员的心跳
- ReplicationCoordinatorImpl::_scheduleHeartbeatToTarget 调度任务-(计划)向成员发起心跳
- ReplicationCoordinatorImpl::_doMemberHeartbeat 执行向成员发起心跳
- ReplicationCoordinatorImpl::_handleHeartbeatResponse 处理心跳响应
- ReplicationCoordinatorImpl::_scheduleNextLivenessUpdate_inlock 调度保活状态检查定时器
- ReplicationCoordinatorImpl::_cancelAndRescheduleElectionTimeout_inlock 取消并重新调度选举超时定时器
- ReplicationCoordinatorImpl::_startElectSelfIfEligibleV1 发起主动选举

db/repl/topology_coordinator_impl.cpp
相关方法
- TopologyCoordinatorImpl::prepareHeartbeatRequestV1 构造心跳请求数据
- TopologyCoordinatorImpl::processHeartbeatResponse 处理心跳响应并构造下一步Action实例

下面这个图,描述了各个方法之间的调用关系

图-主要关系

心跳的实现

首先,在副本集组建完成之后,节点会通过ReplicationCoordinatorImpl::_startHeartbeats_inlock方法开始向其他成员发送心跳:

void ReplicationCoordinatorImpl::_startHeartbeats_inlock() {const Date_t now = _replExecutor.now();_seedList.clear();//获取副本集成员for (int i = 0; i restartHeartbeats();//使用V1的选举协议(3.2之后)if (isV1ElectionProtocol()) {for (auto&amp;&amp; slaveInfo : _slaveInfo) {slaveInfo.lastUpdate = _replExecutor.now();slaveInfo.down = false;}//调度保活状态检查定时器_scheduleNextLivenessUpdate_inlock();}
}

在获得当前副本集的节点信息后,调用_scheduleHeartbeatToTarget方法对其他成员发送心跳,
这里_scheduleHeartbeatToTarget 的实现比较简单,其真正发起心跳是由 _doMemberHeartbeat 实现的,如下:

void ReplicationCoordinatorImpl::_scheduleHeartbeatToTarget(const HostAndPort&amp; target,int targetIndex,Date_t when) {//执行调度,在某个时间点调用_doMemberHeartbeat_trackHeartbeatHandle(_replExecutor.scheduleWorkAt(when,stdx::bind(&amp;ReplicationCoordinatorImpl::_doMemberHeartbeat,this,stdx::placeholders::_1,target,targetIndex)));
}

ReplicationCoordinatorImpl::_doMemberHeartbeat 方法的实现如下:

void ReplicationCoordinatorImpl::_doMemberHeartbeat(ReplicationExecutor::CallbackArgs cbData,const HostAndPort&amp; target,int targetIndex) {LockGuard topoLock(_topoMutex);//取消callback 跟踪_untrackHeartbeatHandle(cbData.myHandle);if (cbData.status == ErrorCodes::CallbackCanceled) {return;}const Date_t now = _replExecutor.now();BSONObj heartbeatObj;Milliseconds timeout(0);//3.2 以后的版本if (isV1ElectionProtocol()) {const std::pair hbRequest =_topCoord-&gt;prepareHeartbeatRequestV1(now, _settings.ourSetName(), target);//构造请求,设置一个timeoutheartbeatObj = hbRequest.first.toBSON();timeout = hbRequest.second;} else {...}//构造远程命令const RemoteCommandRequest request(target, "admin", heartbeatObj, BSON(rpc::kReplSetMetadataFieldName &lt;getTerm()) {//取消并重新调度 electionTimeout定时器cancelAndRescheduleElectionTimeout();}}...//调用topCoord的processHeartbeatResponse方法处理心跳响应状态,并返回下一步执行的ActionHeartbeatResponseAction action = _topCoord-&gt;processHeartbeatResponse(now, networkTime, target, hbStatusResponse, lastApplied);...//调度下一次心跳,时间间隔采用action提供的信息_scheduleHeartbeatToTarget(target, targetIndex, std::max(now, action.getNextHeartbeatStartDate()));//根据Action 执行处理_handleHeartbeatResponseAction(action, hbStatusResponse, false);
}

这里省略了许多细节,但仍然可以看到,在响应心跳时会包含这些事情的处理:
- 对于主节点的成功响应,会重新调度 electionTimeout定时器(取消之前的调度并重新发起)
- 通过_topCoord对象的processHeartbeatResponse方法解析处理心跳响应,并返回下一步的Action指示
- 根据Action 指示中的下一次心跳时间设置下一次心跳定时任务
- 处理Action指示的动作

那么,心跳响应之后会等待多久继续下一次心跳呢? 在 TopologyCoordinatorImpl::processHeartbeatResponse方法中,实现逻辑为:
如果心跳响应成功,会等待heartbeatInterval,该值是一个可配参数,默认为2s;
如果心跳响应失败,则会直接发送心跳(不等待)。

代码如下:

HeartbeatResponseAction TopologyCoordinatorImpl::processHeartbeatResponse(...) {...const Milliseconds alreadyElapsed = now - hbStats.getLastHeartbeatStartDate();Date_t nextHeartbeatStartDate;// 计算下一次 心跳启动时间// numFailuresSinceLastStart 对应连续失败的次数(2次以内)if (hbStats.getNumFailuresSinceLastStart() &lt;= kMaxHeartbeatRetries &amp;&amp;alreadyElapsed = _rsConfig.getElectionTimeoutPeriod()) {...//在保活周期后仍然未更新节点,置为down状态slaveInfo.down = true;//如果当前节点是主,且检测到某个备节点为down的状态,进入memberdown流程if (_memberState.primary()) {//调用_topCoord的setMemberAsDown方法,记录某个备节点不可达,并获得下一步的指示//当大多数节点不可见时,这里会获得让自身降备的指示HeartbeatResponseAction action =_topCoord-&gt;setMemberAsDown(now, memberIndex, _getMyLastDurableOpTime_inlock());//执行指示_handleHeartbeatResponseAction(action,makeStatusWith(),true);}}}//继续调度下一个周期_scheduleNextLivenessUpdate_inlock();
}

可以看到,这个定时器主要是用于实现主节点对其他节点的保活探测逻辑:

当主节点发现大多数节点不可达时(不满足大多数原则),将会让自己执行降备

因此,在一个三节点的副本集中,其中两个备节点挂掉后,主节点会自动降备。 这样的设计主要是为了避免产生意外的数据不一致情况产生。


图- 主自动降备

第二个是_cancelAndRescheduleElectionTimeout_inlock函数,这里则是实现自动Failover的关键了,
它的逻辑中包含了一个选举定时器,代码如下:

void ReplicationCoordinatorImpl::_cancelAndRescheduleElectionTimeout_inlock() {//如果上一个定时器已经启用了,则直接取消if (_handleElectionTimeoutCbh.isValid()) {LOG(4) &lt;&lt; &quot;Canceling election timeout callback at &quot; &lt;&lt; _handleElectionTimeoutWhen;_replExecutor.cancel(_handleElectionTimeoutCbh);_handleElectionTimeoutCbh = CallbackHandle();_handleElectionTimeoutWhen = Date_t();}//仅支持3.2后的V1版本if (!isV1ElectionProtocol()) {return;}//仅备节点可执行if (!_memberState.secondary()) {return;}...//是否可以选举if (!_rsConfig.getMemberAt(_selfIndex).isElectable()) {return;}//检测周期,由 electionTimeout + randomOffset//randomOffset是随机偏移量,默认为 0~0.15*ElectionTimeoutPeriod = 0~1.5sMilliseconds randomOffset = _getRandomizedElectionOffset();auto now = _replExecutor.now();auto when = now + _rsConfig.getElectionTimeoutPeriod() + randomOffset;LOG(4) &lt;&lt; &quot;Scheduling election timeout callback at &quot; &lt;&lt; when;_handleElectionTimeoutWhen = when;//触发调度,时间为 now + ElectionTimeoutPeriod + randomOffset_handleElectionTimeoutCbh =_scheduleWorkAt(when,stdx::bind(&amp;ReplicationCoordinatorImpl::_startElectSelfIfEligibleV1,this,StartElectionV1Reason::kElectionTimeout));
}

上面代码展示了这个选举定时器的逻辑,在每一个检测周期中,定时器都会尝试执行超时回调,
而回调函数指向的是_startElectSelfIfEligibleV1,这里面就实现了主动发起选举的功能,
如果心跳响应成功,通过cancelAndRescheduleElectionTimeout调用将直接取消当次的超时回调(即不会发起选举)
如果心跳响应迟迟不能成功,那么定时器将被触发,进而导致备节点发起选举并成为新的主节点!

同时,这个回调方法(产生选举)被触发必须要满足以下条件:
1. 当前是备节点
2. 当前节点具备选举权限
3. 在检测周期内仍然没有与主节点心跳成功

这其中的检测周期略大于electionTimeout(10s),加入一个随机偏移量后大约是10-11.5s内,猜测这样的设计是为了错开多个备节点主动选举的时间,提升成功率。
最后,将整个自动选举切换的逻辑梳理后,如下图所示:


图-超时自动选举

业务影响评估

副本集发生主备切换的情况下,不会影响现有的读操作,只会影响写操作。 如果使用3.6及以上版本的驱动,可以通过开启retryWrite来降低影响。
但是如果主节点是属于强制掉电,那么整个 Failover 过程将会变长,很可能需要在Election定时器超时后才被副本集感知并恢复,这个时间窗口会在12s以内。
此外还需要考虑客户端或mongos对于副本集角色的监视和感知行为。但总之在问题恢复之前,对于原主节点的任何读写都会发生超时。
因此,对于极为重要的业务,建议最好在业务层面做一些防护策略,比如设计重试机制。

参考链接

https://docs.mongodb.com/manual/replication/#automatic-failover

https://www.percona.com/blog/2016/05/25/mongodb-3-2-elections-just-got-better/

https://www.percona.com/blog/2018/10/10/mongodb-replica-set-scenarios-and-internals/

文章来自MongoDB中文社区

关于zale

唐卓章,华为技术专家,多年互联网研发/架设经验,关注NOSQL 中间件高可用及弹性扩展,在分布式系统架构性能优化方面有丰富的实践经验,目前从事物联网平台研发工作,致力于打造大容量高可用的物联网服务。

转载于:https://my.oschina.net/u/2493367/blog/3069128

副本集是如何实现自动Failover的相关推荐

  1. MongoDB-4.4.2单机安装以及副本集配置

    目录 1 MongoDB-4.4.2的单机安装 1.1 软件包的下载 1.2 安装MongoDB-4.4.2 1.2.1 解压 1.2.2 重命名 1.2.3 配置环境变量 1.3 启动MongoDB ...

  2. (2)MongoDB副本集自动故障转移原理(含客户端)

    前文我们搭建MongoDB三成员副本集,了解集群基本特性,今天我们围绕下图聊一聊背后的细节. 默认搭建的副本集均在主节点读写,辅助节点冗余部署,形成高可用和备份,具备自动故障转移能力. 集群心跳保活 ...

  3. MongoDB之副本集

    MongoDB之副本集 一.简介 MongoDB 是一个基于分布式文件存储的数据库.由 C++ 语言编写.旨在为 WEB 应用提供可扩展的高性能数据存储解决方案. MongoDB 是一个介于关系数据库 ...

  4. 【Redis3】基于Redis sentinel的自动failover主从复制

    [Redis3]基于Redis sentinel的自动failover主从复制 在第二篇中使用2.8.17搭建了主从复制,但是它存在Master单点问题,为了解决这个问题,Redis从2.6开始引入s ...

  5. MongoDB副本集学习(一):概述和环境搭建

    MongoDB副本集概述 以下图片摘自MongoDB官方文档:http://docs.mongodb.org/manual/core/replication-introduction/ Primary ...

  6. MongoDB 副本集之入门篇

    前言:mongodb 因为高性能.高可用性.支持分片等特性,作为非关系型数据库被大家广泛使用.其高可用性主要是体现在 mongodb 的副本集上面(可以简单理解为一主多从的集群),本篇文章主要从副本集 ...

  7. SpringBoot整合MongoDB以及副本集、分片集群的搭建

    整合springboot应用 说明: 这里主要以 springboot 应用为基础应用进行整合开发. Spring Data : Spring 数据框架 JPA .Redis.Elasticsearc ...

  8. 7.Mongodb复制(副本集)

    1.复制 什么是复制 复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性,并可以保证数据的安全性 复制还允许从硬件故障和服务中断中恢复数据 为什么要复制 数据备份 数据灾难恢复 ...

  9. MongoDB 搭建副本集

    副本集(Replica Set)是一组MongoDB实例组成的集群,由一个主(Primary)服务器和多个备份(Secondary)服务器构成.通过Replication,将数据的更新由Primary ...

最新文章

  1. JavaScript速记
  2. 嵌入式linux驱动开发之点亮led(驱动编程思想之初体验)
  3. Python爬虫==【openurl】
  4. 北京交通大学研究生教务处爬虫
  5. ast抽象语法树_新抽象语法树(AST)给 PHP7 带来的变化
  6. 【神经网络】线性模型非线性模型,感知机与神经网络
  7. JSON | JSON字符串和JSON对象的区别
  8. 【Python CheckiO 题解】Remove Accents
  9. python膨胀卷积_python里有没有轻量级的卷积网络库,不需要训练,只想快速前向计算?...
  10. win7提示由于系统注册表文件丢失或损坏,无法开机
  11. linux怎么安装uwf命令,linux实题小练
  12. WinFrom窗体MyQQ程序(附源码)
  13. Unity3d报错:Error building Player: Win32Exception: ApplicationName='xxxxxx/zipalign.exe'
  14. 2010.4 计算机二级等级考试 vb上机试题 第一套 的答案,2012年计算机二级VB上机试题及解题思路第44套...
  15. pentaho使用步骤简介
  16. 使用fiddler代替charles将react中的axios请求重定向到本地文件,兼容谷歌浏览器chrome
  17. nav 计算机网络_计算机网络课件谢希仁
  18. 去掉数组中是数字的引号
  19. orbslam2稠密地图转octomap
  20. 导电滑环检测方法检测导电滑环时要注意什么

热门文章

  1. 后端-医院管理之医院列表实现
  2. 领先特斯拉,中国电车制造商“登陆”东南亚,电气化潮流一触即发
  3. Android 程序员不得不收藏的 90+ 个人博客(持续更新,2021最新华为Android校招面试题
  4. 自行车存放管理系统c语言课程设计,基于51单片机的自行车智能辅助系统设计-(Proteus仿真图+源程序+论文)...
  5. 收费企业邮箱注册,大企业邮箱品牌哪家好?如何注册公司邮箱?
  6. “黑洞”戳破中国版权乱象 迅雷、纸贵、京东、趣链给出区块链解决方案
  7. Win11年度版本更新了啥?详细剖析Win11 22H2
  8. 亚马逊的策略分析:不仅仅是电商,AI构建新的业务支柱
  9. 【C语言】结构体排序
  10. 晶闸管整流器——直流电动及系统的工作原理及调速特性