RocketMQ基础篇

RocketMQ基础篇

前言

这是我学习rocketMQ时参考视频做的一个笔记,与原笔记甚至官方文档类似,供自己查缺补漏。原视频链接

一、常用命令

启动NameServer

# 1.启动NameServer 
nohup sh bin/mqnamesrv &
# 2.查看启动日志
tail -f ~/logs/rocketmqlogs/namesrv.log

启动Broker

# 1.启动Broker
nohup sh bin/mqbroker -n localhost:9876 &
# 2.查看启动日志
tail -f ~/logs/rocketmqlogs/broker.log
#内存要求8G怕了怕了,修改这两个文件中的java虚拟机设置
vi runbroker.sh
vi runserver.sh

关闭rocketMQ

# 1.关闭NameServer
sh bin/mqshutdown namesrv
# 2.关闭Broker
sh bin/mqshutdown broker

1.1 mqadmin命令

名称含义命令选项说明
updateTopic创建更新Topic配置-bBroker地址,只支持单台Broker,地址为ip:port

二、概念

2.1 角色介绍

  • Producer:消息的发送者
  • Consumer:消息接受者
  • Broker:暂存和传输消息
  • NameServer: 管理Broker
  • Topic:区分消息的种类;一个发送者可以发送消息给一个或者多个Topic;一个消息的接收者可以订阅一个或者多个Topic消息
  • Message Queue:相当于Topic的分区;用于并行发送和接受消息

2.2 单机安装

2.2.1 docker方式快速学习

建议学习集群方式,但是集群需要开两台虚拟机,配置不够的同学可以用该方式。

1.创建namesrv文件目录

mkdir -p /usr/rocketmq/data/namesrv/logs
mkdir -p /usr/rocketmq/data/namesrv/store

2.拉取rocketmq镜像

docker pull rocketmqinc/rocketmq:4.4.0

3.启动namesrv容器

docker run -d -p 9876:9876 -v \/usr/rocketmq/data/namesrv/logs:/root/logs -v \/usr/rocketmq/data/namesrv/store:/root/store \--name rmqnamesrv -e "MAX_POSSIBLE_HEAP=100000000" \rocketmqinc/rocketmq:4.4.0 sh mqnamesrv

4.创建broker文件目录

mkdir -p /usr/rocketmq/data/broker/logs
mkdir -p /usr/rocketmq/data/broker/store
mkdir -p /usr/rocketmq/data/broker/conf

5.新建broker.conf配置文件

vim /usr/rocketmq/data/broker/conf/broker.conf

6.配置broker.conf

注意ip地址填自己的ip

brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = 192.168.1.175
namesrvAddr=192.168.1.175:9876
autoCreateTopicEnable=true

7.启动容器配置broker

docker run --name rmqbroker --restart=always -d -p 10911:10911 -p 10909:10909 -v  /usr/rocketmq/data/broker/logs:/root/logs -v  /usr/rocketmq/data/broker/store:/root/store -v  /usr/rocketmq/data/broker/conf/broker.conf:/opt/rocketmq-4.4.0/conf/broker.conf --link rmqnamesrv:namesrv -e "NAMESRV_ADDR=namesrv:9876" -e "MAX_POSSIBLE_HEAP=200000000" rocketmqinc/rocketmq:4.4.0 sh mqbroker -c /opt/rocketmq-4.4.0/conf/broker.conf

8.创建rocketmq-console-ng可视化UI控制台目录

mkdir -p /usr/rocketmq/data/rocketmq-console-ng/logs

9.拉取rocketmq-console-ng可视化UI控制台镜像

docker pull styletang/rocketmq-console-ng

10.启动rocketmq-console-ng可视化UI控制台容器

docker run -d --restart=always --name rocketmq-console-ng -v /usr/rocketmq/data/rocketmq-console-ng/logs:/root/logs  -e "JAVA_OPTS=-Drocketmq.namesrv.addr=192.168.141.110:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false" -p 6080:8080 -t styletang/rocketmq-console-ng:1.0.0

11.访问可视化UI控制台

http://192.168.141.110:6080/

******其他相关配置 ******
{
1.开启相应端口:由上述操作 此处我需要开启的端口有(9876、10909、10911、6080)

firewall-cmd --zone=public --add-port=9876/tcp --permanentfirewall-cmd --zone=public --add-port=10909/tcp --permanentfirewall-cmd --zone=public --add-port=10911/tcp --permanentfirewall-cmd --zone=public --add-port=6080/tcp --permanent

​ 2.配置立即生效:

firewall-cmd --reload

}

三、集群搭建

3.1 集群特点

  • NameServer是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。
  • Broker部署相对复杂,Broker分为Master(主要写操作)与Slave(主要读操作),一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave的对应关系通过指定相同BrokerName,不同的BrokerId,BrokerId为0表示Master,非0表示Slave,Master也可以部署多个。每个Broker与NameServer集群中的所有节点建立长连接,定时注册Topic信息到所有NameServer。
  • Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。
  • Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master,Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。

3.2 集群模式

3.2.1单Master模式

这种风险较大,一单Broker重启或者宕机,会导致整个服务不可用,不建议线上环境使用,可以用于本地测试。

3.2.2多Master模式

一个集群无Slave,全是Master,例如2个或者3个Master,这种模式的优缺点如下:

  • 优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置为RAID10时,即使机器宕机不可恢复的情况下,由于RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高;
  • 缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到影响。

3.2.3多Master多Slave模式(异步)

每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点如下:

  • 优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受到影响,同时Master宕机后,消费者仍然可以从Slave消费,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样;
  • 缺点:Master宕机,磁盘损坏情况下会丢失少量消息。

3.2.4多Master多Slave模式(同步)

