主流消息队列选型对比分析

基础项对比

可用性、可靠性对比

功能性对比

对比分析

  • 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设计

消息存储

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

消息刷盘

  1. 同步刷盘:性能低,可靠性高。
  2. 异步刷盘:性能高,可靠性低。
一般线上采用异步刷盘+异步复制。如果保证绝对可靠性需要同步刷盘+同步双写,但性能很低,可以针对特别重要的消息,单独部署broker。

协议设计与编解码

在Client和Server之间完成一次消息发送时,需要对发送的消息进行一个协议约定,因此就有必要自定义RocketMQ的消息协议。同时,为了高效地在网络中传输消息和对收到的消息读取,就需要对消息进行编解码。在RocketMQ中,RemotingCommand这个类在消息传输过程中对所有数据内容的封装,不但包含了所有的数据结构,还包含了编码解码操作。
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> 请求自定义扩展信息 响应自定义扩展信息

传输内容主要可以分为以下4部分:
(1) 消息长度:总长度,四个字节存储,占用一个int类型;
(2) 序列化类型&消息头长度:同样占用一个int类型,第一个字节表示序列化类型,后面三个字节表示消息头长度;
(3) 消息头数据:经过序列化后的消息头数据;
(4) 消息主体数据:消息主体的二进制字节数据内容;
  1. public ByteBuffer encode() {
  2. // 1> header length size
  3. int length = 4;
  4.  
  5. // 2> header data length
  6. byte[] headerData = this.headerEncode();
  7. length += headerData.length;
  8.  
  9. // 3> body data length
  10. if (this.body != null) {
  11. length += body.length;
  12. }
  13.  
  14. ByteBuffer result = ByteBuffer.allocate(4 + length);
  15.  
  16. // length
  17. result.putInt(length);
  18.  
  19. // header length
  20. result.put(markProtocolType(headerData.length, serializeTypeCurrentRPC));
  21.  
  22. // header data
  23. result.put(headerData);
  24.  
  25. // body data;
  26. if (this.body != null) {
  27. result.put(this.body);
  28. }
  29.  
  30. result.flip();
  31.  
  32. return result;
  33. }

负载均衡

RocketMQ中的负载均衡都在Client端完成,具体来说的话,主要可以分为Producer端发送消息时候的负载均衡和Consumer端订阅消息的负载均衡。

product端负载均衡

  1. 定期获取TopicPublishInfo路由信息
  2. product发送消息时选取一个messageQueue发送消息(默认的负载均衡策略:随机递增取模)
  3. 容错机制(故障延时:指对之前失败的,按一定的时间做退避。发送失败默认有会有重试(同步:2次,异步:1次)同步重试会避开上一次发失败的broker

Consumer端负载均衡

mq消息消费方式
PUSH :消息队列主动将消息推送给消费者
PULL:消费者主动去消息队列拉取
push 和pull 两种方式的对比:
push:消息实时性高,但没有考虑消费端的消费能力
pull:消息实时性低,可能造成大量无效请求
consumer获取消息的模式:
在RocketMQ中,Consumer端的两种消费模式(Push/Pull)都是基于拉模式来获取消息的,为了平衡push/pull的各自的弊端,使用了一种长轮询机制来拉取消息。Push模式只是对pull模式的一种封装,其本质实现为消息拉取线程在从服务器拉取到一批消息后,然后提交到消息消费线程池后,又继续向服务器再次尝试拉取消息。如果未拉取到消息,则延迟一下又继续拉取。
负载均衡
  1. 定时发送心跳包到broker
  2. consumer开始订阅消息会rebalance 一次
  3. 定期rebalance(20s)
  • 获取队列信息
  • 获取消费者信息
  • 排序平均分配(默认)
  • 与上次结果对比

RocketMQ功能实现分析

RocketMQ延时消息

rockeketMQ支持18个级别的延时等级,默认值为:“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”

实现原理

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

消息重试

Consumer消费消息失败后,RocketMQ提供一种重试机制,令消息再消费一次。RocketMQ会为每个消费组都设置一个Topic名称为“%RETRY%+consumerGroup”的重试队列(这里需要注意的是,这个Topic的重试队列是针对消费组,而不是针对每个Topic设置的),用于暂时保存因为各种异常而导致Consumer端无法消费的消息。考虑到异常恢复起来需要一些时间,会为重试队列设置多个重试级别,每个重试级别都有与之对应的重新投递延时,重试次数越多投递延时就越大。RocketMQ对于重试消息的处理是先保存至Topic名称为“SCHEDULE_TOPIC_XXXX”的延迟队列中,后台定时任务按照对应的时间进行Delay后重新保存至“%RETRY%+consumerGroup”的重试队列中。

消费失败策略

  • 重试16次
  • 重试时间间隔递增(通过延时对列完成)
  • 失败后进入私信队列

事务消息

  1. public class TransactionProducer {
  2. public static void main(String[] args) throws MQClientException, InterruptedException {
  3. TransactionListener transactionListener = new TransactionListenerImpl();
  4. TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
  5. ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
  6. @Override
  7. public Thread newThread(Runnable r) {
  8. Thread thread = new Thread(r);
  9. thread.setName("client-transaction-msg-check-thread");
  10. return thread;
  11. }
  12. });
  13.  
  14. producer.setExecutorService(executorService);
  15. //事务监听器
  16. producer.setTransactionListener(transactionListener);
  17. producer.start();
  18.  
  19. String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};
  20. for (int i = 0; i < 10; i++) {
  21. try {
  22. Message msg =
  23. new Message("TopicTest1234", tags[i % tags.length], "KEY" + i,
  24. ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
  25. SendResult sendResult = producer.sendMessageInTransaction(msg, null);
  26. System.out.printf("%s%n", sendResult);
  27.  
  28. Thread.sleep(10);
  29. } catch (MQClientException | UnsupportedEncodingException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33.  
  34. for (int i = 0; i < 100000; i++) {
  35. Thread.sleep(1000);
  36. }
  37. producer.shutdown();
  38. }
  39. }
  40.  
  41. public interface TransactionListener {
  42. /**
  43. * When send transactional prepare(half) message succeed, this method will be invoked to execute local transaction.
  44. *
  45. * @param msg Half(prepare) message
  46. * @param arg Custom business parameter
  47. * @return Transaction state
  48. */
  49. LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);
  50.  
  51. /**
  52. * When no response to prepare(half) message. broker will send check message to check the transaction status, and this
  53. * method will be invoked to get local transaction status.
  54. *
  55. * @param msg Check message
  56. * @return Transaction state
  57. */
  58. LocalTransactionState checkLocalTransaction(final MessageExt msg);
  59. }

