消息中间件:RocketMQ

前言

Apache RocketMQ 是由阿里巴巴开源、现为 Apache 顶级项目的 分布式消息队列 / 消息中间件,用 Java 开发

核心定位:高吞吐、低延迟、高可靠、可水平扩展,面向万亿级消息、大规模分布式系统(如电商、金融、大数据)。

市面上有很多消息中间件,比如:RabbitMQKafkaActiveMQ

  • ActiveMQ:老牌、全能、简单,但高并发弱,现在用得少。
  • RabbitMQ:成熟稳定、协议标准、功能丰富,适合企业通用消息场景。
  • Kafka:极高吞吐、日志型消息、流处理能力强,适合大数据、日志采集。

RabbitMQ / RocketMQ / Kafka / ActiveMQ 核心对比表:

对比维度ActiveMQRabbitMQRocketMQKafka
定位老牌通用消息队列通用型、高可靠消息队列高吞吐、金融级可靠消息队列高吞吐日志 / 流处理平台
开发语言JavaErlangJavaScala/Java
吞吐量低(万级内)中(几千~几万 TPS)高(几万~十几万 TPS)极高(单机几十万 TPS)
延迟较高极低极低
可靠性一般高(持久化 + 镜像队列)极高(金融级,同步刷盘)中高(可配置,极端场景可能丢消息)
事务消息弱支持不支持原生支持(分布式事务首选)不支持
延迟 / 定时消息支持但性能差需插件 / 死信实现原生支持不原生支持,需自行实现
路由能力一般极强(多种交换机 + 灵活路由)中等弱(仅按 Topic + 分区)
功能丰富度丰富最丰富(死信、TTL、优先级等)够用,偏核心场景极简,专注收发与流
集群难度简单中等简单较高(依赖 ZK/KRaft)
适用场景老旧项目、简单小系统企业微服务、通用业务解耦电商 / 订单 / 支付 / 高并发日志采集、用户行为、流计算

安装

我们需要下载两个文件:RocketMQ文件和RocketMQ Dashboard(可视化管理控制台)文件

  • RocketMQ文件

进入官网,选择你要下载的版本(选择Binary二进制包下载)

在这里插入图片描述

如果觉得官网下载速度太慢,推荐使用阿里云镜像下载

  • RocketMQ Dashboard(可视化管理控制台)文件

进入官网,选择你要下载的版本(选择Source源码包下载)

在这里插入图片描述

或者从GitHub上拉取源码。

在这里插入图片描述

如果觉得官网下载速度太慢,推荐使用阿里云镜像下载

Windows 安装

先安装JDK后再进行下面安装操作(不再讲解JDK安装)

在这里插入图片描述

  • RocketMQ 安装

将下载完的rocketmq-all-x.x.x-bin-release.zip,进行解压。

(1)先配置RocketMQ环境变量:

变量名:ROCKETMQ_HOME
变量值:RocketMQ安装路径(如:D:\rocketmq-all-5.4.0-bin-release)

在这里插入图片描述

Path中添加:

%ROCKETMQ_HOME%\bin

在这里插入图片描述
如果不设置环境变量执行时,会报找不到错误

在这里插入图片描述

(2)打开命令提示符(CMD),进入解压后RocketMQbin目录:

CD D:\rocketmq-all-5.4.0-bin-release\bin

(3)启动Name Server,执行命令:

start mqnamesrv.cmd

启动成功,会打开新窗口,如图所示(不要关闭窗口):
在这里插入图片描述
(4)启动Broker,执行命令:

start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true

autoCreateTopicEnable当生产者发送消息到一个还不存在的 Topic 时,Broker 会自动帮你创建这个 Topic

启动成功,会打开新窗口,如图所示(不要关闭窗口):

在这里插入图片描述
至此安装完毕。

启动后,会在C:\Users\用户名目录下创建持久化数据。

  • RocketMQ 可视化管理安装

将下载完的rocketmq-dashboard-x.x.x-source-release.zip,进行解压。

(1)使用Maven构建

mvn clean package -Dmaven.test.skip=true

或者用Idea打开,进行打包(过程很慢有个10多分钟)。

打包过程中你可能会遇到,卡死的情况,如图所示:

在这里插入图片描述
如果你点击终止执行后,再次打包,会遇到另外一个错误:

zip END header not found

这就是因为下载中断导致文件损坏。

我们复制控制台打印的下载地址,手动给他下载

https://nodejs.org/dist/v18.2.0/node-v18.2.0-win-x64.zip

下载完成后,找到Maven仓库,找到目录,删除zip文件

com\github\eirslett\node\18.2.0\

然后把下载好的zip文件,放入目录下(修改文件名去掉v),重新执行打包即可。

(2)打包完成后,进入项目target目录

CD D:\rocketmq-dashboard-rocketmq-dashboard-2.1.0\target\

(3)执行启动命令(你也可以直接再Idea启动)

java -jar rocketmq-dashboard-2.1.0.jar

启动成功,如图所示:

在这里插入图片描述

访问http://127.0.0.1:8082,即可进入可视化页面,如图所示:

在这里插入图片描述

  • 开启登录验证

如果你有需要的话可以开启登录验证。

(1)开启ACL认证

编辑 conf/broker.conf 文件,添加以下配置:

# 启用认证
authenticationEnabled = true
authenticationMetadataProvider = org.apache.rocketmq.auth.authentication.provider.LocalAuthenticationMetadataProvider

# 启用授权
authorizationEnabled = true
authorizationMetadataProvider = org.apache.rocketmq.auth.authorization.provider.LocalAuthorizationMetadataProvider

# 初始化可视化管理员用户(首次启动自动创建,acl的账号密码)
initAuthenticationUser = {"username":"rocketmq","password":"12345678"}

# 组件间认证凭证(用于Broker主从同步、集群内部通信等)
innerClientAuthenticationCredentials = {"accessKey":"rocketmq","secretKey":"12345678"}

(2)先启动Name Server,执行命令:

start mqnamesrv.cmd

(3)再启动Broker(修改broker.conf文件后要引用它),执行命令:

start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true -c ../conf/broker.conf

(4)配置mqadmin工具

启用ACL后,需要配置mqadmin工具的认证凭证才能执行管理命令。

编辑 conf/tools.yml 文件(保持和conf/broker.conf 文件相同),添加以下配置:

# 使用组件间认证凭证保持一致
accessKey: rocketmq
secretKey: 12345678

(5)确认用户创建成功,执行命令:

mqadmin.cmd listUser -n 127.0.0.1:9876 -c DefaultCluster

执行结果,如图所示:

在这里插入图片描述

千万不要创建完成后,以为修改密码直接修改broker.conf文件和conf/tools.yml 文件,重启服务就行,这个时候默认的用户密码已经生成了。别看启动没啥问题,执行命令就会报错,如图所示:

在这里插入图片描述
第一次弄的话,弄了很久,命令也执行不了,也不知道修改成功没有。这个时候,你只需要把配置文件改回之前的配置,然后重启服务就行。

(6)修改ACL账号密码,执行命令:

如果觉得密码太简单,可以修改(除非你需要单引号''做为密码一部分,否则不要加单引号'')。

mqadmin.cmd updateUser -n 127.0.0.1:9876 -c DefaultCluster -u rocketmq -p 1234qwer

执行结果,如图所示:

在这里插入图片描述

修改密码后,编辑 conf/tools.yml 文件,将密码保持一致即可。

# 使用初始化的管理员用户凭证
accessKey: rocketmq
secretKey: 1234qwer

(7)确认用户创建成功,执行命令:

mqadmin.cmd listUser -n 127.0.0.1:9876 -c DefaultCluster

(8)打开rocketmq-dashboard项目,修改配置内容。

编辑src/main/resources/application.yml配置项,修改为你设置的ACL账号密码:

loginRequired: true # 开启登录验证
# 打开配置项,设置ACL
accessKey: rocketmq
secretKey: 1234qwer

(9)然后启动rocketmq-dashboard(你也可以直接再Idea启动)

java -jar rocketmq-dashboard-2.1.0.jar

再次访问http://localhost:8082,就会跳转登录页面

在这里插入图片描述

Linux 安装

先安装JDK后再进行下面安装操作(不再讲解JDK安装)

在这里插入图片描述

  • RocketMQ 安装

将下载完的rocketmq-all-x.x.x-bin-release.zip,上传到服务器,进行解压。

unzip rocketmq-all-5.4.0-bin-release.zip

解压完毕后,需要做两件事

(1)修改日志路径(非必须)

RocketMQ 5.x 全版本默认日志路径,也就是系统用户主目录~/logs/

为了方便查看,我们可以把*.logback.xml中配置信息它修改为当前目录:

sed -i 's/${user.home}/${user.dir}/g' rmq.tools.logback.xml

确认是否修改成功:

