RocketMQ简介

RocketMQ作为一款纯java、分布式、队列模型的开源消息中间件,支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。
RocketMQ优点

1 RocketMQ去除对zk的依赖

2 RocketMQ支持异步和同步两种方式刷磁盘

3 RocketMQ单机支持的队列或者topic数量是5w

4 RocketMQ支持消息重试

5 RocketMQ支持严格按照一定的顺序发送消息

6 RocketMQ支持定时发送消息

7 RocketMQ支持根据消息ID来进行查询

8 RocketMQ支持根据某个时间点进行消息的回溯

9 RocketMQ支持对消息服务端的过滤

10 RocketMQ消费并行度:顺序消费 取决于queue数量,乱序消费 取决于consumer数量
RocketMQ架构原理

RocketMQ专业名词
Producer 生产者角色—投递消息给mq。
Producer Group 生产者组 —
Consumer 消费者 采用拉取/mq推送方式 获取消息offset
Consumer Group 消费者组----在同一个组中,是不允许多个不同的消费者消费同一条消息。
多个消费者消费同一条消息呢? 两个分组 多个不同的分组中可以允许有不同分组中消费者消费同一条消息的。----
以组的名义关联该组消费的offset位置—
Topic
业务队列存放消息
异步发送短信
异步发送邮件
邮件Topic
短信Topic
业务区分不同的队列消息
Queue
会将一个topic主题中的消息存放在多个不同的Queue 与kafka分区模型是一样
中。

Message
生产者投递消息会自动对给消息生成一个全局消息id,后期的可以根据该消息全局id实现业务的防止重复执行------幂等性概念。

Tag
–区分 过滤

Broker
Mq服务器端
Name Server
与zk相同思想,作为rocketmq注册中心 存放生产者消费者 topic主题信息。

Producer
消息生产者,位于用户的进程内,Producer通过NameServer获取所有Broker的路由信息,根据负载均衡策略选择将消息发到哪个Broker,然后调用Broker接口提交消息。

Producer Group
生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组。

Consumer
消息消费者,位于用户进程内。Consumer通过NameServer获取所有broker的路由信息后,向Broker发送Pull请求来获取消息数据。Consumer可以以两种模式启动,广播(Broadcast)和集群(Cluster),广播模式下,一条消息会发送给所有Consumer,集群模式下消息只会发送给一个Consumer。

Consumer Group
消费者组,和生产者类似,消费同一类消息的多个 Consumer 实例组成一个消费者组。

Topic
Topic用于将消息按主题做划分,Producer将消息发往指定的Topic,Consumer订阅该Topic就可以收到这条消息。Topic跟发送方和消费方都没有强关联关系,发送方可以同时往多个Topic投放消息,消费方也可以订阅多个Topic的消息。在RocketMQ中,Topic是一个上逻辑概念。消息存储不会按Topic分开。

Message
代表一条消息,使用MessageId唯一识别,用户在发送时可以设置messageKey,便于之后查询和跟踪。一个 Message 必须指定 Topic,相当于寄信的地址。Message 还有一个可选的 Tag 设置,以便消费端可以基于 Tag 进行过滤消息。也可以添加额外的键值对,例如你需要一个业务 key 来查找 Broker 上的消息,方便在开发过程中诊断问题。

Tag
标签可以被认为是对 Topic 进一步细化。一般在相同业务模块中通过引入标签来标记不同用途的消息。

Broker
Broker是RocketMQ的核心模块,负责接收并存储消息,同时提供Push/Pull接口来将消息发送给Consumer。Consumer可选择从Master或者Slave读取数据。多个主/从组成Broker集群,集群内的Master节点之间不做数据交互。Broker同时提供消息查询的功能,可以通过MessageID和MessageKey来查询消息。Borker会将自己的Topic配置信息实时同步到NameServer。

Queue
Topic和Queue是1对多的关系,一个Topic下可以包含多个Queue,主要用于负载均衡。发送消息时,用户只指定Topic,Producer会根据Topic的路由信息选择具体发到哪个Queue上。Consumer订阅消息时,会根据负载均衡策略决定订阅哪些Queue的消息。

Offset
RocketMQ在存储消息时会为每个Topic下的每个Queue生成一个消息的索引文件,每个Queue都对应一个Offset记录当前Queue中消息条数。

NameServer
NameServer可以看作是RocketMQ的注册中心,它管理两部分数据:集群的Topic-Queue的路由配置;Broker的实时配置信息。其它模块通过Nameserv提供的接口获取最新的Topic配置和路由信息。

Producer/Consumer :通过查询接口获取Topic对应的Broker的地址信息
Broker : 注册配置信息到NameServer, 实时更新Topic信息到NameServe

RocketMQ环境搭建

