探寻MongoDB副本集选举机制 阿里云与MongoDB的DBaaS技术合作创新

本文涉及的产品
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 MongoDB,通用型 2核4GB
简介: 阿里云连续第五年斩获MongoDB合作伙伴奖项,也是唯一获此殊荣的中国云厂商。一起学习MongoDB副本集的选举机制以及可能会出现的特殊情况。

近日,在MongoDB用户大会纽约站上,阿里云荣膺MongoDB“2024年度DBaaS认证合作伙伴奖”。这是阿里云连续第五年斩获 MongoDB 合作伙伴奖项,也是唯一获此殊荣的中国云厂商。


MongoDB是当今全球最受开发者欢迎的非关系型数据库。凭借灵活的模式和丰富的文档结构,MongoDB帮助企业客户及百万开发者们使用丰富的数据结构快速开发应用,多快好省地构建现代应用程序。


阿里云是国内唯一首发MongoDB新版本的云服务厂商。基于阿里云的云原生环境,客户可以更好体验到MongoDB数据库的崭新能力和性能,进而实现业务的快速迭代、开发,降低开销、增加效率。

拟封面.png

自2019年阿里云与MongoDB达成战略合作伙伴关系以来,阿里云数据库MongoDB版已广泛应用于互联网、游戏、汽车出行、零售、金融等行业,累计为数万家客户提供MongoDB云数据库服务,MongoDB也取得了国内营收增长8倍的里程碑式成绩,目前阿里云已经成为MongoDB在中国最大的云服务提供商。


MongoDB全球合作伙伴高级副总裁Alan Chhabra表示,阿里云始终如一地提供优秀的云服务,集成新的 MongoDB产品功能,为中国各行各业提供强大、可扩展且安全的数据库解决方案。通过提供新的 MongoDB云服务,阿里云致力于本地化产品和服务以满足中国市场的新需求,进一步印证了阿里云领先的 DBaaS 提供商的地位。


阿里云数据库负责人李飞飞也表示,与MongoDB合作的四年是非常甜蜜的四年,阿里云不仅仅是单纯的OEM MongoDB数据内核,更多是把MongoDB能力和阿里云云原生的环境紧密结合起来。通过使用具有阿里云特色的MongoDB数据库,客户们能够实现业务的快速迭代、开发、降低开销、增加效率。


2023年,阿里云与MongoDB续签了战略合作协议,双方致力于将MongoDB的最新产品成果与阿里云企业级服务相结合,深耕国内市场,携手技术创新,打造最佳实践。


在今年的用户大会,MongoDB上也宣布推出一项新的云合作伙伴认证计划,该计划名为“Certified by MongoDB DBaaS”(数据库即服务),旨在为云基础设施合作伙伴提供最新MongoDB产品特性的访问权限,并允许他们向自己的客户提供一流的托管数据库服务,包括全文搜索、向量搜索和行业领先的数据加密等功能。阿里云NoSQL数据库产品线负责人张为表示,在当今AI应用飞速发展的时代,MongoDB与仅存储向量数据的附加解决方案不同,其向量搜索能力通过与原有的高性能和可扩展的主数据库紧密集成,客户能够安全地在整个数据库中充分利用生成式 AI 和专有数据,以更少的代码开发和运营开销来更快地实现业务价值。


不远的将来,阿里云作为MongoDB 重要的云合作伙伴之一,也将能够在阿里云MongoDB托管服务中提供向量搜索和全文搜索能力,客户将可以第一时间在阿里云MongoDB使用该能力来构建高级搜索和生成式AI应用程序。

image.png

MongoDB总裁兼首席执行官Dev Ittycheria 及 MongoDB全球合作伙伴高级副总裁Alan Chhabra 共同向 阿里云NoSQL数据库产品线负责人张为 授予“MongoDB 2024年度DBaaS认证合作伙伴”奖项


学习拓展:

MongoDB副本集选举分析


1. 背景

1.1 副本集架构

图1是官方普通三节点副本集的结构图,由一个Primary节点和两个Secondary节点组成,各节点通过Hearbeat判断其他节点是否存活。

image.png

图1 MongoDB副本集架构

图2是云MongoDB的三节点副本集架构,由一个Primary节点、一个Secondary节点和一个Hidden节点组成。Hidden节点是特殊的Secondary节点,它对客户端不可见,通过设置内核priority=0保证不参与选举,提供高可用和备份服务。

image.png 图2 云MongoDB副本集架构