每个Master配置一个Slave,对多对Master-Slave,HA采用同步双写方式,即只有主备都写成功,才向应用返回成功,这种模式的优点如下:

  • 优点:数据与服务都无单电故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
  • 缺点:性能比异步复制模式略低(大约10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。

3.3双主双从集群搭建

3.3.1集群工作流程

  1. 启动NameServer,NameServer起来后监听端口,等待Broker、Consumer连上来,相当于一个路由控制中心。
  2. Broker启动,跟所有的NameServer保持长连接,定时发送心跳包,心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
  3. 收发消息前,先创建Topic,创建Topic时需要制定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
  4. Producer发送消息,启动时先跟NameServer集群中的一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
  5. Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后跟直接跟Broker建立连接通道,开始消费信息。

3.3.2服务器环境配置(Ubuntu)

序号IP角色架构模式
1192.168.141.240nameserver、brokerserverMaster1、Slave2
2192.168.141.241nameserver、brokerserverMaster2、Slave1
1.Host添加信息
cat >> /etc/hosts << EOF
192.168.141.240 rocketMq01-nameserver1
192.168.141.241 rocketMq02-nameserver2
192.168.141.240 rocketMq01-master1
192.168.141.240 rocketMq01-slave2
192.168.141.241 rocketMq02-master2
192.168.141.241 rocketMq02-slave1
EOF

生效重启网卡

netplan apply 

2.关闭防火墙

RocketMQ默认使用3个端口:9876、10911、11011

  • nameserver默认使用9876端口
  • master默认使用10911端口
  • slave默认使用11011端口
3.环境变量配置

在profile文件的末尾加入如下命令

cat >> /etc/profile << EOF
#set rocketmq
export ROCKETMQ_HOME=/opt/moudle/rocketmq-all-4.7.1-bin-release
export PATH=$PATH:$ROCKETMQ_HOME/bin
EOF

环境生效

source /etc/profile

4.创建消息存储路径
mkdir /opt/moudle/rocketmq-all-4.7.1-bin-release/store
mkdir /opt/moudle/rocketmq-all-4.7.1-bin-release/store/commitlog
mkdir /opt/moudle/rocketmq-all-4.7.1-bin-release/store/consumequeue
mkdir /opt/moudle/rocketmq-all-4.7.1-bin-release/store/index

注意:以上配置两台机器都需要配置

5.broker配置文件
1)master1

服务器:192.168.141.240

vi /opt/moudle/rocketmq-all-4.7.1-bin-release/conf/2m-2s-sync/broker-a.properties

修改配置如下:

# 集群名称
brokerClusterName=rocketmq-cluster
# broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
# 0 表示 Master, >0 表示Slave
brokerId=0
# nameServer地址,分号分割
nameservAddr=rocketMq01-nameserver1:9876;rocketMq02-nameserver2:9876
#在发送消息时,自动创建服务器不存在的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
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/opt/moudle/rocketmq-all-4.7.1-bin-release/store
#commitLog 存储路径
storePathCommitLog=/opt/moudle/rocketmq-all-4.7.1-bin-release/store/commitlog
#消费队列存储路径
storePathConsumeQueue=/opt/moudle/rocketmq-all-4.7.1-bin-release/store/consumequeue
#消息索引存储路径
storePathIndex=/opt/moudle/rocketmq-all-4.7.1-bin-release/store/index
#checkpoint 文件存储路径
storeCheckpoint=/opt/moudle/rocketmq-all-4.7.1-bin-release/store/checkpoint
#abort 文件存储路径
abortFile=/opt/moudle/rocketmq-all-4.7.1-bin-release/store/abort
# broker的角色
# ASYNC_MASTER 异步刷盘
# SYNC_MASTER 同步双鞋Master
# SLAVE
brokerRole=SYNC_MASTER
# 刷盘类型
flushDiskType=ASYNC_FLUSH


vi /opt/moudle/rocketmq-all-4.7.1-bin-release/conf/2m-2s-sync/broker-b-s.properties

# 集群名称
brokerClusterName=rocketmq-cluster
# broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
# 0 表示 Master, >0 表示Slave
brokerId=1
# nameServer地址,分号分割
nameservAddr=rocketMq01-nameserver1:9876;rocketMq02-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=20911
#删除文件时间点,默认凌晨4点
deleteWhen=04
#文件保留时间,默认48
fileReservedTime=120
#commitLog每个文件的默认大小1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30w条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave
#commitLog 存储路径
storePathCommitLog=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave/commitlog
#消费队列存储路径
storePathConsumeQueue=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave/consumequeue
#消息索引存储路径
storePathIndex=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave/index
#checkpoint 文件存储路径
storeCheckpoint=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave/checkpoint
#abort 文件存储路径
abortFile=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave/abort
# broker的角色
# ASYNC_MASTER 异步刷盘
# SYNC_MASTER 同步双鞋Master
# SLAVE
brokerRole=SLAVE
# 刷盘类型
flushDiskType=ASYNC_FLUSH

2)master2

服务器:192.168.141.241

vi /opt/moudle/rocketmq-all-4.7.1-bin-release/conf/2m-2s-sync/broker-a-s.properties

修改配置如下:

# 集群名称
brokerClusterName=rocketmq-cluster
# broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
# 0 表示 Master, >0 表示Slave
brokerId=1
# nameServer地址,分号分割
nameservAddr=rocketMq01-nameserver1:9876;rocketMq02-nameserver2:9876
#在发送消息时,自动创建服务器不存在的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
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave
#commitLog 存储路径
storePathCommitLog=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave/commitlog
#消费队列存储路径
storePathConsumeQueue=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave/consumequeue
#消息索引存储路径
storePathIndex=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave/index
#checkpoint 文件存储路径
storeCheckpoint=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave/checkpoint
#abort 文件存储路径
abortFile=/opt/moudle/rocketmq-all-4.7.1-bin-release/store-slave/abort
# broker的角色
# ASYNC_MASTER 异步刷盘
# SYNC_MASTER 同步双鞋Master
# SLAVE
brokerRole=SLAVE
# 刷盘类型
flushDiskType=ASYNC_FLUSH



vi /opt/moudle/rocketmq-all-4.7.1-bin-release/conf/2m-2s-sync/broker-b.properties