注意:一定要配置rocketmq 环境变量 不然启动 mqnamesrv.cmd
报错: Please set the ROCKETMQ_HOME variable in your environment!
启动mqnamesrv

  1. 下载rocketmq安装包
  2. 解压rocketmq安装包
  3. 配置rocketmq环境变量
    系统环境变量配置
    变量名:ROCKETMQ_HOME
    变量值:MQ解压路径\MQ文件夹名
    eg、ROCKETMQ_HOME=D:\rocketmq-all-4.3.0-bin-release
    4.启动 mqnamesrv.cmd

启动mqBroker
Cmd命令框执行进入至‘MQ文件夹\bin’下,然后执行‘start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true’,启动BROKER。成功后会弹出提示框,此框勿关闭。
启动Rocketmq-console
1.下载rocketmq-externals-master
,进入‘rocketmq-externals\rocketmq-console\src\main\resources’文件夹,打开‘application.properties’进行配置。
2. 新增:rocketmq.config.namesrvAddr=127.0.0.1:9876
3. 执行
用CMD进入‘\rocketmq-externals\rocketmq-console’文件夹,执行‘mvn clean package -Dmaven.test.skip=true’,编译生成。
编译成功之后,Cmd进入‘target’文件夹,执行‘java -jar rocketmq-console-ng-2.0.0.jar’,启动‘rocketmq-console-ng-1.0.0.jar’。
4.浏览器中输入‘127.0.0.1:配置端口’,成功后即可查看。
eg:http://127.0.0.1:8088

RocketMQ集群环境
1.集群支持:
  RocketMQ天生对集群的支持非常友好
2.单Master:
  优点:除了配置简单没什么优点
  缺点:不可靠,该机器重启或宕机,将导致整个服务不可用
3.多Master:
  优点:配置简单,性能最高
缺点:可能会有少量消息丢失(配置相关),单台机器重启或宕机期间,该机器下未被消费的消息在机器恢复前不可订阅,影响消息实时性
4.多Master多Slave异步模式:
  每个Master配一个Slave,有多对Master-Slave,集群采用异步复制方式,主备有短消息延迟,毫秒级
  优点:性能同多Master几乎一样,实时性高,主备间切换对应用透明,不需人工干预
  缺点:Master宕机或磁盘损坏时会有少量消息丢失

Rocketmq伪集群搭建

集群:Master节点、Slave节点

Master节点:读写 生产者可以投递消息到Master节点、消费者读取该Master节点消费消息。
Slave节点:只能读 不能够写 消费者只能够读,生产者不能够将消息投递到Slave节点中。

  1. 新增两个broker-a.conf broker-b.conf
    broker-a.conf 配置
    brokerClusterName=DefaultCluster
    #从节点的brokerName必须和主节点一样
    brokerName=broker-a
    #0表示是一个主节点, >0表示Slave
    brokerId=0
    deleteWhen=04
    fileReservedTime=48
    brokerRole=SLAVE
    flushDiskType=ASYNC_FLUSH
    #nameServer地址,分号分割
    namesrvAddr=127.0.0.1:9876
    #允许自动创建主题
    autoCreateTopicEnable=true
    #注意需改端口,并且要和默认的10911相差5以上
    listenPort=10911
    #存储路径
    storePathRootDir=D:\rocketmq\store_master
    #commitLog存储路径
    storePathCommitLog=D:\rocketmq\store_master\commitLog
    #消费队列存储路径
    storePathConsumerQueue=D:\rocketmq\store_master\consumerqueue
    #消息索引存储路径
    storePathIndex=D:\rocketmq\store_master\index
    #checkpoint 文件存储路径
    storeCheckpoint=D:\rocketmq\store_master\checkpoint
    #abort 文件存储路径
    abortFile=D:\rocketmq\store_master\abort

broker-b.conf配置
brokerClusterName=DefaultCluster
#从节点的brokerName必须和主节点一样
brokerName=broker-a
#0表示是一个主节点, >0表示Slave
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH
#nameServer地址,分号分割
namesrvAddr=127.0.0.1:9876
#允许自动创建主题
autoCreateTopicEnable=true
#注意需改端口,并且要和默认的10911相差5以上
listenPort=10921
#存储路径
storePathRootDir=D:\rocketmq\store_slave
#commitLog存储路径
storePathCommitLog=D:\rocketmq\store_slave\commitLog
#消费队列存储路径
storePathConsumerQueue=D:\rocketmq\store_slave\consumerqueue
#消息索引存储路径
storePathIndex=D:\rocketmq\store_slave\index
#checkpoint 文件存储路径
storeCheckpoint=D:\rocketmq\store_slave\checkpoint
#abort 文件存储路径
abortFile=D:\rocketmq\store_slave\abort

启动broker集群

启动broker-a—
F:\path\alibabamq\rocketmq-all-4.3.2-bin-release\bin>mqbroker.cmd -c F:\path\ali
babamq\rocketmq-all-4.3.2-bin-release\conf\cluster\broker-a.conf
The broker[broker-a, 192.168.31.1:10911] boot success. serializeType=JSON and na
me server is 127.0.0.1:9876

