hi,你好!欢迎访问本站!登录
本站由网站地图腾讯云宝塔系统阿里云强势驱动
当前位置:首页 - 教程 - 杂谈 - 正文 君子好学,自强不息!

MongoDB一次节点宕机激发的思索(源码理会)

2019-11-18杂谈搜奇网38°c
A+ A-

目次

  • 简介
  • 日记剖析
  • 副本集 怎样完成 Failover
    • 心跳的完成
    • electionTimeout 定时器
  • 营业影响评价
  • 参考链接

声明:本文同步发表于 MongoDB 中文社区,传送门:
http://www.mongoing.com/archives/26759

简介

近来一个 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。

接下来,我们经由历程一些源码看看该机制是怎样完成的:

<<来自 MongoDB 3.4源码>>

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 < _rsConfig.getNumMembers(); ++i) {
        if (i == _selfIndex) {
            continue;
        }
        //向其他成员发送心跳
        _scheduleHeartbeatToTarget(_rsConfig.getMemberAt(i).getHostAndPort(), i, now);
    }

    //仅仅是革新当地的心跳状况数据
    _topCoord->restartHeartbeats();

    //运用V1的推举协定(3.2今后)
    if (isV1ElectionProtocol()) {
        for (auto&& slaveInfo : _slaveInfo) {
            slaveInfo.lastUpdate = _replExecutor.now();
            slaveInfo.down = false;
        }

        //调理保活状况搜检定时器
        _scheduleNextLivenessUpdate_inlock();
    }
}

在取得当前副本集的节点信息后,挪用_scheduleHeartbeatToTarget要领对其他成员发送心跳,
这里_scheduleHeartbeatToTarget 的完成比较简单,其真正提议心跳是由 _doMemberHeartbeat 完成的,以下:

void ReplicationCoordinatorImpl::_scheduleHeartbeatToTarget(const HostAndPort& target,
                                                            int targetIndex,
                                                            Date_t when) {
    //实行调理,在某个时候点挪用_doMemberHeartbeat
    _trackHeartbeatHandle(
        _replExecutor.scheduleWorkAt(when,
                                     stdx::bind(&ReplicationCoordinatorImpl::_doMemberHeartbeat,
                                                this,
                                                stdx::placeholders::_1,
                                                target,
                                                targetIndex)));
}

ReplicationCoordinatorImpl::_doMemberHeartbeat 要领的完成以下:

void ReplicationCoordinatorImpl::_doMemberHeartbeat(ReplicationExecutor::CallbackArgs cbData,
                                                    const HostAndPort& 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<ReplSetHeartbeatArgsV1, Milliseconds> hbRequest =
            _topCoord->prepareHeartbeatRequestV1(now, _settings.ourSetName(), target);
        //组织要求,设置一个timeout
        heartbeatObj = hbRequest.first.toBSON();
        timeout = hbRequest.second;
    } else {
        ...
    }

    //组织长途敕令
    const RemoteCommandRequest request(
        target, "admin", heartbeatObj, BSON(rpc::kReplSetMetadataFieldName << 1), nullptr, timeout);

    //设置长途敕令回调,指向_handleHeartbeatResponse要领
    const ReplicationExecutor::RemoteCommandCallbackFn callback =
        stdx::bind(&ReplicationCoordinatorImpl::_handleHeartbeatResponse,
                   this,
                   stdx::placeholders::_1,
                   targetIndex);

    _trackHeartbeatHandle(_replExecutor.scheduleRemoteCommand(request, callback));
}

上面的代码中存在的一些细节:

  • 心跳的超时时候,在_topCoord.prepareHeartbeatRequestV1要领中就已设定好了
    详细的算法就是:

**hbTimeout=_rsConfig.getHeartbeatTimeoutPeriodMillis() - alreadyElapsed**

个中heartbeatTimeoutPeriodMillis是可设置的参数,默许是10s, 那末alreadyElapsed是指此前一连心跳失利(最多2次)累计的斲丧时候,在心跳胜利相应或许凌驾10s后alreadyElapsed会置为0。因而能够推断,跟着心跳失利次数的增添,超时时候会越来越短(心跳越发麋集)

  • 心跳实行的回调,指向本身的_handleHeartbeatResponse要领,该函数完成了心跳相应胜利、失利(或是超时)今后的流程处置惩罚。

ReplicationCoordinatorImpl::_handleHeartbeatResponse要领的代码片断:

void ReplicationCoordinatorImpl::_handleHeartbeatResponse(
    const ReplicationExecutor::RemoteCommandCallbackArgs& cbData, int targetIndex) {
    LockGuard topoLock(_topoMutex);

    // remove handle from queued heartbeats
    _untrackHeartbeatHandle(cbData.myHandle);
    ...

    //相应胜利后
    if (responseStatus.isOK()) {
        networkTime = cbData.response.elapsedMillis.value_or(Milliseconds{0});
        const auto& hbResponse = hbStatusResponse.getValue();

        // 只需primary 心跳相应胜利,就会从新调理 electionTimeout定时器
        if (hbResponse.hasState() && hbResponse.getState().primary() &&
            hbResponse.getTerm() == _topCoord->getTerm()) {

            //作废并从新调理 electionTimeout定时器
            cancelAndRescheduleElectionTimeout();
        }
    }
    ...
    //挪用topCoord的processHeartbeatResponse要领处置惩罚心跳相应状况,并返回下一步实行的Action
    HeartbeatResponseAction action = _topCoord->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() <= kMaxHeartbeatRetries &&
        alreadyElapsed < _rsConfig.getHeartbeatTimeoutPeriod()) {
        // 心跳失利,不守候,直接重试心跳
        nextHeartbeatStartDate = now;
    } else {
        // 心跳胜利,守候肯定距离后再次发送(平常是2s)
        nextHeartbeatStartDate = now + heartbeatInterval;
    }

    ...
    // 决议下一步的行动,可能发作 tack over(本备节点优先级更高,且数据与主节点一样新时)
    HeartbeatResponseAction nextAction;
    if (_rsConfig.getProtocolVersion() == 0) {
       ...
    } else {
        nextAction = _updatePrimaryFromHBDataV1(memberIndex, originalState, now, myLastOpApplied);
    }
    nextAction.setNextHeartbeatStartDate(nextHeartbeatStartDate);
    return nextAction;
}

