一、相关推荐
二、基本架构图:

Name server、producer、consumer是无状态的,集群中的每个节点都是一样的。
Broker有两种,master和slave。master主要复制写,slave只能读。
一个slave只能属于一个master。
三、集群模式

- 复制策略:(主从复制)
复制策略是指将Master的数据复制到Slave。并且分为同步复制和异步复制。
同步复制:消息写入master后,master会等待slave同步数据成功后才向producer返回成功ACK(确认信息)
异步复制:消息写入master后,master立即向producer返回成功ACK,无需等待slave同步数据成功
- 刷盘策略:(将消息刷入磁盘,实现持久化)
刷盘策略指的是broker中消息的落盘方式,即消息发送到broker内存后消息持久化到磁盘的方式。分为同步刷盘与异步刷盘。
同步刷盘:当消息持久化到broker的磁盘后才算是消息写入成功。
异步刷盘:当消息写入到broker的内存后即表示消息写入成功,无需等待消息持久化到磁盘。
1)异步刷盘和异步复制都会降低系统的写入延迟,RT变小,提高了系统的吞吐量
2)消息写入到Broker的内存,一般是写入到了PageCache
3)对于异步 刷盘策略,消息会写入到PageCache后立即返回成功ACK。但并不会立即做落盘操作,而是当PageCache到达一定量时会自动进行落盘。
1、单Master模式(这种单节点的理论上不叫集群)
这种方式风险较大,可用性不高,一旦Broker重启或者宕机时,会导致整个服务不可用。不建议线上环境使用,可以用于本地测试。
2、多Master模式
一个集群无Slave,全是Master,例如2个Master或者3个Master,这种模式的优缺点如下:
优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置为RAID10时,即使机器宕机不可恢复情况下,由于RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高;
缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到影响。
3、多Master多Slave模式(异步)
每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点如下:
优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,可用性比较高,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样。
缺点:Master宕机,磁盘损坏情况下会丢失少量消息。(由于异步从机可能还未来得及复制,主机就宕机了。这样可能会丢失少量数据。)
4、多Master多Slave模式(同步)
每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主、从都写成功,才向应用返回成功,这种模式的优缺点如下:
优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
缺点:性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,从机不具备反客为主,不能自动切换为主机。
5、开始搭建(多Master多Slave模式)
5.1、准备(!!搭建失败了)
这里因为只有一台主机,所有通过端口号区分不同的节点。
为了看起来更像两台主机,我们修改一下hosts。

修改etc/hosts
修改hosts有什么用

