RocketMQ应用及原理剖析
主流消息队列选型对比分析
基础项对比

可用性、可靠性对比

功能性对比

对比分析
- Kafka:系统间的流数据通道
- RocketMQ:高性能的可靠消息传输
- RabbitMQ:可靠消息传输
RocketMQ剖析
RocketMQ拓扑图

RocketMQ架构组成
- Producer:消息发布的角色,支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。
- Consumer:消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制,可以满足大多数用户的需求。
- NameServer:NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。主要包括两个功能:Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以动态感知Broker的路由的信息。
- BrokerServer:消息中转角色,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。
部署架构

集群工作流程
- 启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。
- Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
- 收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
- Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
- Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。
RocketMQ设计
消息存储

- CommitLog:存储消息的主体。product生产的消息主要是顺序写入日志文件,当文件满了,写入下一个文件。
- ConsumerQueue:消息的消费队列。引入的目的主要是提高消息消费的性能,由于RocketMQ是基于主题topic的订阅模式,消息消费是针对主题进行的,如果要遍历commitlog文件中根据topic检索消息是非常低效的。
- FileIndex:索引文件。提供了一种可以通过key或时间区间来查询消息的方法
消息刷盘

- 同步刷盘:性能低,可靠性高。
- 异步刷盘:性能高,可靠性低。
协议设计与编解码
Header字段 | 类型 | Request说明 | Response说明 |
code | int | 请求操作码,应答方根据不同的请求码进行不同的业务处理 | 应答响应码。0表示成功,非0则表示各种错误 |
language | LanguageCode | 请求方实现的语言 | 应答方实现的语言 |
version | int | 请求方程序的版本 | 应答方程序的版本 |
opaque | int | 相当于requestId,在同一个连接上的不同请求标识码,与响应消息中的相对应 | 应答不做修改直接返回 |
flag | int | 区分是普通RPC还是onewayRPC的标志 | 区分是普通RPC还是onewayRPC的标志 |
remark | String | 传输自定义文本信息 | 传输自定义文本信息 |
extFields | HashMap<String, String> | 请求自定义扩展信息 | 响应自定义扩展信息 |

