分布式消息队列--RocketMQ

基本概念

在这里插入图片描述

RocketMQ是一个纯java语言编写的分布式消息队列,主要由以下几个组件组成:

  • Producer:消息生产者,负责生产消息,同一类生产者组成一个生产者组;生产者提供了同步、异步、顺序和单向四种发送消息的方式,其中同步和异步需要broker返回确认消息,单向发送方式不需要;
  • NameServer:名称服务,负责路由消息和作为broker的注册中心,多个NameServer形成集群但相互独立;每个broker启动时都会向NameServer注册服务信息,后续通过心跳方式保证当前服务信息的实时性,另外生产者和消费者也通过NameServer找到某个topic对应的broker ip列表;
  • Broker:代理服务器,负责存储和转发消息,同时也存储消息相关的元数据包括主题、队列消息、消费者组和消费偏移量等。Broker主从集群模式有两种包括普通主从集群和Dleger高可用主从集群,普通主从集群中主节点负责响应客户端请求并存储消息,而从节点负责对主节点的消息进行同步保存以及对部分客户端的读响应,普通集群不能完成主从自动切换;Dleger高可用集群则支持从集群中随机选出一个节点作为主节点,当主节点宕机后也会在从节点中选出一个作为主节点;(RocketMQ 4.5版本之前不支持主从自动切换,4.5版本后引入基于Dleger实现的主从切换)
  • Consumer:消息消费者,负责消费消息,同一类消费者组成一个消费者组;RocketMQ提供了拉取式和推动式两种消费方式,拉取式消费一般由消费者程序主动调拉消息方法从broker获取消息,是一种主动权由消费者自身控制的消费方式;而推动式消费中broker收到消息后会主动推送给消费者,该方式一般实时性较高;RocketMQ还提供了集群消费和广播消费两种消费模式,其中集群消费表示同一个消费者组中的每个消费者平均分摊消息;而广播模式则表示同一个消费者组中的每个消费者都接收全量消息;
  • Topic:消息主题,一类消息的集合,一个生产者可以发送消息给一个或多个topic,一个消费者可以接收一个或多个topic的消息;
  • MessageQueue:消息队列,是存储消息的最小单位,类似Kafka中的分区概念,用于并行发送和接收消息;

核心消息样例

1. 顺序消息

消息的顺序问题分为局部有序和全局有序两种;
局部有序:只保证一部分消息被顺序消费
全局有序:所有消息均按照先入先出顺序被消费

RocketMQ只能保证消息的局部有序,不能保证全局有序;消息发送时是通过轮询MessageQueue方式将消息均匀放到不同的消息队列中,消息消费时也是从多个不同的消息队列中消费消息;MessageQueue是RocketMQ发送和消费消息的最小单元,多个MessageQueue之间是相互独立互相隔离的;

实现局部顺序消息的思路:可以将一组有序的消息都放到同一个MessageQueue中即可,通过队列的先入先出特性保证这组消息的有序性;
RocketMQ实现顺序消息:在发送消息时指定一个MessageQueueSelector对象,在其select方法中自定义选择哪个MessageQueue的逻辑,在消费消息时使用MessageListenerOrderly消息监听器;

实现全局顺序消息的思路:可以将这组有序消息的Topic配置成只有一个MessageQueue即可(不推荐)

2. 广播消息

相对于集群消息而言,广播消息可以发送到同一个消费者组的每个消费者,而集群消息只能发送到同一个消费者组中的某一个消费者;

实现广播消息:推模式下消费者在消费消息时可以指定消息的广播模式consumer.setMessageModel(MessageModel.BROADCASTING)

3. 延迟消息

在这里插入图片描述
RocketMQ延迟消息指的是生产者在执行完send方法后,消息并不会立即发送出去,而是等一段时间再发送出去;这种延迟消息是RocketMQ区别于RabbitMQ与Kafka的一种特有功能;

延迟队列名称:SCHEDULE_TOPIC_XXX;

设置延迟消息只需要在Producer发送消息对象Message时指定具体的延迟级别即可,如message.setDelayTimeLevel(3);

开源版RocketMQ延迟消息默认18个固定延迟级别,不同延迟级别对应不同的延迟时间间隔;

4. 批量消息

RocketMQ批量消息指的是生产者将多条消息合并成一个批量消息,一次性发送到消费者端,这样提高消息吞吐量,减少网络IO;

官方建议一次批量消息不能超过1M,实际使用中最大批量消息不能超过4M;并且批量消息必须是相同Topic与相同waitStoreMsgOK,不能是延迟消息与事务消息等;

5. 过滤消息