启动broker-b—
F:\path\alibabamq\rocketmq-all-4.3.2-bin-release\bin>mqbroker.cmd -c F:\path\ali
babamq\rocketmq-all-4.3.2-bin-release\conf\cluster\broker-b.conf
The broker[broker-a, 192.168.31.1:10921] boot success. serializeType=JSON and na
me server is 127.0.0.1:9876

展示界面

Rocketmq集群同步方案

在RocketMQ中 Broker需要实现集群保证高可用(HA)

  1. 构建环境为 一主和一从
    生产者投递消息都是存放在主的Broker中
    从Broker每次定时同步主Brokercommitlog日志文件。
  2. 消费者获取消息— 利弊:
    A.如果实现一主一从读写分离消费模型----
    消费者订阅从节点消费消息,可能会存在延迟问题。(网络数据传输过程中,延迟必然)
    B. 消费者直接订阅我们主Broker消费消息 延迟概率比较低
    默认的情况下,消费者:订阅我们主的Broker消费消息,如果主的Broker节点物理内存占用达到40%,开始采用订阅从节点实现消费,可以提高读写性能。----读写分离消费模型架构物理内存占用达到40%----消息堆积—

如果主Broker宕机的情况下,生产者是无法投递消息,而我们消费者可以
订阅我们从节点实现数据的消费。

消费者 消费进度同步问题

在每个Broker服务器中, 在C:\Users\Administrator\store\config

consumerOffset.json 记录每个分组消费主题消息队列 消费进度。