注册中心:nameserver
127.0.0.1:9876
127.0.0.1:9877
broker:
master:
127.0.0.1:10911
127.0.0.1:10912
slave:
127.0.0.1:11011
127.0.0.1:11012
producer:
consumer:
注意:理论上搭建集群,有以下原则。多个主节点分布在不同ip下,主节点和从节点,不在同一个ip下。这样保证一台主机宕机后,不会出现微服务集体失效。
5.2、修改Brocker配置文件
官方为我们提供了几种方式的实例配置文件。
[root@iz2zedg4ylq9iqtwm11wecz conf]# pwd
/my/rocketMQ/rocketmq-all-4.5.0-bin-release/conf
[root@iz2zedg4ylq9iqtwm11wecz conf]# ls
2m-2s-async(双主双从异步) 2m-noslave (双主) dledger logback_namesrv.xml plain_acl.yml
2m-2s-sync(双主双从同步) broker.conf logback_broker.xml logback_tools.xml tools.yml
查看其中一个配置文件
[root@iz2zedg4ylq9iqtwm11wecz conf]# cd 2m-2s-sync/
[root@iz2zedg4ylq9iqtwm11wecz 2m-2s-sync]# ls
broker-a.properties(主a) broker-a-s.properties(a的从) broker-b.properties (主b) broker-b-s.properties(b的从)
[root@iz2zedg4ylq9iqtwm11wecz 2m-2s-sync]#
- 修改broker-a.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割,这里的rocketmq1和2是同一台主机,在etc/hosts中配置了ip的地址映射
namesrvAddr=rocketmq1:9876;rocketmq2:9877
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store
#commitLog 存储路径
storePathCommitLog=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store/consumequeue
#消息索引存储路径
storePathIndex=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store/index
#checkpoint 文件存储路径
storeCheckpoint=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store/checkpoint
#abort 文件存储路径
abortFile=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
- 修改broker-b.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割,这里的rocketmq1和2是同一台主机,在etc/hosts中配置了ip的地址映射
namesrvAddr=rocketmq1:9876;rocketmq2:9877
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10912
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b
#commitLog 存储路径
storePathCommitLog=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b/consumequeue
#消息索引存储路径
storePathIndex=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b/index
#checkpoint 文件存储路径
storeCheckpoint=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b/checkpoint
#abort 文件存储路径
abortFile=/my/rocketMQ/rocketmq-all-4.5.0-bin-release/store-b/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
- 修改配置broker-a-s.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq1:9876;rocketmq2:9877
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store-a
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store-a/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store-a/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store-a/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store-a/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store-a/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
- 修改配置broker-b-s.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq1:9876;rocketmq2:9877
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11012
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store-b
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store-b/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store-b/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store-b/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store-b/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store-b/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
5.3、启动
- 启动nameserver
[root@iz2zedg4ylq9iqtwm11wecz ~]# nohup sh mqnamesrv &
[1] 10096
[root@iz2zedg4ylq9iqtwm11wecz ~]# nohup: ignoring input and appending output to ‘nohup.out’
[root@iz2zedg4ylq9iqtwm11wecz ~]# jps
4562 newideas-0.0.1-SNAPSHOT.jar
10099 NamesrvStartup
997
10124 Jps
- 启动Broker
[root@iz2zedg4ylq9iqtwm11wecz conf]# nohup sh mqbroker -c /my/rocketMQ/rocketmq-all-4.5.0-bin-release/conf/2m-2s-sync/broker-a.properties &
[2] 10545
[root@iz2zedg4ylq9iqtwm11wecz conf]# nohup: ignoring input and appending output to ‘nohup.out’
[root@iz2zedg4ylq9iqtwm11wecz conf]# jps
4562 newideas-0.0.1-SNAPSHOT.jar
10099 NamesrvStartup
10549 BrokerStartup
997
10616 Jps
四、消息发送样例
1、导入pom依赖
<dependencies>
<!-- 导入rocketmq客户端依赖-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>
</dependencies>
2、运行测试
package com.lihua.rocketmq.producer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
/**
* 生产者发送同步消息
* @author 15594
*/
public class SyncProducer {
public static void main(String[] args) throws Exception {
//实例化消息生产者producer,group1表示这个生产者属于哪个集群
DefaultMQProducer producer = new DefaultMQProducer("group1");
//注册到注册中心
producer.setNamesrvAddr("39.96.52.225:9876");
//启动producer实例
producer.start();
for (int i = 0; i < 100; i++) {
//创建消息,并指定Topic,Tag和消息体。topic:主题,标志了消息的类型 tags:标签,第二主题,子主题 keys:消息关键词。messagebody:具体的消息内容。
//注意:在rocketmq里面所有消息都必须以二进制传输
Message msg = new Message("TopicTest" , "TagA" , ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
// 发送消息到一个Broker
SendResult sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.printf("%s%n", sendResult);
}
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}

五、rocketmq的工作原理

rocketmq的工作原理分三个部分(消息生产过程、消息存储过程、消息消费过程)讲解。
1、消息生产过程
- 首先 Producer或者Producer Group(生产者集群)向Name Server(注册中心)发出请求获得消息Topic的路由信息。简而言之:Producer 只能跟Name Server联系,然后会得到向Name Server中注册了的Broker的信息。有了这些信息(路由表的信息,因为Broker不可能只有一台,一般是集群)才能向Broker存储Topic消息。
- 联系上Name Server后,会从返回Topic的路由表及Broker列表。
- Producer根据代码中指定的Queue选择策略,从Queue列表中选出一个队列,用于后续存储消息。
- Producer向选择出的Queue所在的Broker发出RPC请求,将消息发送到选择出的Queue。
**注意:
1、是先选出Queue(这个过程在Producer进行),在根据Queue所在的Broker发出RPC请求,将消息发送到选择出的Queue
2、Produer对消息做一些特殊处理,例如,消息本身超过4M,则会对其进行压缩
路由表:
实际是一个Map,key为Topic名称,value是一个QueueData实例列表。QueueData并不是一个Queue对应一个QueueData,而是一个Broker中该Topic的所有Queue对应一个QueueData。即,只要涉及到该Topic的Broker,一个Broker对应一个QueueData。QueueData中包含brokerName。简单来说,路由表的key为Topic名称,value则为所有涉及该Topic的BrokerName列表。
Broker列表:
其实际也是一个Map。key为brokerName,value为BrokerData。一个Broker对应一个BrokerData实例,对吗?不对。一套brokerName名称相同的Master-Slave小集群对应一个BrokerData。BrokerData中包含brokerName及一个map。该map的key为brokerId,value为该broker对应的地址。brokerId为0表示该broker为Master,非0表示Slave。
概念很抽象,图解:

1)Queue选择算法
对于无序消息,其Queue选择算法,也称为消息投递算法,常见的有两种:
轮询算法
默认选择算法。该算法保证了每个Queue中可以均匀的获取到消息
该算法存在一个问题:由于某些原因,在某些Broker上的Queue可能投递延迟较严重。从而导致Producer的缓存队列中出现较大的消息积压,影响消息的投递性能。
最小投递延迟算法
该算法会统计每次消息投递的时间延迟,然后根据统计出的结果将消息投递到时间延迟最小的Queue。如果延迟相同,则采用轮询算法投递。该算法可以有效提升消息的投递性能。
该算法也存在一个问题:消息在Queue上的分配不均匀。投递延迟小的Queue其可能会存在大量的消息。而对该Queue的消费者压力会增大,降低消息的消费能力,可能会导致MQ中消息的堆积。
2、消息的存储过程
注意:消息存储的过程是发生在单个Broker中,不要搞混了!!
[root@iz2zedg4ylq9iqtwm11wecz store]# pwd
/root/store
[root@iz2zedg4ylq9iqtwm11wecz store]# ll
total 24
-rw-r--r-- 1 root root 0 Aug 31 10:47 abort
-rw-r--r-- 1 root root 4096 Aug 31 21:36 checkpoint
drwxr-xr-x 2 root root 4096 Aug 30 09:55 commitlog
drwxr-xr-x 2 root root 4096 Aug 31 21:37 config
drwxr-xr-x 3 root root 4096 Aug 29 19:02 consumequeue
drwxr-xr-x 2 root root 4096 Aug 31 10:47 index
-rw-r--r-- 1 root root 4 Aug 31 10:47 lock
[root@iz2zedg4ylq9iqtwm11wecz store]#
- abort:该文件在Broker启动后会自动创建,正常关闭Broker,该文件会自动消失。若在没有- - - 启-动Broker的情况下,发现这个文件是存在的,则说明之前Broker的关闭是非正常关闭。
- checkpoint:其中存储着commitlog、consumequeue、index文件的最后刷盘时间戳
- commitlog:其中存放着commitlog文件,而消息是写在commitlog文件中的
- config:存放着Broker运行期间的一些配置数据
- consumequeue:其中存放着consumequeue文件,队列就存放在这个目录中
- index:其中存放着消息索引文件indexFile
- lock:运行期间使用到的全局资源锁
我们需要注意的是commitlog、consumequeue、index这三个文件夹
1)commitlog
[root@iz2zedg4ylq9iqtwm11wecz commitlog]# pwd
/root/store/commitlog
[root@iz2zedg4ylq9iqtwm11wecz commitlog]# ls
00000000000000000000 # 这个是文件,里面存放着消息的信息。这个文件一般被叫做mappedFile
[root@iz2zedg4ylq9iqtwm11wecz commitlog]#
commitlog这个文件夹是用来存放消息的。Producer发送(生产的消息)会按照顺序写入commitlog文件夹里面的文件中(00000000000000000000,也就是mappedFile)。00000000000000000000 这个文件是有大小规定的,最大只能是10G,超过10G后会创建一个新的mappedFile,这个新的mappedFile的名字也是有规定的,规则如下:
第一个文件名一定是20位0构成的。因为第一个文件的第一条消息的偏移量commitlog offset为0当第一个文件放满时,则会自动生成第二个文件继续存放消息。假设第一个文件大小是1073741820字节(1G = 1073741824字节),则第二个文件名就是00000000001073741824。以此类推,第n个文件名应该是前n-1个文件大小之和。一个Broker中所有mappedFile文件的commitlog offset是连续的。
注意:一个Broker中仅包含一个commitlog目录,所有的mappedFile文件都是存放在该目录中的。即无论当前Broker中存放着多少Topic的消息,这些消息都是被顺序写入到了mappedFile文件中的。也就是说,这些消息在Broker中存放时并没有被按照Topic进行分类存放
- 消息单元