- public ByteBuffer encode() {
- // 1> header length size
- int length = 4;
- // 2> header data length
- byte[] headerData = this.headerEncode();
- length += headerData.length;
- // 3> body data length
- if (this.body != null) {
- length += body.length;
- }
- ByteBuffer result = ByteBuffer.allocate(4 + length);
- // length
- result.putInt(length);
- // header length
- result.put(markProtocolType(headerData.length, serializeTypeCurrentRPC));
- // header data
- result.put(headerData);
- // body data;
- if (this.body != null) {
- result.put(this.body);
- }
- result.flip();
- return result;
- }
负载均衡
product端负载均衡
- 定期获取TopicPublishInfo路由信息
- product发送消息时选取一个messageQueue发送消息(默认的负载均衡策略:随机递增取模)
- 容错机制(故障延时:指对之前失败的,按一定的时间做退避。发送失败默认有会有重试(同步:2次,异步:1次)同步重试会避开上一次发失败的broker
Consumer端负载均衡
mq消息消费方式
push 和pull 两种方式的对比:
consumer获取消息的模式:
负载均衡
- 定时发送心跳包到broker
- consumer开始订阅消息会rebalance 一次
- 定期rebalance(20s)
- 获取队列信息
- 获取消费者信息
- 排序平均分配(默认)
- 与上次结果对比
RocketMQ功能实现分析
RocketMQ延时消息

实现原理
- 替换主题SCHEDULE_TOPIC_XXX,根据延时等级放入对应的队列
- 18个Queue对应18个延时等级
- 每个队列创建定时任务进行调度
- 恢复到期消息重新投递到真实的topic
消息重试

消费失败策略
- 重试16次
- 重试时间间隔递增(通过延时对列完成)
- 失败后进入私信队列
事务消息

- public class TransactionProducer {
- public static void main(String[] args) throws MQClientException, InterruptedException {
- TransactionListener transactionListener = new TransactionListenerImpl();
- TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
- ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
- @Override
- public Thread newThread(Runnable r) {
- Thread thread = new Thread(r);
- thread.setName("client-transaction-msg-check-thread");
- return thread;
- }
- });
- producer.setExecutorService(executorService);
- //事务监听器
- producer.setTransactionListener(transactionListener);
- producer.start();
- String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};
- for (int i = 0; i < 10; i++) {
- try {
- Message msg =
- new Message("TopicTest1234", tags[i % tags.length], "KEY" + i,
- ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
- SendResult sendResult = producer.sendMessageInTransaction(msg, null);
- System.out.printf("%s%n", sendResult);
- Thread.sleep(10);
- } catch (MQClientException | UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- }
- for (int i = 0; i < 100000; i++) {
- Thread.sleep(1000);
- }
- producer.shutdown();
- }
- }
- public interface TransactionListener {
- /**
- * When send transactional prepare(half) message succeed, this method will be invoked to execute local transaction.
- *
- * @param msg Half(prepare) message
- * @param arg Custom business parameter
- * @return Transaction state
- */
- LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);
- /**
- * When no response to prepare(half) message. broker will send check message to check the transaction status, and this
- * method will be invoked to get local transaction status.
- *
- * @param msg Check message
- * @return Transaction state
- */
- LocalTransactionState checkLocalTransaction(final MessageExt msg);
- }
RocketMQ事务消息流程概要
- HALF消息:RMQ_SYS_TRANS_HALF_TOPIC(临时存放消息信息)
- 事务消息替换主体,保存原主题和对列信息
- 半消息对Consumer不可见,不会被投递
怎么记录二阶段的操作?
- OP消息:RMQ_SYS_TARNS_OP_HALF_TOPIC(记录二阶段的操作)
- Rollback:只做记录
- Commit:根据备份信息重新构造消息并投递
扩展
RocketMQ事务消息对业务侵入性强的解决方案

- 开启事务
- 操作本地业务数据
- 插入事务消息数据
- 提交事务
- 发送mq消息
- mq send响应
- mq消息发送成功删除事务消息表中的记录
- 定时补偿模块扫描事务消息表
- 补偿发送mq消息
- mq send 响应
- mq消息发送成功删除事务消息表中的记录
伪代码
- @Transactional
- public void pay(Order order){
- PayTransaction t = buildPayTransaction(order);
- payDao.append(t);
- //producer.sendMessage(buildMessage(t));
- final Message message = buildMessage(t);
- messageDao.insert(message);
- //在事务提交后执行
- triggerAfterTransactionCommit(()->{
- messageClient.send(message);
- messageDao.delete(message);
- });
- }
事务消息表
- CREATE TABLE mq_message(
- id bigint NOT NULL AUTO_INCREMENT,
- content varchar(255) NOT NULL,
- topic char(64) NOT NULL,
- tag char(64),
- status tinyint,
- createtime timestamp,
- PRIMARY KEY(id)
- )
任意时间延时消息实现方案