一般而言,一个应用服务对应一个Topic,而该应用服务下不同业务类型可以通过Tag进行区分,消息消费时可以通过Tag进行过滤消息;

过滤消息的方式:分为Tag过滤和Sql表达式过滤,一个消息默认只能有一个Tag,而一些较复杂的场景可以使用SQL92标准的SQL表达式进行过滤消息;

注意:只有推模式的消费者可以使用Sql过滤;

6. 事务消息(重点)

在这里插入图片描述
事务消息是RocketMQ提供的一个特色功能,旨在分布式系统中保证最终一致性的两阶段提交的消息实现,其可以保证生产者执行本地事务与发送消息两个操作的原子性,即生产者本地事务与发送消息要么同时成功要么同时失败。

事务消息的关键:在TransactionMQProducer中指定一个TransactionListener事务监听器,消息监听器中返回COMMIT_MESSAGE状态的消息会立即被消费者消费,返回ROLLBACK_MESSAGE状态的消息会被丢弃,返回UNKNOWN状态的消息会由Broker过段时间再来回查事务的状态;发送消息时会先将要发送的消息转为HALF消息存入到RMQ_SYS_TRANS_HALF_TOPIC系统主题中(该主题对消费者不可见),经过一系列本地事务回查通过后,再将消息转存到目标Topic中被消费者消费;

核心原理

1. 消息存储
存储时机
  • RocketMQ收到一条消息后,需要向Producer响应ACK并将消息存储起来
  • RocketMQ推送一条消息给Consumer后,需要等待Consumer响应ACK,并将消息标记为已消费,若没有标记为已消费则会不断尝试推送消息
  • RocketMQ需要定期删除一些过期的消息来保证服务一直可用
存储介质

RocketMQ采用类似Kafka的磁盘文件存储消息,使用顺序写磁盘的方式保证了消息存储的速度,使用零拷贝技术加快了磁盘文件的读写速度

  1. 传统文件数据拷贝:(2次系统调用,4次用户态与内核态上下文切换,4次数据拷贝)
    读操作:发生一次系统调用read(), 两次用户态与内核态上下文切换,数据完整拷贝流程即从用户态发起一个系统调用read从用户态切换到内核态工作空间,从磁盘经过DMA拷贝到内核态磁盘缓冲区,再从内核态缓冲区经过CPU拷贝到用户态缓冲区
    写操作:发生一次系统调用write(), 两次用户态和内核态上下文切换,即用户态发起一个系统调用write从用户态切换到内核态工作空间,从用户态缓冲区经过CPU拷贝到内核态Socket缓冲区,再从Socket缓冲区经过DMA拷贝到网卡,同时从内核态切换到用户态工作空间继续工作
  2. 基于传统拷贝优化后的零拷贝:
    由用户态经过mmap映射到内核态缓冲区的优化,相较于传统拷贝少了一次数据拷贝同时也减少了用户态和内核态上下文切换次数,数据完整拷贝流程即从磁盘读取数据经过DMA拷贝到内核态缓冲区,再从内核态缓冲区直接经过CPU拷贝到内核态Socket缓冲区,最后再从内核Socket缓冲区经过DMA拷贝到网卡
  3. 基于Linux内核2.4版本优化后的零拷贝:
    由内核态磁盘缓冲区直接拷贝到网卡的优化,相较于之前的零拷贝少了一次内核缓冲区间的数据拷贝,数据完整拷贝流程即从磁盘读取数据经过DMA拷贝到内核态缓冲区,再从内核缓冲区经过SG-DMA直接拷贝到网卡
存储结构

在这里插入图片描述

在这里插入图片描述

  • CommitLog:存储消息的元数据,所有消息都会顺序存储在多个CommitLog文件中,每个CommitLog文件固定1G大小,以存入文件的第一条消息的偏移量作为CommitLog文件名
  • ConsumeQueue:存储在Commitlog中消息的索引,一个MessageQueue对应一个ConsumerQueue文件,记录的是当前MessageQueue被哪个Consumer消费到哪条CommitLog
  • IndexFile:为消息查询提供通过key或时间区间来查询消息的方法,该方法不影响生产和消费消息的主流程
2. 刷盘方式

RocketMQ消息存盘的方式有两种:同步刷盘和异步刷盘,可以通过Broker配置文件里的 flushDiskType 参数设置 SYNC_FLUSH或ASYNC_FLUSH指定刷盘方式

3. 主从复制

Broker集群模式中,消息会从主节点复制到从节点;
RocketMQ消息主从复制的方式有两种:同步复制和异步复制,也可以通过Broker配置文件里的 brokerRole 参数进行设置ASYNC_MASTER或SYNC_MASTER或SLAVE指定主从复制方式