1.2 名词说明

探活机制

MongoDB副本集的探活是通过Heartbeats实现。心跳检测的周期是默认2s,超过10s没有返回后会标记该节点为不可达。

优先级 priority

表示节点成为primary的相对可能性,在相同条件下更倾向于选举优先级最高的节点成为primary(优先级等于0的节点不会被选举为Primary)

投票 vote

选举投票权,副本集内最多允许7个节点有投票权(优先级大于0的节点投票不能为0)

2. 选举阶段解析

在选举过程中,副本集的Primary不支持读写,Secondary支持读

图3是最常见的选举场景,副本集中Primary节点下线,触发选举。其中一个Secondary节点要求进行选举来保证副本集可用,在获得足够多的投票(超过1/2的总投票)后选举成为新的Primary。

image.png

图3

2.1 触发选举

a. 触发选举的原因
  • 副本集无主超时:无主时间超过electionTimeoutMillis,图3
  • 设置非Primary节点为更高的优先级:Secondary优先级提高,优先选举Secondary为Primary
  • 选举的Primary在追赶阶段发生takeover:后面会解释追赶阶段
  • 单节点选举:单节点结构下选举自己为Primary
b. 候选者需满足的条件
  • 该节点是Secondary或Primary
  • 有最新的oplog
  • 可以"看到"大多数的节点(超过1/2)
  • 不是新加入的节点且不是arbiter
  • 该节点优先级大于0
  • 近期没有执行stepDown命令

2.2 预选举

候选者会进行预选举,判断自身是否可以发起真正选举。

a. 节点投赞成票的条件
  • 该节点不处于以下状态
  • DOWN
  • STARTUP
  • 该节点具有投票能力 — votes > 0
  • 该节点的任期不大于候选者的任期
  • 该节点与候选者版本相同
  • 该节点与候选者副本集名相同
  • 该节点的oplog不比候选者新
  • 当前Primary不健康或优先级低于候选者
b. 预选举失败的原因
  • 投票不足
  • 任期过期
  • Primary投反对票

预选举可以避免进入无意义的选举阶段。若当前节点不可能被选举成功,则预选举就会失败,且不会增加任期term,反正预选举成功term加一,进入新的任期。

下面是没有预选举环节的选举示例: image.png

图4 直接选举


如图4所示,一个互通的3节点集群发生网络隔离,S2与S1和S3不互通。

1) S2节点每次选举超时的时候都会发起一次选举并自增term;由于不能连接到S1和S3,无法获得大多数赞同,选举会失败。如此反复,term会增加到一个相对比较大的值;

2)由于S1和S3满足大多数条件,选举S1成为集群新的主节点,term变为2;

3)当网络连接恢复,S2又可以重新连接到S1和S3之后,其term会通过心跳传递给S1和S3,而这会导致S1 step down成为从节点;

4)选举超时时间过后,集群会重新触发一次选举,无论是S1还是S3成为新的主(S2由于落后所以不可能),其term值会变成11;

上述场景中有term跳变最后一次无意义选举两个问题预选举可以有效解决在尝试自增term并发起选举之前,S2会看看自己有没有可能获得来自S1和S3的选票。如若不满足条件则不会发起真正的选举。

2.3 选举

预选举成功后会进入真正的选举阶段,此时会先自增term。节点会优先投自己一票,然后接收本次选举过程中其他节点的投票。收到超过1/2的赞成票认为预选举成功,进入状态变更阶段。

此时愿意投赞成票的节点限制与预选举大致相同,只多了一条限制:该节点当前任期没有给其他节点投票

2.4 状态变更


处理选举的结果

a.选举成功

  • 更新节点状态,选举成功的节点从SECNONDAY变成PRIMARY状态
  • 通知副本集内所有的secondary节点选举胜利
  • Primary节点进入追赶阶段

b.选举失败

无操作

2.5 追赶

虽然在选举过程中要求候选者的oplog时间比投赞成票节点新,但是选举成功的Primary节点还是会检查是否有其他节点有更新的oplog。

如图4的情况,假如S2有更新的oplog,但是由于网络分区等原因没有参与选举,在选举结束后网络正常,此时Primary节点发现S2有更新的oplog,会去拉取S2的oplog。

追赶阶段有设置超时时间,所以部分情况无法获得其他节点全部的oplog,此时其他节点会进入ROLLBACK状态回滚操作。

2.6 日志示例

a. 副本集配置