# 集群名称
brokerClusterName=rocketmq-cluster
# broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
# 0 表示 Master, >0 表示Slave
brokerId=0
# nameServer地址,分号分割
nameservAddr=rocketMq01-nameserver1:9876;rocketMq02-nameserver2:9876
# 在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
# 是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
# 是否允许 Broker自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
# Broker 对外服务的监听端口
listenPort=20911
# 删除文件时间点,默认凌晨4点
deleteWhen=04
# 文件保留时间,默认48
fileReservedTime=120
# commitLog每个文件的默认大小1G
mapedFileSizeCommitLog=1073741824
# ConsumeQueue每个文件默认存30w条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
# 检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
# 存储路径
storePathRootDir=/opt/moudle/rocketmq-all-4.7.1-bin-release/store
# commitLog 存储路径
storePathCommitLog=/opt/moudle/rocketmq-all-4.7.1-bin-release/store/commitlog
# 消费队列存储路径
storePathConsumeQueue=/opt/moudle/rocketmq-all-4.7.1-bin-release/store/consumequeue
# 消息索引存储路径
storePathIndex=/opt/moudle/rocketmq-all-4.7.1-bin-release/store/index
# checkpoint 文件存储路径
storeCheckpoint=/opt/moudle/rocketmq-all-4.7.1-bin-release/store/checkpoint
# abort 文件存储路径
abortFile=/opt/moudle/rocketmq-all-4.7.1-bin-release/store/abort
# broker的角色
# ASYNC_MASTER 异步刷盘
# SYNC_MASTER 同步双鞋Master
# SLAVE
brokerRole=SYNC_MASTER
# 刷盘类型
flushDiskType=ASYNC_FLUSH


6.修改启动脚本(测试环境)
1)修改runbroker.sh
vi /opt/moudle/rocketmq-all-4.7.1-bin-release/bin/runbroker.sh

JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"

2)修改runserver.sh
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"

7.服务启动
1)启动NameServer集群

在两台机器上分别启动NameServer命令 bin目录下

nohup sh mqnamesrv &		
2)启动Broker集群

·在240上启动master1和slave2 (最好是管理员启动,否则可能会有问题)

nohup sh mqbroker -c /opt/moudle/rocketmq-all-4.7.1-bin-release/conf/2m-2s-sync/broker-a.properties &
nohup sh mqbroker -c /opt/moudle/rocketmq-all-4.7.1-bin-release/conf/2m-2s-sync/broker-b.properties &

四、mqadmin管理工具

4.1 使用方式

进入RocketMQ安装位置,在bin目录下执行./mqadmin {command} {args}

4.2 命令介绍

4.2.1 Topic相关

名称含义命令选项说明
updateTopic创建更新Topic配置-bBroker 地址,表示 topic 所在 Broker,只支持单台Broker,地址为ip:port
-ccluster 名称,表示 topic 所在集群(集群可通过 clusterList 查询)
-h-打印帮助
-nNameServer服务地址,格式 ip:port
-p指定新topic的读写权限( W=2|R=4|WR=6 )
-r可读队列数(默认为 8)
-w可写队列数(默认为 8)
-ttopic 名称(名称只能使用字符 ^[a-zA-Z0-9_-]+$ )
deleteTopic删除Topic-ccluster 名称,表示删除某集群下的某个 topic (集群 可通过 clusterList 查询)
-h打印帮助
-nNameServer 服务地址,格式 ip:port
-ttopic 名称(名称只能使用字符 ^[a-zA-Z0-9_-]+$ )
topicList查看 Topic 列表信息-h打印帮助
-c不配置-c只返回topic列表,增加-c返回clusterName, topic, consumerGroup信息,即topic的所属集群和订阅关系,没有参数
-nNameServer 服务地址,格式 ip:port
topicRoute查看 Topic 路由信息-ttopic 名称
-h打印帮助
-nNameServer 服务地址,格式 ip:port
topicStatus查看 Topic 消息队列offset-ttopic 名称
-h打印帮助
-nNameServer 服务地址,格式 ip:port
topicClusterList查看 Topic 所在集群列表-ttopic 名称
-h打印帮助
-nNameServer 服务地址,格式 ip:port
updateTopicPerm更新 Topic 读写权限-ttopic 名称
-h打印帮助
-nNameServer 服务地址,格式 ip:port
-bBroker 地址,表示 topic 所在 Broker,只支持单台Broker,地址为ip:port
-p指定新 topic 的读写权限( W=2|R=4|WR=6 )
-ccluster 名称,表示 topic 所在集群(集群可通过 clusterList 查询),-b优先,如果没有-b,则对集群中所有Broker执行命令
updateOrderConf从NameServer上创建、删除、获取特定命名空间的kv配置,目前还未启用-h打印帮助
-nNameServer 服务地址,格式 ip:port
-ttopic,键
-vorderConf,值
-mmethod,可选get、put、delete
allocateMQ以平均负载算法计算消费者列表负载消息队列的负载结果-ttopic 名称
-h打印帮助
-nNameServer 服务地址,格式 ip:port
-iipList,用逗号分隔,计算这些ip去负载Topic的消息队列
statsAll打印Topic订阅关系、TPS、积累量、24h读写总量等信息-h打印帮助
-nNameServer 服务地址,格式 ip:port
-a是否只打印活跃topic
-t指定topic

4.2.2 集群相关