RocketMQ事务消息流程概要

上图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。
1.事务消息发送及提交:
(1) 发送消息(half消息)。
  • HALF消息:RMQ_SYS_TRANS_HALF_TOPIC(临时存放消息信息)

    • 事务消息替换主体,保存原主题和对列信息
    • 半消息对Consumer不可见,不会被投递
(2) 服务端响应消息写入结果。
(3) 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)。
(4) 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)
 
2.补偿流程:
(1) 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
(2) Producer收到回查消息,检查回查消息对应的本地事务的状态
(3) 根据本地事务状态,重新Commit或者Rollback
其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。

怎么记录二阶段的操作?

RocketMQ事务消息方案中引入了Op消息的概念,用Op消息标识事务消息已经确定的状态(Commit或者Rollback)。如果一条事务消息没有对应的Op消息,说明这个事务的状态还无法确定(可能是二阶段失败了)。引入Op消息后,事务消息无论是Commit或者Rollback都会记录一个Op操作。Commit相对于Rollback只是在写入Op消息前创建Half消息的索引。
  • OP消息:RMQ_SYS_TARNS_OP_HALF_TOPIC(记录二阶段的操作)

    • Rollback:只做记录
    • Commit:根据备份信息重新构造消息并投递
 

扩展

 

RocketMQ事务消息对业务侵入性强的解决方案

  1. 开启事务
  2. 操作本地业务数据
  3. 插入事务消息数据
  4. 提交事务
  5. 发送mq消息
  6. mq send响应
  7. mq消息发送成功删除事务消息表中的记录
  8. 定时补偿模块扫描事务消息表
  9. 补偿发送mq消息
  10. mq send 响应
  11. mq消息发送成功删除事务消息表中的记录

伪代码

  1. @Transactional
  2. public void pay(Order order){
  3. PayTransaction t = buildPayTransaction(order);
  4. payDao.append(t);
  5. //producer.sendMessage(buildMessage(t));
  6. final Message message = buildMessage(t);
  7. messageDao.insert(message);
  8. //在事务提交后执行
  9. triggerAfterTransactionCommit(()->{
  10. messageClient.send(message);
  11. messageDao.delete(message);
  12. });
  13. }
 

事务消息表

  1. CREATE TABLE mq_message(
  2. id bigint NOT NULL AUTO_INCREMENT,
  3. content varchar(255) NOT NULL,
  4. topic char(64) NOT NULL,
  5. tag char(64),
  6. status tinyint,
  7. createtime timestamp,
  8. PRIMARY KEY(id)
  9.  
  10. )

任意时间延时消息实现方案

改造步骤

  1. Dispatch改造
  2. 延时消息存储
  3. 内存索引(时间轮)
  4. 延时消息投递
 