grep -n "user.home" conf/*.xml

如果没有内容输出就说明全部都修改完毕。

(2)修改启动内存(非必须)

主要修改bin/runserver.shbin/runbroker.sh,默认是4g,看每台机子的配置,如果设置的大太有可能跑不起来

vi runserver.sh

在这里插入图片描述
你也可以单独复制出来统一修改,修改后保存退出。

vi runbroker.sh

在这里插入图片描述
修改后保存退出。

如果你需要执行mqadmin指令,默认1g,你可以选择修改内存大小(非必须)

vi tools.sh

在这里插入图片描述

修改后保存退出。

(3)先启动Name Server,执行命令:

nohup sh bin/mqnamesrv &

因为我们修改了日志存入地址,可以查看bin/logs/rocketmqlogs/namesrv.log文件是否启动成功

tail -f bin/logs/rocketmqlogs/namesrv.log

启动结果,如图所示:

在这里插入图片描述

我们可以在namesrv.log 中看到 ‘The Name Server boot success…’, 表示NameServer 已成功启动。

(4)再启动Broker(修改broker.conf文件后要引用它),执行命令:

nohup sh bin/mqbroker -n localhost:9876 &

可以查看bin/logs/broker.log文件是否启动成功

tail -f bin/logs/rocketmqlogs/broker.log

启动结果,如图所示:
在这里插入图片描述

我们可以在 broker.log 中看到“The broker[brokerName,ip:port] boot success…”,这表明 broker 已成功启动。

启动后,会在/root/store目录下创建持久化数据。

  • RocketMQ 可视化管理安装

将下载完的rocketmq-dashboard-x.x.x-source-release.zip,上传到服务器,进行解压。

unzip rocketmq-dashboard-2.1.0-source-release.zip

如果服务器有Maven

mvn clean package -Dmaven.test.skip=true

或者用Idea打开,进行打包(过程很慢有个10多分钟)。

打包过程中你可能会遇到,卡死的情况,如图所示:

在这里插入图片描述
如果你点击终止执行后,再次打包,会遇到另外一个错误:

zip END header not found

这就是因为下载中断导致文件损坏。

我们复制控制台打印的下载地址,手动给他下载

https://nodejs.org/dist/v18.2.0/node-v18.2.0-win-x64.zip

下载完成后,找到Maven仓库,找到目录,删除zip文件

com\github\eirslett\node\18.2.0\

然后把下载好的zip文件,放入目录下(修改文件名去掉v),重新执行打包即可。

打包完毕后,将jar包上传服务器,并运行:

java -jar rocketmq-dashboard-2.1.0.jar

执行结果如图:

在这里插入图片描述
访问http://ip:8082,即可进入可视化页面,如图所示:

在这里插入图片描述

  • 开启登录验证

如果你有需要的话可以开启登录验证。

(1)开启ACL认证

编辑 conf/broker.conf 文件,添加以下配置:

# 启用认证
authenticationEnabled = true
authenticationMetadataProvider = org.apache.rocketmq.auth.authentication.provider.LocalAuthenticationMetadataProvider

# 启用授权
authorizationEnabled = true
authorizationMetadataProvider = org.apache.rocketmq.auth.authorization.provider.LocalAuthorizationMetadataProvider

# 初始化可视化管理员用户(首次启动自动创建,acl的账号密码)
initAuthenticationUser = {"username":"rocketmq","password":"12345678"}

# 组件间认证凭证(用于Broker主从同步、集群内部通信等)
innerClientAuthenticationCredentials = {"accessKey":"rocketmq","secretKey":"12345678"}

(2)先启动Name Server,执行命令:

nohup sh bin/mqnamesrv &

(3)再启动Broker(修改broker.conf文件后要引用它),执行命令:

nohup sh bin/mqbroker -n localhost:9876  -c ../conf/broker.conf &

如果服务再正常运行中,一般优雅关闭顺序是Broker->Name Server,然后启动顺序Name Server->Broker,如果只修改Broker就只重启Broker即可,命令如下:

sh bin/mqshutdown broker
sh bin/mqshutdown namesrv
# 查 Broker
ps -ef | grep BrokerStartup
# 查 NameServer
ps -ef | grep NamesrvStartup

(4)配置mqadmin工具

启用ACL后,需要配置mqadmin工具的认证凭证才能执行管理命令。

编辑 conf/tools.yml 文件(保持和conf/broker.conf 文件相同),添加以下配置:

# 使用组件间认证凭证保持一致
accessKey: rocketmq
secretKey: 12345678

(5)确认用户创建成功,执行命令:

sh bin/mqadmin listUser -n 127.0.0.1:9876 -c DefaultCluster

执行结果,如图所示:

在这里插入图片描述

千万不要创建完成后,以为修改密码直接修改broker.conf文件和conf/tools.yml 文件,重启服务就行,这个时候默认的用户密码已经生成了。别看启动没啥问题,执行命令就会报错,如图所示:

在这里插入图片描述

第一次弄的话,弄了很久,命令也执行不了,也不知道修改成功没有。这个时候,你只需要把配置文件改回之前的配置,然后重启服务就行。

(6)修改ACL账号密码,执行命令:

如果觉得密码太简单,可以修改(除非你需要单引号''做为密码一部分,否则不要加单引号'')。

sh bin/mqadmin updateUser -n 127.0.0.1:9876 -c DefaultCluster -u rocketmq -p 1234qwer

执行结果,如图所示:

在这里插入图片描述

修改密码后,编辑 conf/tools.yml 文件,将密码保持一致即可。

# 使用初始化的管理员用户凭证
accessKey: rocketmq
secretKey: 1234qwer

(7)确认用户创建成功,执行命令:

sh bin/mqadmin listUser -n 127.0.0.1:9876 -c DefaultCluster

(8)打开rocketmq-dashboard项目,修改配置内容。

编辑src/main/resources/application.yml配置项,修改为你设置的ACL账号密码:

loginRequired: true # 开启登录验证
# 打开配置项,设置ACL
accessKey: rocketmq
secretKey: 1234qwer

(9)然后启动rocketmq-dashboard(你也可以直接再Idea启动)

在实际的运行过程中启动项目后发现开启ACL不会跳转登录页面,然后去Github把源码下载下来,或者使用rocketmq-dashboard-2.0.0版本,详细参考

java -jar rocketmq-dashboard-2.1.1.jar

再次访问http://ip:8082,就会跳转登录页面

在这里插入图片描述

如果你是云服务器,你需要开通9876109098082端口,否则启动报错:

在这里插入图片描述
===================== 这里需要挪到对应介绍的地方 ====================

RocketMQ支持authMode: file / acl 两个模式,进行账号登录:

Dashboard 自己维护一套独立账号体系,账号密码存在本地文件 users.properties,和 RocketMQ Broker 服务端的所有用户、ACLAK/SK 完全无关。

(1)authMode: aclBroker 联动模式),复用 RocketMQ 服务端用户

Dashboard 不自己建账号,网页登录的账号密码,直接复用 RocketMQ Broker 服务端 ACL 里的用户(username/password)。

authMode: acl

启用了acl,控制台必须配置aksk,账号密码等于你 mqadmin listUser管理的 RocketMQ 业务用户。项目重启后,就可以直接使用ACL的账号密码进行登录。

(2)authMode: file(文件模式),Dashboard 自带独立账号

# 本地用户文件路径
dataPath: ./config
authMode: file

jar 同级目录 新建文件夹 config

你的文件夹/
├─ config/
│  └─ users.properties   <-- 新建这个
└─ rocketmq-dashboard-2.1.0.jar

users.properties 配置:

# 格式:用户名=密码,权限(1管理员/0普通用户)
admin=123456,1

确保${rocketmq.config.dataPath}定义的目录存在,并且该目录下创建登录配置文件users.properties, 如果该目录下不存在此文件,则默认使用resources/users.properties文件。

管理页面

RocketMQ DashboardRocketMQ 的管控利器,为用户提供客户端和应用程序的各种事件、性能的统计信息,支持以可视化工具代替 Topic 配置、Broker 管理等命令行操作。

面板功能
运维修改nameserver 地址; 选用 VIPChannel
驾驶舱查看 broker, topic 消息量
集群集群分布,broker 配置、运行信息
主题搜索、筛选、删除、更新/新增主题,消息路由,发送消息,重置消费位点
消费者搜索、删除、新增/更新消费者组,终端,消费详情,配置
消息消息记录,死信消息,消息轨迹等消息详情

登录进来后,首先进入“驾驶舱”面板,导航栏有很多功能(运维、代理、驾驶舱、集群、主题、消费者、生产者、消息、死信消息、消息轨迹、ACL 管理),最右边可以更换主题切换语言(中英文),如图所示:

在这里插入图片描述
“驾驶舱”面板,可以查看broker的消息量(总量/5分钟图);也可以查看单一topic的消息量(总量/趋势图)

  • 运维

运维页面(OPS)是用来集群新增 / 换 NameServer 时,不用改 yml,核心就三件事:改 NameServer 地址、开 / 关 VIPChannel开 / 关 TLS,不用重启服务。

在这里插入图片描述
(1)NameServerAddressList:左边下拉列表选择后,点击UPDATE按钮切换生效。右边可以新增NamesrvAddr,集群地址,多个用分号分隔(如 127.0.0.1:9876;10.0.4.16:9876),点击ADD按钮生效。

(2)IsUseVIPChannel:你可以修改这个服务是否使用VIPChannel(如果你的mq server版本小于3.5.8,请设置不使用)

RocketMQ 有 2 个端口:10911 = 默认普通端口;10909 = VIP 专用端口(更快、优先级更高,老版本 MQ 不支持)

RocketMQ 5.x 已经不推荐用 VIP 通道,开了反而容易连接失败,false最稳、最通用、永不报错。

(3)useTLS :不开启明文传输;开启全程加密传输,防窃听、防中间人劫持、防抓包。

三件事必须同时做,TLS 才算真正生效:

  1. 自己生成 JKS 证书文件
  2. 修改 broker.conf配置证书路径 + 密码,开启 tlsEnable=true
# 开启TLS
tlsEnable=true
# TLS证书路径、密钥密码(需要自己准备jks证书)
tlsKeyStorePath=/usr/local/rocketmq/cert/broker.jks
tlsKeyStorePassword=123456
tlsTrustStorePath=/usr/local/rocketmq/cert/trust.jks
tlsTrustStorePassword=123456
  1. Dashboard 运维页设置 useTLS=true

页面配置,临时生效,优先级高于 yml,重启 Dashboard 会失效;yml 配置,永久生效,重启不丢:

# rocketmq-dashboard-master\application.yml
rocketmq:
  config:
    namesrvAddrs:
      - 127.0.0.1:9876
    # 如果您使用的 RocketMQ 版本低于 3.5.8,rocketmq.config.isVIPChannel 应该为 false,默认值为 true。
    isVIPChannel:
    useTLS: false
  • 代理

Proxy 页面(5.0 新增):支持添加、查询 Proxy 节点。

在这里插入图片描述

以前:客户端 → 直接连 Broker(既存数据,又处理连接 / 权限 / 协议)。

现在(5.x):客户端 → ProxyBroker

Proxy:无状态代理,负责协议适配、鉴权、限流、连接管理(计算层)
Broker:只负责消息存储(存储层)

application.yml中可对proxyAddrproxyAddrs属性进行预配置:

rocketmq:
  config:
    proxyAddr: 127.0.0.1:8080
    proxyAddrs:
      - 127.0.0.1:8080

RocketMQ 5.x 默认 Local 模式:Broker 启动时,自动内嵌拉起了 Proxy

多台 Broker,每台都自带一个 Proxy,每台都有自己独立的 IP:8080,想在 Dashboard 全部监控到,就要逐个把每台的 外网 / 内网 IP:8080 都加到 Proxy 列表里。

  • 集群

集群页面(Cluster)专门用来查看和管理整个集群所有 Broker 节点。

在这里插入图片描述

字段含义
Cluster集群名称(一个 NameServer 下的集群名)
BrokerBroker 节点名(同组主从同名)
NO.角色:0=Master1=Slave
AddressBroker 实际 IP: 端口(默认 10911
VersionRocketMQ 版本
Produce Message TPSTPS 每秒发送消息数
Consumer Message TPSTPS 每秒消费消息数
Yesterday Produce Count昨日生产总数
Yesterday Consume Count昨日消费总数
Today Produce Count今天生产总数
Today Consume Count今天消费总数
diffTotal消息堆积量(消费落后条数)

Operation列,可以点击StatusConfig按钮:

点击Status按钮内容如下(只列举部分,需要的时候问AI):

字段含义
brokerNameBroker 名称
brokerIdBroker 序号
addressBroker地址
msgPutTotalTodayNow今天发送消息总数
brokerActive是否在线运行(true=正常,false=挂了)
msgGetTotalTodayNow今天消费消息总数
bootTimestamp启动时间戳
msgPutTotalYesterdayMorning昨天早上发送量
msgGetTotalYesterdayMorning昨天早上消费量
runtimeBroker已运行时间
getTotalTps总消息读取TPS
ackThreadPoolQueueSizeACK确认队列积压(0=正常
EndTransactionThreadPoolQueueCapacity事务线程池最大容量
ackThreadPoolQueueHeadWaitTimeMillsACK等待时间(0=正常
putMessageFailedTimes消息发送失败次数(0=完美,无失败
msgPutTotalTodayMorning今日早上发送消息总数
putMessageTimesTotal历史总共发送过消息数
msgGetTotalTodayMorning今日早上拉取消息总数
brokerVersionDescBroker版本号
litePullThreadPoolQueueCapacity轻量拉取线程池最大容量
detail详细信息
brokerConfigBroker配置信息

点击Config按钮内容如下(只列举部分,需要的时候问AI):

字段含义
brokerClusterName集群名称(固定)
brokerNameBroker名称(主从一样)
brokerId0=主节点非0=从节点
listenPortBroker端口(必须记住)
namesrvAddrNameServer地址
brokerIP1公网IP
brokerIP2内网IP
useTLS是否加密
slaveReadEnable是否允许从节点读(默认关)
storePathRootDir消息存储根目录(最重要)
deleteWhen凌晨4点删除过期文件
fileReservedTime消息保留小时数(默认48小时)
diskMaxUsedSpaceRatio磁盘占用75%开始清理
diskSpaceWarningLevelRatio磁盘90%报警
autoCreateTopicEnable自动创建Topic(开发开,生产关)
autoCreateSubscriptionGroup自动创建消费组
flushDiskType刷盘方式(ASYNC=异步快SYNC=安全
flushIntervalCommitLog刷盘间隔(500ms)
mappedFileSizeCommitLog单个消息文件大小(1G)
authorizationEnabled开启权限控制
authenticationEnabled开启认证
initAuthenticationUser默认账号
innerClientAuthenticationCredentials内部认证密钥
messageDelayLevel延迟消息等级(18个级别,固定不用改)
sendMessageThreadPoolNums发送消息线程数
pullMessageThreadPoolNums拉取消息线程数
maxMessageSize最大消息大小(4MB)
brokerRole异步主节点(最常用)
  • 主题

Topic 就是消息的「分类 / 仓库名字」,专门管理所有消息主题的控制台。

在这里插入图片描述
页面展示所有的主题,可以通过搜索框查看当前有哪些 Topic系统自动创建的、代码新建的全部能看到。右边展示的 Topic 类型,可以查询指定类型数据:

类型作用日常使用
NORMAL普通消息不顺序、不延时、不事务
Delay延时 / 定时消息延迟任务。比如:下单 15 分钟未支付自动取消订单。
FIFO顺序消息必须按先后顺序。比如:创建订单 → 支付 → 发货,必须按顺序执行。
TRANSACTION事务消息分布式事务一致性
UNSPECIFIED未指定不用管
RETRY消费失败重试队列消费者消费失败后,RocketMQ 自动把消息发到重试队列
DLQ死信队列消息重试多次还是消费失败,就会被扔进 DLQ 死信队列。人工排查错误、修正代码后手动处理
SYSTEMMQ 系统内部主题不用管

右边Add/Update按钮,可以添加/更新主题(如果topicName存在就会更新),REFRESH按钮刷新,一键重新拉取最新数据。

点击Add/Update按钮:

在这里插入图片描述
(1)clusterName:集群名称,创建在哪几个cluster上,比如:你服务器默认就是:DefaultCluster

(2)brokerNameBroker 名称,创建在哪几个broker节点上,比如:单机默认 Broker 名称:broker-a

(3)topicName:主题名,命名规范:字母、数字、下划线、横杠,不能中文、不能空格。

(4)writeQueueNums:写队列数量,生产者发消息,实际写到这些队列里,常规生产:8~16 都行。

(5)readQueueNums:读队列数量,消费者从这些队列拉消息消费,常规用法:writeQueueNums = readQueueNums

(6)permTopic 读写权限控制(权限模式)。6(可读可写,日常开发、生产默认)、4(只读,只能消费,禁止发新消息)、2(只写,只能发消息,禁止消费)、0(禁止读写,临时关停这个 Topic 收发)。

列表中,显示了Topic名称和Operation的一些操作:

(1)Status按钮:查看该主题实时运行状态详情,能看到队列分配、读写偏移量、消息数量、读写权限、刷盘方式、读写权限、读写队列分布等核心运行数据,用来排查消息发不出、消费不到、堆积异常。

在这里插入图片描述

(2)Router按钮:查看这个 Topic 的「路由元信息」,这个 Topic 分布在哪些 BrokerIP 端口、队列数量、读写权限,客户端(生产者 / 消费者)就是靠这些信息来知道连哪里、往哪发、从哪消费。

在这里插入图片描述
(3)Consumer Manage 按钮:消费者管理,专门管理消费组、消费者实例、消费进度、堆积、订阅关系的页面,日常排查消费问题最常用。

在这里插入图片描述
brokerOffset:这个队列里一共存了多少条消息。

consumerOffset:消费者已经读完多少条消息

diffTotal:消息堆积数量,计算公式:

diffTotal = brokerOffset - consumerOffset

diffTotal = 0正常,消息全部消费完,无堆积;diffTotal 越来越大,消费速度跟不上发送速度,消息堵了。

(4)Topic Config按钮:修改Topic,读、写队列数,读写权限控制。参考Add/Update按钮。

在这里插入图片描述
(5)Send Message按钮:往指定 Topic 发测试消息。

在这里插入图片描述

(6)Reset Consumer Offset按钮:重置消费者偏移量。手动修改消费者的消费进度位置,跳过堆积消息或者重新消费历史消息。

在这里插入图片描述
(7)Skip Message Accumulate按钮:一键跳过所有堆积消息,重置到最新位点,把当前消费组的 consumerOffset 直接跳到 brokerOffset,所有没消费的旧消息(diffTotal 堆积)全部跳过、不再消费(diffTotal 立即变 0)。

在这里插入图片描述
(8)Delete按钮:手动删除 Topic / 消费组,删掉这个消息分类,队列、存量消息全部清除。

在这里插入图片描述

  • 消费者

Consumer 消费者页面专门管控消费组、消费实例、消费进度、消息堆积的管理页面,对接所有接收消息的客户端。

在这里插入图片描述

页面展示所有的消费组,可以通过搜索框进行过滤,还可以选择Topic 的消息类型进行过滤:

类型作用日常使用
NORMAL普通消息消息之间无序,多队列、多线程并发消费
FIFO顺序消息严格先入先出(FIFO),保证消息按发送顺序被消费
SYSTEM系统消息RocketMQ 内部系统用的 Topic 类型

点击REFRESH按钮刷新页面;最右边Enable Proxy可以开启代理,使用代理进行查询。

点击Add/Update按钮,添加/更新消费组

在这里插入图片描述
已存在的Group Name会进行,提交后进行更新操作。

Operation列也有一些操作按钮:

(1)CLIENT按钮:查看当前消费组下所有在线客户端实例详情,核对客户端连接、订阅配置、运行参数。

在这里插入图片描述
(2)Consumer Details按钮:查看消费组下所有订阅主题、队列消费进度、堆积、消费客户端、最后消费时间,精准定位消费异常。

在这里插入图片描述
(3)Config按钮:查看 / 修改消费组全局配置参数,管控消费规则、超时、重试、消费模式等核心属性

在这里插入图片描述
(4)REFRESH按钮:刷新对应消费组。

(5)Delete按钮:在指定的broker上删除消费组。

在这里插入图片描述

  • 生产者

Producer页面,用来查看当前集群里所有在线生产者(发送端) 的实时连接与运行状态。

在这里插入图片描述
通过TopicGroup查询在线的消息生产者客户端。

  • 消息

Message页面,追溯消息内容、收发轨迹、异常原因。

在这里插入图片描述
默认通过Topic和时间区间查询(由于数据量大 最多只会展示2000条,多的会被忽略)。

点击Message Key标签,通过TopicKey进行查询(最多只会展示64条)。

在这里插入图片描述

点击Message ID标签,通过TopicMessage ID进行消息的详情查询

在这里插入图片描述
Operation列也有一些操作按钮:

(1)Message Detail按钮:单条消息查看完整元数据、内容、消费记录、操作入口。

在这里插入图片描述

  • 死信队列

DLQMessage页面,存放反复消费失败、达到最大重试次数的消息队列

在这里插入图片描述

RocketMQ-Dashboard2.0.0以上的版本貌似不太稳定,查不出数据,切换成2.0.0版本后就没问题(下面就以2.0.0版本介绍下该页面功能,功能上大致不差)

默认进入Consumer标签页,通过Consumer(消费组)选项和时间范围进行查询、批量重新发送消息、批量导出消息。

在这里插入图片描述

点击Message ID标签,通过Consumer(消费组)选项和MessageId进行查询。

在这里插入图片描述
Operation列也有一些操作按钮:

(1)点击MESSAGE DETAIL按钮:死信消息详情页,查看单条死信完整元数据、内容、重试记录,用来定位消费失败原因、核对原始数据。

在这里插入图片描述
(2)点击RESEND MESSAGE按钮:重新发送消息,进行消费

(3)点击EXPORT按钮:将当前死信消息完整信息(消息体、属性、标签、时间等)导出为文件保存。

  • 消息轨迹

MessageTrace页面,专门用来查一条消息 “从生产→存储→消费→重试→死信” 完整流转记录的地方,是定位消息异常的核心排查工具。

在这里插入图片描述

默认进入Message Key标签页,通过Topic选项和时间范围进行查询。

点击Message ID标签,通过Topic选项和MessageId进行查询。

在这里插入图片描述
Operation列也有一些操作按钮:

(1)点击MESSAGE TRACE DETAIL按钮:查看这条消息的完整生命周期详情。

在这里插入图片描述

  • ACL 管理

ACL Management页面,用来给客户端做身份认证,Topic/Group 级的读写授权,防止乱发、乱消费消息。

5.x Broker 不再支持 4.x 时代的 ACL 动态配置接口,所以Dashboard 2.0.0会报错,切换到2.1.0+更好,一定要主要两个版本直接差异

在这里插入图片描述

默认进入ACL Users标签页,通过cluster选项和broker选项进行凭证查询。

点击Add User按钮,添加 ACL 用户

在这里插入图片描述
点击Modify按钮,修改用户密码、类型、状态,点击Delete按钮删除用户。

点击ACL Permissions标签页,通过cluster选项和broker选项进行权限规则查询。
在这里插入图片描述
点击Modify按钮,修改操作类型、来源IP、决策,点击Delete按钮删除权限规则。

为什么选择RocketMQ?

发布-订阅(Pub/Sub)是一种消息范式,消息的发送者(称为发布者、生产者、Producer)会将消息直接发送给特定的接收者(称为订阅者、消费者、Consumer)。而RocketMQ的基础消息模型就是一个简单的Pub/Sub模型。
在这里插入图片描述
生产者 (Producer)

负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器。RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送

消费者(Consumer)

负责消费消息,一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式:拉取式消费推动式消费

消息主题(Topic)

标识同一类业务逻辑的消息。主题通过TopicName来做唯一标识和区分,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。

在基于主题的系统中,消息被发布到主题或命名通道上。消费者将收到其订阅主题上的所有消息,生产者负责定义订阅者所订阅的消息类别。这是一个基础的概念模型,而在实际的应用中,结构会更复杂。
例如为了支持高并发和水平扩展,中间的消息主题需要进行分区,同一个Topic会有多个生产者,同一个信息会有多个消费者,消费者之间要进行负载均衡等。

消息队列(MessageQueue)

队列是 RocketMQ 中消息存储和传输的实际容器,也是消息的最小存储单元。 RocketMQ 的所有主题都是由多个队列组成,以此实现队列数量的水平拆分和队列内部的流式存储。队列通过QueueId来做唯一标识和区分。

消息(Message)

消息是 RocketMQ 中的最小数据传输单元。生产者将业务数据的负载和拓展属性包装成消息发送到服务端,服务端按照相关语义将消息投递到消费端进行消费。

消息标签(MessageTag)

消息标签是RocketMQ 提供的细粒度消息分类属性,可以在主题层级之下做消息类型的细分。消费者通过订阅特定的标签来实现细粒度过滤。

消息位点(MessageQueueOffset)

消息是按到达RocketMQ 服务端的先后顺序存储在指定主题的多个队列中,每条消息在队列中都有一个唯一的Long类型坐标,这个坐标被定义为消息位点。

消费位点(ConsumerOffset)

一条消息被某个消费者消费完成后不会立即从队列中删除,RocketMQ 会基于每个消费者分组记录消费过的最新一条消息的位点,即消费位点。

消息索引(MessageKey)

消息索引是RocketMQ 提供的面向消息的索引属性。通过设置的消息索引可以快速查找到对应的消息内容。

消费结果(ConsumeResult)

RocketMQ 中PushConsumer消费监听器处理消息完成后返回的处理结果,用来标识本次消息是否正确处理。消费结果包含消费成功和消费失败。

订阅关系(Subscription)

订阅关系是RocketMQ 系统中消费者获取消息、处理消息的规则和状态配置。订阅关系由消费者分组动态注册到服务端系统,并在后续的消息传输中按照订阅关系定义的过滤规则进行消息匹配和消费进度维护。

消息过滤

消费者可以通过订阅指定消息标签(Tag)对消息进行过滤,确保最终只接收被过滤后的消息合集。过滤规则的计算和匹配在RocketMQ 的服务端完成。

消息轨迹

在一条消息从生产者发出到消费者接收并处理过程中,由各个相关节点的时间、地点等数据汇聚而成的完整链路信息。通过消息轨迹,您能清晰定位消息从生产者发出,经由RocketMQ 服务端,投递给消费者的完整链路,方便定位排查问题。

消息堆积

生产者已经将消息发送到RocketMQ 的服务端,但由于消费者的消费能力有限,未能在短时间内将所有消息正确消费掉,此时在服务端保存着未被消费的消息,该状态即消息堆积。

在这里插入图片描述
上图就是一个扩展后的消息模型,包括两个生产者,两个消息Topic,以及两组消费者 Consumer

存储消息Topic的 代理服务器( Broker ),是实际部署过程对应的代理服务器。

为了消息写入能力的水平扩展,RocketMQTopic进行了分区,这种操作被称为队列(MessageQueue)。

为了消费能力的水平扩展,ConsumerGroup的概念应运而生。

相同的ConsumerGroup下的消费者主要有两种负载均衡模式,即广播模式和集群模式

集群模式下,同一个 ConsumerGroup 中的 Consumer 实例是负载均衡消费,如图中 ConsumerGroupA 订阅 TopicATopicA 对应 3个队列,则 GroupA 中的 Consumer1 消费的是 MessageQueue 0MessageQueue 1的消息,Consumer2是消费的是MessageQueue2的消息。
广播模式下,同一个 ConsumerGroup 中的每个 Consumer 实例都处理全部的队列。需要注意的是,广播模式下因为每个 Consumer 实例都需要处理全部的消息,因此这种模式仅推荐在通知推送、配置同步类小流量场景使用。

在这里插入图片描述

Apache RocketMQ 部署架构上主要分为四部分:

  • 生产者Producer
  • 消费者 Consumer
  • 名字服务器 NameServer

NameServer是一个简单的 Topic 路由注册中心,支持 TopicBroker 的动态注册与发现。

主要包括两个功能:

Broker管理NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;

路由信息管理,每个NameServer将保存关于 Broker 集群的整个路由信息和用于客户端查询的队列信息。ProducerConsumer通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。

NameServer通常会有多个实例部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,客户端仍然可以向其它NameServer获取路由信息。

  • 代理服务器 Broker

Broker主要负责消息的存储、投递和查询以及服务高可用保证。

NameServer几乎无状态节点,因此可集群部署,节点之间无任何信息同步。Broker部署相对复杂。

Master-Slave 架构中,Broker 分为 MasterSlave。一个Master可以对应多个Slave,但是一个Slave只能对应一个MasterMasterSlave 的对应关系通过指定相同的BrokerName,不同的BrokerId 来定义,BrokerId为0表示Master,非0表示SlaveMaster也可以部署多个。

每个 BrokerNameServer 集群中的所有节点建立长连接,定时注册 Topic 信息到所有 NameServer

ProducerNameServer 集群中的其中一个节点建立长连接,定期从 NameServer 获取Topic路由信息,并向提供 Topic 服务的 Master 建立长连接,且定时向 Master 发送心跳。Producer 完全无状态。

ConsumerNameServer 集群中的其中一个节点建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 MasterSlave 建立长连接,且定时向 MasterSlave发送心跳。Consumer 既可以从 Master 订阅消息,也可以从Slave订阅消息。

RocketMQ集群工作流程

  1. 启动NameServer

启动NameServerNameServer启动后监听端口,等待BrokerProducerConsumer连接,相当于一个路由控制中心。

  1. 启动 Broker

启动 Broker。与所有 NameServer 保持长连接,定时发送心跳包。心跳包中包含当前 Broker 信息以及存储所有 Topic 信息。注册成功后,NameServer 集群中就有 TopicBroker 的映射关系。

  1. 创建 Topic

创建 Topic 时需要指定该 Topic 要存储在哪些 Broker 上,也可以在发送消息时自动创建Topic

  1. 生产者发送消息

生产者发送消息。启动时先跟 NameServer 集群中的其中一台建立长连接,并从 NameServer 中获取当前发送的 Topic存在于哪些 Broker 上,轮询从队列列表中选择一个队列,然后与队列所在的 Broker建立长连接从而向 Broker发消息。

  1. 消费者接受消息

消费者接受消息。跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,然后开始消费消息。

入门

SpringBoot为例,先引入官方依赖,提供依赖如下:

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.3.6</version>
</dependency>

基础配置(application.yml)

rocketmq:
  # 你的 namesrv 地址
  name-server: 127.0.0.1:9876
  producer:
    group: my-product-group  #  事务生产者组必须唯一
    access-key: admin    # 若启用了 ACL 功能
    secret-key: admin    # 若启用了 ACL 功能
  consumer:
    access-key: rocketmq    # 若启用了 ACL 功能
    secret-key: qwer1234    # 若启用了 ACL 功能

Producer Group(生产者组)是 RocketMQ 的 “强制管理标识”,主要用来:

  1. 保证事务消息不丢失,当生产者宕机、重启、网络断了,RocketMQ 服务器 必须知道这个生产者属于哪个组、回查事务状态、恢复未完成的事务、保证消息不丢、不乱。
  2. 方便管理同一类生产者,出问题能快速定位是哪个业务系统发的消息,标识发送方来源。
生产者

生产者负责发送消息到 BrokerTopic,基本概念包括消息,Tag,Keys,队列和生产者

  • 消息

RocketMQ 消息构成非常简单:

(1)topic,表示要发送的消息的主题。

(2)body 表示消息的存储内容。

(3)properties 表示消息属性。

(4)transactionId 会在事务消息中使用。

Message 可以设置的属性值包括:

字段名默认值必要性说明
Topicnull必填消息所属 topic 的名称
Bodynull必填消息体
Tagsnull选填消息标签,方便服务器过滤使用。目前只支持每个消息设置一个
Keysnull选填代表这条消息的业务关键词
Flag0选填完全由应用来设置,RocketMQ 不做干预
DelayTimeLevel0选填消息延时级别,0 表示不延时,大于 0 会延时特定的时间才会被消费
WaitStoreMsgOKtrue选填表示消息是否在服务器落盘后才返回应答。
  • Tag

TopicTag 都是业务上用来归类的标识,区别在于 Topic 是一级分类,而 Tag 可以理解为是二级分类。使用 Tag 可以实现对 Topic 中的消息进行过滤。

TopicTag 的关系如下图所示。
在这里插入图片描述
(1)Topic:消息主题,通过 Topic 对不同的业务消息进行分类,如不同的消息类型(普通消息、事务消息、定时(延时)消息、顺序消息)使用不同的 Topic,无法通过 Tag 进行区分;没有直接关联的消息,如淘宝交易消息,京东物流消息

(2)Tag:消息标签,用来进一步区分某个 Topic 下的消息分类,消息从生产者发出即带上的属性。

RocketMQ 部署安装包默认开启了 autoCreateTopicEnable 配置,会自动为发送的消息创建 Topic,但该特性仅推荐在初期测试时使用。

# broker.conf
autoCreateTopicEnable=false

生产环境强烈建议管理所有主题的生命周期,关闭自动创建参数(Topic 必须提前手动创建;发不存在 Topic 直接报错:No route info of this topic,消息发送失败),以避免生产集群出现大量无效主题,无法管理和回收,造成集群注册压力增大,影响生产集群的稳定性。

sh bin/mqadmin updateTopic -c DefaultCluster -t TopicTest -n 127.0.0.1:9876

可以看到在执行完命令后,在该台Broker机器上创建了8个队列,名为TopicTestTopic

  • Keys

每个消息可以在业务层面的设置唯一标识码 keys 字段,方便将来定位消息丢失问题。 Broker 端会为每个消息创建索引(哈希索引),应用可以通过 topickey 来查询这条消息内容,以及消息被谁消费。由于是哈希索引,请务必保证 key 尽可能唯一,这样可以避免潜在的哈希冲突

使用过程中避免,系统保留的属性Key

   // 订单Id
   String orderId = "20034568923546";
   message.setKeys(orderId);
  • 队列

为了支持高并发和水平扩展,需要对 Topic 进行分区,在 RocketMQ 中这被称为队列,一个 Topic 可能有多个队列,并且可能分布在不同的 Broker 上。

一般来说一条消息,如果没有重复发送(比如因为服务端没有响应而进行重试),则只会存在在 Topic 的其中一个队列中,消息在队列中按照先进先出的原则存储,每条消息会有自己的位点,每个队列会统计当前消息的总条数,这个称为最大位点 MaxOffset;队列的起始位置对应的位置叫做起始位点 MinOffset。队列可以提升消息发送和消费的并发度。

  • 生产者

生产者(Producer)就是消息的发送者,Apache RocketMQ 拥有丰富的消息类型,可以支持不同的应用场景,在不同的场景中,需要使用不同的消息进行发送。比如在电商交易中超时未支付关闭订单的场景,在订单创建时会发送一条延时消息。这条消息将会在 30 分钟以后投递给消费者,消费者收到此消息后需要判断对应的订单是否已完成支付。如支付未完成,则关闭订单。如已完成支付则忽略,此时就需要用到延迟消息;

普通消息

Apache RocketMQ可用于以三种方式发送消息:同步、异步和单向传输。前两种消息类型是可靠的,因为无论它们是否成功发送都有响应。

  • 同步发送

发送消息后阻塞当前线程,等待 Broker 响应结果(成功 / 失败),拿到回执才继续往下走。

适用场景:必须确认消息发送成功(订单、支付、核心业务)

(1)最简发送(字符串消息)

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        // topic 格式
        SendResult sendResult = rocketMQTemplate.syncSend("dev", "这是一条测试消息");
        System.out.println(sendResult);
        /** Output:
         *  [sendStatus=SEND_OK, msgId=240884560811A3C015E427621E4D6A7D163063947C6B414232C70000
         *  , offsetMsgId=7A33079500002A9F0000000000028E16
         *  , messageQueue=MessageQueue [topic=dev, brokerName=broker-a, queueId=3]
         *  , queueOffset=13, recallHandle=null]
         */
        return "消息发送成功";
    }
}
@Component
@RocketMQMessageListener(
        consumerGroup = "my-consumer-group",
        topic = "dev",
        selectorExpression = "tag1"
)
public class RocketMQConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String s) {
        System.out.println("收到消息:" + s);
    }
}