名称含义命令选项说明
clusterList查看集群信息,集群、BrokerName、BrokerId、TPS等信息-m打印更多信息 (增加打印出如下信息 #InTotalYest, #OutTotalYest, #InTotalToday ,#OutTotalToday)
-h打印帮助
-nNameServer 服务地址,格式 ip:port
-i打印间隔,单位秒
clusterRT发送消息检测集群各Broker RT。消息发往${BrokerName} Topic。-aamount,每次探测的总数,RT = 总时间 / amount
-s消息大小,单位B
-c探测哪个集群
-p是否打印格式化日志,以|分割,默认不打印
-h打印帮助
-m所属机房,打印使用
-i发送间隔,单位秒
-nNameServer 服务地址,格式 ip:port

4.2.3 Broker相关

名称含义命令选项说明
updateBrokerConfig更新 Broker 配置文件,会修改Broker.conf-bBroker 地址,格式为ip:port
-ccluster 名称
-kkey 值
-vvalue 值
-h打印帮助
-nNameServer 服务地址,格式 ip:port
brokerStatus查看 Broker 统计信息、运行状态(你想要的信息几乎都在里面)-bBroker 地址,地址为ip:port
-h打印帮助
-nNameServer 服务地址,格式 ip:port
brokerConsumeStatsBroker中各个消费者的消费情况,按Message Queue维度返回Consume Offset,Broker Offset,Diff,TImestamp等信息-bBroker 地址,地址为ip:port
-t请求超时时间
-ldiff阈值,超过阈值才打印
-o是否为顺序topic,一般为false
-h打印帮助
-nNameServer 服务地址,格式 ip:port
getBrokerConfig获取Broker配置-bBroker 地址,地址为ip:port
-nNameServer 服务地址,格式 ip:port
wipeWritePerm从NameServer上清除 Broker写权限-bBroker 地址,地址为ip:port
-nNameServer 服务地址,格式 ip:port
-h打印帮助
cleanExpiredCQ清理Broker上过期的Consume Queue,如果手动减少对列数可能产生过期队列-nNameServer 服务地址,格式 ip:port
-h打印帮助
-bBroker 地址,地址为ip:port
-c集群名称
cleanUnusedTopic清理Broker上不使用的Topic,从内存中释放Topic的Consume Queue,如果手动删除Topic会产生不使用的Topic-nNameServer 服务地址,格式 ip:port
-h打印帮助
-bBroker 地址,地址为ip:port
-c集群名称
sendMsgStatus向Broker发消息,返回发送状态和RT-nNameServer 服务地址,格式 ip:port
-h打印帮助
-bBrokerName,注意不同于Broker地址
-s消息大小,单位B
-c发送次数

4.2.4消息相关

名称含义命令选项说明
queryMsgById根据offsetMsgId查询msg,如果使用开源控制台,应使用offsetMsgId,此命令还有其他参数,具体作用请阅读QueryMsgByIdSubCommand。-imsgId
-h打印帮助
-nNameServer 服务地址,格式 ip:port
queryMsgByKey根据消息 Key 查询消息-kmsgKey
-tTopic 名称
-h打印帮助
-nNameServer 服务地址,格式 ip:port
queryMsgByOffset根据 Offset 查询消息-bBroker 名称,(这里需要注意 填写的是 Broker 的名称,不是 Broker 的地址,Broker 名称可以在 clusterList 查到)
-iquery 队列 id
-ooffset 值
-ttopic 名称
-h打印帮助
-nNameServer 服务地址,格式 ip:port
queryMsgByUniqueKey根据msgId查询,msgId不同于offsetMsgId,区别详见常见运维问题。-g,-d配合使用,查到消息后尝试让特定的消费者消费消息并返回消费结果-h打印帮助
-nNameServer 服务地址,格式 ip:port
-iuniqe msg id
-gconsumerGroup
-dclientId
-ttopic名称
checkMsgSendRT检测向topic发消息的RT,功能类似clusterRT-h打印帮助
-nNameServer 服务地址,格式 ip:port
-ttopic名称
-a探测次数
-s消息大小
sendMessage发送一条消息,可以根据配置发往特定Message Queue,或普通发送。-h打印帮助
-nNameServer 服务地址,格式 ip:port
-ttopic名称
-pbody,消息体
-kkeys
-ctags
-bBrokerName
-iqueueId
consumeMessage消费消息。可以根据offset、开始&结束时间戳、消息队列消费消息,配置不同执行不同消费逻辑,详见ConsumeMessageCommand。-h打印帮助
-nNameServer 服务地址,格式 ip:port
-ttopic名称
-bBrokerName
-o从offset开始消费
-iqueueId
-g消费者分组
-s开始时间戳,格式详见-h
-d结束时间戳
-c消费多少条消息
printMsg从Broker消费消息并打印,可选时间段-h打印帮助
-nNameServer 服务地址,格式 ip:port
-ttopic名称
-c字符集,例如UTF-8
-ssubExpress,过滤表达式
-b开始时间戳,格式参见-h
-e结束时间戳
-d是否打印消息体
printMsgByQueue类似printMsg,但指定Message Queue-h打印帮助
-nNameServer 服务地址,格式 ip:port
-ttopic名称
-iqueueId
-aBrokerName
-c字符集,例如UTF-8
-ssubExpress,过滤表达式
-b开始时间戳,格式参见-h
-e结束时间戳
-p是否打印消息
-d是否打印消息体
-f是否统计tag数量并打印
resetOffsetByTime按时间戳重置offset,Broker和consumer都会重置-h打印帮助
-nNameServer 服务地址,格式 ip:port
-g消费者分组
-ttopic名称
-s重置为此时间戳对应的offset
-f是否强制重置,如果false,只支持回溯offset,如果true,不管时间戳对应offset与consumeOffset关系
-c是否重置c++客户端offset

4.2.5消费者、消费组相关

名称含义命令选项说明
consumerProgress查看订阅组消费状态,可以查看具体的client IP的消息积累量-g消费者所属组名
-s是否打印client IP
-h打印帮助
-nNameServer 服务地址,格式 ip:port
consumerStatus查看消费者状态,包括同一个分组中是否都是相同的订阅,分析Process Queue是否堆积,返回消费者jstack结果,内容较多,使用者参见ConsumerStatusSubCommand-h打印帮助
-nNameServer 服务地址,格式 ip:port
-gconsumer group
-iclientId
-s是否执行jstack
getConsumerStatus获取 Consumer 消费进度-g消费者所属组名
-t查询主题
-iConsumer 客户端 ip
-nNameServer 服务地址,格式 ip:port
-h打印帮助
updateSubGroup更新或创建订阅关系-nNameServer 服务地址,格式 ip:port
-h打印帮助
-bBroker地址
-c集群名称
-g消费者分组名称
-s分组是否允许消费
-m是否从最小offset开始消费
-d是否是广播模式
-q重试队列数量
-r最大重试次数
-i当slaveReadEnable开启时有效,且还未达到从slave消费时建议从哪个BrokerId消费,可以配置备机id,主动从备机消费
-w如果Broker建议从slave消费,配置决定从哪个slave消费,配置BrokerId,例如1
-a当消费者数量变化时是否通知其他消费者负载均衡
deleteSubGroup从Broker删除订阅关系-nNameServer 服务地址,格式 ip:port
-h打印帮助
-bBroker地址
-c集群名称
-g消费者分组名称
cloneGroupOffset在目标群组中使用源群组的offset-nNameServer 服务地址,格式 ip:port
-h打印帮助
-s源消费者组
-d目标消费者组
-ttopic名称
-o暂未使用

4.2.6 连接相关

名称含义命令选项说明
consumerConnec tion查询 Consumer 的网络连接-g消费者所属组名
-nNameServer 服务地址,格式 ip:port
-h打印帮助
producerConnec tion查询 Producer 的网络连接-g生产者所属组名
-t主题名称
-nNameServer 服务地址,格式 ip:port
-h打印帮助

4.2.7NameServer相关

名称含义命令选项说明
updateKvConfig更新NameServer的kv配置,目前还未使用-s命名空间
-kkey
-vvalue
-nNameServer 服务地址,格式 ip:port
-h打印帮助
deleteKvConfig删除NameServer的kv配置-s命名空间
-kkey
-nNameServer 服务地址,格式 ip:port
-h打印帮助
getNamesrvConfig获取NameServer配置-nNameServer 服务地址,格式 ip:port
-h打印帮助
updateNamesrvConfig修改NameServer配置-nNameServer 服务地址,格式 ip:port
-h打印帮助
-kkey
-vvalue

4.2.8其他

名称含义命令选项说明
startMonitoring开启监控进程,监控消息误删、重试队列消息数等-nNameServer 服务地址,格式 ip:port
-h打印帮助

4.3 注意事项

  • 几乎所有命令都需要配置-n表示NameServer地址,格式为ip:port
  • 几乎所有命令都可以通过-h获取帮助
  • 如果既有Broker地址(-b)配置项又有clusterName(-c)配置项,则优先以Broker地址执行命令;如果不配置Broker地址,则对集群中所有主机执行命令

五、集群监控平台搭建

RocketMQ有一个对其扩展的开源项目incubator-rocketmq-externals,这个项目中有一个子模块叫rocketmq-console,这个便是管理控制台项目了,先将incubator-rocketmq-externals拉到本地,因为我们需要自己对rocketmq-console进行编译打包运行。

5.5.2 下载并编译打包

git clone https://github.com/apache/rocketmq-externals
cd rocketmq-console
mvn clean package -D maven.test.skip=true

注意:打包前在rocketmq-console中配置namesrv集群地址:

rocketmq.config.namesrvAddr=192.168.25.135:9876;192.168.25.138:9876

启动rocketmq-console:

java -jar rocketmq-console-ng-1.0.0.jar

启动成功后,我们就可以通过浏览器访问http://localhost:8080进入控制台界面了,如下图:

六、消息发送样例

6.1基本使用步骤

  • 导入MQ客户端依赖
<dependency>
     <groupId>org.apache.rocketmq</groupId>
     <artifactId>rocketmq-client</artifactId>
     <version>4.4.0</version>
</dependency>
  • 消息发送者步骤分析
1.创建消息生产者producer,并制定生产者组名
2.制定Nameserver地址
3.启动producer
4.创建消息对象,指定主题Topic、Tag和消息体
5.发送消息
6.关闭生产者producer

  • 消息生产者步骤分析
1.创建消费者consumer,指定消费者组名
2.指定Nameserver地址
3.订阅主题Topic和Tag
4.设置回调函数,处理消息
5.启动消费者consumer

6.2基本使用案例

6.2.1消息发送

1)发送同步消息

