在RocketMQ 5.0以前,有两种集群部署模式,分别为主从模式(Master-Slave模式)和Dledger模式。

主从模式

主从模式中分为Master和Slave两个角色,集群中可以有多个Master节点,一个Master节点可以有多个Slave节点。Master节点负责接收生产者发送的写入请求,将消息写入CommitLog文件,Slave节点会与Master节点建立连接,从Master节点同步消息数据(有同步复制和异步复制两种方式)。

消费者可以从Master节点拉取消息,也可以从Slave节点拉取消息。



在RocketMQ 4.5版 本之前,如果Master宕机,不支持自动将Slave切换为Master,需要人工介入。

Dledger模式

为了解决主从架构下Slave不能自动切换为Master的问题,4.5版本之后提供了DLedger模式,使用Raft算法,如果Master节点出现故障,可以自动从Slave节点中选举出新的Master进行切换。

存在问题

(1)根据Raft算法的多数原则,集群至少有三个节点以上,在消息写入时,也需要大多数的Follower节点响应成功才能认为消息写入成功;

(2)Dledger模式下,进行消息写入的时候,使用的是openmessaging包中提供的接口,无法利用RocketMQ原生的存储和复制能力(比如非Dledger模式下使用暂存池方式写入);

(3)存在两套日志复制流程(主从模式下一套、Dledger模式下一套),不统一;

主从同步实现原理

Dledger模式下的日志复制

Controller模式

为了解决如上问题,RocketMQ 5.0以后推出了Controller模式,它的特点如下:

(1)在主从部署模式下就具有自动切换Master的能力,5.0之前需要使用DLedger才可以;

(2)可以利用RocketMQ原生存储复制能力,并统一RocketMQ的存储和复制能力;

RocketMQ 5.0对Broker选主相关的功能进行了抽离,放在Controller中,实现了在主从部署模式下就可以自动切换Master,Controller可以独立部署也可以嵌入在NameServer中部署。

独立部署下的Controller:

嵌入NameServer中的部署图如下:

Controller

也称为Controller控制器,一般集群中部署多个Controller,使用Raft算法选举出一个Active DLedger Controller作为主控制器,它主要用来管理一个SyncStateSet集合,

这个集合中存储的是一组跟上Master进度的Broker节点集合,如果Controller发现某个Master Broker下线时,会从集合中选出新的Master Broker并切换,Controller可以单独部署可以嵌在NameServer中部署。

SyncStateSet

SyncStateSet中维护了一个Broker副本组集合,包含当前Master Broker和它的Slave Broker,需要注意在集合内的节点都是跟上Master进度的节点,在节更变动时,由Master Broker向Controller控制器发起变更请求,更新Controller中的SyncStateSet数据,在选举Master的时候,Controller只需从这个列表中选出一个节点成为新的Master即可。

节点变更分为Shrink操作和Expand操作,需要Master Broker发起,它会通过定时任务以及在数据同步过程中判断是否需要进行Shrink或Expand。

Shrink

Shrink指的是将SyncStateSet副本集合中与Master节点差距过大的副本移除,差距的判断条件如下:

  1. 节点是否与Master Broker的连接已断,如果断开需要将该节点从SyncStateSet移除;
  2. 节点的复制进度是否过大,新增了haMaxTimeSlaveNotCatchup参数,Master Broker会通过定时任务扫描每一个Slave节点的复制信息,里面有每个节点上一次跟上Master进度的时间戳lastCaughtUpTimeMs,如果当前时间减去这个lastCaughtUpTimeMs超过了haMaxTimeSlaveNotCatchup的值,会认为该Slave节点的复制进度过后;
haMaxTimeSlaveNotCatchup:表示Slave没有跟上 Master 的最大时间间隔,若在 SyncStateSet 中的 slave 超过该时间间隔会将其从 SyncStateSet 移除。默认为 15000(15s)。

Expand

如果Master Broker发现某个Slave节点赶上了Master节点的进度,需要将其重新加入到SyncStateSet。

需要注意以上两个操作,都需要Master Broker向Controller节点发送通知,请求更新SyncStateSet中的数据。