4. 负载均衡

分为Producer负载均衡和Consumer负载均衡;

Producer发送消息负载均衡

Producer向Broker发送消息时,默认会轮询目标topic的MessageQueue,并采取递增取模的方式往不同MessageQueue发送消息已达到消息平均分配到不同broker的不同MessageQueue中;

为保证消息局部有序,Producer发送消息时可以指定一个MessageQueueSelector对象将消息发送到指定的MessageQueue中

Consumer消费消息负载均衡

Consumer消费消息有两种模式,集群模式和广播模式,其中广播模式是指每个订阅某个主题的Consumer都可以收到全量的消息,而集群模式是指每个订阅某个主题的Consumer平均消费该主题下MessageQueue的消息,所以消费者负载均衡只针对集群消费模式;

如何提高Consumer的消费并行度?
1.同一个消费者组下通过增加消费者实例数量来提高并行度,并且消费者实例数量不能超过订阅该主题下所有的MessageQueue的个数,否则消费无效
2.通过提高单个消费者实例的并行消费线程数量来提高并行度,可以设置 consumeThreadMin 最小并发线程数和 consumeThreadMax 最大并发线程数
3.通过提高消费者批次最大消费消息数量来提高并行度,可以设置Consumer的 consumeMessageBatchMaxSize 参数提高消费吞吐量

5. 消息重试

消息重试针对非广播消息模式,当消费者消费消息失败后可以通过设置返回状态达到消息重试的功能

集群模式下实现消息重试的方式
  1. 在消息监听器接口实现中返回 Action.ReconsumeLater(推荐)
  2. 在消息监听器接口实现中返回 null
  3. 在消息监听器接口实现中抛出异常

若希望消息消费失败后不重试,则可以返回 Action.CommitMessage

重试消息的处理

RocketMQ默认允许每条消息最多重试16次,且按照默认的间隔时间进行重试;

可以通过配置参数 consumer.setMaxReconsumeTimes(n) 指定允许每条消息的最大重试次数,当自定义的重试次数超过16次则重试间隔均为2小时;

消息最大重试次数的设置对相同消费者组下的所有消费者都有效,并且最后启动的消费者会覆盖之前启动的消费者配置;

RocketMQ4.7.1版本之前,每次消息重试时的MessageID都是一样的,在4.7.1版本时每次消息重试都会重新创建新的MessageID;

若消息达到最大重试次数后还是失败,则消息会进入到死信队列中,不再被投递;
重试的消息会进入一个 %RETRY%+ConsumerGroup 的队列中,并且一个重试队列对应一个消费者组;

6. 消息幂等

RocketMQ消息重复分为三种:
1.发送消息时重复:Producer向MQ成功发送消息并持久化后突然网络中断或客户端宕机导致应答失败时,Producer会尝试再次发送消息,后续Consumer就会收到重复消息
2.消费消息时重复:MQ向Consumer成功投递消息并完成处理后突然网络中断导致应答失败时,Consumer为了保证消息至少被消费一次,MQ会重试投递消息,后续Consumer就会收到重复消息
3.负载均衡时重复:当MQ的Broker或客户端重启或扩容或缩容触发Rebalance机制时,Consumer可能会收到重复消息

MQ系统中消息幂等的实现语义有三种:
1.at most once 每条消息最多只会被消费一次(RocketMQ通过异步发送和单向发送等方式保证最多一次)
2.at least once 每条消息最少会被消费一次(RocketMQ通过同步发送和事务消息等方式保证至少一次)
3.exactly once 刚好一次:每条消息都只会被消费一次(RocketMQ无法保证刚好一次,只能由业务系统自行保证)

消息幂等的处理

RocketMQ4.7.1之前版本,可以采用每个消息唯一的MessageID作为判断幂等的依据;RocketMQ4.7.1之后的版本由于消息每次投递(消息重试)时都会重建MessageID,所以建议使用客户端系统唯一业务标识判断消息幂等,尤其在幂等性要求较高的场景。

7. 死信队列

当消息消费失败重试时达到最大重试次数后还是失败,则消息会转发到当前消费者组对应的死信队列中;
通常来说消息进入死信队列说明了消息在消费处理过程中出现错误且无法自行恢复,此时需要人工干预排查原因并处理,可以将死信队列中的消息转发到正常的topic重新进行消费或者直接丢弃;
死信队列名称:%DLQ%+ConsumerGroup