这种可靠性同步地发送方式使用的比较广泛,比如:重要的消息通知,短信通知。

package com.example.demo.rocketmq.base;

import org.apache.rocketmq.client.exception.MQBrokerException;
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;
import org.apache.rocketmq.remoting.exception.RemotingException;

import java.io.UnsupportedEncodingException;

/**
 * 功能描述: 发送同步消息
 *
 * @author luxiaomeng
 * @date 2020/9/21   11:08
 * 修改日志: 暂无
 */
public class SyncProducer {
    public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
        // 实例化消息生产者Producer   参数生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //  设置NameServer的地址  可以冒号隔开
        producer.setNamesrvAddr("192.168.141.110:9876");

        // 启动Produer案例
        producer.start();
        for (int i = 0; i < 100; i++) {
            // 创建消息,并制定topic,tag和消息体
            Message msg = new Message("TopicTest" /* Topic */,
                    "TagA"/*Tag*/,
                    ("Hello RocketMQ" + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /*消息体*/
            );
            // 发送消息到一个Broker
            SendResult sendResult = producer.send(msg);
            // 通过sendResult返回消息是否成功送达
            System.out.printf("%s%n", sendResult);
        }
        // 如果不再发送消息,关闭Producer实例
        producer.shutdown();
    }
}

2)发送异步消息

异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应

package com.example.demo.rocketmq.base;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 功能描述: 发送异步消息
 *
 * @author luxiaomeng
 * @date 2020/9/21   13:34
 * 修改日志: 暂无
 */
public class AsyncProducer {


    public static void main(String[] args) throws Exception {
        final CountDownLatch countDownLatch = new CountDownLatch(5);
        // 实例化消息生产者Producer
        DefaultMQProducer producer = new DefaultMQProducer("group2");
        // 设置NameServer的地址
        producer.setNamesrvAddr("192.168.141.110:9876");

        // 启动Producer实例
        producer.start();
//        producer.setRetryTimesWhenSendAsyncFailed(0);
        for (int i = 0; i < 5; i++) {
            final int index = i;
            // 创建消息,并指定Topic,Tag和消息体
            Message msg = new Message("TopicTest",
                    "TagB",
//                    "OrderID188",
                    "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
            // SendCallback接收异步返回结果的回调
            producer.send(msg, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    countDownLatch.countDown();
                    System.out.printf("%-10d OK %s %n", index,
                            sendResult.getMsgId());
                }

                @Override
                public void onException(Throwable e) {
                    System.out.printf("%-10d Exception %s %n", index, e);
                    countDownLatch.countDown();
                    e.printStackTrace();
                }
            });
        }
        countDownLatch.await(5, TimeUnit.SECONDS);
        // 如果不再发送消息,关闭Producer实例。
        producer.shutdown();
    }
}