electionTimeout 定时器

至此,我们已知道了心跳完成的一些细节,默许状况下副本集节点会每2s向其他节点发出心跳(默许的超时时候是10s)。
假如心跳胜利,将会延续以2s的频次继承发送心跳,在心跳失利的状况下,则会马上重试心跳(以更短的超时时候),一直到心跳恢复胜利或许凌驾10s的周期。

那末,心跳失利是怎样触发主备切换的呢,electionTimeout 又是怎样发挥作用?

在前面的历程当中,与electionTimeout参数相干两个要领以下,它们也离别对应了零丁的定时器:

  • ReplicationCoordinatorImpl::_scheduleNextLivenessUpdate_inlock 提议保活状况搜检定时器
  • ReplicationCoordinatorImpl::_cancelAndRescheduleElectionTimeout_inlock 从新提议推举超时定时器

第一个是 _scheduleNextLivenessUpdate_inlock这个函数,它的作用在于保活状况检测,以下:

void ReplicationCoordinatorImpl::_scheduleNextLivenessUpdate_inlock() {
    //仅仅支撑3.2+
    if (!isV1ElectionProtocol()) {
        return;
    }

   
    // earliestDate 取一切节点中更新时候最早的(以尽量早的发明题目)
    // electionTimeoutPeriod 默许为 10s
    auto nextTimeout = earliestDate + _rsConfig.getElectionTimeoutPeriod();
   
    // 设置超时回调函数为 _handleLivenessTimeout
    auto cbh = _scheduleWorkAt(nextTimeout,
                               stdx::bind(&ReplicationCoordinatorImpl::_handleLivenessTimeout,
                                          this,
                                          stdx::placeholders::_1));
}

因而,在约莫10s后,假如没有什么不测,_handleLivenessTimeout将会被触发,以下:


void ReplicationCoordinatorImpl::_handleLivenessTimeout(...) {

    ...
    for (auto&& slaveInfo : _slaveInfo) {
        ...

        //lastUpdate 不够新(小于electionTimeout)
        if (now - slaveInfo.lastUpdate >= _rsConfig.getElectionTimeoutPeriod()) {
            ...
            //在保活周期后依然未更新节点,置为down状况
            slaveInfo.down = true;

            //假如当前节点是主,且检测到某个备节点为down的状况,进入memberdown流程
            if (_memberState.primary()) {
 
                //挪用_topCoord的setMemberAsDown要领,纪录某个备节点不可达,并取得下一步的指导
               //当大多数节点不可见时,这里会取得让本身降备的指导
                HeartbeatResponseAction action =
                    _topCoord->setMemberAsDown(now, memberIndex, _getMyLastDurableOpTime_inlock());
                //实行指导
                _handleHeartbeatResponseAction(action,
                                               makeStatusWith<ReplSetHeartbeatResponse>(),
                                               true);
            }
        }
    }
    //继承调理下一个周期
    _scheduleNextLivenessUpdate_inlock();
}

能够看到,这个定时器重如果用于完成主节点对其他节点的保活探测逻辑:

当主节点发明大多数节点不可达时(不满足大多数准绳),将会让本身实行降备

因而,在一个三节点的副本集合,个中两个备节点挂掉后,主节点会自动降备。 如许的设想重如果为了防止发生不测的数据不一致状况发生。


图- 主自动降备

第二个是_cancelAndRescheduleElectionTimeout_inlock函数,这里则是完成自动Failover的症结了,
它的逻辑中包括了一个推举定时器,代码以下:

void ReplicationCoordinatorImpl::_cancelAndRescheduleElectionTimeout_inlock() {

    //假如上一个定时器已启用了,则直接作废
    if (_handleElectionTimeoutCbh.isValid()) {
        LOG(4) << "Canceling election timeout callback at " << _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.5s
    Milliseconds randomOffset = _getRandomizedElectionOffset();
    auto now = _replExecutor.now();
    auto when = now + _rsConfig.getElectionTimeoutPeriod() + randomOffset;
  
    LOG(4) << "Scheduling election timeout callback at " << when;
    _handleElectionTimeoutWhen = when;

    //触发调理,时候为 now + ElectionTimeoutPeriod + randomOffset
    _handleElectionTimeoutCbh =
        _scheduleWorkAt(when,
                        stdx::bind(&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/

  选择打赏方式
微信赞助

打赏

QQ钱包

打赏

支付宝赞助

打赏

  移步手机端
MongoDB一次节点宕机激发的思索(源码理会)

1、打开你手机的二维码扫描APP
2、扫描左则的二维码
3、点击扫描获得的网址
4、可以在手机端阅读此文章
未定义标签

本文来源:搜奇网

本文地址:https://www.sou7.cn/282077.html

关注我们:微信搜索“搜奇网”添加我为好友

版权声明: 本文仅代表作者个人观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。请记住本站网址https://www.sou7.cn/搜奇网。

发表评论

选填

必填

必填

选填

请拖动滑块解锁
>>