选举Master

不管是Controller独立部署,还是嵌入到NameServer中部署,Controller都会监听每个Broker的连接,Broker会定期向Controller发送心跳包,Controller会定时扫描,如果某个Broker心跳包发送超时,会认为这个Broker已经失效,此时会判断Broker是否是Master角色,如果是Master角色就需要从该组的SyncStateSet中重新选出一个节点作为Master。

选举Master的方式比较简单,从该组的SyncStateSet中,挑选一个心跳包发送正常的Slave成为新的Master节点即可,并将结果通知到该组所有的Broker,每个Broker也会定时向Controller发送请求获取主备信息。

Broker端设计

主从架构部署模式下,需要配置brokerRole和brokerId,也就是手动分配Master和Slave,在Controller模式下,这两个参数会失效,不需要再进行配置,角色和ID由Controller来分配。

Controller模式下增加了controllerAddr参数,Broker在启动时,需要配置这个参数,设置每个controller的地址:

controllerAddr:controller的地址,多个controller中间用分号隔开。例如controllerAddr = 127.0.0.1:9877;127.0.0.1:9878;127.0.0.1:9879

Broker上线

Broker配置了每个Controller的地址,Broker启动时,会先向Controller注册,并获取角色关系和brokerId,通过角色关系可以知道自己是Master还是Slave,之后再向NameServer注册。

Broker可以通过任意一个Controller获取Active Controller节点的IP,后台也会有一个定时任务,定时更新Active Controller节点的IP。

主备关系确定

初始化时,第一个Broker在向Controller注册的时候,此时并没有该Broker组的SyncStateSet,所以Active Controller会将第一个向其发送请求共识的Broker设置为Master,之后该组的其他节点会设置为Slave,Master节点的brokerId为0,

Slave节点从1开始编号,往后递增。

由于Controller控制每个节点的角色,所以每个Broker也会定时向Controller发送请求获取主备信息,以便在角色发生变化的时候可以及时更新。

日志复制

  • MasterEpoch(Epoch):Master的任期号,与Term类似,每一任Master都会有一个对应的MasterEpoch任期号,这个任期号的值由Controller控制,单独递增;
  • StartOffset:每一任Master除了有一个任期号之外,还会取当选时对应CommitLog文件中最大的偏移量(MaxPhyOffset),作为本任期期间日志的起始偏移量,记作StartOffset;
  • EpochFile:用于存放每一任Master对应的日志起始偏移量(<MasterEpoch, StartOffset> 序列),存储在 ~/store文件夹下;

当Broker成为Master时,会进行如下操作:

  1. 获取当前CommitLog文件中最后一条消息的偏移量,也就是MaxPhyOffset的值,作为StartOffset;
  2. 将当前任期号MasterEpoch和起始偏移量StartOffset的值持久化到EpochFile文件中;
  3. 监听Slave节点的连接;

日志复制整体流程

Broker在接收Controller指令之后,会根据Controller的选举结果,转变对应的角色,分别为Master和Slave。

连接阶段

连接阶段用于Master节点与Slave节点间建立连接:

  1. Master节点开始监听连接;
  2. Slave节点请求与Master节点建立连接;

HandShake阶段

Master节点与Slave节点连接建立成功之后,进入HandShake阶段:

  1. Slave节点向Master节点发送HandShake包,里面包含一些状态信息及Slave的地址,数据格式如下:

    • Current State:表示当前状态,当前是HandShake阶段,所以表示HandShake;
    • Flags:一些标志位;
    • SlaveAddressLength:Salve节点的地址长度;
    • SlaveAddress:Slave节点的地址,发送给Master节点后,在下个阶段Master节点会判断是否需要将Slave节点加入到SyncStateSet中;
  2. Master节点向Slave节点回复HandShake包,Slave节点收到Master节点回复的包后,会使用本地的Epoch+StartOffset与Master传输的对比,找到截断点进行日志截断,与Master的日志保持一致,Master节点回复的HandShake包数据格式如下:

    • Current State:表示当前状态,当前是HandShake阶段;
    • Body Size:存储Body的长度;
    • Offset:表示当前Master节点的CommitLog最大偏移量;
    • Epoch:表示当前Master节点任期号;
    • Body:Master端记录的所有任期信息,是一个集合,所以总大小为EpochEntry大小 * EpochEntry条数;