sendResult:发送状态。SEND_OK(发送成功), FLUSH_DISK_TIMEOUT(刷盘超时), FLUSH_SLAVE_TIMEOUT(同步到备超时), SLAVE_NOT_AVAILABLE(备不可用),如果发送失败会抛出异常。

msgId:全链路追踪核心,根据 ID 在 RocketMQ 控制台 / 日志查询消息轨迹、消费情况、异常原因。

messageQueue + queueId:观察消息负载均衡是否均匀;顺序消息依赖队列保证顺序。

queueOffset:消息在队列的存储位置,Broker 靠 offset 记录消费进度。

消息大小。默认值:不超过4 MB。不涉及消息压缩,仅计算消息体body的大小。建议不超过4 MB。

(2)带 Tag 过滤(业务分流,最常用)

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        // topic:tag 格式
        SendResult sendResult = rocketMQTemplate.syncSend("dev:tag1", "这是一条测试消息");
        return "消息发送成功";
    }
}

(3)自定义消息对象(复杂实体)

public class User implements Serializable {
    private Integer id;
    private String username;
}
@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        // 自定义消息对象
        User user = new User();
        user.setUsername("test");
        SendResult sendResult = rocketMQTemplate.syncSend("dev:tag1", user);
        return "消息发送成功";
    }
}