一个死信队列对应一个消费者组,并且只有消费者组产生了死信队列MQ才会创建这个死信队列
死信队列中包含对应消费者组所有的死信消息,不区分消息属于哪个topic,死信队列中消息不会被消费者正常消费
死信队列消息有效期与普通队列相同默认3天,对应broker.conf中的fileReservedTime属性,不论消息是否被消费,超过这个最长时间的消息都会被删除

单机搭建部署

  1. 下载rocketmq运行版和rocketmq-console控制台压缩包并解压

cd /usr/local
#下载rocketmq运行版压缩包
wget https://archive.apache.org/dist/rocketmq/4.7.1/rocketmq-all-4.7.1-bin-release.zip
unzip rocketmq-all-4.7.1-bin-release.zip

在这里插入图片描述

mv rocketmq-all-4.7.1-bin-release/ rocketmq-4.7.1/
cd rocketmq-4.7.1
#创建mq数据存储目录
mkdir data && ll

在这里插入图片描述

#下载rocketmq控制台压缩包并解压
wget https://github.com/apache/rocketmq-dashboard/archive/refs/heads/master.zip
unzip master.zip
ll rocketmq-dashboard-master/

在这里插入图片描述

  1. 修改rocketmq-dashboard配置

vi rocketmq-dashboard-master/src/main/resources/application.yml

#指定控制台端口
server.port=6789
#指定nameserver的地址
rocketmq.config.namesrvAddrs=192.168.126.134:9876
在这里插入图片描述
:wq

  1. 修改rocketmq配置:在rocketmq-4.7.1/conf/broker.conf配置文件添加如下配置

vi conf/broker.conf

brokerClusterName = DefaultCluster
brokerName = broker-zfy
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
#nameserver地址
namesrvAddr=192.168.126.134:9876
brokerIP1=192.168.126.134
#默认为true,如关闭,则需要每次管理员在web中创建
#autoCreateTopicEnable=true
#数据存储路径
storePathRootDir=/usr/local/rocketmq-4.7.1/data

:wq

  1. 修改日志配置

vi conf/logback_namesrv.xml
vi conf/logback_broker.xml

两个配置文件均修改为如下配置:

#configuration标签下新增LOG_HOME日志属性
<property name="LOG_HOME" value="../logs"></property>
#将配置文件中“${user.home}/logs/rocketmqlogs”全局替换为“${LOG_HOME}:%s/${user.home}\/logs\/rocketmqlogs/${LOG_HOME}/g
#保存退出
:wq

logback_namesrv.xml
在这里插入图片描述
logback_broker.xml
在这里插入图片描述

  1. 根据当前服务器资源情况适当修改启动配置文件runserver.sh,runbroker.sh(注:本次测试虚拟机总内存1G)

#查看当前虚拟机内存使用情况
top

在这里插入图片描述
可用内存剩余1g左右,可以设置-Xms初始堆和-Xmx最大堆大小为256m以及-Xmn年轻代大小为85m

-Xms256m -Xmx256m -Xmn85m

在这里插入图片描述
在这里插入图片描述

  1. 依次启动nameserver和broker

#启动nameserver
nohup bin/mqnamesrv > /dev/null 2>&1 &
#启动broker
nohup bin/mqbroker -c conf/broker.conf > /dev/null 2>&1 &
#停止broker
bin/mqshutdown broker
#停止nameserver
bin/mqshutdown namesrv

在这里插入图片描述

  1. 编译打包rocketmq-dashboard并启动

cd rocketmq-dashboard-master
mvn -Dmaven.test.skip=true clean package

在这里插入图片描述

cp target/rocketmq-dashboard-1.0.1-SNAPSHOT.jar …/ && cd … && ll

在这里插入图片描述

#启动控制台
java -jar rocketmq-dashboard-1.0.1-SNAPSHOT.jar
#或后台启动并输出日志到文件rocketmq-console.log
nohup java -jar rocketmq-dashboard-1.0.1-SNAPSHOT.jar > /dev/null 2>&1 &
#或后台启动不输出任何日志
nohup java -jar rocketmq-dashboard-1.0.1-SNAPSHOT.jar > /dev/null 2>&1 &

  1. 访问监控管理控制台

http://192.168.126.134:6789

在这里插入图片描述

集群搭建部署

正式RocketMQ集群有三种部署方式,分别为两主两从异步、两主两从同步以及两主无从模式;另外为了达到高可用一般都会使用Dleger实现主从自动切换;

一般企业中若需要MQ快响应、支持高吞吐且允许少量消息丢失场景时可以采用两主两从异步刷盘方式;
若需要消息高安全度、不丢失且可接受响应时间略长的场景可以采用两主两从同步刷盘方式;