mappedFile文件内容由一个个的消息单元构成(每一行)。每个消息单元中包含消息总长度MsgLen、消息的物理位置physicalOffset、消息体内容Body、消息体长度BodyLength、消息主题Topic、Topic长度TopicLength、消息生产者BornHost、消息发送时间戳BornTimestamp、消息所在的队列QueueId、消息在Queue中存储的偏移量QueueOffset等近20余项消息相关属性。
2) consumequeue
[root@iz2zedg4ylq9iqtwm11wecz consumequeue]# pwd
/root/store/consumequeue
[root@iz2zedg4ylq9iqtwm11wecz consumequeue]# ls
TopicTest TopicTest1 TopicTest2
[root@iz2zedg4ylq9iqtwm11wecz consumequeue]#
如果里面什么的没有可以通过Producer去生产几个 topic
package com.lihua.rocketmq.producer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
/**
* 生产者发送同步消息
* @author 15594
*/
public class SyncProducer {
public static void main(String[] args) throws Exception {
//实例化消息生产者producer,group1表示这个生产者属于哪个集群
DefaultMQProducer producer = new DefaultMQProducer("group1");
//注册到注册中心
producer.setNamesrvAddr("39.96.52.225:9876");
//启动producer实例
producer.start();
for (int i = 0; i < 10; i++) {
//创建消息,并指定Topic,Tag和消息体。topic:主题,标志了消息的类型 tags:标签,第二主题,子主题 keys:消息关键词。messagebody:具体的消息内容。
//注意:在rocketmq里面所有消息都必须以二进制传输
Message msg = new Message("TopicTest2" , "TagA2" , ("Hello RocketMQ2 " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
// 发送消息到一个Broker
SendResult sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.printf("%s%n", sendResult);
}
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}

consumequeue(也就是00000这个文件)文件名也由20位数字构成,表示当前文件的第一个索引条目的起始位移偏移量。与mappedFile文件名不同的是,其后续文件名是固定的。因为consumequeue文件大小是固定不变的。每个consumequeue文件可以包含30w个索引条目,每个索引条目包含了三个消息重要属性:消息在mappedFile文件中的偏移量CommitLog Offset、消息长度、消息Tag的hashcode值。这三个属性占20个字节,所以每个文件的大小是固定的30w * 20字节。
- 索引条目

3)消息的读写(存取)

queueOffset : 写入队列时,写入哪个位置 消费offset :Consumer消费到这个Queue的哪个位置了。(读取到哪个位置) 消息offset = 消费offset + 1 (也就是下一个消费的消息的谁)
- 消息写入
先将详细写入commitLog,然后再写入consumequeue
- Broker根据queueId(这个队列id在producer中通过指定的算法确定),获取到该消息对应索引条目要在consumequeue目录中的写入偏移量,即QueueOffset
- 将queueId、queueOffset等数据,与消息一起封装为消息单元
- 将消息单元写入到commitlog
- 同时,形成消息索引条目
- 将消息索引条目分发到相应的consumequeue
- 消息读取
- Consumer获取到其要消费消息所在Queue的消费偏移量offset,计算出其要消费消息的消息offset。
消费偏移量offset,即消费进度,即consumer消费到了该Queue的第几条消息。
消息offset = 消费offset + 1可以查看一下消费偏移量(消费进度): ```powershell [root@iz2zedg4ylq9iqtwm11wecz config]# pwd /root/store/config [root@iz2zedg4ylq9iqtwm11wecz config]# ll total 36 -rw-r--r-- 1 root root 27 Sep 1 09:24 consumerFilter.json -rw-r--r-- 1 root root 27 Sep 1 09:24 consumerFilter.json.bak -rw-r--r-- 1 root root 189 Sep 1 09:24 consumerOffset.json -rw-r--r-- 1 root root 189 Sep 1 09:24 consumerOffset.json.bak -rw-r--r-- 1 root root 21 Sep 1 09:24 delayOffset.json -rw-r--r-- 1 root root 21 Sep 1 09:24 delayOffset.json.bak -rw-r--r-- 1 root root 2505 Aug 29 19:05 subscriptionGroup.json -rw-r--r-- 1 root root 2627 Aug 31 22:19 topics.json -rw-r--r-- 1 root root 2445 Aug 31 22:19 topics.json.bak [root@iz2zedg4ylq9iqtwm11wecz config]# cat consumerOffset.json { "offsetTable":{ "TopicTest@please_rename_unique_group_name_4":{0:249,1:250,2:249,3:249 # 消费进度 }, "%RETRY%please_rename_unique_group_name_4@please_rename_unique_group_name_4":{0:0 } } }[root@iz2zedg4ylq9iqtwm11wecz config]# ```
- Consumer向Broker发送拉取请求,其中会包含其要拉取消息的Queue、消息offset及消息Tag。
- Broker计算在该consumequeue中的queueOffset。
queueOffset = 消息offset * 20字节
-
从该queueOffset处开始向后查找第一个指定Tag的索引条目。
-
解析该索引条目的前8个字节,即可定位到该消息在commitlog中的commitlog offset
-
从对应commitlog offset中读取消息单元,并发送给Consumer
4) indexFile(这个文件的作用根据用户指定的key和时间查询消息)
indexFile里面存放了消息的索引,根据key进行消息查询的功能。(这里的key是生产者生产信息时由用户指定的)
- key什么

[root@iz2zedg4ylq9iqtwm11wecz index]# pwd
/root/store/index
[root@iz2zedg4ylq9iqtwm11wecz index]# ls
20210831104707863 # indexFile ,文件名为indexFile 文件创建时的时间
[root@iz2zedg4ylq9iqtwm11wecz index]#
- indexFile文件创建的时机
- 当第一条带key的消息发送来后,系统发现没有indexFile,此时会创建第一个indexFile文件
- 当一个indexFile中挂载的index索引单元数量超出2000w个时,会创建新的indexFile。当带key的消息发送到来后,系统会找到最新的indexFile,并从其indexHeader的最后4字节中读取到indexCount。若indexCount >= 2000w时,会创建新的indexFile。
由于可以推算出,一个indexFile的最大大小是:(40 + 500w * 4 + 2000w * 20)字节
- indexFile文件 存储条目的结构




key的hash值 % 500w的结果即为slot槽位,然后将该slot值修改为该index索引单元的indexNo,根据这个indexNo可以计算出该index单元在indexFile中的位置。不过,该取模结果的**重复率是很高(碰撞)**的,为了解决该问题,在每个index索引单元中增加了preIndexNo,用于指定该slot中当前index索引单元的前一个index索引单元。而slot中始终存放的是其下最新的index索引单元的indexNo,这样的话,只要找到了slot就可以找到其最新的index索引单元,而通过这个index索引单元就可以找到其之前的所有index索引单元。
indexNo是一个在indexFile中的流水号,从0开始依次递增。即在一个indexFile中所有indexNo是以此递增的。indexNo在index索引单元中是没有体现的,其是通过indexes中依次数出来的。
- 查询流程

3、消息的消费
先获取消息,再消费消息。
1)获取消息的两种方式:
- 主动pull(拉取)
Consumer主动从Broker中拉取消息,主动权由Consumer控制。一旦获取了批量消息,就会启动消费过程。不过,该方式的实时性较弱,即Broker中有了新的消息时消费者并不能及时发现并消费。
注意:拉取时间间隔由用户指定,间隔短可能会空拉取(无用功),时间长实效性差 - 由Brokerpush(推送)
该模式下Broker收到数据后会主动推送给Consumer。该获取方式一般实时性较高。
该获取方式是典型的发布-订阅模式,即Consumer向其关联的Queue注册了监听器,一旦发现有新的消息到来就会触发回调的执行,回调方法是Consumer去Queue中拉取消息。而这些都是基于Consumer与Broker间的长连接的。长连接的维护是需要消耗系统资源的。
总结:
- pull 实时性差,但便于应用控制消息的拉取。
- push实时性强,但会占用较多的系统资源。
2)消息的两种消费模式:
-
广播消费(一个消息能被所有消费者消费)
一个队列中,所有消息都被消费组(集群)中的所有消费者消费