3)单向发送消息

这种方式主要用在不特别关心发送结果的场景,例如日志发送。

package com.example.demo.rocketmq.base;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

/**
 * 功能描述: 单向发送消息
 *
 * @author luxiaomeng
 * @date 2020/9/21   14:11
 * 修改日志: 暂无
 */
public class OnewayProducer {
    public static void main(String[] args) throws Exception {
        // 实例化消息生产者Producer
        DefaultMQProducer producer = new DefaultMQProducer("group3");
        // 设置NameServer的地址
        producer.setNamesrvAddr("192.168.141.110:9876");

        // 启动Producer实例
        producer.start();
        for (int i = 0; i < 100; i++) {
            //  创建消息,并制定Topic,Tag和消息体
            Message msg = new Message("TopicTest" /* Topic */,
                    "TagA"/*Tag*/,
                    ("Hello RocketMQ" + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /*消息体*/
            );
            producer.sendOneway(msg);
        }
        producer.shutdown();
    }
}


6.2.2 消费消息

1)负载均衡模式

消费者采用负载均衡方式消费信息,多个消费者消费消息队列消息,每个消息者处理的信息不同

package com.example.demo.rocketmq.base;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;

import java.util.List;

/**
 * 功能描述: 负载均衡模式(系统默认)
 *
 * @author luxiaomeng
 * @date 2020/9/21   14:26
 * 修改日志: 暂无
 */
public class LoadbalancingConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        consumer.setNamesrvAddr("192.168.141.110:9876");
        // 订阅Topic和tag
        consumer.subscribe("TopicTest", "*");

        //负载均衡模式消费
        consumer.setMessageModel(MessageModel.CLUSTERING);

        // 注册回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                System.out.printf("%s Receive New Messages: %s %n",
                        Thread.currentThread().getName(), list);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //启动消息者
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

2)广播模式模式

消费者采用广播的方式消费消息,每个消费者消费的消息都是相同的

package com.example.demo.rocketmq.base;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;

import java.util.List;

/**
 * 功能描述: 广播模式
 *
 * @author luxiaomeng
 * @date 2020/9/21   14:52
 * 修改日志: 暂无
 */
public class BroadcastConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        consumer.setNamesrvAddr("192.168.141.110:9876");
        // 订阅Topic和tag
        consumer.subscribe("TopicTest", "*");

        //负载广播模式消费
        consumer.setMessageModel(MessageModel.BROADCASTING);

        // 注册回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                System.out.printf("%s Receive New Messages: %s %n",
                        Thread.currentThread().getName(), list);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //启动消息者
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}


6.2.3 顺序消息

消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。

顺序消费的原理解析,在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。

下面用订单进行分区有序的示例。一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定是同一个队列。

消息生产者
package com.example.demo.rocketmq.message.order;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 功能描述: 顺序消息生产者
 *
 * @author luxiaomeng
 * @date 2020/9/21   22:05
 * 修改日志: 暂无
 */
public class Producer {

    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("order");

        producer.setNamesrvAddr("192.168.141.110:9876");

        producer.start();

        String[] tags = new String[]{"TagA", "TagC", "TagD"};

        // 订单列表
        List<OrderStep> orderList = new Producer().buildOrders();

        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = sdf.format(date);
        for (int i = 0; i < 10; i++) {
            // 加个时间前缀
            String body = dateStr + " Hello RocketMQ " + orderList.get(i);
            Message msg = new Message("TopicTest", tags[i % tags.length], "KEY" + i, body.getBytes());

            /**
             * @param msg 消息对象
             * @param MessageQueueSelector 消息队列的选择器
             * @param 选择队列的业务标识
             * @return
             */
            /*队列集合*//*消息对象*//*业务标识的参数*/
            SendResult sendResult = producer.send(msg, (mqs, msg1, arg) -> {
                System.out.println("队列长度"+mqs);
                Long id = (Long) arg;  //根据订单id选择发送queue
                long index = id % mqs.size();
                return mqs.get((int) index);
            }, orderList.get(i).getOrderId());//订单id

            System.out.println(String.format("SendResult status:%s, queueId:%d, body:%s",
                    sendResult.getSendStatus(),
                    sendResult.getMessageQueue().getQueueId(),
                    body));
        }

