高可用究竟指的是什么?请参考:关于高可用的系统

RocketMQ做了以下的事情来保证系统的高可用

  • 多master部署,防止单点故障
  • 消息冗余(主从结构),防止消息丢失
  • 故障恢复(本篇暂不讨论)

那么问题来了:

  • 怎么支持多broker的写?
  • 怎么实现消息冗余?

下面分开说明这两个问题

多master集群

这里强调出master集群,是因为需要多个broker set,而一个broker set只有一个master(见下文的“注意”),所以是master集群

broker有三种角色:ASYNC_MASTER、SYNC_MASTER和SLAVE,这些角色常用的搭配为:

  1. ASYNC_MASTER、SLAVE:容许丢消息,但是要broker一直可用,master异步传输CommitLog到slave
  2. SYNC_MASTER、SLAVE:不允许丢消息,master同步传输CommitLog到slave
  3. ASYNC_MASTER:如果只是想简单部署则使用这种方式

master:负责消息的读写

slave:只负责读消息

SYNC_MASTER与ASYNC_MASTER的区别是sync会等待消息传输到slave才算消息写完成,而async不会同步等待,而是异步复制到slave

RocketMQ的架构图(原图地址

注意:在RocketMQ里面有一个概念broker set,一个broker set由一个master和多个slave组成,一个broker set内的每个broker的brokerName相同。

在broker集群中每个master相互之间是独立,master之间不会有交互,每个master维护自己的CommitLog、自己的ConsumeQueue,但是每一个master都有可能收到同一个topic下的producer发来的消息

为了支持多master集群,需要解决几个问题:

  • namesrv怎么管理broker
  • producer发送消息的时候知道发送到哪一个broker(为什么是master)

1. namesrv怎么管理broker

broker启动的时候会向namesrv注册自己的信息

// org.apache.rocketmq.broker.BrokerController#registerBrokerAll
public synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway) {
TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper(); // 省略中间代码...
RegisterBrokerResult registerBrokerResult = this.brokerOuterAPI.registerBrokerAll(
this.brokerConfig.getBrokerClusterName(),
this.getBrokerAddr(),
this.brokerConfig.getBrokerName(),
this.brokerConfig.getBrokerId(),
this.getHAServerAddr(),
topicConfigWrapper,
this.filterServerManager.buildNewFilterServerList(),
oneway,
this.brokerConfig.getRegisterBrokerTimeoutMills());
// 省略中间代码...
}

信息中包括:

clusterName:broker 集群的名字,如:DefaultCluster

brokerAddr:broker的ip:port,如:192.168.0.102:10911

brokerName:注意这个字段,上面介绍过了,一个broker set中的brokerName是相同的,需要在部署的时候配置

brokerId:用来唯一标示一个broker set中的broker,master是0(org.apache.rocketmq.common.MixAll#MASTER_ID),slave是正整数

haServerAddr:haServer的ip:port,如:192.168.0.102:10912

topicConfigWrapper:是比较复杂的数据结构,主要包含了broker上所有的topic信息,如:

{
"dataVersion": {
"counter": 2,
"timestamp": 1514252649572
},
"topicConfigTable": {
"TopicTest": {
"order": false,
"perm": 6,
"readQueueNums": 4,
"topicFilterType": "SINGLE_TAG",
"topicName": "TopicTest",
"topicSysFlag": 0,
"writeQueueNums": 4
},
"%RETRY%please_rename_unique_group_name_4": {
"order": false,
"perm": 6,
"readQueueNums": 1,
"topicFilterType": "SINGLE_TAG",
"topicName": "%RETRY%please_rename_unique_group_name_4",
"topicSysFlag": 0,
"writeQueueNums": 1
}
}
}

上面包含了两个topic:TopicTest和%RETRY%please_rename_unique_group_name_4,相关字段的含义:

order:是否是顺序消息

perm:表明该topic的权限,可读(4)、可写(2)、可继承(1),通过位运算组合

readQueueNums:决定了consume消费的MessageQueue共有几个

writeQueueNums:决定了producer发送消息的MessageQueue共有几个

这些信息发送给namesrv之后,namesrv转化为自己的数据结构,namesrv处理broker注册的方法是:

// org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#registerBroker
public RegisterBrokerResult registerBroker(
final String clusterName,
final String brokerAddr,
final String brokerName,
final long brokerId,
final String haServerAddr,
final TopicConfigSerializeWrapper topicConfigWrapper,
final List<String> filterServerList,
final Channel channel) {
RegisterBrokerResult result = new RegisterBrokerResult();
try {
try {
// 省略中间代码...
// 这里会判断只有master才会创建QueueData,因为只有master才包含了读写队列的信息
// slave没有自己独立的读写队列信息(salve不会创建自己的queue信息),只是和master的的读写队列信息一致
if (null != topicConfigWrapper
&& MixAll.MASTER_ID == brokerId) {
if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion())
|| registerFirst) {
ConcurrentMap<String, TopicConfig> tcTable =
topicConfigWrapper.getTopicConfigTable();
if (tcTable != null) {
for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
// 这个方法创建了QueueData,QueueData包含broker set下的读写队列的信息
this.createAndUpdateQueueData(brokerName, entry.getValue());
}
}
}
} // 省略中间代码...
} catch (Exception e) {
log.error("registerBroker Exception", e);
} return result;
}