日志截断

  • endOffset:下一任期的StartOffset,如果没有下一任期,那么取当前CommitLog的最大偏移量作为endOffset;

Slave中将每一任Epoch对应的<Startoffset,Endoffset>序列存储在一个TreeMap中(从大到小排序):

TreeMap<Epoch, Pair<startOffset,endOffset>> epochMap;

Slave节点会遍历所有的任期(从大到小),然后根据任期号Epoch获取Master节点对应的<startOffset,endOffset>序列进行对比,如果Slave的Epoch与Master一致,并且StartOffset相等,取两者中较小的那个endOffset作为截断位点,之后Slave节点修正自己的<epoch,startoffset>信息,然后进入Transfer阶段进行日志传输。如果未找到截断位点,会一直向后遍历直到找到。

Slave保证在截断位点位置之前的日志与Master一致,之后从截断位点位置开始从Master复制日志。

// Slave从大到小遍历所有的任期
while (iterator.hasNext()) {
// 任期信息及对应的<startOffset,endOffset>
Map.Entry<Epoch, Pair<startOffset,endOffset>> curEntry = iterator.next();
// 根据Epoch任期号获取Master节点对应的<startOffset,endOffset>
Pair<startOffset,endOffset> masterOffset=findMasterOffsetByEpoch(curEntry.getKey());
// 如果获取不为空,并且startOffset相等
if(masterOffset != null &&
curEntry.getKey().getObejct1() == masterOffset.getObejct1()) {
// 返回较小的那个endOffset
truncateOffset = Math.min(curEntry.getKey().getObejct2(), masterOffset.getObejct2());
break;
}
}

Transfer阶段

在Transfer阶段,Master节点会不断向Slave发送日志包,开始进行日志复制:

  1. Master节点向Slave节点发送日志包;
  2. Slave节点收到日志包之后,会检测Epoch是否发生变化,然后更新本地的EpochFile,之后向Master节点回复ACK;
  3. Master节点处理Slave节点回复的ACK响应;

参考

RIP-44 Support DLedger Controller

RocketMQ设计思想

RocketMQ官方文档

【RocketMQ】RocketMQ 5.0新特性(三)- Controller模式的更多相关文章

  1. 返璞归真 asp.net mvc (7) - asp.net mvc 3.0 新特性之 Controller

    原文:返璞归真 asp.net mvc (7) - asp.net mvc 3.0 新特性之 Controller [索引页][源码下载] 返璞归真 asp.net mvc (7) - asp.net ...

  2. C++2.0新特性(三)——<=default,=delete、alias(别名)、noexcept、override、final、以及和const对比>

    一.=default,=delete 1.首先我们要回顾一下类默认函数的概念: C++中,当我们设计与编写一个类时,若不显著申明,则类会默认为我们提供如下几个函数: (1)构造函数(A()).(2)析 ...

  3. C# 6.0 新特性 (三)

    主构造函数 自动属性初始化表达式尤其适合与主构造函数结合使用.主构造函数为降低常见对象模式的繁琐程度提供了一种方法.此功能自五月以来已显著改进.更新包括: 主构造函数的可选实现主体:这将支持此前不受支 ...

  4. ASP.NET Web API 2.0新特性:Attribute Routing1

    ASP.NET Web API 2.0新特性:Attribute Routing[上篇] 对于一个针对ASP.NET Web API的调用请求来说,请求的URL和对应的HTTP方法的组合最终决定了目标 ...

  5. Spring Boot 2(一):Spring Boot 2.0新特性

    Spring Boot 2(一):Spring Boot 2.0新特性 Spring Boot依赖于Spring,而Spring Cloud又依赖于Spring Boot,因此Spring Boot2 ...

  6. android6.0、7.0、8.0新特性总结之开发应用时加以考虑的一些主要变更。

    android6.0 参考一:简书Android 6.0 新特性详解 参考二:关于Android6.0以上系统的权限问题 参考三:值得你关注的Android6.0上的重要变化(一) 参考四:值得你关注 ...

  7. atitit.Servlet2.5 Servlet 3.0 新特性 jsp2.0 jsp2.1 jsp2.2新特性

    atitit.Servlet2.5 Servlet 3.0 新特性 jsp2.0 jsp2.1 jsp2.2新特性   1.1. Servlet和JSP规范版本对应关系:1 1.2. Servlet2 ...

  8. C# 7.0 新特性3: 模式匹配

    本文参考Roslyn项目Issue:#206,及Docs:#patterns. 1. C# 7.0 新特性1: 基于Tuple的“多”返回值方法 2. C# 7.0 新特性2: 本地方法 3. C# ...

  9. C# 7.0 新特性4: 返回引用

    本文参考Roslyn项目中的Issue:#118. 1. C# 7.0 新特性1: 基于Tuple的“多”返回值方法 2. C# 7.0 新特性2: 本地方法 3. C# 7.0 新特性3: 模式匹配 ...

  10. C#发展历程以及C#6.0新特性

    一.C#发展历程 下图是自己整理列出了C#每次重要更新的时间及增加的新特性,对于了解C#这些年的发展历程,对C#的认识更加全面,是有帮助的. 二.C#6.0新特性 1.字符串插值 (String In ...

