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
- 下载rocketmq安装包
- 解压rocketmq安装包
- 配置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节点中。
- 新增两个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)
- 构建环境为 一主和一从
生产者投递消息都是存放在主的Broker中
从Broker每次定时同步主Brokercommitlog日志文件。 - 消费者获取消息— 利弊:
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)
- 在RocketMQ Broker集群中 分为 Master、Slave
- Master做写操作Slave(备份主节点commitlog日志文件)数据
----Slave会同步Master节点中所有commitlog文件数据。
如果当Master节点宕机了,这时候Master节点就无法写入数据(生产者无法投递数据),
但是消费者可以根据Slave(备份)节点消费数据,注意的是 从Slave(备份)节点无法
写入数据。
设置该参数:slaveReadEnable 从服务器不允许读;
RocketMQ读写分离机制:
- 默认的情况下,RocketMQ优先从主Master节点拉取数据信息;
- 如果主服务器的消息堆积过多,占用物理内存40%后,开始建议使用从节点消费,实现
读写分离消费模型,提高IO读写的性能。
RocketMQ消费者进度如何同步:
- 当主节点宕机之后,消费者会使用从服务器提交消费记录,每次消费的记录会保存在当前Broker存储目录:D:\rocketmq\store_slave\config consumerOffset.json
- 从服务器会开启一个定时任务向主服务器发送同步消费进度, 实现主从消费进度同步、不管是在主消费还是在从消费,消费者会优先将该消费的进度汇报给主的服务器,而且我们消费者将消费的进度保存在内存中,当主节点宕机之后,有恢复的话,发送最新消费者进度给主的服务器,这时候就避免了重复消费进度的问题。
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 中存放那些信息呢?
- NameServer 存储Broker ip和端口号信息
- 存放topic主题信息
NameServer 与Broker 关系
- 在Broker配置文件中配置了多个不同的NameServer 集群地址
- Broker 启动的时候,读取到该多个不同的NameServer 地址
- Broker会与所有的NameServer 建立长连接
- 会将自己的信息注册到每个NameServer 上存储起来。
nameServer如何知道Broker 宕机呢?
- 续命的设计:Broker 与每个NameServer 建立长连接之后,每隔30s的时间
发送一个心跳续约包给Broker 告诉我还在存活状态 - nameServer 定时器每隔10s的时间检测 故障Broker ,如果发生故障Broker 会直接剔除。
与nacos、eureka 实现服务注册中心原理是一样的。
生产者:
- 在生产者客户端配置文件中,配置了多个连接nameserver地址
- 采用轮询算法 连接nameserver 如果能够获取到BrokerIP和端口信息
发送请求 - 获取到BrokerIP 发送请求存储该消息。
Broker 与 nameserver心跳续约间隔30s
如果生产者获取到一个故障的Broker地址,实现发送消息如果失败的情况下
如何处理
答案:
- 生产者会默认的情况下 会重试三次
- 重试多次还是失败的情况下,就从新连接nameserver 获取下一条Broker地址
实现调用 存储消息。 - 生产者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发送消息有三种方式:同步、异步和单向
三种发送方式
-
单向 生产者投递消息到mq中,不需要返回结果。
优点:延迟概率比较低
缺点:丢失消息数据
投递消息过程比较耗时时间5毫秒 -
异步 生产者投递消息到mq中,使用回调形式返回。
投递消息过程比较耗时时间5毫秒
补偿---- -
同步
生产者投递消息到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中,消费者处理消息业务逻辑的时候 是采用多线程。
如何解决消息顺序一致性的问题?
- 生产者投递消息根据key 投递到同一个队列中存放
- 消费者应该订阅到同一个队列实现消费
- 最终应该使用同一个线程去消费消息(不能够实现多线程消费。)
实际上做业务逻辑开发中,很少有需要保证消息顺序一致性问题。
1.在rocketmq 消费者默认是多线程异步消费的,开发者需要设定指定保证消息顺序一致性问,也就是同一个队列消息最终被同一个线程实现消费。
- 生产者指定相同的消息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,
生产者如何投递消息:
- 生产者在投递消息到mq服务器端,会将该消息存放在commitlog日志文件中(顺序写)、
- Mq后台就会开启一个异步的线程将该commitlogoffset实现分配存放到不同队列中。
消费者如何消费:
- 消费者消费消息的时候订阅到队列(consumequeue),根据queueoffset 获取到该commitlogoffset
- 在根据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文件中查找。
- 官方:为什么rocketmq commitlog日志文件大小最多只能有一个GB.
- 分段存储 后期日志清理比较方便;
- 零拷贝
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实现分布式事务的原理:
- 生产者投递一个半消息给我们RocketMQ服务器端存放,该消息暂时无法被我们消费者
消费。 - RocketMQ将该消息落地存放硬盘中,RocketMQ发送ACK给生产者。
- 生产者收到事件监听之后,开始执行生产者本地事务的操作;
- 如果生产者执行本地的事务操作,如果成功的情况下,则发送一个提交通知给RocketMQ
服务器端,RocketMQ服务器端将该消息,推送给消费者消费。 - 如果生产者执行本地事务操作,如果失败的情况下,则发送一个回滚通知给rocketmq服务器端,rocketmq服务器端在从本地将该消息删除,不会给消费者消费。
核心思想:确保生产者一定将消息投递到mq服务器端,生产者必须先一定执行完成,在执行消费者。
RocketMQLocalTransactionState.ROLLBACK;—回滚
RocketMQLocalTransactionState.COMMIT—提交
RocketMQLocalTransactionState.UNKNOWN— 不是提交也不是回滚。
存在的一些问题:
- 如果生产者将该消息投递成功之后,但是生产者如果执行本地事务如果失败的情况下,
发送ack给rocketmq 回滚该消息即可,不会被消费者消费。 - 如果生产者将该消息投递成功之后,本地事务执行成功呢,但是不返回状态给rocketmq,如何处理呢?
Rocketmq服务器端 在默认的情况下 每隔60s 检查本地事务是否已经执行过,如果执行过的情况下,则提交该提交,如果没有执行该事务的情况下,则回滚。
常见错误
Lock failed,MQ already started
将 broker 的 master 和 slave 节点放在同一台机器上,配置的storePath相同导致的,修改配置文件,改为不同的路径即可解决。