注意:如果是在云服务器等多网卡环境中安装RocketMQ,当本地PC外网访问云服务器时,需要修改broker.conf配置文件中的brokerIP1属性,否则无法访问

本次示例使用三个服务器节点node1, node2, node3, 集群部署的是两主两从异步复制异步刷盘方式,其中三个namesrv实例分别部署到这三个节点中,另外四个broker实例以交叉主从的方式部署到node2与node3节点中,即node2部署broker-a与broker-b-s实例,node3部署broker-b与broker-a-s实例,最后node1节点再部署一个RocketMQ集群管理控制台

部署步骤:

创建操作用户与密码

#创建集群操作用户与密码
useradd jeffrey
passwd jeffrey (输入zfy)

系统配置

关闭防火墙

#停掉防火墙
systemctl stop firewalld.service
#查看防火墙状态(看是否是 not running)
firewall-cmd --state

设置免密登录(如可以直接在node1上直接ssh或scp到其他机器不用输密码)

#切换jeffrey用户
su jeffrey
#第一个机器node1上生成公私密钥key(无需密码)
ssh-keygen
#将秘钥key分发给其他机器
ssh-copy-id node1
ssh-copy-id node2
ssh-copy-id node3

安装java环境

确保已安装java,并且已配置好java环境变量
在这里插入图片描述

安装RocketMQ

在上面单机版搭建部署的基础上,配置好RocketMQ的环境变量
在这里插入图片描述

创建数据存储目录

mkdir -p /usr/local/rocketmq-4.7.1/rocketmq-cluster/data/commitlog
mkdir -p /usr/local/rocketmq-4.7.1/rocketmq-cluster/data/consumequeue
mkdir -p /usr/local/rocketmq-4.7.1/rocketmq-cluster/data/index
mkdir -p /usr/local/rocketmq-4.7.1/rocketmq-cluster/data/checkpoint
mkdir -p /usr/local/rocketmq-4.7.1/rocketmq-cluster/data/abort

配置RocketMQ

进入rocketmq安装目录,修改conf/2m-2s-async/目录下broker-a.properties,broker-b-s.properties,broker-b.properties,broker-a-s.properties四个配置文件

1.在node2中配置broker-a.properties

#broker集群名称
brokerClusterName=Rocketmq-Cluster
#broker名称,注意同一个主节点和从节点的broker名称是一样的
brokerName=broker-a
#broker id, 0表示master,>0表示slave
brokerId=0
#文件删除时间,默认凌晨4点
deleteWhen=04
#文件保存时间,默认48小时(若超过该保存时间,无论消息是否被消费都会被删除)
fileReservedTime=48
#broker角色,分为ASYNC_MASTER,SYNC_MASTER,SLAVE三种
brokerRole=ASYNC_MASTER
#刷盘类型,分为ASYNC_FLUSH,SYNC_FLUSH两种
flushDiskType=ASYNC_FLUSH
#namesrv地址,分号隔开
namesrvAddr=192.168.126.134:9876;192.168.126.135:9876
#broker ip(多网卡环境中需要设置该值,否则无法访问)
brokerIP1=192.168.126.134
#发送消息到不存在的topic时,默认创建带4个MessageQueue的topic
defaultTopicQueueNums=4
#是否自动创建topic,线上建议关闭
autoCreateTopicEnable=true
#是否自动创建订阅组,线上建议关闭
autoCreateSubscriptionGroup=true
#broker对外服务的监听端口
listenPort=10911
#每个CommitLog文件的大小,默认1G
mapedFileSizeCommitLog=1073741824
#每个ConsumeQueue文件保存CommitLog中消息索引的数量,默认30万个
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#磁盘文件最大使用空间占比
#diskMaxUsedSpaceRatio=88
#数据存储根目录
storePathRootDir=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data
#commitLog存储路径
#storePathCommitLog=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/commitlog
#consumeQueue存储路径
#storePathConsumeQueue=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/consumequeue
#index存储路径
#storePathIndex=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/index
#checkpoint文件存储路径
#storeCheckpoint=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/checkpoint
#abort文件存储路径
#abortFile=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/abort
#最大消息大小,单位字节
maxMessageSize=65536
#是否启用检查事务消息
#checkTransactionMessageEnable=false
#发送消息线程池数量
#sendMessageThreadPoolNums=128
#拉取消息线程池数量
#pullMessaeThreadPoolNums=128

2.在node2中配置broker-b-s.properties