随机推荐

  1. PostgreSQL JDBC 开发指导

    JDBC 驱动程序 目录 设置 JDBC 驱动程序 初始化驱动程序 使用 SSL 发出查询和处理结果 调用存储函数和过程 存储二进制数据 JDBC 转义 PostgreSQL 扩展的 JDBC API ...

  2. Windows/DOS与Unix文件格式之间的相互转换(/r/n问题)

    PS:今天遇到一个文件转换问题,现在将网上搜索到资料贴出来.. 第一个资料 Windows/DOS与Unix文件格式是不同的,问题一般就是出在/r/n问题上. 回车(CR)和换行(LF)符都是用来表示 ...

  3. Github入门教程(新版)

    GitHub 的介绍与使用 GitHub 注册一个账号 直接在首页注册即可啦 要注意的是 第一项 username 别人是可见的 后面修改也会比较麻烦,所以起个好名字很重要 个人主页介绍 刚注册好的页 ...

  4. 面由 AI 生|ZegoAvatar 捏脸技术解析

    一.AI"卷"进实时互动 2021年,元宇宙概念席卷全球,国内各大厂加速赛道布局,通过元宇宙为不同的应用场景的相关内容生态进行赋能.针对"身份"."沉 ...

  5. 【微信小程序的开发】初步认识

    目录 项目结构 页面组成 json配置文件 ​ app.json ​ project.config.json ​ sitemap.json ​ 每个页面的json ​ 实例 wxml ​ 标签名称 ​ ...

  6. Angular: Error: NG0100: ExpressionChangedAfterItHasBeenChecked

    错误原因 当变更检测完成后又更改了表达式的值时,Angular就会抛出ExpressionChangedAfterItHasBeenCheckedError 错误,Angular只会在开发模式下抛出此 ...

  7. DBSCAN聚类

    一.概述   DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法,簇集的划定完全由样本的聚集 ...

  8. .NET ORM 鉴别器 和 TDengine 使用 -SqlSugar

    SqlSugar ORM SqlSugar 是一款 老牌 .NET 开源多库架构ORM框架 ,一套代码能支持多种数据库像像Admin.net.Blog.Core.CoreShop等知名开源项目都采用了 ...

  9. [mysql]状态检查常用SQL

    前言 使用MySQL自身命令获取数据库服务状态. 连接数 -- 最大使用连接数 show status like 'Max_used_connections'; -- 系统配置的最大连接数 show ...

  10. 使用kubeadm部署kubernetes

    k8s版本:1.15.0 前期准备 节点: master:172.50.13.103(2核2G) node-1:172.50.13.104(2核2G) node-2:172.50.13.105(2核2 ...