(4)原生 Message 对象(自定义消息属性、扩展字段)

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        Message<String> message = MessageBuilder
                .withPayload("这是一条测试消息")
                .setHeader("bizCode", "LOGIN") // 自定义消息属性
                .build();
        SendResult sendResult = rocketMQTemplate.syncSend("dev:tag1", message);
        return "消息发送成功";
    }
}

消息自定义属性。长度建议:属性的KeyValue总长度不超过16 KB。

syncSend 是同步阻塞,设置控制单次发送请求的最长阻塞时间(毫秒,默认3000 ms),防止无限等待:

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        try {
            SendResult sendResult = rocketMQTemplate.syncSend("dev:tag1", "这是一条测试消息", 3000);
        } catch (Exception e){
            System.out.println("消息发送超时或失败");
        }
        return "消息发送成功";
    }
}

请求超时时间。默认值:3000毫秒,取值范围建议不要超过30000毫秒,请根据实际应用设置合理的取值,避免线程阻塞时间过长。

网络中断、Broker 宕机、ACL 错误、队列满都会抛异常,建议 try-catch

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        try {
            SendResult sendResult = rocketMQTemplate.syncSend("dev:tag1", "这是一条测试消息");
        } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) {
            // 发送失败:日志告警、重试、数据库记录失败消息
            e.printStackTrace();
        }
        return "消息发送成功";
    }
}
  • 异步发送

