文章目录
一、MQ简介
为什么使用MQ
消息队列是一种先进先出的数据结构
其应用场景主要包含以下3个方面
-
应用解耦
比如下图的订单系统,在没有使用MQ的时候,
订单系统
需要调用支付系统
、库存系统
、物流系统
,如果在调用支付系统
的时候,支付系统挂掉了。那么就会影响库存系统
和物流系统
的调用。影响用户的体验。
使用消息队列解耦合,系统的耦合性就会提高了。比如物流系统发生故障,需要几分钟才能来修复,在这段时间内,物流系统要处理的数据被缓存到消息队列中,用户的下单操作正常完成。当物流系统回复后,补充处理存在消息队列中的订单消息即可,终端系统感知不到物流系统发生过几分钟故障。
-
流量削峰
应用系统如果遇到系统请求流量的瞬间猛增,有可能会将系统压垮。有了消息队列可以将大量请求缓存起来,分散到很长一段时间处理,这样可以大大提到系统的稳定性和用户体验。
一般情况,为了保证系统的稳定性,如果系统负载超过阈值,就会阻止用户请求,这会影响用户体验,而如果使用消息队列将请求缓存起来,等待系统处理完毕后通知用户下单完毕,这样总比不能下单体验要好。
业务系统正常时段的QPS如果是1000,流量最高峰是10000,为了应对流量高峰配置高性能的服务器显然不划算,这时可以使用消息队列对峰值流量削峰
-
数据分发
如上图B、C、D、E系统需要使用A系统的数据,A系统就得手动的编写调用代码。如果日后再有F、G系统也需要A系统的数据的话,A系统还得重新编写代码。工作量十分大。
通过消息队列可以让数据在多个系统更加之间进行流通。数据的产生方不需要关心谁来使用数据,只需要将数据发送到消息队列,数据使用方直接在消息队列中直接获取数据即可
MQ的优点和缺点
**优点:**解耦、削峰、数据分发
缺点:
-
系统可用性降低
系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务造成影响。
如何保证MQ的高可用?
-
系统复杂度提高
MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ进行异步调用。
如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
-
一致性问题
A系统处理完业务,通过MQ给B、C、D三个系统发消息数据,如果B系统、C系统处理成功,D系统处理失败。
如何保证消息数据处理的一致性?
各种MQ产品的比较
二、RocketMQ快速入门
RocketMQ是阿里巴巴2016年MQ中间件,使用Java语言开发,在阿里内部,RocketMQ承接了例如“双11”等高并发场景的消息流转,能够处理万亿级别的消息。
准备工作
-
下载RocketMQ
RocketMQ最新版本:4.5.1
-
环境要求
- Linux64位系统
- JDK1.8(64位)
- 源码安装需要安装Maven 3.2.x
安装RocketMQ
-
安装步骤
解压安装包
# 安装解压命令 yum install -y unzip zip # 解压 unzip rocketmq-all-4.5.1-bin-release.zip
进入安装目录
cd rocketmq-all-4.5.1-bin-release
-
目录介绍
- bin:启动脚本,包括shell脚本和CMD脚本
- conf:实例配置文件 ,包括broker配置文件、logback配置文件等
- lib:依赖jar包,包括Netty、commons-lang、FastJSON等
启动RocketMQ
启动NameServer
# 1.启动NameServer
nohup sh mqnamesrv &
# 2.查看启动日志
tail -f ~/logs/rocketmqlogs/namesrv.log
启动Broker
# 1.启动Broker
nohup sh mqbroker -n localhost:9876 &
# 2.查看启动日志
tail -f ~/logs/rocketmqlogs/broker.log
-
问题描述:
RocketMQ默认的虚拟机内存较大,启动Broker如果因为内存不足失败,需要编辑如下两个配置文件,修改JVM内存大小
# 编辑runbroker.sh和runserver.sh修改默认JVM大小
vi runbroker.sh
vi runserver.sh
- 参考设置:
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
测试RocketMQ
-
发送消息
# 1.设置环境变量 export NAMESRV_ADDR=localhost:9876 # 2.使用安装包的Demo发送消息 sh tools.sh org.apache.rocketmq.example.quickstart.Producer
-
接收消息
# 1.设置环境变量 export NAMESRV_ADDR=localhost:9876 # 2.接收消息 sh tools.sh org.apache.rocketmq.example.quickstart.Consumer
-
错误解决
# 如果发生错误,先查看当前目录下的nohup.out日志文件,再查看~/logs/下面的日志文件 # 如果启动失败,很有可能是Java环境变量的问题,还有就是内存大小的问题 # 如果在测试的时候出现错误,很有可能是内存不足导致的,可以使用命令free -h来查看剩余的可用内存
-
测试效果
关闭RocketMQ
# 1.关闭NameServer
sh mqshutdown namesrv
# 2.关闭Broker
sh mqshutdown broker
三、RocketMQ集群搭建
各种角色的简介
- Producer:消息的发送者;举例:发信者
- Consumer:消息接收者;举例:收信者
- Broker:暂存和传输消息;举例:邮局
- NameServer:管理Broker;举例:各个邮局的管理机构
- Topic:区分消息的种类;一个发送者可以发送消息给一个或者多个Topic;一个消息的接收者可以订阅一个或者多个Topic消息
- Message Queue:相当于是Topic的分区;用于并行发送和接收消息
集群搭建的方式
-
集群的特点
-
Name Server
是无状态的,因为每一个Broker
都会向所有的Name Server
发送消息,因此Name Server
之间的数据是一致的,不需要做数据同步,因此搭建Name Server
的集群只需要启动多个Name Server
即可。 -
Broker
集群通常是按组出现的,一个组内有一个主节点和多个从节点,主节点负责写入数据,从节点负责读取数据。Broker
通过Broker Name
来区分是否为一组,如果Broker Name
相同,则为一组。通过Broker ID
来区分主从节点,如果Broker ID
为0,则为主节点。 -
Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。
-
Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。
-
-
集群的模式
① 单Master模式
这种方式风险较大,一旦Broker重启或者宕机时,会导致整个服务不可用。不建议线上环境使用,可以用于本地测试。
② 多Master模式
一个集群无Slave,全是Master,例如2个Master或者3个Master,这种模式的优缺点如下:
- 优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置为RAID10时,即使机器宕机不可恢复情况下,由于RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高;
- 缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到影响。
③ 多Master多Slave模式(异步)
每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点如下:
- 优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样;
- 缺点:Master宕机,磁盘损坏情况下会丢失少量消息。
④ 多Master多Slave模式(同步)
每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主备都写成功,才向应用返回成功,这种模式的优缺点如下:
- 优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
- 缺点:性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。
双主双从集群搭建
工作流程
- 启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。
- Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
- 收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
- Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
- Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。
准备工作
以下的操作需要在两台服务器上都要执行
-
服务器
序号 IP 角色 架构模式 1 192.168.25.135 nameserver、brokerserver Master1、Slave2 2 192.168.25.138 nameserver、brokerserver Master2、Slave1 -
Host添加信息
vim /etc/hosts
配置如下:
# nameserver 10.180.150.56 rocketmq-nameserver1 116.62.119.24 rocketmq-nameserver2 # broker 10.180.150.56 rocketmq-master1 10.180.150.56 rocketmq-slave2 116.62.119.24 rocketmq-master2 116.62.119.24 rocketmq-slave1
配置完成后, 重启网卡
systemctl restart network
-
防火墙配置
# 关闭防火墙 systemctl stop firewalld.service # 查看防火墙的状态 firewall-cmd --state # 禁止firewall开机启动 systemctl disable firewalld.service
RocketMQ默认使用3个端口:9876 、10911 、11011 。如果防火墙没有关闭的话,那么防火墙就必须开放这些端口:
nameserver
默认使用 9876 端口master
默认使用 10911 端口slave
默认使用11011 端口 -
环境变量配置
vim /etc/profile
在profile文件的末尾加入如下命令
#set rocketmq ROCKETMQ_HOME=/usr/local/rocketmq/rocketmq-all-4.4.0-bin-release PATH=$PATH:$ROCKETMQ_HOME/bin export ROCKETMQ_HOME PATH
输入:wq! 保存并退出, 并使得配置立刻生效:
source /etc/profile
-
创建消息存储路径
mkdir -p /usr/local/rocketmq/store2 mkdir -p /usr/local/rocketmq/store2/commitlog mkdir -p /usr/local/rocketmq/store2/consumequeue mkdir -p /usr/local/rocketmq/store2/index
broker配置文件
服务器:10.180.150.56
vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-a.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-b-s.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
服务器:116.62.119.24
vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-b.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-a-s.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
修改启动脚本
# 下面两个文件分别进行加载
vi /usr/local/rocketmq/bin/runbroker.sh
vim /usr/local/rocketmq/bin/runserver.sh
需要根据内存大小进行适当的对JVM参数进行调整:
#===================================================
# 开发环境配置 JVM Configuration
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
服务启动
-
启动
NameServer
集群分别在10.180.150.56和116.62.119.24启动
NameServer
cd /usr/local/rocketmq/bin nohup sh mqnamesrv &
-
启动Broker集群
在10.180.150.56上启动master1
和
slave2`master1
cd /usr/local/rocketmq/bin nohup sh mqbroker -c /home/software/rocketmq-all-4.5.1-bin-release/conf/2m-2s-sync/broker-a.properties &
slave2:
cd /usr/local/rocketmq/bin nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-b-s.properties &
在116.62.119.24上启动master2和slave1
master2
cd /usr/local/rocketmq/bin nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-b.properties &
slave1
cd /usr/local/rocketmq/bin nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-a-s.properties &
-
出现的问题
java.lang.RuntimeException: Lock failed,MQ already started at org.apache.rocketmq.store.DefaultMessageStore.start(DefaultMessageStore.java:220) at org.apache.rocketmq.broker.BrokerController.start(BrokerController.java:824) at org.apache.rocketmq.broker.BrokerStartup.start(BrokerStartup.java:64) at org.apache.rocketmq.broker.BrokerStartup.main(BrokerStartup.java:58)
解决方法:我们要启动的每一个broker要指定不一样的storePath 路径,也就是在配置文件中修改即可
-
启动后使用
JPS
命令查看进程
集群监控平台搭建
RocketMQ
有一个对其扩展的开源项目incubator-rocketmq-externals,这个项目中有一个子模块叫rocketmq-console
,这个便是管理控制台项目了,先将incubator-rocketmq-externals拉到本地,因为我们需要自己对rocketmq-console
进行编译打包运行。
-
下载编译并打包
git clone https://github.com/apache/rocketmq-externals cd rocketmq-console mvn clean package -Dmaven.test.skip=true
注意:打包前在
rocketmq-console
中配置namesrv
集群地址,需要修改项目的application.yaml
文件: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
进入控制台界面了,如下图:
四、消息发送案例
-
导入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
基本案例
发送同步消息
同步的方式可靠性强,比如重要的消息通知短信通知等
public static void main(String[] args) throws Exception {
// 1.创建消息生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 2.指定Nameserver地址
producer.setNamesrvAddr("116.62.119.24:9876");
// 如果连接阿里云服务器需要添加下面一行代码
producer.setVipChannelEnabled(false);
// 3.启动producer
producer.start();
Message msg;
SendResult sendResult;
for (int i = 0; i < 100; i++) {
// 4.创建消息对象,指定主题Topic、Tag和消息体
msg = new Message("TopicTest", "TagA",("Hello RocketMQ " + i).getBytes());
// 5.发送消息到一个Broker
sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.printf("%s%n", sendResult);
}
// 6.关闭生产者producer
producer.shutdown();
}
发送异步消息
异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应。
注意如果虚拟机中安装的
RocketMQ
报错的话,注意加上Thread.sleep(500);
public static void main(String[] args) throws Exception {
// 1.创建消息生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 2.指定Nameserver地址
producer.setNamesrvAddr("116.62.119.24:9876");
// 如果连接阿里云服务器需要添加下面一行代码
producer.setVipChannelEnabled(false);
// 3.启动producer
producer.start();
Message msg;
SendResult sendResult;
for (int i = 0; i < 100; i++) {
// 4.创建消息对象,指定主题Topic、Tag和消息体
msg = new Message("TopicTest", "TagA",("Hello RocketMQ " + i).getBytes());
// 5.发送消息到一个Broker
producer.send(msg, new SendCallback() {
//执行成功的回调函数
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(sendResult);
}
//抛出异常的回调
@Override
public void onException(Throwable throwable) {
System.out.println("抛出异常" + throwable);
}
});
Thread.sleep(500);
}
// 6.关闭生产者producer
producer.shutdown();
}
发送单向消息
这种方式主要用在不特别关心发送结果的场景,例如日志发送。
public static void main(String[] args) throws Exception {
// 1.创建消息生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 2.指定Nameserver地址
producer.setNamesrvAddr("116.62.119.24:9876");
// 如果连接阿里云服务器需要添加下面一行代码
producer.setVipChannelEnabled(false);
// 3.启动producer
producer.start();
Message msg;
SendResult sendResult;
for (int i = 0; i < 100; i++) {
// 4.创建消息对象,指定主题Topic、Tag和消息体
msg = new Message("TopicTest", "TagA",("Hello RocketMQ " + i).getBytes());
// 5.发送消息到一个Broker
producer.sendOneway(msg);
Thread.sleep(500);
}
// 6.关闭生产者producer
producer.shutdown();
}
消费消息
负载均衡
消费者采用负载均衡方式消费消息,多个消费者共同消费队列消息,每个消费者处理的消息不同
public static void main(String[] args) throws Exception {
// 1.创建消息生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 2.指定Nameserver地址
producer.setNamesrvAddr("116.62.119.24:9876");
// 如果连接阿里云服务器需要添加下面一行代码
producer.setVipChannelEnabled(false);
//负载均衡模式消费
consumer.setMessageModel(MessageModel.CLUSTERING);
// 3.启动producer
producer.start();
Message msg;
SendResult sendResult;
for (int i = 0; i < 100; i++) {
// 4.创建消息对象,指定主题Topic、Tag和消息体
msg = new Message("TopicTest", "TagA",("Hello RocketMQ " + i).getBytes());
// 5.发送消息到一个Broker
producer.send(msg, new SendCallback() {
//执行成功的回调函数
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(sendResult);
}
//抛出异常的回调
@Override
public void onException(Throwable throwable) {
System.out.println("抛出异常" + throwable);
}
});
Thread.sleep(500);
}
// 6.关闭生产者producer
producer.shutdown();
}
广播模式
消费者采用广播的方式消费消息,每个消费者消费的消息都是相同的
public static void main(String[] args) throws Exception {
// 1.创建消费者Consumer,制定消费者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
// 2.指定Nameserver地址
consumer.setNamesrvAddr("116.62.119.24:9876");
// 3.订阅主题Topic和Tag
consumer.subscribe("TopicTest","TagA");
//阿里云服务器
consumer.setVipChannelEnabled(false);
//负载均衡模式消费
consumer.setMessageModel(MessageModel.BROADCASTING);
// 4.设置回调函数,处理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg : list) {
System.out.println(new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 5.启动消费者consumer
consumer.start();
}
顺序消息
-
原理
虽然MQ底层使用的是先进先出的队列,但是
Broker
中有很多个队列。当生产者依次进行发送消息时,消息被放在Broker
中的不同队列中,从而提高了消息消费的效率,但是这样操作也会导致消费者消费消息时顺序不一致。
因此提出了全局消息一致
和局部消息一致
全局消息一致
:保证broker
中队列顺序的一致性
局部消息一致
:将想要保证消息一致的消息全部放到一个队列中,如下图所示
顺序消息生产
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("116.62.119.24:9876");
producer.setVipChannelEnabled(false);
producer.start();
List<OrderStep> orderSteps = OrderStep.buildOrders();
// 发送消息
for (int i=0;i<orderSteps.size();i++) {
String body = orderSteps.get(i)+ "";
Message message = new Message("TopicA", "orders", "i=" + i, body.getBytes());
/*
* 参数一: 消息对象
* 参数二:消息队列的选择器
* 参数三:选择队列的业务标识(这里是订单ID)
*/
SendResult send = producer.send(message, new MessageQueueSelector() {
/**
* @param list 队列集合
* @param message 消息对象
* @param o 业务标识的参数
* @return
*/
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
long orderId = (long) o;
long index = orderId % list.size();
return list.get((int) index);
}
}, orderSteps.get(i).getOrderId());
System.out.println("发送结果为:" + send);
}
producer.shutdown();
}
}
public 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 + '\'' +
'}';
}
/**
* 生成模拟订单数据
*/
public static 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;
}
}
顺序消息消费
public class ConsumerInOrder {
public static void main(String[] args) throws Exception {
// 1.创建消费者Consumer,制定消费者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
// 2.指定Nameserver地址
consumer.setNamesrvAddr("10.180.150.56:9876");
// 3.订阅主题Topic和Tag
consumer.subscribe("TopicA","orders");
//阿里云服务器
consumer.setVipChannelEnabled(false);
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
for (MessageExt msg : list) {
System.out.println("线程:【" + Thread.currentThread().getName() + "】 消费消息:"+ new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.out.println("消费者启动");
}
}
延时消息
比如电商里,提交了一个订单就可以发送一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。
public static void main(String[] args) throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("116.62.119.24:9876");
// 如果连接阿里云服务器需要添加下面一行代码
producer.setVipChannelEnabled(false);
producer.start();
Message msg;
SendResult sendResult;
for (int i = 0; i < 100; i++) {
// 4.创建消息对象,指定主题Topic、Tag和消息体
msg = new Message("TopicTest", "TagA",("Hello RocketMQ " + i).getBytes());
//设置延迟消息级别
msg.setDelayTimeLevel(2);
sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.printf("%s%n", sendResult);
}
// 6.关闭生产者producer
producer.shutdown();
}
上面代码就比普通的发送消息增加了以下代码:
//设置延迟消息级别
msg.setDelayTimeLevel(2);
延迟级别为以下几个级别
// 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
批量发送
如果每次只发送不超过4MB的消息,则很容易使用批处理,样例如下:
若果超过4M需要进行消息的截取
public static void main(String[] args) throws Exception{
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("116.62.119.24:9876");
// 如果连接阿里云服务器需要添加下面一行代码
producer.setVipChannelEnabled(false);
producer.start();
List<Message> list = new ArrayList<>();
Message msg1 = new Message("BatchTopic","Tag1","Hello Word 1".getBytes());
Message msg2 = new Message("BatchTopic","Tag1","Hello Word 2".getBytes());
Message msg3 = new Message("BatchTopic","Tag1","Hello Word 3".getBytes());
list.add(msg1);
list.add(msg2);
list.add(msg3);
SendResult send = producer.send(list);
System.out.println("发送结果:" + send);
// 6.关闭生产者producer
producer.shutdown();
}
过滤消息
在大多数情况下,TAG是一个简单而有用的设计,其可以来选择您想要的消息。例如:
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE");
consumer.subscribe("TOPIC", "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 |
------------
生产者中使用:
msg.putUserProperty("i",String.valueOf(i));
消费者中使用:
consumer.subscribe("Fillter1", MessageSelector.bySql("i>2"));
事务消息
执行流程
事务消息指的是一组消息存在着事务,当消息被生产者提交到
Broker
时,此时是不能被消费的,称为半消息
。当生产者发送了提交或者是回滚的时候,这一组消息才能被消费。如果生产者发送的是回滚
或者是长时间没有响应的话那么Broker
会删除这条消息。
(1) 发送消息(half消息)。
(2) 服务端响应消息写入结果。
(3) 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)。
(4) 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)
事务的三种状态
事务消息共有三种状态,提交状态、回滚状态、中间状态
事务消息生产者
public static void main(String[] args) throws Exception{
TransactionMQProducer producer = new TransactionMQProducer("group2");
producer.setNamesrvAddr("116.62.119.24:9876");
producer.setVipChannelEnabled(false);
//添加事务的监听器
producer.setTransactionListener(new TransactionListener() {
//执行本地事务
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
if (StringUtils.equals("TAG1", message.getTags())) {
return LocalTransactionState.COMMIT_MESSAGE;
} else if (StringUtils.equals("TAG2", message.getTags())) {
return LocalTransactionState.ROLLBACK_MESSAGE;
} else {
return LocalTransactionState.UNKNOW;
}
}
//进行消息事务的状态回查
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
System.out.println("MQ检查消息Tag【"+messageExt.getTags()+"】的本地事务执行结果");
return LocalTransactionState.COMMIT_MESSAGE;
}
});
producer.start();
String []tags = {"TAG1","TAG2","TAG3"};
for (int i = 0; i < 2; i++) {
Message msg = new Message("Transaction", tags[i],("Hello RocketMQ " + i).getBytes());
//第二个参数为可以将事务控制应用到某一个参数上面,也可以应用到全部消息上 应用到全部上传个null即可
TransactionSendResult result = producer.sendMessageInTransaction(msg, null);
System.out.printf("发送结果:" + result);
Thread.sleep(500);
}
}