一.单机安装
rocketmq-all-4.4.0-bin-release.zip
1.启动NameServer
# 1.启动NameServer
nohup sh ./mqnamesrv &
# 2.查看启动日志
tail -f ~/logs/rocketmqlogs/namesrv.log
2. 启动Broker
# 1.启动Broker
nohup sh ./mqbroker -n localhost:9876 &
# 2.查看启动日志
tail -f ~/logs/rocketmqlogs/broker.log
3.启动没啥异常但是不成功
vi runbroker.sh
vi runserver.sh
参考设置:
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m
4.测试Rocketmq
4.1发送消息
# 1.设置环境变量
export NAMESRV_ADDR=localhost:9876
# 2.使用安装包的Demo发送消息
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
4.2接收消息
# 1.设置环境变量
export NAMESRV_ADDR=localhost:9876
# 2.接收消息
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
4.3关闭RocketMQ
# 1.关闭NameServer
sh ./mqshutdown namesrv
# 2.关闭Broker
sh ./mqshutdown broker
二.集群安装
Producer:消息的发送者;
Consumer:消息接收者;
Broker:暂存和传输消息;
NameServer:管理Broker;
Topic:区分消息的种类;一个发送者可以发送消息给一个或者多个Topic;一个消息的接收者可以订阅一个或者多个topic消息.
Message Queue:相当于是Topic的分区;用于并行发送和接收消息
2.1集群模式
2.1.1单Master模式
一旦Broker重启或者宕机时,会导致整个服务不可用
2.1.2 多master模式
一个集群无Slave,全是Master.
优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置为RAID10时,即使机器宕机不可恢复情况下,由于RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高;
缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到影响。
2.1.3多Master多Slave模式(异步)
每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点如下:
优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样;
缺点:Master宕机,磁盘损坏情况下会丢失少量消息。
2.1.4 多Master多Slave模式(同步)
每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主备都写成功,才向应用返回成功,这种模式的优缺点如下:
优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
缺点:性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。
2.2.4 部署集群模式(同步双写)
工作流程:
1. 启动NameServer,NameServer起来后监听端口,等待Broker、Producer、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建立连接通道,开始消费消息。
2.2.5 服务器环境
序号 | IP | 角色 | 架构模式 |
1 | 192.168.174.204 | nameserver brokerserver | Master1、Slave2 |
2 | 192.168.174.205 | nameserver brokerserver | Master2、Slave1 |
2.2.6 修改hosts 信息
192.168.174.204 rocketmq-nameserver1
192.168.174.205 rocketmq-nameserver2
192.168.174.204 rocketmq-master1
192.168.174.204 rocketmq-slave2
192.168.174.205 rocketmq-master2
192.168.174.205 rocketmq-slave1
重启网卡: systemctl restart network
2.2.7防火墙配置
# 开放name server默认端口
firewall-cmd --remove-port=9876/tcp --permanent
# 开放master默认端口
firewall-cmd --remove-port=10911/tcp --permanent
# 开放slave默认端口 (当前集群模式可不开启)
firewall-cmd --remove-port=11011/tcp --permanent
# 重启防火墙
firewall-cmd --reload
2.2.8环境变量配置
#set rocketmq
ROCKETMQ_HOME=/usr/local/rocketmq/rocketmq-all-4.4.0-bin-release
PATH=$PATH:$ROCKETMQ_HOME/bin
export ROCKETMQ_HOME PATH
2.2.9 创建消息存储路径
mkdir /usr/local/rocketmq/store
mkdir /usr/local/rocketmq/store/commitlog
mkdir /usr/local/rocketmq/store/consumequeue
mkdir /usr/local/rocketmq/store/index
mkdir /usr/local/rocketmq/store_slave
2.2.10 配置文件
第一台(192.168.174.204):
/opt/soft/rocketmq/conf/2m-2s-sync/broker-a.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=0
brokerIp1:192.168.174.204
#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=/opt/soft/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/opt/soft/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/opt/soft/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/opt/soft/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/opt/soft/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/opt/soft/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
enablePropertyFilter = true
filterSupportRetry = true
/opt/soft/rocketmq/conf/2m-2s-sync/broker-b-s.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=1
brokerIp1:192.168.174.204
#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=/opt/soft/rocketmq/store_slave
#commitLog 存储路径
storePathCommitLog=/opt/soft/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/opt/soft/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/opt/soft/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/opt/soft/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/opt/soft/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
enablePropertyFilter = true
filterSupportRetry = true
第二台(192.168.174.205):
/opt/soft/rocketmq/conf/2m-2s-sync/broker-b.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=0
brokerIp2:192.168.174.205
#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=/opt/soft/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/opt/soft/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/opt/soft/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/opt/soft/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/opt/soft/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/opt/soft/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
enablePropertyFilter = true
filterSupportRetry = true
/opt/soft/rocketmq/conf/2m-2s-sync/broker-a-s.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=1
brokerIp2:192.168.174.205
#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=/opt/soft/rocketmq/store_slave
#commitLog 存储路径
storePathCommitLog=/opt/soft/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/opt/soft/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/opt/soft/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/opt/soft/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/opt/soft/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
enablePropertyFilter = true
filterSupportRetry = true
第1,2 台 启动namesrv
nohup sh /opt/soft/rocketmq/bin/mqnamesrv &
启动 第1台 broker
nohup sh mqbroker -c /opt/soft/rocketmq/conf/2m-2s-sync/broker-a.properties &
nohup sh mqbroker -c /opt/soft/rocketmq/conf/2m-2s-sync/broker-b-s.properties &
启动 第2台 broker
nohup sh mqbroker -c /opt/soft/rocketmq/conf/2m-2s-sync/broker-b.properties &
nohup sh mqbroker -c /opt/soft/rocketmq/conf/2m-2s-sync/broker-a-s.properties &
# 1.关闭NameServer
sh /opt/soft/rocketmq/bin/mqshutdown namesrv
# 2.关闭Broker
sh /opt/soft/rocketmq/bin/mqshutdown broker
tail -f ~/logs/rocketmqlogs/broker.log
三 Springboot整合rocketmq.
3.1pom.xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.9.0</version>
</dependency>
3.2application.yml
rocketmq:
name-server: 192.168.174.204:9876;192.168.174.205:9876
producer:
group: codeTest
consumer:
group: codeTest
3.3 生产者
3.3.1普通推送
1.消息的顺序问题: 将不同的消息发送到同一个服务器上.
2.消息的重复问题: (1)保持幂等性 (不管多少条相同的数据,结果都是一条数据)(2):利用一张日志表,来到的数据都有自身的id 记录下,再来的数据与之id相比较.记录的不在记录.
@Autowired
private RocketMQTemplate template;
/**
* 普通推送
*/
public void sendDataSync() {
DefaultMQProducer producer = template.getProducer();
int num = 10;
while (num > 0) {
String message = "sendMessage" + num;
Message message1 = new Message("test-topic-Sync", "test-tag-Sync", message.getBytes(StandardCharsets.UTF_8));
try {
SendResult sendResult = producer.send(message1);
if (sendResult.getSendStatus() == SendStatus.SEND_OK) {
Console.log("普通消息发送成功");
} else {
Console.log("普通消息发送失败:[{}]", num, sendResult.getSendStatus());
}
} catch (Exception e) {
Console.error("普通消息发送报错" + e);
}
num--;
}
}
@Autowired
private ProducerService service;
/**
* 推送顺序消费
*/
@RequestMapping(value = "/sendSync", method = RequestMethod.GET)
public void sendSync() {
service.sendDataSync();
}
3.3.2 顺序推送
public void sendOrderly() {
try {
DefaultMQProducer producer = template.getProducer();
int num = 10;
while (num > 0) {
String message = "sendMeeage1_" + num;
Message message1 = new Message("test-topic-orderly", "test-tag-orderly", "1_" + num, message.getBytes(StandardCharsets.UTF_8));
SendResult send1 = producer.send(message1, (list, message2, o) -> {
Integer integer = (Integer) o;
int index = integer % list.size();
return list.get(index);
}, 0);
message = "sendMeeage2_" + num;
Message message3 = new Message("test-topic-orderly", "test-tag-orderly", "2_" + num, message.getBytes(StandardCharsets.UTF_8));
SendResult send2 = producer.send(message1, (list, message2, o) -> {
Integer integer = (Integer) o;
int index = integer % list.size();
return list.get(index);
}, 1);
message = "sendMeeage3_" + num;
Message message4 = new Message("test-topic-orderly", "test-tag-orderly", "3_" + num, message.getBytes(StandardCharsets.UTF_8));
SendResult send3 = producer.send(message1, (list, message2, o) -> {
Integer integer = (Integer) o;
int index = integer % list.size();
return list.get(index);
}, 2);
num--;
}
} catch (Exception e) {
Console.error("推送信息失败,异常异常信息:[{}]", e.getStackTrace());
}
}
@RequestMapping(value = "/sendOrderly", method = RequestMethod.GET)
public void sendOrderly(){
service.sendOrderly();
}
@Component
@RocketMQMessageListener(consumerGroup = "test-consumer-orderly", topic = "test-topic-orderly",
selectorExpression = "test-tag-orderly", consumeThreadMax = 5,
consumeMode = ConsumeMode.ORDERLY)
public class MyPushOrderlyListener implements RocketMQListener<String> {
private static Map<String, Object> map = new HashMap<>();
private static Long startTime = 0L;
private static Long endTime = 0L;
@Override
public void onMessage(String s) {
if (map.size()==0){
startTime = System.currentTimeMillis();
}
Console.log("有序消费监听接收到消息:[{}]", s);
map.put(s, null);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (map.size() == 10) {
endTime = System.currentTimeMillis();
Console.log("有序消费完成, 耗时[{}]!", endTime - startTime);
map = new HashMap<>();
}
}
}
3.3.3 立即消费
public void sendDataAsync(){
int num=10;
try{
while (num>1){
DefaultMQProducer producer = template.getProducer();
String message="message"+num;
Message message1 = new Message("test-topic-currently", "test-tag-currently", message.getBytes(StandardCharsets.UTF_8));
SendResult send = producer.send(message1);
if (send.getSendStatus() == SendStatus.SEND_OK) {
Console.log("普通消息发送成功");
} else {
Console.log("普通消息发送失败:[{}]", num, send.getSendStatus());
}
}
}catch (Exception e) {
Console.log("推送数据错误:[{}]",e.getMessage());
}
num--;
}
/**
* 推送立刻消费
* */
@RequestMapping(value = "/sendCurrently", method = RequestMethod.GET)
public void sendCurrently(){
service.sendDataAsync();
}
@Component
@RocketMQMessageListener(consumerGroup = "test-consumer-currently", topic = "test-topic-currently",
selectorExpression = "test-tag-currently", consumeThreadMax = 2,
consumeMode = ConsumeMode.CONCURRENTLY)
public class MyPushCurrentlyConsumer implements RocketMQListener<String> {
private static Map<String, Object> map = new HashMap<>();
private static Long startTime = 0L;
private static Long endTime = 0L;
@Override
public void onMessage(String s) {
if (map.size() ==0){
startTime = System.currentTimeMillis();
}
Console.log("并发消费监听接收到消息:[{}]", s );
map.put(s,null);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (map.size()==10){
endTime= System.currentTimeMillis();
Console.log("并发消费完成, 耗时[{}]!", endTime - startTime);
}
}
}
3.3.4 集群推送
public void sendDataCluter(){
try{
DefaultMQProducer producer = template.getProducer();
int num =10;
while (num>1){
String sendMessage="sendMessage_"+num;
Message message = new Message("test-topic-currently-sc", "test-tag-currently-sc", sendMessage.getBytes(StandardCharsets.UTF_8));
SendResult send = producer.send(message);
if (send.getSendStatus()==SendStatus.SEND_OK){
Console.log("集群模式推送数据成功:[{}]",num);
}else {
Console.log("集群模式推送数据失败");
}
num--;
}
Console.log("集群数据推送完成!");
}catch (Exception e){
Console.log("集群数据推送异常:[{}]",e.getMessage());
}
}
/**
* 推送, 集群消费
*/
@RequestMapping(value = "/sendCluster", method = RequestMethod.GET)
public void sendCluster(){
service.sendDataCluter();
}
@Component
public class MyPushConsumer {
private static final String groupName="test-consumer-currently";
private static final String topic="test-topic-currently-sc";
private static final String tag="test-tag-currently-sc";
@Scheduled(initialDelay = 3000, fixedDelay = 5000)
public void consumer1(){
Console.log("集群模式push监听1启动");
try {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
consumer.setNamesrvAddr("192.168.174.204:9876;192.168.174.205:9876");
consumer.subscribe(topic,tag);
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener(new BasePushListener1());
consumer.setConsumeTimeout(1L);
consumer.start();
} catch (MQClientException e) {
Console.error("集群模式监听异常,异常信息:[{}]", e.getMessage());
}
}
@Scheduled(initialDelay = 3000, fixedDelay = 5000)
public void consumer2(){
Console.log("集群模式push监听2启动");
try {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
consumer.setNamesrvAddr("192.168.174.204:9876;192.168.174.205:9876");
consumer.subscribe(topic,tag);
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener(new BasePushListener2());
consumer.setConsumeTimeout(1L);
consumer.start();
} catch (MQClientException e) {
Console.error("集群模式监听异常,异常信息:[{}]", e.getMessage());
}
}
private class BasePushListener1 implements MessageListenerConcurrently {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
if (CollUtil.isNotEmpty(list)){
for (MessageExt messageExt : list) {
String topic = messageExt.getTopic();
try {
String body = new String(messageExt.getBody(), "UTF-8");
String tags = messageExt.getTags();
Console.log("push消费者1收到消息:" + " topic :" + topic + " ,tags : " + tags + " ,msg : " + body);
} catch (UnsupportedEncodingException e) {
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
}
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
private class BasePushListener2 implements MessageListenerConcurrently {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
if (CollUtil.isNotEmpty(list)){
for (MessageExt messageExt : list) {
String topic = messageExt.getTopic();
try {
String body = new String(messageExt.getBody(), "UTF-8");
String tags = messageExt.getTags();
Console.log("push消费者2收到消息:" + " topic :" + topic + " ,tags : " + tags + " ,msg : " + body);
} catch (UnsupportedEncodingException e) {
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
}
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
}
3.3.5 推送,主动消费
public void pull(){
try{
DefaultMQProducer producer = template.getProducer();
int num =10;
while (num>1){
String sendMessage="sendMessage_"+num;
Message message = new Message("test-topic-pull", "test-tag-pull", sendMessage.getBytes(StandardCharsets.UTF_8));
SendResult send = producer.send(message);
if (send.getSendStatus()==SendStatus.SEND_OK){
Console.log("pull数据成功:[{}]",num,send.getQueueOffset());
}else {
Console.log("pull数据失败");
}
num--;
}
Console.log("pull完成!");
}catch (Exception e){
Console.log("pull异常:[{}]",e.getMessage());
}
}
/**
* 推送, 主动消费
*/
@RequestMapping(value = "/pull", method = RequestMethod.GET)
public void pull(){
service.pull();
}
@Component
public class MyPullConsumer {
private static final Map<MessageQueue,Long> offsetMap=new HashMap<>();
private static final DefaultMQPullConsumer consumer;
private static final String groupName="codeTest";
static {
consumer = new DefaultMQPullConsumer(groupName);
consumer.setNamesrvAddr("192.168.174.204:9876;192.168.174.205:9876");
try {
consumer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
@Scheduled(initialDelay = 3000, fixedDelay = 1000)
public void pullMessage(){
try {
Set<MessageQueue> messageQueues = consumer.fetchSubscribeMessageQueues("test-topic-pull");
if (messageQueues.size()>0){
for (MessageQueue queue : messageQueues) {
Long offset=0L;
if (offsetMap.containsKey(queue)){
offset = offsetMap.get(queue);
}
PullResultExt pullResultExt = (PullResultExt) consumer.pullBlockIfNotFound(queue, null, offset, 32);
offsetMap.put(queue,pullResultExt.getNextBeginOffset());
PullStatus status = pullResultExt.getPullStatus();
UpdateConsumerOffsetRequestHeader requestHeader = new UpdateConsumerOffsetRequestHeader();
if (status==PullStatus.FOUND){
List<MessageExt> list = pullResultExt.getMsgFoundList();
if (CollUtil.isNotEmpty(list)){
for (MessageExt messageExt : list) {
if (System.currentTimeMillis()-messageExt.getBornTimestamp() <=1000){
System.out.println("当前消息队列:[" + queue.getQueueId() + "],接收到消息:[" + new String(messageExt.getBody()) + "]");
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
3.3.6 单向推送
/**
* 单向推送
* */
public void oneWaySend(){
try{
DefaultMQProducer producer = template.getProducer();
int num =100;
while (num>0){
String numbers = RandomUtil.randomNumbers(10);
String sendMessage="oneWaySendMessage"+numbers;
Message message = new Message("test-topic-oneway", "test-tag-oneway", sendMessage.getBytes(StandardCharsets.UTF_8));
producer.sendOneway(message);
num--;
Console.log("单向推送消息完成!");
}
}catch (Exception e){
Console.log("单向推送消息异常,异常信息:[{}]", e.getMessage());
}
}
/**
* 单向推送
*/
@RequestMapping(value = "/oneWaySend", method = RequestMethod.GET)
public void oneWaySend(){
service.oneWaySend();
}
@Component
@RocketMQMessageListener(consumerGroup = "test-consumer-oneway",topic = "test-topic-oneway",selectorExpression = "test-tag-oneway")
public class MyOneWayListener implements RocketMQListener<String> {
@Override
public void onMessage(String s) {
Console.log("单向监听接收到消息:[{}]", s);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.3.7 异步推送
public void asyncSend(){
try{
int num =10;
String topic="test-topic-async";
String tag="test-topic-async";
String destination=topic+":"+tag;
while (num>0){
String sendMessage="asyncSendMessage_"+num;
Message message = new Message(topic, tag, sendMessage.getBytes(StandardCharsets.UTF_8));
template.asyncSend(destination, message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
if (sendResult.getSendStatus()==SendStatus.SEND_OK){
Console.log("异步推送[{}]成功, 返回:[{}]!", sendMessage, sendResult.getSendStatus());
}else {
Console.log("异步推送[{}]失败, 返回:[{}]!", sendMessage, sendResult.getSendStatus());
}
}
@Override
public void onException(Throwable throwable) {
Console.log("异步推送[{}]异常, 异常信息:[{}]!", sendMessage, throwable.getMessage());
}
});
}
}catch (Exception e){Console.log(e);}
}
/**
* 异步推送
*/
@RequestMapping(value = "/asyncSend", method = RequestMethod.GET)
public void asyncSend(){
service.asyncSend();
}
@Component
@RocketMQMessageListener(consumerGroup = "test-consumer-async",
topic = "test-topic-async",consumeThreadMax = 2,selectorExpression ="test-topic-async" )
public class MyAsyncListener implements RocketMQListener<Message> {
private static Map<String,Object> map =new HashMap<>();
private static Long start_time=0L;
private static Long end_time=0L;
@Override
public void onMessage(Message message) {
if (map.size()==0){
start_time=System.currentTimeMillis();
}
Console.log("异步推送消费监听接收到消息:[{}]",new String(message.getBody()));
map.put(new String(message.getBody()),null);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
Console.error(e);
}
if (map.size()==10){
end_time=System.currentTimeMillis();
Console.log("异步推送消费完成, 耗时[{}]!",end_time-start_time);
}
}
}
3.3.8 延迟推送
public void delaySend(){
try{
DefaultMQProducer producer = template.getProducer();
int num =10;
while (num>0){
String sendMessage="delaySendMessage_"+num;
Message message = new Message("test-topic-delay", "test-tag-delay", sendMessage.getBytes(StandardCharsets.UTF_8));
int level = getDelayLevel(num);
message.setDelayTimeLevel(level);
SendResult send = producer.send(message);
if (send.getSendStatus()==SendStatus.SEND_OK){
Console.log("延迟推送消息[{}]成功, 延迟级别:[{}]!", num, level);
}else {
Console.log("延迟推送消息[{}]失败, 推送返回状态:[{}]", num, send.getSendStatus());
}
num--;
Console.log("单向推送消息完成!");
}
}catch (Exception e){
Console.log("单向推送消息异常,异常信息:[{}]", e.getMessage());
}
}
public int getDelayLevel(int second){
int delayLevel = 0;
// 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
if (second > 0 && second < 5) {
delayLevel = 1;
} else if (second >= 5 && second < 10) {
delayLevel = 2;
} else if (second >= 10 && second < 30) {
delayLevel = 3;
} else if (second >= 30 && second < 60) {
delayLevel = 4;
} else if (second >= 60) {
delayLevel = 5;
}
/**
* 普通延迟推送
*/
@RequestMapping(value = "/delaySend", method = RequestMethod.GET)
public void delaySend(){
service.delaySend();
}
@Component
@RocketMQMessageListener(consumerGroup = "test-consumer-delay",topic = "test-topic-delay",selectorExpression ="test-tag-delay" )
public class MyDelayListener implements RocketMQListener<String> {
@Override
public void onMessage(String s) {
Console.log("延迟监听接收到消息:[{}]", s);
}
}
3.3.9 过滤延时
/**
* 过滤推送
*/
public void filterSend(){
try{
DefaultMQProducer producer = template.getProducer();
producer.setSendLatencyFaultEnable(true);
int num =10;
while (num>0){
String sendMessage="delaySendMessage_"+num;
Message message = new Message("test-topic-filter", "test-tag-filter", sendMessage.getBytes(StandardCharsets.UTF_8));
message.putUserProperty("id",String.valueOf(num));
SendResult send = producer.send(message);
if (send.getSendStatus()==SendStatus.SEND_OK){
Console.log("过滤推送消息成功!");
}else {
Console.log("过滤推送消息失败, 推送返回状态:[{}]", send.getSendStatus());
}
num--;
Console.log("过滤推送消息完成!");
}
}catch (Exception e){
Console.log("过滤推送消息异常,异常信息:[{}]", e.getMessage());
}
}
@RequestMapping(value = "/filterSend", method = RequestMethod.GET)
public void filterSend(){
service.filterSend();
}
@Component
public class MyFilterConsumer {
private static final String groupName = "test-consumer-filter";
private static final String topic = "test-topic-filter";
private static final String tag = "test-tag-filter";
private static final DefaultMQPushConsumer consumer;
static {
consumer = new DefaultMQPushConsumer(groupName);
consumer.setNamesrvAddr("192.168.174.204:9876;192.168.174.205:9876");
}
public void filterConsumer(){
try {
consumer.subscribe(topic, MessageSelector.bySql("id between 3 and 7"));
consumer.registerMessageListener(new BasePushListener());
consumer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
private class BasePushListener implements MessageListenerConcurrently{
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
if (CollUtil.isNotEmpty(list)){
try{
for (MessageExt messageExt : list) {
String topic = messageExt.getTopic();
String s = new String(messageExt.getBody(), "UTF-8");
String tags = messageExt.getTags();
System.out.println(messageExt.getProperty("id"));
System.out.println("过滤消费收到消息:" + " topic :" + topic + " ,tags : " + tags + " ,msg : " + s);
}
}catch (Exception e){
Console.log(e);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
}
@Component
@RocketMQMessageListener(consumerGroup = "test-consumer-filter",
topic = "test-topic-filter",selectorType = SelectorType.SQL92,selectorExpression = "id = 5")
public class MyFilterListner implements RocketMQListener<String> {
@Override
public void onMessage(String s) {
Console.log("过滤监听接收到消息:[{}]", s);
}
}
3.3.10 推送,无消费,web查询信息
public void sendWeb() {
try {
DefaultMQProducer producer = template.getProducer();
int num = 10;
while (num > 0) {
String sendMessage = "sendMessage_" + num;
Message message = new Message("test-topic-web", "test-tag-web", sendMessage.getBytes(StandardCharsets.UTF_8));
SendResult sendResult = producer.send(message);
if (sendResult.getSendStatus() == SendStatus.SEND_OK) {
Console.log("普通web推送消息:[{}]成功!", num);
} else {
Console.log("普通web推送消息:[{}]失败, 推送返回状态:[{}]", num, sendResult.getSendStatus());
}
num --;
}
Console.log("普通web推送消息完成!");
} catch (Exception e) {
Console.log("普通web推送消息异常,异常信息:[{}]", e.getMessage());
}
}
/**
* 推送, 无消费, web查询信息
*/
@RequestMapping(value = "/sendWeb", method = RequestMethod.GET)
public void sendWeb(){
service.sendWeb();
}
四 rocketmq补充
4.1 按照发送特点
4.1.1 同步消息: 同步发送是指消息发送方发出数据后,会阻塞直到MQ服务方发回响应消息。
4.1.2 异步消息: 异步发送是指发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。MQ 的异步发送,需要用户实现异步发送回调接口(SendCallback),在执行消息的异步发送时,应用不需要等待服务器响应即可直接返回,通过回调接口接收服务器响应,并对服务器的响应结果进行处理。应用场景:异步发送一般用于链路耗时较长,对 RT 响应时间较为敏感的业务场景,例如用户视频上传后通知启动转码服务,转码完成后通知推送转码结果等。
4.1.3 单向消息: 单向(one-way)消息:单向(Oneway)发送特点为只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答。此方式发送消息的过程耗时非常短,一般在微秒级别。应用场景:适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。
4.2按照使用功能特点
4.2.1 普通消息
4.2.2 顺序消息
4.2.3 广播消息
4.2.4 延时消息
4.2.5 批量消息
4.2.6 事务消息