发送后不阻塞线程,立即返回;结果通过回调函数异步通知(成功 / 失败)。

适用场景:异步发送一般用于链路耗时较长,对响应时间较为敏感的业务场景。例如,视频上传后通知启动转码服务,转码完成后通知推送转码结果等。

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        rocketMQTemplate.asyncSend("dev:tag1", "这是一条测试消息", new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("异步发送成功:" + sendResult.getMsgId());
            }

            @Override
            public void onException(Throwable e) {
                System.err.println("异步发送失败");
                e.printStackTrace();
            }
        });
        return "消息发送成功";
    }
}
  • 单向模式发送

只发消息,完全不等待 Broker 响应、无回调、无返回值,只管发送不管结果。

适用场景:适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        rocketMQTemplate.sendOneWay("dev:tag1", "这是一条测试消息");
        return "消息发送成功";
    }
}

convertAndSend()Spring封装的方法,相当于单向发送

  • RPC 消息

RocketMQ 同步 RPC 消息,微服务同步通信、远程调用替代 HTTP

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        String resp = rocketMQTemplate.sendAndReceive("reply_dev",MessageBuilder.withPayload("这是一条测试消息").build(), String.class);
        System.out.println(resp);
        /** Output:
         *  消费成功
         */
        return "消息发送成功";
    }
}
@Component
@RocketMQMessageListener(
        consumerGroup = "my-consumer-group3",
        topic = "reply_dev"
)
public class RocketMQConsumer implements RocketMQReplyListener<String, String> {

    @Override
    public String onMessage(String msgs) {
        System.out.println("当前消费内容:" + msgs);
        return "消费成功";
    }
}

rocketmq-spring-boot-starter2.3.6发送会报CODE: 10006 DESC: send request message to <reply_dev> OK, but wait reply message timeout, 3000 ms.错误。

rocketmq-clientrocketmq-acl更换到5.3.0即可:

        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.3.5</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.rocketmq</groupId>
                    <artifactId>rocketmq-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.rocketmq</groupId>
                    <artifactId>rocketmq-acl</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>5.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-acl</artifactId>
            <version>5.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-3-starter</artifactId>
            <version>1.2.25</version>
        </dependency>
    </dependencies>
顺序消息

RocketMQ 的 Topic 默认包含多个消息队列(Queue),消息会被轮询分发到不同队列

  • 单个队列内:消息严格遵循 FIFO(先进先出),天然有序;
  • 多个队列间:消息无法保证全局顺序。

顺序消息是一种对消息发送和消费顺序有严格要求的消息。对于一个指定的Topic,消息严格按照先进先出(FIFO)的原则进行消息发布和消费,即先发布的消息先消费,后发布的消息后消费(就是通过路由规则,把同一业务的消息全部发送到同一个队列,再配合消费端规则,让消息按照发送顺序被消费)。

核心场景:订单链路:创建订单 → 支付订单 → 发货 → 完成,顺序不能乱。


@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        String orderId = "2026061415470000001";
        for (int i = 0; i < 20; i++) {
            rocketMQTemplate.syncSendOrderly("dev:tag1", "这是一条测试消息"+i, orderId);
        }
        return "消息发送成功";
    }
}
@Component
@RocketMQMessageListener(
        consumerGroup = "my-consumer-group",
        topic = "dev",
        selectorExpression = "tag1",
        consumeMode = ConsumeMode.ORDERLY // 必须写!
)
public class RocketMQConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String s) {
        System.out.println("收到消息:" + s);
    }
}

消费组内建议只启动 1 个消费者实例,否则负载均衡会打乱顺序;顺序消息禁止使用广播消费、重试,破坏顺序规则。

定时/延迟消息

定时消息和延时消息本质相同,都是服务端根据消息设置的定时时间在某一固定时刻将消息投递给消费者消费

  • 定时消息:是 RocketMQ 提供的一种高级消息类型,消息被发送至服务端后,在指定时间后才能被消费者消费。通过设置一定的定时时间可以实现分布式场景的延时调度触发效果。
@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        try {
            System.out.println(LocalDateTime.now());
            long deliverTime = System.currentTimeMillis() + 10 * 1000;
            rocketMQTemplate.syncSendDeliverTimeMills("dev:tag1", "这是一条测试消息", deliverTime);
        } catch (Exception e){
            System.out.println("消息发送超时或失败");
        }
        return "消息发送成功";
    }
}
@Component
@RocketMQMessageListener(
        consumerGroup = "my-consumer-group",
        topic = "dev",
        selectorExpression = "tag1"
)
public class RocketMQConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String s) {
        System.out.println(LocalDateTime.now());
        System.out.println("收到消息:" + s);
    }
}

定时时长最大值默认为24小时,不支持自定义修改;定时时间的格式为毫秒级的Unix时间戳,您需要将要设置的时刻转换成时间戳形式必须设置为当前时间之后,若设置到当前时间之前,则定时不生效,服务端会立即投递消息)。

  • 延迟消息:是指消息发送到RocketMQ后,并不期望立马投递这条消息,而是延迟一定时间后才投递到Consumer进行消费。

核心场景:电商订单超时关闭:下单后 30 分钟未支付 → 自动关单、回库存

在这里插入图片描述

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        try {
            Message<String> message = MessageBuilder.withPayload("这是一条测试消息").build();
            System.out.println(LocalDateTime.now());
            SendResult sendResult = rocketMQTemplate.syncSend("dev:tag1", message, 3000, 3);
        } catch (Exception e){
            System.out.println("消息发送超时或失败");
        }
        return "消息发送成功";
    }
}