改造步骤
- Dispatch改造
- 延时消息存储
- 内存索引(时间轮)
- 延时消息投递
- class CommitLogDispatcherBuildConsumeQueue implements CommitLogDispatcher {
- @Override
- public void dispatch(DispatchRequest request) {
- final int tranType = MessageSysFlag.getTransactionValue(request.getSysFlag());
- switch (tranType) {
- case MessageSysFlag.TRANSACTION_NOT_TYPE:
- case MessageSysFlag.TRANSACTION_COMMIT_TYPE:
- DefaultMessageStore.this.putMessagePositionInfo(request);
- break;
- case MessageSysFlag.TRANSACTION_PREPARED_TYPE:
- case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:
- break;
- }
- }
- }
RocketMQ应用及原理剖析的更多相关文章
- ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)
ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...
- ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行
ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...
- 【Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析】
原文:[Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析] [注意:]团队里总是有人反映卸载Xamarin,清理不完全.之前写过如何完全卸载清理剩余的文件.今天写了Windows下的批命令 ...
- 【Xamarin 跨平台机制原理剖析】
原文:[Xamarin 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原 ...
- iPhone/Mac Objective-C内存管理教程和原理剖析
http://www.cocoachina.com/bbs/read.php?tid-15963.html 版权声明 此文版权归作者Vince Yuan (vince.yuan#gmail.com)所 ...
- 【Xamain 跨平台机制原理剖析】
原文:[Xamain 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原生 ...
- Python字符串原理剖析------万恶的+号
字符串原理剖析pyc文件,执行python代码时,如果导入了其他的.py文件,那么执行过程中会自动生成一个与其同名的.pyc文件,该文件就是python解释器变异之后产生的字节码 PS:代码经过编译可 ...
- MapReduce/Hbase进阶提升(原理剖析、实战演练)
什么是MapReduce? MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算.概念"Map(映射)"和"Reduce(归约)",和他们 ...
- ASP.NET Core 运行原理剖析
1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...
随机推荐
- 自动下载MarkDown格式会议论文的程序
近期师兄发给我一个压缩包让我整理文献,而我发现压缩包里的内容是这样: 这样: 和这样的: 我大概看了一下,可能有270多篇文章是这种格式,俗话说的好,没有困难的工作,只有勇敢的研究僧.所以决定用Pyt ...
- Oracle 表空间和权限
表空间 表空间是数据库的逻辑划分,一个表空间只能属于一个数据库.所有的数据库对象都存放在指定的表空间中.但主要存放的是表,所以称作表空间. Oracle中很多优化都是基于表空间的设计理念而实现的,一个 ...
- ICCV2021 | TransFER:使用Transformer学习关系感知的面部表情表征
前言 人脸表情识别(FER)在计算机视觉领域受到越来越多的关注.本文介绍了一篇在人脸表情识别方向上使用Transformer来学习关系感知的ICCV2021论文,论文提出了一个TransFER ...
- [bzoj5416]冒泡排序
结论:一个序列是好序列当且仅当其不存在长度为3的下降子序列 证明:考虑提示,一个长度为3的下降子序列必然会交换三次, 而这三次带来的收益实际上只有2,因此不合法 同时还可以得到:第i个数,要么是前缀最 ...
- [luogu1737]旷野大计算
- 力扣 - 剑指 Offer 27. 二叉树的镜像
题目 剑指 Offer 27. 二叉树的镜像 思路1(递归) 我们可以使用深度优先搜索,先递归到链表的末尾,然后从末尾开始两两交换.就相当于后续遍历而已 记得要先保存下来node.right节点,因为 ...
- linux 同时执行多个命令及几个基础命令
先后不同的命令用分号:隔开即可 基础命令: 1.cd 进入目录 /代表根目录,.代表当前目录,..代表上一级目录 2.ls 显示当前目录下的所有文件和文件夹 -F区分目录和文件,文件后边是*代表可执行 ...
- 洛谷 P5249 - [LnOI2019]加特林轮盘赌(期望 dp+高斯消元)
题面传送门 期望真 nm 有意思,所以蒟蒻又来颓期望辣 先特判掉 \(P_0=0\) 的情况,下面假设 \(P_0\ne 0\). 首先注意到我们每次将加特林对准一个人,如果这个人被毙掉了,那么相当于 ...
- 实现类似Tab选项卡功能关键代码
//放置显示不同Activity的控件 private LinearLayout mainContentLayout; private LocalActivityManager localActivi ...
- DAS,NAS,SAN,简介
根据服务器类型分为:封闭系统的存储和开放系统的存储,封闭系统主要指大型机,开放系统指基于Windows.UNIX.Linux等操作系统的服务器;开放系统的存储分为:内置存储和外挂存储;外挂存储根据连接 ...