Primary:priority=2, votes=1

Secondary:priority=1,votes=1

Hidden:priority=0,votes=1

b. 场景

副本集因为Primary节点不可用,触发选举

# 触发选举
REPL_HB  Heartbeat to xx.xx.xx.xx:xx failed after 2 retries, response status: HostUnreachable: Error connecting to xx.xx.xx.xx:xx :: caused by :: Connection refused
REPL     Starting an election, since we've seen no PRIMARY in the past 10000ms
# 预选举
REPL     conducting a dry run election to see if we could be elected. current term: 3
REPL     VoteRequester(term 3 dry run) failed to receive response from xx.xx.xx.xx:xx: HostUnreachable: Error connecting to xx.xx.xx.xx:xx :: caused by :: Connection refused
REPL     VoteRequester(term 3 dry run) received a yes vote from xx.xx.xx.xx:xx; response message: { term: 3, voteGranted: true, reason: "", ok: 1.0, operationTime: Timestamp(1706176453, 1), $clusterTime: { clusterTime: Timestamp(1706176453, 1)
REPL     VoteRequester(term 4) failed to receive response from xx.xx.xx.xx:xx: HostUnreachable: Error connecting to xx.xx.xx.xx:xx :: causedby :: Connection refused
REPL     dry election run succeeded, running for election in term 4
# 选举
REPL     VoteRequester(term 4) received a yes vote from xx.xx.xx.xx:xx; response message: { term: 4, voteGranted: true, reason: "", ok: 1.0, operationTime: Timestamp(1706176453, 1), 
REPL     election succeeded, assuming primary role in term 4
# 状态变更
REPL     transition to PRIMARY from SECONDARY
REPL     Resetting sync source to empty, which was :xx
# 追赶阶段
REPL     Entering primary catch-up mode.
REPL     Caught up to the latest optime known via heartbeats after becoming primary. Target optime: { ts: Timestamp(1706176453, 1), t: 3 }. My Last Applied: { ts: Timestamp(1706176453, 1), t: 3 }
REPL     Exited primary catch-up mode.
REPL     Stopping replication producer
# 允许写入
REPL     transition to primary complete; database writes are now permitted

1.触发选举

在Heartbeat多次探活失败后,副本集内部确认无主,触发选举

2.预选举

由于当前副本集内只有Secondary priority > 0,所以只有其发起了预选举。在预选举中收到一个赞成票,由于自动投票给自己,所以满足大多数,预选举成功,term加1。

3.选举和状态变更

同上,获得大多数赞成票,选举成功,假定term 4期间为Primary,将节点状态设置为PRIMARY

4.追赶阶段

进入追赶阶段,根据heartbeats获得最新的oplog时间,确认与当前的oplog时间相同,结束追赶阶段。选举成功,允许写入。

3. 代码解析

3.1 触发选举

_startElectSelfIfEligibleV1(reason)

根据传递的选举原因判断该节点是否可以为候选者 (becomeCandidateIfElectable)

switch (reason) {
    case StartElectionReasonEnum::kElectionTimeout:
        # 无主,副本集内探活10s内无primary
        LOG_FOR_ELECTION(0) << 
          "Starting an election, since we've seen no PRIMARY in the past "
                            << _rsConfig.getElectionTimeoutPeriod();
        break;
    case StartElectionReasonEnum::kPriorityTakeover:
        # 优先级抢占,有节点设置更高优先级
        LOG_FOR_ELECTION(0) << 
          "Starting an election for a priority takeover";
        break;
    case StartElectionReasonEnum::kStepUpRequest:
    case StartElectionReasonEnum::kStepUpRequestSkipDryRun:
        # 主动提升为primary
        # 跳过预选举
        LOG_FOR_ELECTION(0) << 
          "Starting an election due to step up request";
        break;
    case StartElectionReasonEnum::kCatchupTakeover:
        # 追赶抢占,primary处于catchup阶段时发生了takeover
        LOG_FOR_ELECTION(0) << 
          "Starting an election for a catchup takeover";
        break;
    case StartElectionReasonEnum::kSingleNodePromptElection:
        # 单节点选举
        LOG_FOR_ELECTION(0) << 
          "Starting an election due to single node replica set prompt election";
        break;
    default:
        MONGO_UNREACHABLE;
}

_startElectSelfV1_inlock(reason)
_startElectSelfV1_inlock

处理中间过程,对于kStepUpRequestSkipDryRun跳过预选举直接进入选举步骤,否则进入预选举阶段。


预选举与跳过预选举

// 获取当前term
long long term = _topCoord->getTerm();
int primaryIndex = -1;
// 跳过预选举
if (reason == TopologyCoordinator::StartElectionReason::kStepUpRequestSkipDryRun) {
    // term自增
    long long newTerm = term + 1;
    log() << "skipping dry run and running for election in term " << newTerm;
    // 真正选举
    _startRealElection_inlock(newTerm, reason);
    lossGuard.dismiss();
    return;
}

// 选举准备
_voteRequester.reset(new VoteRequester);

// Only set primaryIndex if the primary's vote is required during the dry run.
if (reason == TopologyCoordinator::StartElectionReason::kCatchupTakeover) {
    primaryIndex = _topCoord->getCurrentPrimaryIndex();
}
// 预选举
_processDryRunResult(term, reason);
lossGuard.dismiss();


3.2 预选举

_processDryRunResult

预选举失败原因

  • kInsufficientVotes:获得的投票不足
  • kStaleTerm:自身term过期
  • kPrimaryRespondedNo:primary拒绝投票
  • kSuccessfullyElected:未知问题

预选举成功则增加term,调用_startRealElection_inlock开始选举

预选举失败及成功

const VoteRequester::Result endResult = _voteRequester->getResult();
// 预选举失败
if (endResult == VoteRequester::Result::kInsufficientVotes) {
    log() << "not running for primary, we received insufficient votes";
    return;
} else if (endResult == VoteRequester::Result::kStaleTerm) {
    log() << "not running for primary, we have been superseded already";
    return;
} else if (endResult == VoteRequester::Result::kPrimaryRespondedNo) {
    log() << "not running for primary, the current primary responded no in the dry run";
    return;
} else if (endResult != VoteRequester::Result::kSuccessfullyElected) {
    log() << "not running for primary, we received an unexpected problem";
    return;
}

// 预选举成功
long long newTerm = originalTerm + 1;
log() << "dry election run succeeded, running for election in term " << newTerm;
// 选举
_startRealElection_inlock(newTerm, reason);


3.3 选举

_startRealElection_inlock

选举

// 给自己投票
_topCoord->voteForMyselfV1()
..
  // 保存副本集投票情况
  _writeLastVoteForMyElection()
....
    // 向其他节点发送投票请求
    _startVoteRequester_inlock()
......
      // 收到投票结果并处理
      _onVoteRequestComplete()
........
        // 副本集状态机更改
        _postWonElectionUpdateMemberState_inlock()

状态机更改

// 修改role等配置
_topCoord->processWinElection(_electionId, ts);
const PostMemberStateUpdateAction nextAction =
    _updateMemberStateFromTopologyCoordinator_inlock(nullptr);
// 设置同步端为空
_onFollowerModeStateChange();
// 通知其他节点选举成功
_restartHeartbeats_inlock();
// 进入追赶阶段
_catchupState->start_inlock();

3.4 追赶阶段

// 通过心跳获得最新oplog来追赶
_handleHeartbeatResponse()
// 判断自身和最新oplog的时间
CatchupState::signalHeartbeatUpdate_inlock()
// 结束追赶阶段
CatchupState::abort_inlock()

4. 案例分析

a.背景

实例写入量过大,primary cpu被打满并开始出现主备延迟(secondary有延迟,hidden无延迟),客户主动下发主备切换希望降低流量,但是切换长时间不成功,引起客户担忧,最终在客户停止写入业务一分钟后,主备切换成功。

b.分析

实例状态更改前后如下表。


HA前状态

HA后状态

Primary

A

B

Secondary

B

A

Hidden

C

C

客户下发正常主备切换后,B的优先级提高至2,A的优先级降低为1,B优先级提升,但是由于和其他节点延迟过高,导致无法成为候选者,没有触发预选举。

ELECTION  Not starting an election for a priority takeover, since we are not electable due to: Not standing for election because member is not caught up enough to the most up-to-date member to call for priority takeover - must be within 2 seconds (mask 0x80)

客户停止写入业务后,切换快速成功,预期需要等待B同步结束后才会完成选举,但是B延迟7个小时,担心数据有丢失,因此需要确定当时选举成功的原因。

查看A日志中无Rollback,确认没有数据回滚操作。

查看B日志,第一条日志显示业务写入中追oplog,但是存在很大延时,符合监控数据。

第二条日志显示Secondary 最新的oplog已经和Primary时间相同,此时可以成为候选者,后续也正常选举成功。

REPL Scheduled new oplog query Fetcher source: A database: local query: { find: "oplog.rs", filter: { ts: { $gte: Timestamp(xxx1) } }, tailable: true, oplogReplay: true, awaitData: true, term: xx}
REPL Choosing new sync source. Our current sync source is not primary and does not have a sync source, so we require that it is ahead of us. Current sync source: A, my last fetched oplog optime: { ts: Timestamp(xxx2), t: xx }, latest oplog optime of sync source: { ts: Timestamp(xxx2), t: xx } (sync source does not know the primary)


相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
4天前
|
存储 负载均衡 NoSQL
MongoDB分片技术:实现水平扩展的利器
【4月更文挑战第30天】MongoDB的分片技术是应对数据增长和复杂业务需求的解决方案,它将数据水平拆分存储在多个实例上,实现数据库的水平扩展。分片带来水平扩展性、负载均衡、高可用性和灵活的数据管理。分片工作涉及mongos路由进程、config server和shard实例。设置分片包括部署配置服务器、添加分片、启动mongos、配置分片键和开始分片。选择合适的分片键和有效管理能确保系统性能和稳定性。
|
2天前
|
存储 机器学习/深度学习 弹性计算
|
2天前
|
存储 弹性计算 监控
要怎么与阿里云合作
5月更文挑战第13天
14 4
|
2天前
|
人工智能 云计算
阿里云携手合作伙伴得云AI举办《AI赋能 · 智能革新沙龙》
阿里云与得云AI联合举办了《AI赋能·智能革新沙龙》,探讨云计算和AI前沿技术。
|
4天前
|
Cloud Native 安全 数据中心
|
4天前
|
监控 NoSQL MongoDB
【MongoDB 专栏】MongoDB 的副本集故障转移与恢复
【5月更文挑战第11天】MongoDB的副本集是高可用性关键,提供数据冗余和自动故障转移。由主节点和从节点组成,主节点处理写操作,从节点同步数据。当主节点故障,副本集通过选举产生新主节点,确保服务不间断。故障转移涉及节点优先级和数据同步状态的考量。恢复阶段解决数据不一致,重点包括节点部署监控、数据同步策略、选举机制和备份恢复计划。网络延迟和大规模数据可能带来挑战,需优化网络、性能调优和定期演练。随着技术进步,副本集的故障转移与恢复将更高效、智能,保障数据安全,支撑业务系统的稳定运行。
【MongoDB 专栏】MongoDB 的副本集故障转移与恢复
|
5天前
|
NoSQL 大数据 数据处理
MongoDB聚合框架与复杂查询优化:技术深度解析
【4月更文挑战第30天】本文深入探讨了MongoDB的聚合框架和复杂查询优化技术。聚合框架包含$match、$group、$sort和$project阶段,用于数据处理和分析,提供灵活性和高性能。优化查询涉及创建合适索引、使用聚合框架、简化查询语句、限制返回结果数、避免跨分片查询、只查询所需字段及使用$inc操作符。理解这些技术有助于提升MongoDB在大数据和复杂查询场景下的性能。
|
5天前
|
存储 监控 NoSQL
MongoDB实时数据分析与流处理的技术探讨
【4月更文挑战第30天】MongoDB支持实时数据分析与流处理,其灵活数据模型适合非结构化数据存储。通过BSON格式、高性能查询和聚合管道,MongoDB满足实时分析需求。变更数据流功能提供实时事件处理,可用于跨实例数据同步和复杂事件处理。在电商等场景中,MongoDB可实现实时销售监控、用户行为分析及异常检测。结合流处理框架,助力企业做出明智决策。
|
4天前
|
监控 NoSQL MongoDB
MongoDB性能调优:监控与诊断工具的技术探讨
【4月更文挑战第30天】本文探讨了MongoDB性能调优,重点关注监控与诊断工具。MongoDB自带的Shell和Profiler有助于理解数据库性能,而MMS、PMM和mongostat等第三方工具则提供实时监控和深度分析。调优实践包括优化索引、调整内存配置、分片与复制、硬件升级及查询优化。通过这些工具和策略,可有效提升MongoDB性能。
|
2天前
|
存储 SQL NoSQL
什么是 MongoDB,为什么它是当今最受欢迎的数据库之一?
什么是 MongoDB,为什么它是当今最受欢迎的数据库之一?