上面涉及到的namesrv的几个重要数据结构

// 每个cluster下的broker set信息,一个brokerName对应的broker set
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
// 每个broker set中的broker信息(set中有哪些broker,每个broker的brokerId和brokerAddr)
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
// 每个broker的存活情况
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
// 每个topic下的queue信息,包括每个broker set中读写队列的个数,consumer消费消息和producer发送消息的路由信息都从这个数据结构中获取
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;

所以,namesrv通过将broker注册来的信息构造成自己的数据结构:

  • 每个cluster有哪些broker set
  • 每个broker set包括哪些broker,brokerId和broker的ip:port
  • 每个broker的存活情况,根据每次broker上报来的信息,清除可能下线的broker
  • 每个topic的消息队列信息,几个读队列,几个写队列

namesrv汇总所有的broker的这些信息,然后供consumer和producer拉取

2. producer发送消息的时候知道发送到哪一个master

之前我们知道producer发送消息的时候发往哪一个broker是由MessageQueue决定的,所以我们先要搞清楚producer发送消息时候的MessageQueue怎么来的。producer维护了一个topicPublishInfoTable,里面包含了每个topic对应的MessageQueue,所以问题就变成了topicPublishInfoTable怎么构造的。

producer发送消息之前都会获取topic对应的队列信息,当topicPublishInfoTable中没有的时候会从namesrv获取,获取的方法如下:

// org.apache.rocketmq.client.impl.factory.MQClientInstance#updateTopicRouteInfoFromNameServer(java.lang.String, boolean, org.apache.rocketmq.client.producer.DefaultMQProducer)
public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean isDefault,
DefaultMQProducer defaultMQProducer) {
try {
if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
try {
TopicRouteData topicRouteData;
if (isDefault && defaultMQProducer != null) {
// 省略中间代码...
} else {
// 从manesrv获取topic的路由信息,namesrv从topicQueueTable获取到该topic对应的所有的QueueData
// 然后将每个brokerName下的BrokerData返回
topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, 1000 * 3);
}
// 省略中间代码...
for (BrokerData bd : topicRouteData.getBrokerDatas()) {
// 每个broker set下所有的broker地址(ip:port)
this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs());
}
// Update Pub info
{
// 将从namesrv获取到的路由信息转换为TopicPublishInfo
// 期间会将没有master的broker set的queue信息去除
TopicPublishInfo publishInfo = topicRouteData2TopicPublishInfo(topic, topicRouteData);
// 省略中间代码...
} catch (InterruptedException e) {
log.warn("updateTopicRouteInfoFromNameServer Exception", e);
} return false;
}