#集群名称
brokerClusterName=Rocketmq-Cluster
#broker名称
brokerName=broker-b
#broker id
brokerId=1
#文件删除时间
deleteWhen=04
#文件保留时间
fileReservedTime=48
#broker角色
brokerRole=SLAVE
#刷盘类型
flushDiskType=ASYNC_FLUSH
#namesrv地址,分号隔开
namesrvAddr=192.168.126.134:9876;192.168.126.135:9876
#broker ip(多网卡环境中需要设置该值,否则无法访问)
brokerIP1=192.168.126.134
#发送消息到不存在的topic时,默认创建带4个MessageQueue的topic
defaultTopicQueueNums=4
#是否自动创建topic,线上建议关闭
autoCreateTopicEnable=true
#是否自动创建订阅组,线上建议关闭
autoCreateSubscriptionGroup=true
#broker对外服务的监听端口
listenPort=10911
#每个CommitLog文件的大小,默认1G
mapedFileSizeCommitLog=1073741824
#每个ConsumeQueue文件保存CommitLog中消息索引的数量,默认30万个
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#磁盘文件最大使用空间占比
#diskMaxUsedSpaceRatio=88
#数据存储根目录
storePathRootDir=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data
#commitLog存储路径
#storePathCommitLog=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/commitlog
#consumeQueue存储路径
#storePathConsumeQueue=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/consumequeue
#index存储路径
#storePathIndex=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/index
#checkpoint文件存储路径
#storeCheckpoint=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/checkpoint
#abort文件存储路径
#abortFile=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/abort
#最大消息大小,单位字节
maxMessageSize=65536
#是否启用检查事务消息
#checkTransactionMessageEnable=false
#发送消息线程池数量
#sendMessageThreadPoolNums=128
#拉取消息线程池数量
#pullMessaeThreadPoolNums=128

3.在node3中配置broker-b.properties

#broker集群名称
brokerClusterName=Rocketmq-Cluster
#broker名称,注意同一个主节点和从节点的broker名称是一样的
brokerName=broker-b
#broker id, 0表示master,>0表示slave
brokerId=0
#文件删除时间,默认凌晨4点
deleteWhen=04
#文件保存时间,默认48小时(若超过该保存时间,无论消息是否被消费都会被删除)
fileReservedTime=48
#broker角色,分为ASYNC_MASTER,SYNC_MASTER,SLAVE三种
brokerRole=ASYNC_MASTER
#刷盘类型,分为ASYNC_FLUSH,SYNC_FLUSH两种
flushDiskType=ASYNC_FLUSH
#namesrv地址,分号隔开
namesrvAddr=192.168.126.134:9876;192.168.126.135:9876
#broker ip(多网卡环境中需要设置该值,否则无法访问)
brokerIP1=192.168.126.135
#发送消息到不存在的topic时,默认创建带4个MessageQueue的topic
defaultTopicQueueNums=4
#是否自动创建topic,线上建议关闭
autoCreateTopicEnable=true
#是否自动创建订阅组,线上建议关闭
autoCreateSubscriptionGroup=true
#broker对外服务的监听端口
listenPort=10911
#每个CommitLog文件的大小,默认1G
mapedFileSizeCommitLog=1073741824
#每个ConsumeQueue文件保存CommitLog中消息索引的数量,默认30万个
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#磁盘文件最大使用空间占比
#diskMaxUsedSpaceRatio=88
#数据存储根目录
storePathRootDir=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data
#commitLog存储路径
#storePathCommitLog=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/commitlog
#consumeQueue存储路径
#storePathConsumeQueue=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/consumequeue
#index存储路径
#storePathIndex=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/index
#checkpoint文件存储路径
#storeCheckpoint=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/checkpoint
#abort文件存储路径
#abortFile=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/abort
#最大消息大小,单位字节
maxMessageSize=65536
#是否启用检查事务消息
#checkTransactionMessageEnable=false
#发送消息线程池数量
#sendMessageThreadPoolNums=128
#拉取消息线程池数量
#pullMessaeThreadPoolNums=128

4.在node3中配置broker-a-s.properties