Dispatch改造点,增加一种特殊队列存储任意时间延时 改动量比较大,可以增加一种消息类型即可改造
  1. class CommitLogDispatcherBuildConsumeQueue implements CommitLogDispatcher {
  2.  
  3. @Override
  4. public void dispatch(DispatchRequest request) {
  5. final int tranType = MessageSysFlag.getTransactionValue(request.getSysFlag());
  6. switch (tranType) {
  7. case MessageSysFlag.TRANSACTION_NOT_TYPE:
  8. case MessageSysFlag.TRANSACTION_COMMIT_TYPE:
  9. DefaultMessageStore.this.putMessagePositionInfo(request);
  10. break;
  11. case MessageSysFlag.TRANSACTION_PREPARED_TYPE:
  12. case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:
  13. break;
  14. }
  15. }
  16. }

RocketMQ应用及原理剖析的更多相关文章

  1. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  2. ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

    ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...

  3. 【Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析】

    原文:[Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析] [注意:]团队里总是有人反映卸载Xamarin,清理不完全.之前写过如何完全卸载清理剩余的文件.今天写了Windows下的批命令 ...

  4. 【Xamarin 跨平台机制原理剖析】

    原文:[Xamarin 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原 ...

  5. iPhone/Mac Objective-C内存管理教程和原理剖析

    http://www.cocoachina.com/bbs/read.php?tid-15963.html 版权声明 此文版权归作者Vince Yuan (vince.yuan#gmail.com)所 ...

  6. 【Xamain 跨平台机制原理剖析】

    原文:[Xamain 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原生 ...

  7. Python字符串原理剖析------万恶的+号

    字符串原理剖析pyc文件,执行python代码时,如果导入了其他的.py文件,那么执行过程中会自动生成一个与其同名的.pyc文件,该文件就是python解释器变异之后产生的字节码 PS:代码经过编译可 ...

  8. MapReduce/Hbase进阶提升(原理剖析、实战演练)

    什么是MapReduce? MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算.概念"Map(映射)"和"Reduce(归约)",和他们 ...

  9. ASP.NET Core 运行原理剖析

    1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...

随机推荐

  1. 自动下载MarkDown格式会议论文的程序

    近期师兄发给我一个压缩包让我整理文献,而我发现压缩包里的内容是这样: 这样: 和这样的: 我大概看了一下,可能有270多篇文章是这种格式,俗话说的好,没有困难的工作,只有勇敢的研究僧.所以决定用Pyt ...

  2. Oracle 表空间和权限

    表空间 表空间是数据库的逻辑划分,一个表空间只能属于一个数据库.所有的数据库对象都存放在指定的表空间中.但主要存放的是表,所以称作表空间. Oracle中很多优化都是基于表空间的设计理念而实现的,一个 ...

  3. ICCV2021 | TransFER:使用Transformer学习关系感知的面部表情表征

    ​  前言  人脸表情识别(FER)在计算机视觉领域受到越来越多的关注.本文介绍了一篇在人脸表情识别方向上使用Transformer来学习关系感知的ICCV2021论文,论文提出了一个TransFER ...

  4. [bzoj5416]冒泡排序

    结论:一个序列是好序列当且仅当其不存在长度为3的下降子序列 证明:考虑提示,一个长度为3的下降子序列必然会交换三次, 而这三次带来的收益实际上只有2,因此不合法 同时还可以得到:第i个数,要么是前缀最 ...

  5. [luogu1737]旷野大计算

  6. 力扣 - 剑指 Offer 27. 二叉树的镜像

    题目 剑指 Offer 27. 二叉树的镜像 思路1(递归) 我们可以使用深度优先搜索,先递归到链表的末尾,然后从末尾开始两两交换.就相当于后续遍历而已 记得要先保存下来node.right节点,因为 ...

  7. linux 同时执行多个命令及几个基础命令

    先后不同的命令用分号:隔开即可 基础命令: 1.cd 进入目录 /代表根目录,.代表当前目录,..代表上一级目录 2.ls 显示当前目录下的所有文件和文件夹 -F区分目录和文件,文件后边是*代表可执行 ...

  8. 洛谷 P5249 - [LnOI2019]加特林轮盘赌(期望 dp+高斯消元)

    题面传送门 期望真 nm 有意思,所以蒟蒻又来颓期望辣 先特判掉 \(P_0=0\) 的情况,下面假设 \(P_0\ne 0\). 首先注意到我们每次将加特林对准一个人,如果这个人被毙掉了,那么相当于 ...

  9. 实现类似Tab选项卡功能关键代码

    //放置显示不同Activity的控件 private LinearLayout mainContentLayout; private LocalActivityManager localActivi ...

  10. DAS,NAS,SAN,简介

    根据服务器类型分为:封闭系统的存储和开放系统的存储,封闭系统主要指大型机,开放系统指基于Windows.UNIX.Linux等操作系统的服务器;开放系统的存储分为:内置存储和外挂存储;外挂存储根据连接 ...