到此,producer也知道自己可以向哪些MessageQueue发送消息了,接下来就是producer的负载均衡算法选出其中一个MessageQueue发送消息(org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#selectOneMessageQueue,这个暂时不详表),MessageQueue包含的信息有topic、brokerName、queueId,但是producer发送的时候得知道broker的ip:port信息,而且一个brokerName对应的是一个broker set,并不能确定具体的broker,所以接下来应该找到具体的broker

// org.apache.rocketmq.client.impl.factory.MQClientInstance#findBrokerAddressInPublish
public String findBrokerAddressInPublish(final String brokerName) {
// 上面updateTopicRouteInfoFromNameServer方法将broker set下的broker地址信息保存到brokerAddrTable
// 再次重申:一个broker set下的broker的brokerName相同
HashMap<Long/* brokerId */, String/* address */> map = this.brokerAddrTable.get(brokerName);
if (map != null && !map.isEmpty()) {
// 没有花样,就是直接返回brokerId时MixAll.MASTER_ID的broker的ip:port信息
// 前面说过master的brokerId就是MixAll.MASTER_ID,所以获取到的broker是broker set中的master
return map.get(MixAll.MASTER_ID);
} return null;
}

终于真相大白,producer只会向是master的broker发送消息,也就是一个broker set中brokerId是0的broker。

producer只能发送消息到master,而不能发送到slave,这也说明了master负责读“写”,而slave只负责读(当然,这里只说明了“写”的部分,关于master 和slave的“读”下一篇介绍)。

总结

本篇介绍了RocketMQ究竟做了什么来实现作为一个消息队列中间件的高可用,由于篇幅会偏长,所以分为两篇文章来说明,下一篇说明文中遗留下的另一个问题——RocketMQ源码 — 六、 RocketMQ高可用(2)


参考:

关于高可用的系统

RocketMQ Architecture

RocketMQ源码 — 三、 Producer消息发送过程

RocketMQ源码 — 六、 RocketMQ高可用(1)的更多相关文章

  1. Linux源码安装RabbitMQ高可用集群

    1.环境说明 linux版本:CentOS Linux release 7.9.2009 erlang版本:erlang-24.0 rabbitmq版本:rabbitmq_server-3.9.13 ...

  2. RocketMQ源码详解 | Broker篇 · 其五:高可用之主从架构

    概述 对于一个消息中间件来讲,高可用功能是极其重要的,RocketMQ 当然也具有其对应的高可用方案. 在 RocketMQ 中,有主从架构和 Dledger 两种高可用方案: 第一种通过主 Brok ...

  3. 读完 RocketMQ 源码,我学会了如何优雅的创建线程

    RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时.高可靠的消息发布与订阅服务. 这篇文章,笔者整理了 RocketMQ 源码中创建线程的几点技巧,希望大家读完之后,能 ...

  4. 源码分析 RocketMQ DLedger(多副本) 之日志复制(传播)

    目录 1.DLedgerEntryPusher 1.1 核心类图 1.2 构造方法 1.3 startup 2.EntryDispatcher 详解 2.1 核心类图 2.2 Push 请求类型 2. ...

  5. 【RocketMQ源码学习】- 4. Client 事务消息源码解析

    介绍 > 基于4.5.2版本的源码 1. RocketMQ是从4.3.0版本开始支持事务消息的. 2. RocketMQ的消息队列能够保证生产端,执行数据和发送MQ消息事务一致性,而消费端的事务 ...

  6. RocketMQ 源码分析 —— Message 发送与接收

    1.概述 Producer 发送消息.主要是同步发送消息源码,涉及到 异步/Oneway发送消息,事务消息会跳过. Broker 接收消息.(存储消息在<RocketMQ 源码分析 —— Mes ...

  7. 【RocketMQ源码分析】深入消息存储(1)

    最近在学习RocketMQ相关的东西,在学习之余沉淀几篇笔记. RocketMQ有很多值得关注的设计点,消息发送.消息消费.路由中心NameServer.消息过滤.消息存储.主从同步.事务消息等等. ...

  8. 【RocketMQ源码分析】深入消息存储(3)

    前文回顾 CommitLog篇 --[RocketMQ源码分析]深入消息存储(1) ConsumeQueue篇 --[RocketMQ源码分析]深入消息存储(2) 前面两篇已经说过了消息如何存储到Co ...

  9. RocketMQ源码详解 | Producer篇 · 其二:消息组成、发送链路

    概述 在上一节 RocketMQ源码详解 | Producer篇 · 其一:Start,然后 Send 一条消息 中,我们了解了 Producer 在发送消息的流程.这次我们再来具体下看消息的构成与其 ...

随机推荐

  1. Android文本框-android学习之旅(十七 )

    文本框简介 文本框属于基本的andoid控件,TextView继承了View是最基本的文本框,它的子类包括EditView和Button等,TextView的大部分方法,它的子类也可以使用. Text ...

  2. 05 Activity生命周期

    生命周期:一个Activity从创建到销毁经过的全部方法 1.onCreate() 创建一个Activity的时候执行的方法 2.onStart()Activity可以被看见到时候无法交互(没有焦点) ...

  3. 【一天一道LeetCode】#345. Reverse Vowels of a String

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Write a ...

  4. [GitHub]第五讲:团队合作流程

    文章转载自:http://blog.csdn.net/loadsong/article/details/51591631 前几天还都是一个开发者唱独角戏.但是尽管如此也可以看出 Git 带来的便利了, ...

  5. (七十一)关于UITableView退出崩溃的问题和滚动到底部的方法

    [TableView退出崩溃的问题] 最近在使用TableView时偶然发现在TableView中数据较多时,如果在滚动过程中退出TableView到上一界面,会引起程序的崩溃,经过网上查阅和思考我发 ...

  6. WIP完工入库及完工退回的几个重要问题

    1.必须向CST_COMP_SNAP_INTERFACE表中插入此工单所有工序的数据(也就是说同样的工单插入多条,只是工序号不一样) 标准文档: Note: If there are multiple ...

  7. Java进阶(二十五)Java连接mysql数据库(底层实现)

    Java进阶(二十五)Java连接mysql数据库(底层实现) 前言 很长时间没有系统的使用java做项目了.现在需要使用java完成一个实验,其中涉及到java连接数据库.让自己来写,记忆中已无从搜 ...

  8. 4.2、Libgdx各个模块概览

    (原文:http://www.libgdx.cn/topic/34/4-2-libgdx%E5%90%84%E4%B8%AA%E6%A8%A1%E5%9D%97%E6%A6%82%E8%A7%88) ...

  9. 【算法导论】最小生成树之Prime法

    关于最小生成树的概念,在前一篇文章中已经讲到,就不在赘述了.下面介绍Prime算法:         其基本思想为:从一个顶点出发,选择由该顶点出发的最小权值边,并将该边的另一个顶点包含进来,然后找出 ...

  10. Ubuntu快速截图

    以前截图,都是按Print键全屏截图,Alt+Print可以截当前的窗口.同时把系统自带的截图工具放到面板上,用的时候点击一下,再选择区域截图,很是不方便.不过,Ubuntu允许自己定义快捷键.要自己 ...