例子中设置的等级是3,也就是发送者发送后,10s后消费者才能收到消息。

RocketMQ有提供syncSendDelayTimeMills()方法进行延时消息:

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        try {
            System.out.println(LocalDateTime.now());
            // 等待指定秒数
            rocketMQTemplate.syncSendDelayTimeSeconds("dev:tag1", "这是一条测试消息", 5);
            // 等待指定毫秒数
            rocketMQTemplate.syncSendDelayTimeMills("dev:tag1", "这是一条测试消息", 10000);
        } catch (Exception e){
            System.out.println("消息发送超时或失败");
        }
        return "消息发送成功";
    }
}
批量消息

默认情况下,消费者单条拉取、单条处理消息。RocketMQ可以将一些消息聚成一批以后进行发送,可以增加吞吐率,并减少API和网络调用次数。

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        try {
            List<Message> msgList = new ArrayList<>();
            for (int i = 0; i < 40; i++) {
                Message message = new Message("dev", "tag1", ("这是一条测试消息" + i).getBytes(StandardCharsets.UTF_8));
                msgList.add(message);
            }
            rocketMQTemplate.getProducer().send(msgList);
        } catch (Exception e) {
            System.out.println("消息发送超时或失败");
        }
        return "消息发送成功";
    }
}
@Component
@RocketMQMessageListener(
        consumerGroup = "my-consumer-group",
        topic = "dev",
        selectorExpression = "tag1"
)
public class RocketMQConsumer implements RocketMQListener<String>, RocketMQPushConsumerLifecycleListener {

    @Override
    public void onMessage(String msgs) {

        System.out.println("本次 【"+LocalDateTime.now()+"】 内容:" + msgs);
    }

    @Override
    public void prepareStart(DefaultMQPushConsumer consumer) {
        // 每次拉取时间间隔
        consumer.setPullInterval(3000);
        // 单次回调最大10条
        consumer.setConsumeMessageBatchMaxSize(10);
        // 单次网络拉取最大10条
        consumer.setPullBatchSize(10);
    }
}

获取消息最大批次 默认值:32条。建议按照实际业务设置合理的参数值,一次获取消息数量过大容易在消费失败时造成大批量消息重复。

通过设置拉取时间间隔和条数,可以直观的看到批量拉取的数据。

RocketMQ 默认单批消息总大小不能超过 4MB,如果批量条数多、消息体大,会触发报错:

message body size exceeded max limited

调优方式(Broker 配置):

# broker.conf
maxMessageSize=8388608  # 调整为 8MB
事务消息

在一些对数据一致性有强需求的场景,可以用RocketMQ 事务消息来解决,从而保证上下游数据的一致性。

RocketMQ 事务消息分三大阶段:

  1. 第一阶段会发送一个半事务消息发送到了 Broker,这条消息对消费者不可见,消费者拉取不到。需要对该消息的二次确认。

  2. 执行本地业务事务,收到 Broker 半消息响应后,执行业务本地数据库操作(新增订单、扣库存等)。

  3. 第二阶段生产者根据本地事务执行结果向服务端提交二次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:

    • 二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。
    • 二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
  4. 事务状态回查,生产者发送半消息后宕机、网络中断,没给 Broker 发送 commit/rollback,或服务端收到的二次确认结果为Unknown未知状态,查询本地事务执行状态,自动补提交或回滚,保证最终一致。

在这里插入图片描述