#集群名称
brokerClusterName=Rocketmq-Cluster
#broker名称
brokerName=broker-a
#broker id
brokerId=1
#文件删除时间
deleteWhen=04
#文件保留时间
fileReservedTime=48
#broker角色
brokerRole=SLAVE
#刷盘类型
flushDiskType=ASYNC_FLUSH
#namesrv地址,分号隔开
namesrvAddr=192.168.126.134:9876;192.168.126.135:9876
#broker ip(多网卡环境中需要设置该值,否则无法访问)
brokerIP1=192.168.126.135
#发送消息到不存在的topic时,默认创建带4个MessageQueue的topic
defaultTopicQueueNums=4
#是否自动创建topic,线上建议关闭
autoCreateTopicEnable=true
#是否自动创建订阅组,线上建议关闭
autoCreateSubscriptionGroup=true
#broker对外服务的监听端口
listenPort=10911
#每个CommitLog文件的大小,默认1G
mapedFileSizeCommitLog=1073741824
#每个ConsumeQueue文件保存CommitLog中消息索引的数量,默认30万个
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#磁盘文件最大使用空间占比
#diskMaxUsedSpaceRatio=88
#数据存储根目录
storePathRootDir=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data
#commitLog存储路径
#storePathCommitLog=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/commitlog
#consumeQueue存储路径
#storePathConsumeQueue=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/consumequeue
#index存储路径
#storePathIndex=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/index
#checkpoint文件存储路径
#storeCheckpoint=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/checkpoint
#abort文件存储路径
#abortFile=/usr/local/rocketmq-4.7.1/rocketmq-cluster/data/abort
#最大消息大小,单位字节
maxMessageSize=65536
#是否启用检查事务消息
#checkTransactionMessageEnable=false
#发送消息线程池数量
#sendMessageThreadPoolNums=128
#拉取消息线程池数量
#pullMessaeThreadPoolNums=128
node1中配置rocketmq-dashboard

控制台管理服务配置对应的namesrv节点地址
在这里插入图片描述

启动RocketMQ
  1. 启动三个node的NameSrv

nohup mqnamesrv > /dev/null 2>&1 &

  1. 启动Broker
    先启动两个master broker,再启动两个slave broker.

nohup mqbroker -c conf/2m-2s-async/broker-a.properties
nohup mqbroker -c conf/2m-2s-async/broker-b.properties
nohup mqbroker -c conf/2m-2s-async/broker-a-s.properties
nohup mqbroker -c conf/2m-2s-async/broker-b-s.properties

  1. 启动dashboard管理界面

nohup java -jar rocketmq-dashboard-1.0.1-SNAPSHOT.jar > /dev/null 2>&1 &

  1. 启动状态检查

jps

  1. 测试rocketmq管理界面

  2. 命令行快速验证
    RocketMQ安装目录提供了一个tools.sh工具用来在命令行快速验证RocketMQ服务
    注意:需要临时指定一个环境变量或者在.bash_profile文件中配置一个环境变量NAMESRV_ADDR指定NameSrv地址,这样在下面两个案例测试类运行的时候就会直接读取这景变量中的NameSrv地址。

#发送消息
tools.sh org.apache.rocketmq.example.quickstart.Producer
#接收消息
tools.sh org.apache.rocketmq.example.quickstart.Consumer

SpringBoot整合RocketMQ

导入依赖

注意rocketmq-spring-boot-starter依赖版本必须要与实际安装的rocketmq服务端版本一致!

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.7.1</version>
</dependency>
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>
配置文件
server:
  port: 8080
spring:
  application:
    name: rocketmq-demo

---
#rocketmq config
rocketmq:
  name-server: 192.168.126.134:9876
  producer:
    group: zfy-producer-group
测试
Producer
package com.itjeffrey.rocketmq.tet.service;

import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * 生产者
 * @From: Jeffrey
 * @Date: 2022/11/29
 */
@Slf4j
@Service
public class Producer {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 发送普通消息
     */
    @Scheduled(cron = "0/30 * * * * ?")
    public void sendMsg(){
        String msg = "test message";
        rocketMQTemplate.convertAndSend("topic-zfy", msg);
        log.info("send msg ok!");
    }

    /**
     * 发送事务消息
     */
    @Scheduled(cron = "0 0/5 * * * ?")
    public void sendTransactionMsg() throws Exception{
        String[] tags = {"TAG1", "TAG2", "TAG3", "TAG4", "TAG5"};
        //发送10条消息,其中
        for (int i = 1; i <= 10; i++) {
            String msg = "test transaction message:" + i;
            Message<String> message = MessageBuilder.withPayload(msg)
                //自定义Header属性
                //发送消息到达事务监听器时,TAGS属性会丢失,TRANSACTION_ID不会丢失,MyHeader属性也可以拿到
                .setHeader(RocketMQHeaders.TRANSACTION_ID, "TxID_" + i)
                .setHeader(RocketMQHeaders.TAGS, tags[(i - 1) % tags.length])
                .setHeader("MyHeader", "MyHeader_" + i)
                .build();
            String dest = "topic-zfy-tx" + ":" + tags[(i - 1) % tags.length];
            TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(dest, message, dest);
            log.info(result.toString());
            TimeUnit.MILLISECONDS.sleep(50);
        }
        log.info("send transaction msg ok!");
        log.info("------------------------------------------");
    }
}
ProducerTransactionListener
package com.itjeffrey.rocketmq.tet.service;