        producer.shutdown();
    }

    /**
     * 订单的步骤
     */
    private static class OrderStep {
        private long orderId;
        private String desc;

        public long getOrderId() {
            return orderId;
        }

        public void setOrderId(long orderId) {
            this.orderId = orderId;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        @Override
        public String toString() {
            return "OrderStep{" +
                    "orderId=" + orderId +
                    ", desc='" + desc + '\'' +
                    '}';
        }
    }

    /**
     * 生成模拟订单数据
     */
    private List<OrderStep> buildOrders() {
        List<OrderStep> orderList = new ArrayList<OrderStep>();

        OrderStep orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111065L);
        orderDemo.setDesc("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103117235L);
        orderDemo.setDesc("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111065L);
        orderDemo.setDesc("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103117235L);
        orderDemo.setDesc("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111065L);
        orderDemo.setDesc("完成");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("推送");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103117235L);
        orderDemo.setDesc("完成");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("完成");
        orderList.add(orderDemo);

        return orderList;
    }
}

消息消费者
package com.example.demo.rocketmq.message.order;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 功能描述: 顺序消息消费,带事务方式(应用可控制Offset什么时候提交)
 *
 * @author luxiaomeng
 * @date 2020/9/21   22:07
 * 修改日志: 暂无
 */
public class ConsumerInOrder {
    public static void main(String[] args) throws Exception{
        DefaultMQPushConsumer consumer = new
                DefaultMQPushConsumer("order");
        consumer.setNamesrvAddr("192.168.141.110:9876");
        /**
         * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费<br>
         * 如果非第一次启动,那么按照上次消费的位置继续消费
         */
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        consumer.subscribe("TopicTest", "TagA || TagC || TagD");

        consumer.registerMessageListener(new MessageListenerOrderly() {

            Random random = new Random();

            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                context.setAutoCommit(true);
                for (MessageExt msg : msgs) {
                    // 可以看到每个queue有唯一的consume线程来消费, 订单对每个queue(分区)有序
                    System.out.println("consumeThread=" + Thread.currentThread().getName() + "queueId=" + msg.getQueueId() + ", content:" + new String(msg.getBody()));
                }

                try {
                    //模拟业务逻辑处理中...
//                    TimeUnit.SECONDS.sleep(random.nextInt(10));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        consumer.start();

        System.out.println("Consumer Started.");
    }
}

6.2.4 延时消息

比如电商里,提交了一个订单就可以发送一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。

消息生产者
package com.example.demo.rocketmq.message.delay;

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;

/**
 * 功能描述: 延迟消息生产者
 *
 * @author luxiaomeng
 * @date 2020/9/22   9:36
 * 修改日志: 暂无
 */
public class ScheduledMessageProducer {


    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("producer");
        producer.setNamesrvAddr("192.168.141.110:9876");
        producer.start();
        int totalMessagesToSend = 100;
        for (int i = 0; i < totalMessagesToSend; i++) {
            Message message = new Message("TestTopic", ("Hello scheduled message" + i).getBytes());
            //  设置延时等级3,这个消息将在10s之后发送
            message.setDelayTimeLevel(3);
            // 发送消息
            producer.send(message);
        }
        // 关闭生产者
        producer.shutdown();

    }
}



消息消费者
package com.example.demo.rocketmq.message.delay;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

/**
 * 功能描述: 延迟消息消费者
 *
 * @author luxiaomeng
 * @date 2020/9/22   9:32
 * 修改日志: 暂无
 */
public class ScheduledMessageConsumer {
    public static void main(String[] args) throws Exception {
        //实例化消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer");
        consumer.setNamesrvAddr("192.168.141.110:9876");
        //订阅Topic
        consumer.subscribe("TestTopic","*");
        // 注册消息监听者
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println("Receive message[msgId=" + message.getMsgId() + "] " + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later");
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        // 启动消费者
        consumer.start();

    }
}

使用限制
// org/apache/rocketmq/store/config/MessageStoreConfig.java
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";

现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18

6.2.5 批量消息

批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic,相同的waitStoreMsgOK,而且不能是延时消息。此外,这一批消息的总大小不应超过4MB。先在官网1MB?

生产者

如果您每次只发送不超过4MB的消息,则很容易使用批处理

package com.example.demo.rocketmq.message.batch;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;

import java.util.ArrayList;
import java.util.List;

/**
 * 功能描述: 批量消息生成者
 *
 * @author luxiaomeng
 * @date 2020/9/22   10:04
 * 修改日志: 暂无
 */
public class BatchProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("producer");
        producer.setNamesrvAddr("192.168.141.110:9876");
        producer.start();
        List<Message> msgs = new ArrayList<>();
        Message message1 = new Message("batchTopic", ("批量消息1" ).getBytes());
        Message message2 = new Message("batchTopic", ("批量消息2" ).getBytes());
        Message message3 = new Message("batchTopic", ("批量消息3" ).getBytes());
        msgs.add(message1);
        msgs.add(message2);
        msgs.add(message3);

        // 发送消息
        producer.send(msgs);

        //把大的消息分裂成若干个小的消息
//        ListSplitter splitter = new ListSplitter(messages);
//        while (splitter.hasNext()) {
//            try {
//                List<Message>  listItem = splitter.next();
//                producer.send(listItem);
//            } catch (Exception e) {
//                e.printStackTrace();
//                //处理error
//            }
//        }
        // 关闭生产者
        producer.shutdown();

    }

}

如果消息的总长度可能大于4MB时,这时候最好把消息进行分割

package com.example.demo.rocketmq.message.batch;

import org.apache.rocketmq.common.message.Message;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 功能描述: 消息分割
 *
 * @author luxiaomeng
 * @date 2020/9/22   10:12
 * 修改日志: 暂无
 */
public class ListSplitter implements Iterator<List<Message>> {
    private final int SIZE_LIMIT = 1024 * 1024 * 4;
    private final List<Message> messages;
    private int currIndex;
    public ListSplitter(List<Message> messages) {
        this.messages = messages;
    }
    @Override
    public boolean hasNext() {
        return currIndex < messages.size();
    }
    @Override
    public List<Message> next() {
        int nextIndex = currIndex;
        int totalSize = 0;
        for (; nextIndex < messages.size(); nextIndex++) {
            Message message = messages.get(nextIndex);
            int tmpSize = message.getTopic().length() + message.getBody().length;
            Map<String, String> properties = message.getProperties();
            for (Map.Entry<String, String> entry : properties.entrySet()) {
                tmpSize += entry.getKey().length() + entry.getValue().length();
            }
            tmpSize = tmpSize + 20; // 增加日志的开销20字节
            if (tmpSize > SIZE_LIMIT) {
                //单个消息超过了最大的限制
                //忽略,否则会阻塞分裂的进程
                if (nextIndex - currIndex == 0) {
                    //假如下一个子列表没有元素,则添加这个子列表然后退出循环,否则只是退出循环
                    nextIndex++;
                }
                break;
            }
            if (tmpSize + totalSize > SIZE_LIMIT) {
                break;
            } else {
                totalSize += tmpSize;
            }

        }
        List<Message> subList = messages.subList(currIndex, nextIndex);
        currIndex = nextIndex;
        return subList;
    }
}

消费者
package com.example.demo.rocketmq.message.batch;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

/**
 * 功能描述: 批量消息发送者
 *
 * @author luxiaomeng
 * @date 2020/9/22   10:04
 * 修改日志: 暂无
 */
public class BatchConsumer {
    public static void main(String[] args) throws Exception {
        //实例化消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer");
        consumer.setNamesrvAddr("192.168.141.110:9876");
        //订阅Topic
        consumer.subscribe("batchTopic","*");
        // 注册消息监听者
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println(new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        // 启动消费者
        consumer.start();

    }

}

6.2.6 过滤消息

1) 采用TAG过滤消息
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE");
consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC");
2) 采用SQL表达式

消费者将接收包含TAGA或TAGB或TAGC的消息。但是限制是一个消息只能有一个标签,这对于复杂的场景可能不起作用。在这种情况下,可以使用SQL表达式筛选消息。SQL特性可以通过发送消息时的属性来进行计算。在RocketMQ定义的语法下,可以实现一些简单的逻辑。下面是一个例子:

------------
| message  |
|----------|  a > 5 AND b = 'abc'
| a = 10   |  --------------------> Gotten
| b = 'abc'|
| c = true |
------------
------------
| message  |
|----------|   a > 5 AND b = 'abc'
| a = 1    |  --------------------> Missed
| b = 'abc'|
| c = true |
------------
SQL基本语法

RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。

  • 数值比较,比如:>,>=,<,<=,BETWEEN,=;
  • 字符比较,比如:=,<>,IN;
  • IS NULL 或者 IS NOT NULL;
  • 逻辑符号 AND,OR,NOT;

常量支持类型为:

  • 数值,比如:123,3.1415;
  • 字符,比如:‘abc’,必须用单引号包裹起来;
  • NULL,特殊的常量
  • 布尔值,TRUEFALSE

只有使用push模式的消费者才能用使用SQL92标准的sql语句,接口如下:

public void subscribe(finalString topic, final MessageSelector messageSelector)
消息生产者

发送消息时,你能通过putUserProperty来设置消息的属性

DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
producer.start();
Message msg = new Message("TopicTest",
   tag,
   ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// 设置一些属性
msg.putUserProperty("a", String.valueOf(i));
SendResult sendResult = producer.send(msg);

producer.shutdown();
消息消费者

用MessageSelector.bySql来使用sql筛选消息

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
// 只有订阅的消息有这个属性a, a >=0 and a <= 3
consumer.subscribe("TopicTest", MessageSelector.bySql("a between 0 and 3");
consumer.registerMessageListener(new MessageListenerConcurrently() {
   @Override
   public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
       return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
   }
});
consumer.start();

6.2.7事务消息

流程分析

Producer GroupRocketMQ BrokerConsumer Group
生产消息1.发送Prepare消息->写消息到HalfTopic
执行本地事务<-2回调方法<-写成功
执行成功->3.commot或rollaback写消息到OpTopic
4.commit
6.返回超时或返回Unkow状态写消息到RealTopic5.下发消息消费消息
事务回查<-7.回调方法比较HalfTopic与OptTopic的消息,确定需要回查的消息进行事务回查
  1. 发送消息(half消息)。
  2. 服务端响应消息写入结果。
  3. 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)。
  4. 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)

事务补偿

  1. 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
  2. Producer收到回查消息,检查回查消息对应的本地事务的状态
  3. 根据本地事务状态,重新Commit或者Rollback

其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。

事务消息状态

事务消息共有三种状态,提交状态、回滚状态、中间状态:

  • TransactionStatus.CommitTransaction: 提交事务,它允许消费者消费此消息。
  • TransactionStatus.RollbackTransaction: 回滚事务,它代表该消息将被删除,不允许被消费。
  • TransactionStatus.Unknown: 中间状态,它代表需要检查消息队列来确定状态。
事务消息生产者

使用 TransactionMQProducer类创建生产者,并指定唯一的 ProducerGroup,就可以设置自定义线程池来处理这些检查请求。执行本地事务后、需要根据执行结果对消息队列进行回复。回传的事务状态在请参考前一节。

package com.example.demo.rocketmq.message.transation;

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.ArrayList;
import java.util.List;

/**
 * 功能描述: 事务消息
 *
 * @author luxiaomeng
 * @date 2020/9/22   10:46
 * 修改日志: 暂无
 */
public class TransationProducer {
    public static void main(String[] args) throws Exception {
        TransactionMQProducer producer = new TransactionMQProducer("producer");
        producer.setNamesrvAddr("192.168.141.110:9876");

        producer.setTransactionListener(new TransactionListener() {
//            /*执行本地事务*/
            @Override
            public LocalTransactionState executeLocalTransaction(Message message, Object o) {
                return LocalTransactionState.UNKNOW;
            }

            /*该方法:MQ进行消息事务状态回查*/
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
                System.out.println("test");
                return LocalTransactionState.ROLLBACK_MESSAGE;
            }
        });
        producer.start();
        Message message = new Message("Transaction", ("事务消息1").getBytes());

        // 发送消息
        TransactionSendResult transactionSendResult = producer.sendMessageInTransaction(message, null);
        System.out.println("发送结果:"+transactionSendResult.getSendStatus());
        producer.shutdown();
    }
}

使用限制
  1. 事务消息不支持延时消息和批量消息。
  2. 为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为 15 次,但是用户可以通过 Broker 配置文件的 transactionCheckMax参数来修改此限制。如果已经检查某条消息超过 N 次的话( N = transactionCheckMax ) 则 Broker 将丢弃此消息,并在默认情况下同时打印错误日志。用户可以通过重写 AbstractTransactionCheckListener 类来修改这个行为。
  3. 事务消息将在 Broker 配置文件中的参数 transactionMsgTimeout 这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制,该参数优先于 transactionMsgTimeout 参数。
  4. 事务性消息可能不止一次被检查或消费。
  5. 提交给用户的目标主题消息可能会失败,目前这依日志的记录而定。它的高可用性通过 RocketMQ 本身的高可用性机制来保证,如果希望确保事务消息不丢失、并且事务完整性得到保证,建议使用同步的双重写入机制。
  6. 事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同,事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值