// 生产者
@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        String orderId = "20266162012010001";
        Message<String> message = MessageBuilder.withPayload("这是一条测试消息").build();
        rocketMQTemplate.sendMessageInTransaction("dev:tag1", message, orderId);
        return "消息发送成功";
    }
}
// 半事务消息
@Component
@RocketMQTransactionListener
public class OrderTransactionListener implements RocketMQLocalTransactionListener {
    /**
     * 阶段1:收到半消息,执行本地数据库事务
     * @param message MQ半消息
     * @param o 发送时传入的自定义业务参数
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        String orderId = (String) o;
        try {
            // 执行业务本地事务:创建订单、扣库存
            boolean order = createOrder(orderId);
            if (order) {
                // 本地事务成功 → 提交消息
                return RocketMQLocalTransactionState.COMMIT;
            }
            return RocketMQLocalTransactionState.ROLLBACK;
        } catch (Exception e) {
            // 本地事务异常 → 回滚,删除半消息
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
    /**
     * 阶段2:事务状态回查(Broker定时回调兜底)
     * 生产者宕机、网络中断,Broker查本地事务状态
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        String orderId = new String((byte[]) message.getPayload());
        return queryOrderStatus(orderId) ? RocketMQLocalTransactionState.COMMIT : RocketMQLocalTransactionState.ROLLBACK;
    }
    // 模拟本地事务执行
    private boolean createOrder(String orderId) {
        // 实际业务逻辑:操作数据库/缓存等
        return true;
    }

    // 模拟查询本地事务状态
    private boolean queryOrderStatus(String orderId) {
        // 实际查询数据库状态
        return true;
    }
}
// 消费者
@Component
@RocketMQMessageListener(
        consumerGroup = "my-consumer-group",
        topic = "dev",
        selectorExpression = "tag1"
)
public class RocketMQConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String msgs) {

        System.out.println("当前消费内容:" + msgs);
    }
}

需要注意的是,服务端仅仅会按照参数尝试指定次数,超过次数后事务会强制回滚,因此未决事务的回查时效性非常关键,需要按照业务的实际风险来设置

事务异常检查间隔,默认值:60秒。事务异常检查间隔指的是,半事务消息因系统重启或异常情况导致没有提交,生产者客户端会按照该间隔时间进行事务状态回查。 间隔时长不建议设置过短,否则频繁的回查调用会影响系统性能。
半事务消息第一次回查时间,默认值:[事务异常检查间隔] * 最大限制,不超过1小时。
半事务消息最大超时时长 默认值:4小时。 * 取值范围:不支持自定义修改。

消费者

消费者负责从 Broker 拉取 Topic 消息,执行业务逻辑,是消息的接收、处理端。先介绍消费组、消费位点、推等概念。

  • 消费组

一组相同业务功能的消费者实例归为同一个消费组,组名唯一。

消息系统的重要作用之一是削峰填谷,但比如在电商大促的场景中,如果下游的消费者消费能力不足的话,大量的瞬时流量进入会后堆积在服务端。此时,消息的端到端延迟(从发送到被消费的时间)就会增加,对服务端而言,一直消费历史数据也会产生冷读。因此需要增加消费能力来解决这个问题,除了去优化消息消费的时间,最简单的方式就是扩容消费者。

RocketMQ 有两种消费模式,分别是:

(1)集群消费模式:当使用集群消费模式时,RocketMQ 认为任意一条消息只需要被消费组内的任意一个消费者处理即可。

在这里插入图片描述

(2)广播消费模式:当使用广播消费模式时,RocketMQ 会将每条消息推送给消费组所有的消费者,保证消息至少被每个消费者消费一次,因此即使扩缩消费者数量也无法提升或降低消费能力。

在这里插入图片描述

  • 负载均衡

集群模式下,同一个消费组内的消费者会分担收到的全量消息,这里的分配策略是怎样的?如果扩容消费者是否一定能提升消费能力?

RocketMQ 提供了多种集群模式下的分配策略,包括平均分配策略、机房优先分配策略、一致性hash分配策略等,可以通过如下代码进行设置相应负载均衡策略

consumer.setAllocateMessageQueueStrategy(new AllocateMessageQueueAveragely());

默认的分配策略是平均分配,这也是最常见的策略。平均分配策略下消费组内的消费者会按照类似分页的策略均摊消费。
在这里插入图片描述

在平均分配的算法下,可以通过增加消费者的数量来提高消费的并行度。

但也不是一味地增加消费者就能提升消费能力的,Topic的总队列数小于消费者的数量时,消费者将分配不到队列,即使消费者再多也无法提升消费能力。
在这里插入图片描述

  • 消费位点

Broker 给每条消息分配递增编号 offset;消费者消费成功后提交 offset,下次重启从最新 offset 继续拉消息,保证不重复漏消费。
在这里插入图片描述
RocketMQ中每个队列都会记录自己的最小位点、最大位点。一般情况下消费位点正常更新,不会出现消息重复,但如果消费者发生崩溃或有新的消费者加入群组,就会触发重平衡,重平衡完成后,每个消费者可能会分配到新的队列,而不是之前处理的队列。为了能继续之前的工作,消费者需要读取每个队列最后一次的提交的消费位点,然后从消费位点处继续拉取消息。但在实际执行过程中,由于客户端提交给服务端的消费位点并不是实时的,所以重平衡就可能会导致消息少量重复。

Push 消费和 Pull 消费

RocketMQ的消费模式可以大致分为两种,一种是推Push,一种是拉Pull

  • Push 消费

消费者客户端主动向 Broker 发起长轮询请求Broker 有新消息时立刻推送给消费者;无消息时客户端会阻塞等待(长轮询),业务层感知是 “消息主动推送过来”,因此叫 Push 消费。

@RocketMQMessageListener 底层封装的就是 Push 模式,开箱即用。

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        rocketMQTemplate.syncSend("dev:tag1", "这是一条测试消息");
        return "消息发送成功";
    }
}
@Service
@RocketMQMessageListener(
        topic = "my-consumer-group",
        consumerGroup = "dev",
        selectorExpression = "tag1",
        consumeMode = ConsumeMode.CONCURRENTLY, // 并发模式(默认)
        consumeThreadNumber = 10 // 消费线程数(默认20)
)
public class RocketMQConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String msgs) {
        System.out.println("收到消息:" + msgs);
    }
}
  • Pull 消费

Pull是客户端需要主动到服务端取数据,优点是客户端可以依据自己的消费能力进行消费,但拉取的频率也需要用户自己控制,拉取频繁容易造成服务端和客户端的压力,拉取间隔长又容易造成消费不及时。灵活性极高,但需要自己处理异常、位点、重试,框架很少用,多用于大数据离线同步。

配置 Bean

@Configuration
public class RocketPullConfig {
    @Resource
    private RocketMQTemplate rocketMQTemplate;
    @Value("${rocketmq.consumer.access-key}")
    private String accessKey;

    @Value("${rocketmq.consumer.secret-key}")
    private String secretKey;

    @Bean(destroyMethod = "shutdown")
    public DefaultMQPullConsumer pullConsumer() throws Exception {
        // 1. 构建ACL鉴权钩子
        SessionCredentials credentials = new SessionCredentials(accessKey, secretKey);
        AclClientRPCHook rpcHook = new AclClientRPCHook(credentials);
        // 消费者组,Pull消费group不能和Push消费混用
        DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("my_consumer_group", rpcHook);
        consumer.setNamesrvAddr(rocketMQTemplate.getProducer().getNamesrvAddr());
        // 启动
        consumer.start();
        return consumer;
    }
}

业务示例:

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @Resource
    private DefaultMQPullConsumer pullConsumer;

    @GetMapping("/send")
    public String send() {
        rocketMQTemplate.syncSend("dev:tag1", "这是一条测试消息");
        return "消息发送成功";
    }

    @GetMapping("/pull")
    public String pull() throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
        // 1. 获取当前Topic所有队列
        Set<org.apache.rocketmq.common.message.MessageQueue> mqSet = pullConsumer.fetchSubscribeMessageQueues("dev");

        for (MessageQueue mq : mqSet) {
            // 2. 获取该队列本地存储的offset(Broker维护位点)
            long offset = pullConsumer.fetchConsumeOffset(mq, false);
            if (offset < 0) {
                // 无消费记录,从队列最小位点开始
                offset = pullConsumer.minOffset(mq);
            }

            while (true) {
                // 3. 同步拉取消息
                PullResult pullResult = pullConsumer.pull(mq, "tag1", offset, 10);
                PullStatus status = pullResult.getPullStatus();

                if (status == PullStatus.FOUND) {
                    // 拿到消息列表
                    for (MessageExt msg : pullResult.getMsgFoundList()) {
                        String content = new String(msg.getBody());
                        System.out.println("Pull拉取消息:" + content);
                        // 业务处理逻辑...
                    }
                    // 4. 更新offset为下一次拉取起点
                    offset = pullResult.getNextBeginOffset();
                    // 5. 提交offset到Broker持久化(关键!不提交下次重复拉)
                    pullConsumer.updateConsumeOffset(mq, offset);
                } else if (status == PullStatus.NO_NEW_MSG) {
                    // 当前队列无新消息,跳出循环处理下一个队列
                    break;
                } else {
                    // 位点异常、缓冲区空等,退出
                    break;
                }
            }
        }
        return "拉取完成";
    }
}

拉取状态枚举 PullStatusFOUND:拉到消息;NO_NEW_MSG:无新消息;OFFSET_ILLEGAL:位点非法;BUFFER_EMPTY:缓冲区空。

Lite Pull ConsumerRocketMQ 4.6.0推出的Pull Consumer,相比于原始的Pull Consumer更加简单易用。

配置 Bean

@Configuration
public class RocketPullConfig {
    @Resource
    private RocketMQTemplate rocketMQTemplate;
    @Value("${rocketmq.consumer.access-key}")
    private String accessKey;

    @Value("${rocketmq.consumer.secret-key}")
    private String secretKey;

    @Bean(destroyMethod = "shutdown")
    public DefaultLitePullConsumer pullConsumer() throws Exception {
        // 1. 构建ACL鉴权钩子
        SessionCredentials credentials = new SessionCredentials(accessKey, secretKey);
        AclClientRPCHook rpcHook = new AclClientRPCHook(credentials);
        DefaultLitePullConsumer consumer = new DefaultLitePullConsumer("my_consumer_group", rpcHook);
        consumer.setNamesrvAddr(rocketMQTemplate.getProducer().getNamesrvAddr());
        consumer.subscribe("dev", "*"); // 订阅 Topic 并过滤 Tag,tagA||tagB 多标签过滤,* 消费全部
        consumer.setAutoCommit(false); // 关闭自动提交,业务手动commit(推荐)
        consumer.setPullBatchSize(10); // 单次拉取最大消息条数
        // 启动消费者,自动完成队列分配rebalance
        consumer.start();
        return consumer;
    }
}

业务示例:

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @Resource
    private LitePullConsumer litePullConsumer;

    @GetMapping("/send")
    public String send() {
        rocketMQTemplate.syncSend("dev:tag1", "这是一条测试消息");
        return "消息发送成功";
    }

    @GetMapping("/pull")
    public String pull() {
        // 长轮询拉取,阻塞等待消息,超时返回空
        List<MessageExt> msgs = litePullConsumer.poll(3000);
        boolean allSuccess = true;
        for (MessageExt msg : msgs) {
            try {
                String content = new String(msg.getBody());
                System.out.println("Pull拉取消息:" + content);
            } catch (Exception e) {
                allSuccess = false;
                // 单条失败,标记整体不提交offset,下次重拉整批
                break;
            }
        }
        if (allSuccess) {
            // 全部处理成功再提交位点
            litePullConsumer.commitSync();
        }
        return "拉取完成";
    }
}
集群模式和广播模式
  • 集群模式 CLUSTERING(默认)

同一消费组内多条实例分摊队列、负载均衡,一条消息只会被组内某一台实例消费一次。

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        rocketMQTemplate.syncSend("order_topic", "这是一条测试消息");
        return "消息发送成功";
    }
}
@Component
@RocketMQMessageListener(
        topic = "order_topic",
        consumerGroup = "order_consumer_group",
        messageModel = MessageModel.CLUSTERING // 默认就是集群,可以省略
)
public class OrderConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String s) {
        System.out.println("订单消费收到消息:" +s);
    }
}

启动当前项目的多台消费者实例,保证同一条消息只处理一次。

  • 广播模式 BROADCASTING

同一消费组内每一台实例都会收到全量消息,每条消息在组内被所有实例各自独立消费一遍。

@Component
@RocketMQMessageListener(
        topic = "order_topic",
        consumerGroup = "order_consumer_group",
        messageModel = MessageModel.BROADCASTING // 手动开启广播模式
)
public class OrderConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String s) {
        System.out.println("订单消费收到消息:" +s);
    }
}

启动当前项目的多台消费者实例,所有实例都消费同一条消息。

跨消费组天然等于广播
不同 ConsumerGroup 互相隔离。
同一个 Topic,两个不同消费组,两组都会完整消费全部消息,这是天然广播,不需要开启广播模式。

消息过滤

消息过滤是指消息生产者向Topic中发送消息时,设置消息属性对消息进行分类,消费者订阅Topic时,根据消息属性设置过滤条件对消息进行过滤,只有符合过滤条件的消息才会被投递到消费端进行消费。

RocketMQ支持的消息过滤方式有两种,Tag过滤和SQL92过滤。

  • Tag过滤(默认)

简单过滤场景,消费者订阅的Tag和发送者设置的消息Tag相互匹配,则消息被投递给消费端进行消费。

@Component
@RocketMQMessageListener(
        topic = "order_topic",
        consumerGroup = "order_consumer_group",
        selectorType = SelectorType.TAG,  // 默认就是TAG,可以省略
        selectorExpression = "TAG_PAY" // 只接受TAG_PAY消息
)
public class OrderConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String s) {
        System.out.println("订单消费收到消息:" +s);
    }
}
@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        rocketMQTemplate.syncSend("order_topic:TAG_PAY", "这是一条测试消息");
        return "消息发送成功";
    }
}

Tag 或关系(||

selectorExpression = "TAG_PAY||TAG_REFUND"

接收全部消息(默认值 *

selectorExpression = "*"
  • SQL92过滤

复杂过滤场景。发送者设置Tag或消息属性,消费者订阅满足SQL92过滤表达式的消息被投递给消费端进行消费。

@Component
@RocketMQMessageListener(
        topic = "order_topic",
        consumerGroup = "order_consumer_group",
        selectorType = SelectorType.SQL92,   // 开启SQL过滤
        selectorExpression = "orderAmount > 50 AND status = 'PAID'" // SQL语法设置过滤表达式
)
public class OrderConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String s) {
        System.out.println("订单消费收到消息:" +s);
    }
}
@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        Message<String> message = MessageBuilder.withPayload("这是一条测试消息")
                .setHeader("orderAmount", 100)
                .setHeader("status", "PAID")
                .setHeader("region", "GZ")
                .build();
        rocketMQTemplate.syncSend("order_topic", message);
        return "消息发送成功";
    }
}

Broker 必须开启配置才能启用 SQL 过滤:

# broker.conf
enablePropertyFilter=true

SQL属性过滤使用SQL92语法作为过滤规则表达式,语法规范如下:

在这里插入图片描述

消息重试和死信队列
  • 消息重试

消费者业务代码抛出异常,RocketMQ 不会直接丢弃这条消息。RocketMQ会在重试间隔时间后,将消息重新投递给Consumer消费,若达到最大重试次数后消息还没有成功被消费,则消息将被投递至死信队列。

消息重试只针对集群消费模式生效;广播消费模式不提供失败重试特性,即消费失败后,失败消息不再重试,继续消费新的消息

@RestController
public class RocketMQProducer {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public String send() {
        rocketMQTemplate.syncSend("order_topic", "这是一条测试消息");
        return "消息发送成功";
    }
}
@Component
@RocketMQMessageListener(
        consumerGroup = "order_consumer_group",
        topic = "order_topic"

)
public class OrderConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String s) {
        System.out.println("订单消费收到消息:" +s);
        // 模拟业务异常,触发自动重试
        int i = 1 / 0;
    }
}

只要抛出异常,框架自动触发重试,默认 16 次不断重试,间隔越来越长,16 次后进入死信。

在这里插入图片描述
并发消费消费失败后会将消费失败的消息重新投递回服务端,再等待服务端重新投递回来,在这期间会正常消费队列后面的消息。

并发消费失败后并不是投递回原Topic,而是投递到一个特殊Topic,其命名为%RETRY%ConsumerGroupName,集群模式下并发消费每一个ConsumerGroup会对应一个特殊Topic,并会订阅该Topic

(1)最大重试次数:可以直接在注解里配置最大重试次数

@Component
@RocketMQMessageListener(
        consumerGroup = "order_consumer_group",
        topic = "order_topic",
        maxReconsumeTimes = 3 // 最大重试次数

)
public class OrderConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String s) {
        System.out.println("订单消费收到消息:" +s);
        // 模拟业务异常,触发自动重试
        int i = 1 / 0;
    }
}

(2)重试间隔:消息消费失败后再次被投递给Consumer消费的间隔时间,只在顺序消费中起作用

@Component
@RocketMQMessageListener(
        consumerGroup = "order_consumer_group",
        topic = "order_topic",
        consumeMode = ConsumeMode.ORDERLY,
        maxReconsumeTimes = 3 ,// 最大重试次数
        suspendCurrentQueueTimeMillis = 5000 // 失败后暂停队列5秒

)
public class OrderConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String s) {
        System.out.println("订单消费收到消息:" +s);
        // 模拟业务异常,触发自动重试
        int i = 1 / 0;
    }
}

顺序消费消费失败后会先在客户端本地重试直到最大重试次数,这样可以避免消费失败的消息被跳过,消费下一条消息而打乱顺序消费的顺序

  • 死信队列

当一条消息初次消费失败,重试达到最大重试次数后,若消费依然失败,该消息不会立刻被丢弃,而是将其发送到该消费者对应的特殊队列中,这类消息称为死信消息Dead-Letter Message),存储死信消息的特殊队列称为死信队列(Dead-Letter Queue),死信队列是死信Topic下分区数唯一的单独队列。

死信消息对应的ConsumerGroup的死信Topic名称为%DLQ%ConsumerGroupName,死信队列的消息将不会再被消费。

@Component
@RocketMQMessageListener(
        consumerGroup = "dlq-order-consumer-group",
        topic = "%DLQ%order_consumer_group"
)
public class RocketMQDLQConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String s) {
        // 在这里处理死信消息
        System.out.println("处理死信消息:" + s);

    }
}
注解详细

@RocketMQMessageListener注解有很多可配置的参数,比较常用的,再上述内容中都有讲解,这里统一介绍。

@Component
@RocketMQMessageListener(
        topic = "order_topic",
        consumerGroup = "order_consumer_group",
        selectorType = SelectorType.TAG,
        selectorExpression = "TAG_PAY||TAG_REFUND",
        consumeMode = ConsumeMode.CONCURRENTLY,
        messageModel = MessageModel.CLUSTERING,
        consumeThreadNumber = 10,
        consumeThreadMax = 30,
        maxReconsumeTimes = 3,
        consumeTimeout = 5
)
public class OrderConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String message) {

    }
}
  • topic:必填,要订阅的消息主题,只允许填写单个 Topic

Topic命名应该尽量使用简短、常用的字符,避免使用特殊字符。特殊字符会导致系统解析出现异常,字符过长可能会导致消息收发被拒绝。建议:1~64个字符。

  • consumerGroup :必填,消费者分组名称,集群内唯一,同一组多实例做负载均衡,不要和生产者 group 混用。自动生成重试主题:%RETRY%groupName、死信主题:%DLQ%groupName

消费组命名规范:项目_功能_consumer_group,唯一不重复,禁止多业务共用。建议:1~64个字符。

  • selectorType:非必填,默认SelectorType.TAG,可选值:TAG:按标签过滤;、SQL92:按消息自定义属性执行 SQL 条件过滤;
  • selectorExpression:非必填,默认*,过滤表达式:TAG 模式:"TAG_A||TAG_B"*代表接收全部 TagSQL92 模式:amount > 100 AND area='GZ'
  • consumeMode:非必填,默认ConsumeMode.CONCURRENTLY并发消费,多线程并行,不保序;ConsumeMode.ORDERLY顺序消费,单队列串行处理,保证消息有序;
  • messageModel:非必填,默认MessageModel.CLUSTERING一条消息只会被组内一台实例消费;自带重试 + 死信队列;生产业务首选;MessageModel.BROADCASTING广播模式,组内所有实例都收到全部消息;没有消息重试,异常直接丢失消息,只用于缓存刷新、配置推送。
  • consumeThreadMax:非必填,默认64,消费线程池最大线程数。
  • consumeThreadNumber:非必填,默认20。消费线程池初始核心线程数。并发消费依靠这两个参数控制吞吐;顺序消费单队列只会占用 1 个线程。
  • maxReconsumeTimes:非必填,默认-1表示16 次重试。单条消息最大重试次数,只对集群并发消费生效;广播模式无效。
  • consumeTimeout:非必填,默认15分钟,单条消息最大处理超时时间。如果业务处理超过 15 分钟未返回,SDK 判定消费失败,触发重试。
  • replyTimeout:非必填,默认3000 毫秒,RPC 调用超时,主要用于事务回查、RPC 消息场景。
  • accessKeysecretKey:非必填,默认从配置文件读取,无值为空,ACL 权限认证,开启 Broker 权限控制时填写密钥。
  • enableMsgTrace:非必填,默认false。是否开启消息轨迹追踪。开启后会记录生产、投递、消费全链路日志,用于排查消息丢失问题。
  • customizedTraceTopic:非必填,默认空,自定义轨迹 Topic,不填则使用系统默认轨迹主题。
  • nameServer:非必填,默认读取全局 yml 中的 name-server 地址,可以单独给当前消费者指定 NameServer,覆盖全局配置。
  • accessChannel:非必填,云 RocketMQ 专用,标识内网 / 外网接入通道,自建集群一般不用配置。
  • tlsEnable:非必填,默认false,是否开启 TLS 加密连接。
  • namespacenamespaceV2:非必填,默认空,云服务实例多租户隔离、命名空间隔离,自建单机集群一般留空。
  • delayLevelWhenNextConsume:非必填,默认0,并发消费重试,手动指定下一次重试的延迟等级。0 = 跟随 Broker 默认阶梯重试等级;填入数字 (1~18) 可以强制指定本次重试等待时长。
  • suspendCurrentQueueTimeMillis:非必填,默认1000ms,顺序消息消费失败时,暂停当前队列多久(毫秒),之后再重试本条消息。并发模式此参数无效。
  • awaitTerminationMillisWhenShutdown:非必填,默认1000ms,应用优雅停机时,等待正在执行的消费任务完成的最长时间;超时则强制终止线程。
  • instanceName:非必填,默认DEFAULT,消费者实例名称,单机多实例消费时用来区分客户端,一般保持默认即可。

最佳实践

生产者

  • Tags的使用

一个应用尽可能用一个Topic,而消息子类型则可以用tags来标识。

  • Keys的使用

每个消息在业务层面的唯一标识码要设置到keys字段,方便将来定位消息丢失问题。服务器会为每个消息创建索引(哈希索引),应用可以通过topickey来查询这条消息内容,以及消息被谁消费。由于是哈希索引,请务必保证key尽可能唯一,这样可以避免潜在的哈希冲突。

  • 日志的打印

消息发送成功或者失败要打印消息日志,务必要打印SendResultkey字段。send消息方法只要不抛异常,就代表发送成功。发送成功会有多个状态(参考简单消息介绍),在sendResult里定义。

  • 消息发送失败处理方式

Producersend方法本身支持内部重试,重试逻辑如下:

至多重试2次(同步发送为2次,异步发送为0次)。

如果发送失败,则轮转到下一个Broker。这个方法的总耗时时间不超过sendMsgTimeout设置的值,默认10s

如果本身向broker发送消息产生超时异常,就不会再重试。

以上策略也是在一定程度上保证了消息可以发送成功。如果业务对消息可靠性要求比较高,建议应用增加相应的重试逻辑:比如调用send同步方法发送失败时,则尝试将消息存储到db,然后由后台线程定时重试,确保消息一定到达Broker

消费者

  • 消费过程幂等

RocketMQ无法避免消息重复(Exactly-Once),所以如果业务对消费重复非常敏感,务必要在业务层面进行去重处理。可以借助关系数据库进行去重。首先需要确定消息的唯一键,可以是msgId,也可以是消息内容中的唯一标识字段,例如订单Id等。在消费之前判断唯一键是否在关系数据库中存在。如果不存在则插入,并消费,否则跳过。(实际过程要考虑原子性问题,判断是否存在可以尝试插入,如果报主键冲突,则插入失败,直接跳过)

msgId一定是全局唯一标识符,但是实际使用中,可能会存在相同的消息有两个不同msgId的情况(消费者主动重发、因客户端重投机制导致的重复等),这种情况就需要使业务字段进行重复消费。

  • 消费打印日志

如果消息量较少,建议在消费入口方法打印消息,消费耗时等,方便后续排查问题。

  • 消息在服务器上可以保存多长时间?

存储的消息将最多保存 3 天,超过 3 天未使用的消息将被删除。

JVM/OS配置

推荐使用最新发布的 JDK 1.8 版本。通过设置相同的 XmsXmx 值来防止 JVM 调整堆大小以获得更好的性能。生产环境 JVM 配置如下所示:

-server -Xms8g -Xmx8g 

垃圾回收,建议使用 JDK 1.8 自带的 G1 收集器:

-XX:+UseG1GC 
-XX:G1HeapRegionSize=16m   
-XX:G1ReservePercent=25 
-XX:InitiatingHeapOccupancyPercent=30

日志配置

RocketMQ 客户端启动后,会按照如下的默认配置生成日志文件:

  • 日志保存路径:/{user.home}/logs/rocketmqlogs/ 其中{user.home}是指启动当前Java进程的用户的根目录
  • 保存历史日志文件的最大个数:10个
  • 日志级别:INFO
  • 单个日志文件大小: 1GB

通用配置

rocketmq:
  # NameServer集群地址,多节点分号分隔,生产必须集群
  name-server: 192.168.1.100:9876;192.168.1.101:9876

  # 生产者全局配置
  producer:
    # 生产者分组,按服务名区分,全局唯一
    group: ${spring.application.name}_producer_group
    # 同步发送超时时间
    send-message-timeout: 3000
    # 同步发送失败重试次数,默认固定 2 
    retry-times-when-send-failed: 2
    # 异步发送失败重试,默认固定 2 
    retry-times-when-send-async-failed: 2
    # 消息轨迹,线上排查消息丢失必备,强制开启
    enable-msg-trace: true
    # 关闭VIP通道,避免内网环境各种连接异常
    vip-channel-enabled: false
    # ACL权限密钥,集群开启权限管控时配置
    access-key: ${ROCKETMQ_AK:}
    secret-key: ${ROCKETMQ_SK:}

  # 消费者全局公共配置
  consumer:
    enable-msg-trace: true
    vip-channel-enabled: false
    access-key: ${ROCKETMQ_AK:}
    secret-key: ${ROCKETMQ_SK:}
内容概要:本文档是一份针对2025-2026年Java后端大厂面试的高频考点全面梳理,涵盖Java基础、集合框架、并发编程、JVM、Spring全家桶、MySQL、Redis、消息队列、分布式与微服务等核心技术模块。内容不仅包括经典概念辨析(如String与StringBuilder区别、HashMap底层结构),还深入源码机制与设计原理(如Spring三级缓存解决循环依赖、AOP动态代理实现),并结合实际场景探讨问题排查与技术选型(如GC调优、缓存穿透解决方案)。特别强调从“背八股”向源码理解、线上排障和设计权衡的能力转变,体现当前面试趋势的深度化与实战化。; 适合人群:具备1-3年工作经验,准备冲击中高级Java岗位的研发人员,尤其适合希望系统提升面试竞争力、深入理解主流技术底层原理的开发者。; 使用场景及目标:①应对大厂Java后端技术面试,掌握高频考点与最新趋势;②深入理解核心技术的设计动机与实现细节,如ConcurrentHashMap的线程安全机制、分布式ID生成方案对比;③提升实际问题分析与解决能力,如Full GC排查、事务失效定位等。; 阅读建议:此资源以面试为导向,兼具广度与深度,建议结合自身项目经验进行对照学习,注重理解“为什么”而非仅仅记忆结论,对关键知识点应动手验证(如ThreadLocal内存泄漏实验),并在模拟面试中强化表达逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值