import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.apache.rocketmq.spring.support.RocketMQUtil;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 事务消息监听器
 * 注意:一个事务消息监听器对应的是一个事务流程
 * 若有多个事务,可以采用扩展RocketMQTemplate方式,定制化属性实现自定义事务监听需求
 *     @ExtRocketMQTemplateConfiguration()
 *     public class ExtRocketMQTemplate extends RocketMQTemplate {
 *     }
 *
 * @From: Jeffrey
 * @Date: 2022/11/30
 */
@Slf4j
@Service
@RocketMQTransactionListener(rocketMQTemplateBeanName = "rocketMQTemplate")
public class ProducerTransactionListener implements RocketMQLocalTransactionListener {

    private Map<String, Message> txMsgMap = new ConcurrentHashMap<>();

    /**
     * 执行本地事务
     * @param message
     * @param args 参数,对应rocketMQTemplate.sendMessageInTransaction方法传过来的第三个参数
     * @return
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object args) {
        String txId = message.getHeaders().get(RocketMQHeaders.PREFIX + RocketMQHeaders.TRANSACTION_ID).toString();
        txMsgMap.put(txId, message);
        log.info("executeLocalTransaction msg: " + message);
        String dest = args.toString();
        //spring message 转换为 rocket message,获取transactionId, tags等信息
        org.apache.rocketmq.common.message.Message rocketmqMsg = RocketMQUtil.convertToRocketMessage(new StringMessageConverter(), "utf-8", dest, message);
        String tags = rocketmqMsg.getTags();
        if(tags.contains("TAG1")){
            return RocketMQLocalTransactionState.COMMIT;
        }else if(tags.contains("TAG2")){
            return RocketMQLocalTransactionState.ROLLBACK;
        }else{
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }

    /**
     * 检查本地事务
     * @param message
     * @return
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        //springboot默认在自定义Header的key前面加上前缀PREFIX
        String txId = message.getHeaders().get(RocketMQHeaders.PREFIX + RocketMQHeaders.TRANSACTION_ID).toString();
        Message originalMsg = txMsgMap.get(txId);
        log.info("checkLocalTransaction msg: " + originalMsg);
        String tags = message.getHeaders().get(RocketMQHeaders.PREFIX + RocketMQHeaders.TAGS).toString();
        if(tags.contains("TAG3")){
            return RocketMQLocalTransactionState.COMMIT;
        }else if(tags.contains("TAG4")){
            return RocketMQLocalTransactionState.ROLLBACK;
        }else{
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }
}
Consumer
package com.itjeffrey.rocketmq.tet.service;

import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener;
import org.springframework.stereotype.Service;

/**
 * 普通消息消费者
 * @From: Jeffrey
 * @Date: 2022/11/29
 */
@Slf4j
@Service
@RocketMQMessageListener(consumerGroup = "zfy-consumer-group", topic = "topic-zfy", consumeMode = ConsumeMode.CONCURRENTLY)
public class Consumer implements RocketMQListener<String>, RocketMQPushConsumerLifecycleListener {

    @Override
    public void onMessage(String msg) {
        log.info("receive msg -> " + msg);
    }

    /**
     * 同一个消费者组下有多个消费者需要消费不同TOPIC消息时,需要指定当前Consumer实例名称,否则程序会报错
     * @param defaultMQPushConsumer
     */
    @Override
    public void prepareStart(DefaultMQPushConsumer defaultMQPushConsumer) {
        defaultMQPushConsumer.setInstanceName("Instance1");
    }
}
ConsumerInTransaction
package com.itjeffrey.rocketmq.tet.service;

import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener;
import org.springframework.stereotype.Service;

/**
 * 事务消息消费者
 * @From: Jeffrey
 * @Date: 2022/11/29
 */
@Slf4j
@Service
@RocketMQMessageListener(consumerGroup = "zfy-consumer-group", topic = "topic-zfy-tx", consumeMode = ConsumeMode.CONCURRENTLY)
public class ConsumerInTransaction implements RocketMQListener<String>, RocketMQPushConsumerLifecycleListener {

    @Override
    public void onMessage(String msg) {
        log.info("receive transaction msg -> " + msg);
    }

    @Override
    public void prepareStart(DefaultMQPushConsumer defaultMQPushConsumer) {
        defaultMQPushConsumer.setInstanceName("Instance2");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值