-
集群消费(一个消息,只能被消费一次)
一个队列中,一个消息只能被消费组(集群)里的一个消费者消费

3)两种消费模式的进度保存:
-
广播模式:
消费进度保存在consumer端。 因为广播模式下consumer group中每个consumer都会消费所有消息,但它们的消费进度是不同。所以consumer各自保存各自的消费进度。 -
集群模式:
消费进度保存在broker中。 consumer group中的所有consumer共同消费同一个Topic中的消息,同一条消息只会被消费一次。消费进度会参与到了消费的负载均衡中,故消费进度是需要共享的。
集群模式下消费进度保存在broker的consumerOffset.json文件下
[root@iz2zedg4ylq9iqtwm11wecz config]# pwd
/root/store/config
[root@iz2zedg4ylq9iqtwm11wecz config]# ls
consumerFilter.json consumerOffset.json.bak subscriptionGroup.json
consumerFilter.json.bak delayOffset.json topics.json
consumerOffset.json delayOffset.json.bak topics.json.bak
[root@iz2zedg4ylq9iqtwm11wecz config]# cat consumerOffset.json
{
"offsetTable":{
"TopicTest@please_rename_unique_group_name_4":{0:249,1:250,2:249,3:249 # 这个就是集群的消费进度
},
"%RETRY%please_rename_unique_group_name_4@please_rename_unique_group_name_4":{0:0
}
}
}
4、Queue分配算法
1、Queue分配算法在消费者客户端进行,Queue选择算法在Producer 生产者客户端进行。
2、一个Topic中的Queue只能由Consumer Group中的一个Consumer进行消费,而一个Consumer可以同时消费多个Queue中的消息。
1) 平均分配
根据avg = QueueCount / ConsumerCount的计算结果进行分配的。如果能够整除,则按顺序将avg个Queue逐个分配Consumer;如果不能整除,则将多余出的Queue按照Consumer顺序逐个分配。
2) 环形分配
环形平均算法是指,根据消费者的顺序,依次在由queue队列组成的环形图中逐个分配。
先将queue队列组成的环形图。然后消费者绕着环进行分配