“topic_2020_mayikt@mayikt-group23”:{0:2,1:1,2:3,3:0

topic_2020_mayikt 主题名称
mayikt-group23消费者分组名称

0:2,1:1,
0:队列id 消费进度 offset为2的位置
1:队列id 消费者进度 offset为1的位置

1.从节点定时的形式同步主节点消费记录信息,不管消费者订阅主节点还是从节点
最终都会优先的将消费记录结果给主节点,如果节点真的宕机的情况下,先记录在
从节点。
2.如果主节点现在突然存活的情况下,从哪个位置开始消费呢?
如果我们消费者内存中有缓存消费进度的情况下,连接到主节点修改最新消费者进度记录。
如果消费者内存没有缓存消费进度的情况下,可能会发生重复消费的问题。

在RocketMQ中 Broker需要实现集群保证高可用(HA)

  1. 在RocketMQ Broker集群中 分为 Master、Slave
  2. Master做写操作Slave(备份主节点commitlog日志文件)数据

----Slave会同步Master节点中所有commitlog文件数据。
如果当Master节点宕机了,这时候Master节点就无法写入数据(生产者无法投递数据),
但是消费者可以根据Slave(备份)节点消费数据,注意的是 从Slave(备份)节点无法
写入数据。

设置该参数:slaveReadEnable 从服务器不允许读;

RocketMQ读写分离机制:

  1. 默认的情况下,RocketMQ优先从主Master节点拉取数据信息;
  2. 如果主服务器的消息堆积过多,占用物理内存40%后,开始建议使用从节点消费,实现
    读写分离消费模型,提高IO读写的性能。

RocketMQ消费者进度如何同步:

  1. 当主节点宕机之后,消费者会使用从服务器提交消费记录,每次消费的记录会保存在当前Broker存储目录:D:\rocketmq\store_slave\config consumerOffset.json
  2. 从服务器会开启一个定时任务向主服务器发送同步消费进度, 实现主从消费进度同步、不管是在主消费还是在从消费,消费者会优先将该消费的进度汇报给主的服务器,而且我们消费者将消费的进度保存在内存中,当主节点宕机之后,有恢复的话,发送最新消费者进度给主的服务器,这时候就避免了重复消费进度的问题。

Springboot整合方式

注意springboot整合rocketmq server端 版本一定要与rocketmq 不然可能启动报错

Maven依赖

org.springframework.boot
spring-boot-starter-parent
2.2.4.RELEASE





org.springframework.boot
spring-boot-starter-web


org.projectlombok
lombok


org.apache.rocketmq
rocketmq-spring-boot-starter
2.0.1

生产者
@Autowired
private RocketMQTemplate rocketMQTemplate;

/**

  • 普通消息投递 单向发送
    */
    @GetMapping("/sendMsg")
    public String sendMsg() {
    MsgEntity msg = new MsgEntity(“mayikt” + UUID.randomUUID().toString(), 1234);
    rocketMQTemplate.convertAndSend(RocketMQConfig.TOPIC_NAME, msg);
    return “投递消息 => " + msg.toString() + " => 成功”;
    }

消费者
/**

  • @ClassName RocketMQConsumer

  • @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com

  • @Version V1.0
    **/
    @Service
    @Slf4j
    @RocketMQMessageListener(consumerGroup = “mayikt-group5”, topic = “topic_meite”)
    public class RocketMQConsumer implements RocketMQListener {
    @Override
    public void onMessage(MsgEntity msgEntity) {

     log.info("消费者监听到消息:<msg:{}>", msgEntity);
    

    }
    }

配置文件
spring:
application:
name: mayikt-rocketmq
server:
port: 8000
rocketmq:

rocketmq地址

name-server: 127.0.0.1:9876
producer:
# 必须填写 group
group: mayikt-group

Rocketmq配置文件详解
所属集群名字
brokerClusterName=rocketmq-cluster
此处需手动更改
broker名字,注意此处不同的配置文件填写的不一样
附加:按配置文件文件名来匹配
brokerName=broker-a
0 表示Master, > 0 表示slave
brokerId=0
此处许手动更改
(此处nameserver跟host配置相匹配,9876为默认rk服务默认端口)nameServer 地址,分号分割
附加:broker启动时会跟nameserver建一个长连接,broker通过长连接才会向nameserver发新建的topic主题,然后java的客户端才能跟nameserver端发起长连接,向nameserver索取topic,找到topic主题之后,判断其所属的broker,建立长连接进行通讯,这是一个至关重要的路由的概念,重点,也是区别于其它版本的一个重要特性
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
在发送消息时,自动创建服务器不存在的Topic,默认创建的队列数
defaultTopicQueueNums=4
是否允许Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
是否允许Broker自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
Broker 对外服务的监听端口
listenPort=10911
删除文件时间点,默认是凌晨4点
deleteWhen=04
文件保留时间,默认48小时
fileReservedTime=120
commitLog每个文件的大小默认1G
附加:消息实际存储位置,和ConsumeQueue是mq的核心存储概念,之前搭建2m环境的时候创建在store下面,用于数据存储,consumequeue是一个逻辑的概念,消息过来之后,consumequeue并不是把消息所有保存起来,而是记录一个数据的位置,记录好之后再把消息存到commitlog文件里
mapedFileSizeCommitLog=1073741824
ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
destroyMapedFileIntervalForcibly=120000
redeleteHangedFileInterval=120000
检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
存储路径
storePathRootDir=/usr/local/rocketmq/store
commitLog存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
消费队列存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
限制的消息大小
maxMessageSize=65536
flushCommitLogLeastPages=4
flushConsumeQueueLeastPages=2
flushCommitLogThoroughInterval=10000
flushConsumeQueueThoroughInterval=60000
Broker 的角色
• ASYNC_MASTER 异步复制Master
• SYNC_MASTER 同步双写Master
• SLAVE
brokerRote=ASYNC_MASTER
刷盘方式
• ASYNC_FLUSH 异步刷盘
• SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
checkTransactionMessageEnable=false
发消息线程池数量
sendMessageTreadPoolNums=128
拉消息线程池数量
pullMessageTreadPoolNums=128

Rocketmq队列分区模型

Rocketmq 底层存储结构中 将一个主题分成n多个不同的
队列来实现存放消息。
创建了一个主题:需要指定队列个数 默认是4和16
写队列数量: 对我们生产者写投递队列数量 16
读队列数量: 对我们消费者获取消息队列数量 16

在rocketmq中,如果一个topic只有一个队列的情况下支持的并行能力比较弱,所以会将一个topic分成分成n多个不同的队列queue来实现存放, 类似于Kafka的分区模型概念。

writeQueueNums:写队列数,表示producer发送到的MessageQueue的队列个数
readQueueNums:读队列数,表示Consumer读取消息的MessageQueue队列个数
注:这两个值需要相等,在集群模式下如果不相等,writeQueueNums=6,readQueueNums=3, 那么每个broker上会有3个queue的消息是无法消费的。
perm 队列的权限:2表示w、4表示r 6表示rw
架构模拟图

注意我们rocketmq与kafka设计思想原理相同,消息存储在mq服务器端中,消费者不管消费失败还是成功,最终消息还是会存在mq服务器端,可以通过日志策略清理消息。

RocketMQ解决方案

如果现在消费者获取消息、处理器都是同一个线程的情况下,
有可能会影响到我们消费者速率—
消费者获取消息10毫秒—处理消息5s 5.001s
Rocketmq中 消费者消费的消息,采用多线程的形式,需要注意消息顺序一致性的问题。

与kafka思想是一样的,消费者不管消费成功还是失败,最终消息不会立即被删除。
后期都是通过日志删除策略,定时删除消息。

NameServer原理

NameServer注册中心 类似于zookeeper、nacos 存储 服务节点信息。

Rocketmq 不使用Zookeeper而使用Nameserver实现注册中心呢?
Cp(每个节点副本必须要保证数据一致性) ap(每个节点副本数据不一致
,保证可用性。)
Kafka:副本机制 副本选举依赖 控制器 选举依赖Zookeeper—选举过程

在Rocketmq中 人为在配置文件中指定那个Broker 为主节点 ,而在kafka中依赖
与zookeeper实现选举的。

NameServer 每个节点相互之间没有数据过程、都是独立的 (ap模式)
保证可用性、不保证每个NameServer 数据的一致性问题。

NameServer 与Broker 、生产者、消费者关系。

NameServer 中存放那些信息呢?

  1. NameServer 存储Broker ip和端口号信息
  2. 存放topic主题信息

NameServer 与Broker 关系

  1. 在Broker配置文件中配置了多个不同的NameServer 集群地址
  2. Broker 启动的时候,读取到该多个不同的NameServer 地址
  3. Broker会与所有的NameServer 建立长连接
  4. 会将自己的信息注册到每个NameServer 上存储起来。

nameServer如何知道Broker 宕机呢?

  1. 续命的设计:Broker 与每个NameServer 建立长连接之后,每隔30s的时间
    发送一个心跳续约包给Broker 告诉我还在存活状态
  2. nameServer 定时器每隔10s的时间检测 故障Broker ,如果发生故障Broker 会直接剔除。
    与nacos、eureka 实现服务注册中心原理是一样的。

生产者:

  1. 在生产者客户端配置文件中,配置了多个连接nameserver地址
  2. 采用轮询算法 连接nameserver 如果能够获取到BrokerIP和端口信息
    发送请求
  3. 获取到BrokerIP 发送请求存储该消息。

Broker 与 nameserver心跳续约间隔30s

如果生产者获取到一个故障的Broker地址,实现发送消息如果失败的情况下
如何处理
答案:

  1. 生产者会默认的情况下 会重试三次
  2. 重试多次还是失败的情况下,就从新连接nameserver 获取下一条Broker地址
    实现调用 存储消息。
  3. 生产者ack模式 ,生产者必须要将消息落地存放在硬盘中 才认为消息投递成功。

NameServer类似于zookeeper实现服务注册中心

为什么RocketMQ不使用zookeeper?而使用NameServer作为注册中心呢?
Zookeeper实现注册中心 模式CP模式 保证数据的一致性
NameServer 保证AP模式 可用性

特点:
NameServer集群之间不需要数据同步

注册分析:
Broker启动的时候,会读取Broker.config 配置文件,获取到多个不同的nameServer集群的地址列表,会将Broker信息注册给所有的NameServer存储与所有的NameServer保持长连接。
注册信息:
Broker 的IP和端口号 主题信息、集群信息 过滤器等。

NameSever如何知道我们Broker宕机呢?
续约机制:
Broker:
Rocketmq底层基于netty实现的,每个Broker在默认每隔30
S时间给nameServer发送一个心跳续约包,告诉我Broker还在存活。

Name 每隔10s时间检测,故障Broker节点,则剔除。

Broker关系
每个NameServer相互之间都是独立,不会做任何数据同步,采用ap模式思想

Broker信息注册:Broker启动的时候,会根据配置文件读取到多个NameServer地址,轮询的将Broker信息注册到每个NameServer上,这样每个NameServer都有该Broker信息。

剔除:Broker需要每隔30s时间给每个NameServer发送一个心跳续约,如果没有发送心跳续约的话,NameServer 会有一个定时器每隔每10s中扫描一次,检测故障的Broker,则会剔除。
最终每个Broker与NameSserver保持长连接。

生产者关系

如果是每隔30s发送续约,也就是意味着30s后 才能够剔除该Broker
真好在这时候 生产者发送到该故障节点如何处理?

使用ack模式 确认刷盘成功 才属于生产者消息投递成功。

相关疑问

生产者发送消息三种模式
Producer发送消息有三种方式:同步、异步和单向
三种发送方式

  1. 单向 生产者投递消息到mq中,不需要返回结果。
    优点:延迟概率比较低
    缺点:丢失消息数据
    投递消息过程比较耗时时间5毫秒

  2. 异步 生产者投递消息到mq中,使用回调形式返回。
    投递消息过程比较耗时时间5毫秒
    补偿----

  3. 同步
    生产者投递消息到mq中,采用同步的形式获取到返回消息是否有
    投递成功的结果,导致接口延迟概率比较大。
    投递消息过程比较耗时时间10毫秒

发送请求 基于请求与响应

1.同步发送:发送请求模式属于同步的,发送该条消息不需等待该条消息发送成功之后,才可以继续发送下一条。
2.异步发送:采用异步的发送模式,不需要同步阻塞等待,通过回调的形式监听生产者消息投递结果
3单向发送:只负责发送消息给mq,不管是否有发送成功。

同步发送

/**

  • 同步发送
  • @throws Exception
    */
    @GetMapping("/sync")
    public void sync() {
    MsgEntity msg = new MsgEntity(“mayikt” + UUID.randomUUID().toString(), 1234);
    SendResult sendResult = rocketMQTemplate.syncSend(RocketMQConfig.TOPIC_NAME, msg);
    log.info(“同步发送字符串{}, 发送结果{}”, msg.toString(), sendResult);
    }

异步发送
/**

  • 异步发送

  • @throws Exception
    */
    @GetMapping(“async”)
    public void async() {
    MsgEntity msg = new MsgEntity(“mayikt” + UUID.randomUUID().toString(), 1234);
    log.info(">msg:<<" + msg);
    rocketMQTemplate.asyncSend(RocketMQConfig.TOPIC_NAME, msg.toString(), new SendCallback() {
    @Override
    public void onSuccess(SendResult var1) {
    log.info(“异步发送成功{}”, var1);
    }

     @Override
     public void onException(Throwable var1) {
         log.info("异步发送失败{}", var1);
     }
    

    });
    }

单向发送

/**

  • 普通消息投递 单向发送
    */
    @GetMapping("/sendMsg")
    public String sendMsg() {
    MsgEntity msg = new MsgEntity(“mayikt” + UUID.randomUUID().toString(), 1234);
    rocketMQTemplate.convertAndSend(RocketMQConfig.TOPIC_NAME, msg);
    return “投递消息 => " + msg.toString() + " => 成功”;
    }

顺序消息

Rocketmq中,消费者处理消息业务逻辑的时候 是采用多线程。

如何解决消息顺序一致性的问题?

  1. 生产者投递消息根据key 投递到同一个队列中存放
  2. 消费者应该订阅到同一个队列实现消费
  3. 最终应该使用同一个线程去消费消息(不能够实现多线程消费。)

实际上做业务逻辑开发中,很少有需要保证消息顺序一致性问题。

1.在rocketmq 消费者默认是多线程异步消费的,开发者需要设定指定保证消息顺序一致性问,也就是同一个队列消息最终被同一个线程实现消费。

  1. 生产者指定相同的消息key,根据hashKey运算投递到同一个队列中,最终被同一个消费者消费。

相关代码

生产者

String uuid = UUID.randomUUID().toString();
SendResult result1 = rocketMQTemplate.syncSendOrderly(RocketMQConfig.TOPIC_SEQUENTIAL, “insert”, uuid);
log.info(“insert:” + result1.toString());
SendResult result2 = rocketMQTemplate.syncSendOrderly(RocketMQConfig.TOPIC_SEQUENTIAL, “update”, uuid);
log.info(“update:” + result2.toString());
SendResult result3 = rocketMQTemplate.syncSendOrderly(RocketMQConfig.TOPIC_SEQUENTIAL, “delete”, uuid);
log.info(“delete:” + result3.toString());

消费者

@Service
@Slf4j
@RocketMQMessageListener(consumerGroup = “mayikt-group20”, topic = “topic_seq”, consumeMode = ConsumeMode.ORDERLY
)
public class RocketMQConsumer01 implements RocketMQListener {
@Override
public void onMessage(String msg) {
try {
Random r = new Random(100);
int i = r.nextInt(500);
Thread.sleep(i);
} catch (Exception e) {

    }
    log.info("消费者监听到消息:<msg:{}>", msg);
}

}

结果

消息存储结构
在win的安装rocketmq,消息物理存放在
C:\Users\Administrator\store

commitlog:消息的存储目录
config:运行期间一些配置信息
consumequeue:消息消费队列存储目录
index:消息索引文件存储目录
abort:如果存在abort文件说明Broker非正常关闭,该文件默认启动时创建,正常退出时删除
checkpoint:文件检测点。存储commitlog文件最后一次刷盘时间戳、consumequeue最后一次刷盘时间、index索引文件最后一次刷盘时间戳。

学习kafka 的时候,topic主题消息分成n多个不同的partition 分区存放,
而在我们的rocketmq中,将一个topic主题的消息分成多个不同的Queue存放。

前提条件:commitlog日志文件没有满的情况下:
在rocketmq中所有topic主题对应的队列的消息都会存放在同一个commitlog日志文件中,

消费者在消费消息的时候 不会直接与commitlog日志文件打交道。
Rocketmq 提供消费队列 (逻辑概念)

Commitlog日志文件 存放消息内容主体----Commitlogoffset

ConsumeQueue 不存放消息主体,只存放消息的Commitlogoffset、msgsize、msgtag

Queue offset与Commitlogoffset 之间区别?
Queue offset—消息存放在ConsumeQueue 消费偏移量的位置
Commitlogoffset ----消息物理存放位置

[queueId=2, storeSize=242, queueOffset=25, sysFlag=0, bornTimestamp=1611665447721, bornHost=/192.168.31.1:55706, storeTimestamp=1611665447722, storeHost=/192.168.31.1:10911, msgId=C0A81F0100002A9F0000000000039330, commitLogOffset=234288, bodyCRC=336380854, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic=‘topic_ide_mayikt’, flag=0, properties={MIN_OFFSET=0, MAX_OFFSET=26,

生产者如何投递消息:

  1. 生产者在投递消息到mq服务器端,会将该消息存放在commitlog日志文件中(顺序写)、
  2. Mq后台就会开启一个异步的线程将该commitlogoffset实现分配存放到不同队列中。

消费者如何消费:

  1. 消费者消费消息的时候订阅到队列(consumequeue),根据queueoffset 获取到该commitlogoffset
  2. 在根据commitlogoffset 去commitlog日志文件中查找到该消息主体返回给客户端。

消息的commitlogoffset 如何存放在不同的consumequeue中。
Consumequeue==16
投递消息 消息key
消息key%16=1
Consumequeue 中 Consumeoffset 对应 一条消息(没有对应消息主体)—commitlogoffset

消费者消费我们消息 在(kafka、rocketmq)中 消费成功或者失败都不会立即将该消息删除,日志清理策略删除。

Rocketmq消息存储结构
1.(commitlog日志文件没有满的情况下)在rocketmq中所有的消息日志,都存放在同一个commitlog日志文件中,(默认是1GB来存储 好处1零拷贝映射 好处2 分段 非常好方式管理清理日志文件 commitlog命名就是上一个commitlog日志文件中最后一个commitlog-offset值)
,ConsumeQueue类似于在kafka中的 partition 分区模型, 而ConsumeQueue存放的是commitlog开始的commitLogoffset、msgsize、tag。
2.每次消费者读取消息的时候,先读取ConsumeQueue中获取到commitLogoffset,在根据该
commitLogoffset查找commitLog日志文件获取到消息体返回给消费者客户端。

3.与kafka的设计不同
根据阿里巴巴消息中间件团队的测试,如果每个topic中的partition 分区存储的消息过多
,可能会影响到磁盘io的读写性能,所以采用ConsumeQueue存放少量的数据,消息读取还是通过commitlog文件中查找。

  1. 官方:为什么rocketmq commitlog日志文件大小最多只能有一个GB.
  2. 分段存储 后期日志清理比较方便;
  3. 零拷贝
    Rocketmq读写使用:MappedByteBuffer 零拷贝
    因为rocketmq 中日志文件存储采用文件映射机制+mmap 减少用户态与内核态来回拷贝的次数,从而可以提高性能
    MappedByteBuffer 文件存储映射,只能映射用户态1.5GB-2GB 所以RocketMQ日志文件存储commitlog默认为1G大小。

commitLog文件
commitlog文件的存储地址:KaTeX parse error: Undefined control sequence: \store at position 5: HOME\̲s̲t̲o̲r̲e̲\commitlog{fileName},每个文件的大小默认1G =102410241024,commitlog的文件名fileName,名字长度为20位,左边补零,剩余为起始偏移量;比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当这个文件满了,第二个文件名字为00000000001073741824,起始偏移量为1073741824,以此类推,第三个文件名字为00000000002147483648,起始偏移量为2147483648 ,消息存储的时候会顺序写入文件,当文件满了,写入下一个文件。

幂等问题

整合方式

消费者幂等问题

当消费者消费消息失败的时候,可以返回继续重复消费,这时候rocketmq会给
该消费者不断的重试,重试的过程中需要注意幂等性问题。
在rocketmq中,根据返回ack 状态:
ConsumeConcurrentlyStatus
CONSUME_SUCCESS --消费成功
RECONSUME_LATER— 消费失败继续重试消费。

Messageid

对我们每条消息生成一个messageid------根据该id 实现消费者幂等性问题

Messageid 生成规则:关联存放在那个mq服务器端、commitlog-offset值。

Messageid --C0A812E0495C18B4AAC276054FC20005 转码
获取到commitlog-offset —查找物理对应消息位置。

Rocketmq中的 messageid 长度一共占用16个字节,其中包含消息存储的ip和端口号,
消息commitLogOffset,客户端发送请求传递messageid 给mq服务器端,mq服务器端
根据该commitlogoffset值查找对应的消息返回给客户端。
消息过滤

1.Tag就是相当于给消息打一个标签,比如我的文章 属于那些标签下,用户如果订阅了该标签就可能刷到该文章。
2. 一个消息可能打上了多个tag标签,消费者订阅该主题和tag标签,标签如果一致的情况下就可以获取到该消息。
原理:在我们ConsumeQueue中,存储结构为 commitoffset、size、messagetag 会根据消费者传递的tag hash值与队列中每条消息的tag hash 做比较,如果相等的情况则获取该消息,如果不相等的情况下,则不会获取该消息。

消费者组
与kafka基本原理相同,同一个消费者中,只能有一个消费者消费消息,
多个不同的消费者组可以消费同一条消息。
消费成功之后,提交offset。
分布式事务
消息的可用性

刷盘:将数据从pagecache中刷盘到硬盘中存储
,避免数据的丢失。
刷盘模式:
1.同步刷盘:
生产者必须等待该消息 从pagecache刷盘到硬盘中,在返回
Ack给生产者。
优点:可以保证消息不丢失
缺点:影响到整体接口吞吐量
应用场景:金融支付类 msg

2.异步刷盘

生产者不需要等待pagecache刷盘到硬盘中结果,完全采用异步的形式
优点:
提高整体接口吞吐量
缺点:
有可能消息会丢失 (概率不是很大)

应用场景:行为分析---- 100 -12 98%

刷盘策略

RocketMQ,默认会将消息持久化存放在硬盘,首先会
写入到系统PageCahe中,让后再刷盘到硬盘,这样就可以保证
硬盘与PageCache中数据完全一致性。

同步刷盘:

消息真正刷盘到磁盘中,才会返回给生产者,只要磁盘没有坏,这样做
可以保证消息不丢失,但是可能会影响整体的吞吐量。

异步刷盘:

读写充分利用pagecache,消息写入到pagecache成功之后,采用异步的形式
刷盘到硬盘中,可以提高系统的吞吐量,但是可能消息会丢失。

RocketMQ解决分布式事务问题

RocketMQ解决分布式事务思想与原理

RocketMQ在其消息定义的基础上,对事务消息扩展了两个相关的概念:
1.Half(Prepare) Message——半消息(预处理消息)
半消息是一种特殊的消息类型,该状态的消息暂时不能被Consumer消费。当一条事务消息被成功投递到Broker上,但是Broker并没有接收到Producer发出的二次确认时,该事务消息就处于"暂时不可被消费"状态,该状态的事务消息被称为半消息。
2.Message Status Check——消息状态回查
由于网络抖动、Producer重启等原因,可能导致Producer向Broker发送的二次确认消息没有成功送达。如果Broker检测到某条事务消息长时间处于半消息状态,则会主动向Producer端发起回查操作,查询该事务消息在Producer端的事务状态(Commit 或 Rollback)。可以看出,Message Status Check主要用来解决分布式事务中的超时问题。

RocketMQ的事务消息是基于两阶段提交实现的,也就是说消息有两个状态,prepared和commited。当消息执行完send方法后,进入的prepared状态,进入prepared状态以后,就要执行executeLocalTransaction方法,这个方法的返回值有3个,也决定着这个消息的命运,

COMMIT_MESSAGE:提交消息,这个消息由prepared状态进入到commited状态,消费者可以消费这个消息;
ROLLBACK_MESSAGE:回滚,这个消息将被删除,消费者不能消费这个消息;
UNKNOW:未知,这个状态有点意思,如果返回这个状态,这个消息既不提交,也不回滚,还是保持prepared状态,而最终决定这个消息命运的,是checkLocalTransaction这个方法。

RocketMQ实现分布式事务的原理:

  1. 生产者投递一个半消息给我们RocketMQ服务器端存放,该消息暂时无法被我们消费者
    消费。
  2. RocketMQ将该消息落地存放硬盘中,RocketMQ发送ACK给生产者。
  3. 生产者收到事件监听之后,开始执行生产者本地事务的操作;
  4. 如果生产者执行本地的事务操作,如果成功的情况下,则发送一个提交通知给RocketMQ
    服务器端,RocketMQ服务器端将该消息,推送给消费者消费。
  5. 如果生产者执行本地事务操作,如果失败的情况下,则发送一个回滚通知给rocketmq服务器端,rocketmq服务器端在从本地将该消息删除,不会给消费者消费。
    核心思想:确保生产者一定将消息投递到mq服务器端,生产者必须先一定执行完成,在执行消费者。

RocketMQLocalTransactionState.ROLLBACK;—回滚
RocketMQLocalTransactionState.COMMIT—提交
RocketMQLocalTransactionState.UNKNOWN— 不是提交也不是回滚。

存在的一些问题:

  1. 如果生产者将该消息投递成功之后,但是生产者如果执行本地事务如果失败的情况下,
    发送ack给rocketmq 回滚该消息即可,不会被消费者消费。
  2. 如果生产者将该消息投递成功之后,本地事务执行成功呢,但是不返回状态给rocketmq,如何处理呢?

Rocketmq服务器端 在默认的情况下 每隔60s 检查本地事务是否已经执行过,如果执行过的情况下,则提交该提交,如果没有执行该事务的情况下,则回滚。

常见错误
Lock failed,MQ already started

将 broker 的 master 和 slave 节点放在同一台机器上,配置的storePath相同导致的,修改配置文件,改为不同的路径即可解决。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值