5、Rebalance(再平衡)
- Rebalance意义:
Rebalance存在的意义是为了提高并行消费能力。 。经过Queue分配算法后,已近将Queue和Consumer分配好了(相当于达到了最大的并发消费能力)。当Queue和Consumer发生变化(新增、减少)时,我们就要重新调用Queue分配算法进行再平衡,以保证最大的并发消费能力。
注意:Rebalance针对集群消费模式,因为广播消费Queue和Consumer变化时不受影响。广播消费需要消费者遍历所有的Queue,然后然后消费说有的消息。
- Rebalance带来的问题:
Rebalance机制虽然提升了并发消费的性能,但是也带来了一下问题。
- 消费暂停
在新增了一个Consumer后,会触发Rebalance,此时原Consumer就需要暂停部分队列的消费。 - 消费重复
集群消费模式,消费进度是保存在Broker上,当offset(消费进度)是异步提交的,如果消息已近被消费了,异步返回消费成功的ACK,但是由于正在进行Rebalance,Broker可能会及收不到ACK,也就是没有修改消费进度,但是消息却被消费了。下一次就会发生重复消费。 - 消费突刺
由于Rebalance可能导致重复消费,如果需要重复消费的消息过多,或者因为Rebalance暂停时间过长从而导致积压了部分消息。消息积压,那么有可能会导致在Rebalance结束之后瞬间需要消费很多消息。
6、至少消费一次的原则
RocketMQ有一个原则:每条消息必须要被成功消费一次。
那么什么是成功消费呢?Consumer在消费完消息后会向其消费进度记录器提交其消费消息的offset,offset被成功记录到记录器中,那么这条消费就被成功消费了。
什么是消费进度记录器?
对于广播消费模式来说,Consumer本身就是消费进度记录器。 对于集群消费模式来说,Broker是消费进度记录器。
7、消费幂等
1)什么是消费幂等
幂等:若某操作执行多次与执行一次对系统产生的影响是相同的,则称该操作是幂等的。
消费幂等:当出现消费者对某条消息重复消费的情况时,重复消费的结果与消费一次的结果是相同的,并且多次消费并未对业务系统产生任何负面影响,那么这个消费过程就是消费幂等的。
2)消费幂等出现的原因
1、 网络不稳定,已近处理的请求,由于网络故障没有返回确认ACK。那么发送端就会再次发送消息。保证消息至少被消费一次的原则。
2、 同一时间用户多次发起相同的请求,就会产生几次相同的请求。
3)消除幂等问题
幂等令牌:是生产者和消费者两者中的既定协议,通常指具备唯⼀业务标识的字符串。例如,订单号、流水号。一般由Producer随着消息一同发送来的。
1、对于常见的系统,幂等性操作的通用性解决方案是:
-
首先通过缓存(redis)去重。在缓存中如果已经存在了某幂等令牌,则说明本次操作是重复性操作;若缓存没有命中,则进入下一步。
-
在唯一性处理之前,先在数据库中查询幂等令牌作为索引的数据是否存在。若存在,则说明本次操作为重复性操作;若不存在,则进入下一步。
-
在同一事务中完成三项操作:唯一性处理后,将幂等令牌写入到缓存,并将幂等令牌作为唯一索引的数据写入到DB中。
2、解决方案举例
以支付场景为例:
-
当支付请求到达后,首先在Redis缓存中却获取key为支付流水号的缓存value。
若value不空,则说明本次支付是重复操作,业务系统直接返回调用侧重复支付标识;
若value为空,则进入下一步操作 -
到DBMS中根据支付流水号查询是否存在相应实例。
若存在,则说明本次支付是重复操作,业务系统直接返回调用侧重复支付标识;
若不存在,则说明本次操作是首次操作,进入下一步完成唯一性处理 -
在分布式事务中完成【唯一性处理】三项操作:
完成支付任务
将当前支付流水号作为key,任意字符串作为value,通过set(key, value, expireTime)将数据写入到Redis缓存
将当前支付流水号作为主键,与其它相关数据共同写入到DBMS
8、消费进度offset
消费进度offset是用来记录每个Queue的不同消费组的消费进度的。根据消费进度记录器的不同,可以分为两种模式:本地模式和远程模式。其中本地模式对应着广播消费模式,远程模式对应着集群消费模式。
1)offset本地管理模式
因为每条消息会被所有的消费者消费,每个消费者管理自己的消费进度,各个消费者之间不存在消费进度的交集。所以每个消费者都将消费进度记录在自己本地。
offset相关数据以json的形式持久化到Consumer本地磁盘文件中,默认文件路径为当前用户主目录下的.rocketmq_offsets/
c
l
i
e
n
t
I
d
/
{clientId}/
clientId/{group}/Offsets.json。其中c l i e n t I d 为 当 前 消 费 者 i d , 默 认 为 i p @ D E F A U L T ; {clientId}为当前消费者id,默认为ip@DEFAULT;clientId为当前消费者id,默认为ip@DEFAULT;{group}为消费者组名称。
2)offset远程管理模式
当消费模式为集群消费时,offset使用远程模式管理。因为所有Cosnumer实例对消息采用的是均衡消费,所有Consumer共享Queue的消费进度。消费进度被记录在broker中,文件路径为当前用户主目录下的store/config/consumerOffset.json。
4)在代码中使用offset
消费者是如何从最开始持续消费消息的?消费者要消费的第一条消息的起始位置是用户自己通过consumer.setConsumeFromWhere()方法指定的。
在Consumer启动后,其要消费的第一条消息的起始位置常用的有三种,这三种位置可以通过枚举类型常量设置。这个枚举类型为ConsumeFromWhere。
CONSUME_FROM_LAST_OFFSET:从queue的当前最后一条消息开始消费 CONSUME_FROM_FIRST_OFFSET:从queue的第一条消息开始消费 CONSUME_FROM_TIMESTAMP:从指定的具体时间戳位置的消息开始消费。这个具体时间戳是通过另外一个语句指定的 。consumer.setConsumeTimestamp(“20210701080000”) yyyyMMddHHmmss当消费完一批消息后,Consumer会提交其消费进度offset给Broker,Broker在收到消费进度后会将其更新到那个双层Map(ConsumerOffsetManager)及consumerOffset.json文件中,然后向该Consumer进行ACK,而ACK内容中包含三项数据:当前消费队列的最小offset(minOffset)、最大offset(maxOffset)、及下次消费的起始offset(nextBeginOffset)。
六、消息的清理
消息被消费过后会被清理掉吗?不会的。
消息是被顺序存储在commitlog文件的,且消息大小不定长,所以消息的清理是不可能以消息为单位进行清理的,而是以commitlog文件为单位进行清理的。否则会急剧下降清理效率,并实现逻辑复杂。
commitlog文件存在一个过期时间,默认为72小时,即三天。除了用户手动清理外,在以下情况下也会被自动清理,无论文件中的消息是否被消费过:
-
文件过期,且到达清理时间点(默认为凌晨4点)后,自动清理过期文件
-
文件过期,且磁盘空间占用率已达过期清理警戒线(默认75%)后,无论是否达到清理时间点,都会自动清理过期文件
-
磁盘占用率达到清理警戒线(默认85%)后,开始按照设定好的规则清理文件,无论是否过期。默认会从最老的文件开始清理
-
磁盘占用率达到系统危险警戒线(默认90%)后,Broker将拒绝消息写入
本文详细介绍了RocketMQ的集群模式(单Master、多Master多Slave异步/同步)、搭建步骤、消息发送样例、工作原理(生产与消费过程、存储策略、Rebalance机制等),以及关键概念如消费幂等、offset管理。
&spm=1001.2101.3001.5002&articleId=119986207&d=1&t=3&u=99ad5bcd1b684c7b8250f4156fc5f385)
2万+

被折叠的 条评论
为什么被折叠?



