参考文档:https://help.aliyun.com/document_detail/29543.html?spm=a2c4g.29543.0.0.24e6e4e86BFvl5#section-6p0-n6o-oty
MQ(Message Queue)是一种跨进程的通信机制,用于传递消息。通俗点说,就是一个先进先出的数据结构。
最常见的一个场景是用户注册后,需要发送注册邮件和短信通知,以告知用户注册成功。传统的做法如下:
此架构下注册、邮件、短信三个任务全部完成后,才返回注册结果到客户端,用户才能使用账号登录。
但是对于用户来说,注册功能实际只需要注册系统存储用户的账户信息后,该用户便可以登录,而后续的注册短信和邮件不是即时需要关注的步骤。
所以实际当数据写入注册系统后,注册系统就可以把其他的操作放入对应的消息队列 MQ 中然后马上返回用户结果,由消息队列 MQ 异步地进行这些操作。架构图如下:
异步解耦是消息队列 MQ 的主要特点,主要目的是减少请求响应时间和解耦。主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时,由于使用了消息队列MQ,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦合。
流量削峰也是消息队列 MQ 的常用场景,一般在秒杀或团队抢购(高并发)活动中使用广泛。在秒杀或团队抢购活动中,由于用户请求量较大,导致流量暴增,秒杀的应用在处理如此大量的访问流量后,下游的通知系统无法承载海量的调用量,甚至会导致系统崩溃等问题而发生漏通知的情况。为解决这些问题,可在应用和下游通知系统之间加入消息队列 MQ。
秒杀处理流程如下所述:
号称最快的消息队列系统,尤其针对大吞吐量的需求场景。扩展性好,开发比较灵活,采用C语言实现,实际上只是一个socket库的重新封装,如果做为消息队列使用,需要开发大量的代码。ZeroMQ仅提供非持久性的队列,也就是说如果down机,数据将会丢失。
使用erlang语言开发,性能较好,适合于企业级的开发。但是不利于做二次开发和维护。
历史悠久的Apache开源项目。已经在很多产品中得到应用,实现了JMS1.1规范,可以和spring-jms轻松融合,实现了多种协议,支持持久化到数据库,对队列数较多的情况支持不好。
阿里巴巴的MQ中间件,由java语言开发,性能非常好,能够撑住双十一的大流量,而且使用起来很简单。
Kafka是Apache下的一个子项目,是一个高性能跨语言分布式Publish/Subscribe消息队列系统,相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。
RocketMQ是阿里巴巴开源的分布式消息中间件,现在是Apache的一个顶级项目。在阿里内部使用非常广泛,已经经过了"双11"这种万亿级的消息流转。
接下来我们先在linux平台下安装一个RocketMQ的服务
https://rocketmq.apache.org/release-notes/2023/03/26/4.9.5
下载时要注意与springcloudalibaba版本匹配
版本说名链接地址
[root@bogon RocketMQ]# unzip rocketmq-all-4.9.5-bin-release.zip
[root@bogon RocketMQ]# ll
total 32136
drwxr-xr-x. 6 root root 103 Mar 27 14:47 rocketmq-all-4.9.5-bin-release
-rw-r--r--. 1 root root 32906177 May 24 10:22 rocketmq-all-4.9.5-bin-release.zip
runserver.sh
vi runserver.sh
# JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
runbroker.sh
vi runbroker.sh
# JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
tools.sh
vi tools.sh
# JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m"
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m"
开启自动动创建Topic功能
在conf/broker.conf⽂件中加⼊如下配置,开启自动动创建Topic功能。
autoCreateTopicEnable=true
## 创建日志目录
cd bin
mkdir logs
# nohup ./mqnamesrv &:属于后台以静默⽅式启动
# ./mqnamesrv:属于终端启动,直接输出日志信息,按 ctrl+c 可直接关闭退出
nohup ./mqnamesrv > logs/mqnamesrv.out 2>1 &
查看启动状态,在当前目录下会有一个nohup.out的日志文件,可以打开查看。
## 查看日志
tail -f logs/mqnamesrv.out
## 看到以下表示启动成功
The Name Server boot success. serializeType=JSON
解决报错
## 报错
ERROR: Please set the JAVA_HOME variable in your environment, We need java(x64)! !!
## 解决 配置jdk环境变量
# 需要 export 环境变量
# 启动命令,并且常驻内存:注意ip地址要配置成为服务的ip地址,保证地址以及端口能够访问
# nohup ./mqbroker -n 192.168.109.149:9876 & :属于后台以静默⽅式启动
# sh ./mqbroker -n 92.168.109.149:9876 :属于终端启动,直接输出日志信息,按 ctrl+c 可直接关闭退出
nohup ./mqbroker -n 192.168.109.149:9876 > logs/mqbroker.out 2>1 &
查看启动状态,启动之后同样提示将日志信息追加到了当前目录下的nohup.out文件中。
## 查看日志
tail -f logs/mqbroker.out
## 看到以下表示启动成功
The broker[linux1, 192.168.109.149:10911] boot success. serializeType=JSON and name server is 192.168.109.149:9876
解决没反应:
#删除/root/store/*
cd /root/store
rm -rf *
# 重新启动broker
nohup ./mqbroker -n 192.168.109.149:9876 > logs/mqbroker.out 2>1 &
发送/接收消息之前,需要告诉客户端(Producer、Consumer)名称服务器的位置,RocketMQ 提供了多种方法来实现这一点.
[root@linux1 rocketmq]# export NAMESRV_ADDR=localhost:9876
[root@linux1 rocketmq]# sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
OpenJDK 64-Bit Server VM warning: MaxNewSize (262144k) is equal to or greater than the entire heap (262144k). A new max generation size of 261632k will be used.
16:19:05.806 [main] DEBUG i.n.u.i.l.InternalLoggerFactory - Using SLF4J as the default logging framework
RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.PlatformDependent0).
RocketMQLog:WARN Please initialize the logger system properly.
SendResult [sendStatus=SEND_OK, msgId=AC11000176396FF3C5B512F379FA0000, offsetMsgId=AC11000100002A9F0000000000000000, messageQueue=MessageQueue [topic=TopicTest, brokerName=linux1, queueId=3], queueOffset=0]
......
SendResult [sendStatus=SEND_OK, msgId=AC11000176396FF3C5B512F382B603E7, offsetMsgId=AC11000100002A9F00000000000317BF, messageQueue=MessageQueue [topic=TopicTest, brokerName=linux1, queueId=2], queueOffset=249]
16:19:08.609 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[172.17.0.1:10911] result: true
16:19:08.631 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[127.0.0.1:9876] result: true
[root@linux1 rocketmq]# sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
OpenJDK 64-Bit Server VM warning: MaxNewSize (262144k) is equal to or greater than the entire heap (262144k). A new max generation size of 261632k will be used.
16:21:15.395 [main] DEBUG i.n.u.i.l.InternalLoggerFactory - Using SLF4J as the default logging framework
Consumer Started.
ConsumeMessageThread_3 Receive New Messages: [MessageExt [brokerName=linux1, queueId=2, storeSize=201, queueOffset=1, sysFlag=0, bornTimestamp=1659601146477, bornHost=/192.168.0.101:48216, storeTimestamp=1659601146478, storeHost=/172.17.0.1:10911, msgId=AC11000100002A9F000000000000057F, commitLogOffset=1407, bodyCRC=988340972, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='TopicTest', flag=0, properties={MIN_OFFSET=0, MAX_OFFSET=250, CONSUME_START_TIME=1659601275866, UNIQ_KEY=AC11000176396FF3C5B512F37A6D0007, CLUSTER=DefaultCluster, WAIT=true, TAGS=TagA}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 55], transactionId='null'}]]
ConsumeMessageThread_4 Receive New Messages: [MessageExt [brokerName=linux1, queueId=2, storeSize=202, queueOffset=2, sysFlag=0, bornTimestamp=1659601146500, bornHost=/192.168.0.101:48216, storeTimestamp=1659601146501, storeHost=/172.17.0.1:10911, msgId=AC11000100002A9F00000000000008A4, commitLogOffset=2212, bodyCRC=2088767104, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='TopicTest', flag=0, properties={MIN_OFFSET=0, MAX_OFFSET=250, CONSUME_START_TIME=1659601275867, UNIQ_KEY=AC11000176396FF3C5B512F37A84000B, CLUSTER=DefaultCluster, WAIT=true, TAGS=TagA}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 49, 49], transactionId='null'}]]
消息发送完毕之后就会退出,在同一窗口中可以使用消费者类来进行接收消息,消费是多线程的。
与启动顺序相反进行关闭,先关闭 broker、在关闭 nameserv
./mqshutdown broker
./mqshutdown namesrv
如上图所示,整体可以分成4个角色,分别是:NameServer,Broker,Producer,Consumer。
下载地址:https://github.com/apache/rocketmq-externals/tags
下载rocketmq-console-1.0.0:https://codeload.github.com/apache/rocketmq-externals/zip/refs/tags/rocketmq-console-1.0.0
解压后按照
rocketmq-externals-rocketmq-console-1.0.0\rocketmq-console\pom.xml文件中的如下项创建一个maven工程,然后将解压的rocketmq-externals-rocketmq-console-1.0.0\rocketmq-console路径下所有文件复制到新创建的maven项目的跟路径下,刷新ieda即可。
<groupId>org.apache</groupId>
<artifactId>rocketmq-console-ng</artifactId>
<packaging>jar</packaging>
使用IDEA打开项目,修改其src/main/resources中的application.properties配置文件。
原来的端口号为8080,如果8080端口被占用,修改为一个不常用的端口。
指定RocketMQ的name server地址,集群环境为集群1:9876;集群2:9876;集群3:9876。
server.port=7777 #项目启动后的端口号
rocketmq.config.namesrvAddr=192.168.109.149:9876 #nameserv的地址,注意防火墙要开启
9876端口
\# 进入控制台项目,将工程打成jar包
mvn clean package -Dmaven.test.skip=true
\# 启动控制台
java -jar target/rocketmq-console-ng-1.0.0.jar
http://127.0.0.1:8080
接下来我们模拟一种场景: 下单成功之后,向下单用户发送短信。设计图如下:
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
rocketmq:
name-server: 192.168.109.149:9876
producer:
group: reposotory-server # 消息分组
@RestController
@RequestMapping("/mq")
public class RocketMQController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/send")
public void send(){
rocketMQTemplate.convertAndSend("repository-topic", "hello world from repository!!");
}
}
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
rocketmq:
name-server: 192.168.109.149:9876
@Service
@RocketMQMessageListener(consumerGroup = "reposotory-server", topic = "repository-topic")
public class SmsService implements RocketMQListener<String> {
private Logger logger = LoggerFactory.getLogger("user-server");
@Override
public void onMessage(String s) {
logger.info("收到一个订单信息{},接下来发送短信"+s);
}
}
其中,@RocketMQMessageListener注解表示这是一个消息监听器,用于监听指定的topic和consumerGroup的消息。RocketMQListener是Spring RocketMQ Starter提供的消息监听器接口,可以实现onMessage方法来处理接收到的消息。
除了发送和接收String类型的消息,RocketMQ还支持发送和接收各种其他类型如User、Order等,具体类型与是心啊RocketMQListener接口的泛型一致即可。
RocketMQ提供三种方式来发送普通消息:可靠同步发送、可靠异步发送和单向发送。
同步发送是指消息发送方发出数据后,会在收到接收方发回响应之后才发下一个数据包的通讯方式。
此种方式应用场景非常广泛,例如重要通知邮件、报名短信通知、营销短信系统等可靠性要求高的的数据或者需要实时响应的数据。
异步发送是指发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。发送方通过回调接口接收服务器响应,并对响应结果进行处理。
异步发送一般用于链路耗时较长,对 RT 响应时间较为敏感的业务场景,例如用户视频上传后通知启动转码服务,转码完成后通知推送转码结果等。
单向发送是指发送方只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答。
适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = OrderApplication.class)
public class MessageTypeTest {
@Autowired
private RocketMQTemplate rocketMQTemplate;
//同步消息
@Test
public void testSyncSend() {
//参数一: topic, 如果想添加tag 可以使用"topic:tag"的写法
//参数二: 消息内容
SendResult sendResult =
rocketMQTemplate.syncSend("test-topic-1", "这是一条同步消息");
System.out.println(sendResult);
}
//异步消息
@Test
public void testSyncSendMsg() {
//参数一: topic, 如果想添加tag 可以使用"topic:tag"的写法
//参数二: 消息内容
//参数三: 回调函数, 处理返回结果
rocketMQTemplate.asyncSend("test-topic-1", "这是一条异步消息", new SendCallback({
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(sendResult);
}
@Override
public void onException(Throwable throwable) {
System.out.println(throwable);
}
});
//因为是异步发送,所以让线程不要终止以便测试
Thread.sleep(30000000);
}
//单向消息
@Test
public void testOneWay() {
rocketMQTemplate.sendOneWay("test-topic-1", "这是一条单向消息");
}
}
接收异步、同步消息可以使用RocketMQ的消息监听器。通过实现RocketMQListener接口来监听指定Topic上的消息,异步处理消息时不需要等待,示例代码如下:
@Component
@RocketMQMessageListener(topic = "test_topic", consumerGroup = "group_name", messageType = MessageType.STRING)
public class StringMessageListener implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println("接收到String类型的消息:" + message);
}
}
在上面的示例中,通过使用@RocketMQMessageListener注解指定了Topic和Consumer Group,消息被接收到后会自动调用onMessage方法进行处理。
需要注意的是,在异步接收消息时,RocketMQ会启动多个消费线程来处理消息,需要确保消息处理的线程安全性。
顺序消息是消息队列提供的一种严格按照顺序来发布和消费的消息类型
//同步顺序消息[异步顺序 单向顺序写法类似]
public void testSyncSendOrderly()
{
//第三个参数用于队列的选择
rocketMQTemplate.syncSendOrderly("test-topic-1", "这是一条异步顺序消息",
"xxxx");
}
在RocketMQ中,消息可以以广播模式发送,广播模式的消息会被所有订阅了该topic的消费者都接收到。
在消费者端设置消费组的名称,消费者监听以该名称为标识的消费组,这样同一消费组内的消费者将不会重复消费相同的消息。
下面是一个简单的广播模式下消息的发送和接收的示例:
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void sendBroadcastMsg(String msg) {
Message<String> message = MessageBuilder.withPayload(msg).build();
SendResult sendResult = rocketMQTemplate.syncSend("broadcast_topic", message);
System.out.printf("广播消息发送结果:MsgId:%s SendStatus:%s%n", sendResult.getMsgId(), sendResult.getSendStatus());
}
在上述代码中,通过调用RocketMQTemplate的syncSend方法实现对广播消息的同步发送。广播模式需要注意消息发送后的消费状态。
@Component
@RocketMQMessageListener(topic = "broadcast_topic", consumerGroup = "broadcast_consumer_group")
public class BroadcastMessageListener implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.printf("接收到广播消息: %s %n", message);
}
}
上述示例代码中,通过使用@RocketMQMessageListener注解指定了订阅的Topic和Consumer Group,实现对广播消息的接收。接收广播模式的消息时,消息广播到所有订阅该topic的消费者,每个消费者都会独立接收到广播的消息。
需要注意的是,广播消息可能会被重复消费,消费者收到广播消息后会自动提交offset,若后续有新的消费者加入,则会从消费组最早的消费offset位置重新消费。
可以使用Spring Boot提供的RocketMQTemplate来发送延时消息,具体代码如下:
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DelayMsgProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void sendMsg(String message) {
// 发送延时消息,设置延时10秒(即该消息将在10秒后被消费)
rocketMQTemplate.syncSend("my_topic", message,
3000, // 延时级别3,延时10秒
rocketMQTemplate.getProducer().getRetryTimesWhenSendFailed());
}
}
RocketMQTemplate提供了syncSend
方法,第三个参数是延时级别,第四个参数是消息发送失败时的重试次数。以上示例中延时级别为3,即延时10秒。
如果需要在Spring Boot应用中接收延时消息,推荐使用@RocketMQMessageListener注解,具体代码如下:
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
@Component
@RocketMQMessageListener(topic = "my_topic", consumerGroup = "my_group",
selectorExpression = "DELAY >= 3000")
public class DelayMsgConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println(message);
}
}
@RocketMQMessageListener
注解通过selectorExpression
属性设置只接收延时级别大于等于3的消息。当收到延时消息后,Spring会自动调用onMessage
方法来处理消息。
RocketMQ支持通过延时级别来控制消息的延时时间,延时级别设置在消息发送时,具体的延时时间由该延时级别对应的配置参数决定。RocketMQ定义了18个延时级别,级别从1到18,延时时间从1秒到2小时不等,各级别对应的延时时间和配置参数如下:
延时级别 | 延时时间 | 配置参数 |
---|---|---|
1 | 1s | delayLevel=1 |
2 | 5s | delayLevel=2 |
3 | 10s | delayLevel=3 |
4 | 30s | delayLevel=4 |
5 | 1m | delayLevel=5 |
6 | 2m | delayLevel=6 |
7 | 3m | delayLevel=7 |
8 | 4m | delayLevel=8 |
9 | 5m | delayLevel=9 |
10 | 6m | delayLevel=10 |
11 | 7m | delayLevel=11 |
12 | 8m | delayLevel=12 |
13 | 9m | delayLevel=13 |
14 | 10m | delayLevel=14 |
15 | 20m | delayLevel=15 |
16 | 30m | delayLevel=16 |
17 | 1h | delayLevel=17 |
18 | 2h | delayLevel=18 |
例如,若要设置一个延时30秒的消息,可以将延时级别设为4,参数设置为delayLevel=4
。
需要注意的是,这里提到的延时时间是指消息发送后到达Broker存储的时间,与消息从Broker发送到消费者端的时间无关。在实际使用过程中,考虑到网络延迟等因素,消息的最终消费时间可能会比预设的延时时间略晚。
下面是一个使用SpringBoot整合RocketMQ的示例代码,演示如何发送和接收批量消息。
发送批量消息:
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class BatchMsgProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void sendBatchMsg() {
List<String> messages = new ArrayList<>();
messages.add("Message 1");
messages.add("Message 2");
messages.add("Message 3");
messages.add("Message 4");
messages.add("Message 5");
try {
SendResult result = rocketMQTemplate.syncSend("my_topic", messages);
System.out.println("Batch message send result: " + result);
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
在以上示例中,使用了RocketMQTemplate的syncSend()方法,来发送批量消息。syncSend()方法的第二个参数可以是一个List集合,每个元素表示一条消息。具体使用时,可以将多条消息打包成一个List集合,然后将集合作为syncSend()方法的第二个参数。最后通过获取SendResult对象,来获取消息发送的结果信息。
接收批量消息的RocketMQListener:
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@RocketMQMessageListener(consumerGroup = "my_group", topic = "my_topic")
public class BatchMsgListener implements RocketMQListener<List<String>> {
@Override
public void onMessage(List<String> messages) {
System.out.println("Received batch message: " + messages);
}
}
在以上示例中,通过实现RocketMQListener接口,来接收List类型的批量消息。在重写的onMessage()方法中,处理接收到的批量消息。
需要特别注意的是,RocketMQListener的泛型需要根据发送的消息类型进行设置。如果发送的是String类型的批量消息,那么RocketMQListener的泛型就应该设置为List。如果发送的是其他类型的批量消息,比如自定义的对象,那么需要自定义RocketMQMessageConverter实现类,来将消息转换为指定的类型。
生产者:
@GetMapping("/sendFilterMsg")
public void sendFilterMsg() throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
Message msg = new Message("topName1","TagA","Hello MQ".getBytes());
DefaultMQProducer producer = new DefaultMQProducer("order-group2");
producer.setNamesrvAddr("192.168.109.149:9876");
producer.setSendLatencyFaultEnable(true);
producer.start();
SendResult result = producer.send(msg);
logger.info(result.getSendStatus().toString());
producer.shutdown();
}
消费者:
@Configuration
public class MqConsumerConfig {
private Logger logger = LoggerFactory.getLogger("MqConsumerConfig");
@Bean
public DefaultMQPushConsumer defaultMQPushConsumer() throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order-group2");
consumer.setNamesrvAddr("192.168.109.149:9876");
//订阅所有Tag
//consumer.subscribe("topName1","*");
//订阅多个Tag
// consumer.subscribe("topName1","TagA||TagB");
//订阅单个Tag
consumer.subscribe("topName1","TagA");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg : list){
logger.info("消息主题:"+msg.getTopic());
logger.info("消息主体:"+msg.getBody(),"utf-8");
logger.info("消息表情:"+msg.getTags());
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
return consumer;
}
}
同一个消费者多次订阅某个Topic下的Tag,以最后一次订阅的Tag为准。
RocketMQ提供了事务消息,通过事务消息就能达到分布式事务的最终一致。
事务消息交互流程:
两个概念:
事务消息发送步骤:
事务消息回查步骤:
//事物日志
@Entity(name = "shop_txlog")
@Data
public class TxLog {
@Id
private String txLogId;
private String content;
private Date date;
}
@Service
public class OrderServiceImpl4 {
@Autowired
private OrderDao orderDao;
@Autowired
private TxLogDao txLogDao;
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void createOrderBefore(Order order) {
String txId = UUID.randomUUID().toString();
//发送半事务消息
rocketMQTemplate.sendMessageInTransaction("tx_producer_group","tx_topic",MessageBuilder.withPayload(order).setHeader("txId",txId).build(),order);
}
//本地事物
@Transactional
public void createOrder(String txId, Order order) {
//本地事物代码
orderDao.save(order);
//记录日志到数据库,回查使用
TxLog txLog = new TxLog();
txLog.setTxLogId(txId);
txLog.setContent("事物测试");
txLog.setDate(new Date());
txLogDao.save(txLog);
}
}
@RocketMQTransactionListener(txProducerGroup = "tx_producer_group")
public class OrderServiceImpl4Listener implements RocketMQLocalTransactionListener {
@Autowired
private TxLogDao txLogDao;
@Autowired
private OrderServiceImpl4 orderServiceImpl4;
//执行本地事物
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg,Object arg) {
try {
//本地事物
orderServiceImpl4.createOrder((String) msg.getHeaders().get("txId"),(Order) arg);
return RocketMQLocalTransactionState.COMMIT;
}
catch (Exception e) {
return RocketMQLocalTransactionState.ROLLBACK;
}
}
//消息回查
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
//查询日志记录
TxLog txLog = txLogDao.findById((String)
msg.getHeaders().get("txId")).get();
if (txLog == null) {
return RocketMQLocalTransactionState.COMMIT;
} else {
return RocketMQLocalTransactionState.ROLLBACK;
}
}
@RocketMQMessageListener(
consumerGroup = "shop",//消费者分组
topic = "order-topic",//要消费的主题
consumeMode = ConsumeMode.CONCURRENTLY, //消费模式:无序和有序
messageModel = MessageModel.CLUSTERING, //消息模式:广播和集群,默认是集群
)
public class SmsService implements RocketMQListener<Order> {
}
RocketMQ支持两种消息模式:
RocketMQ设计之初就考虑了消息的可靠性传输,提供了多种方式来保证消息的不丢失。具体的实现机制包括消息复制、消息确认和消息重试等。
RocketMQ的消息复制机制采用主从同步方式,主节点负责将消息发送给所有的从节点,从节点接收到消息后进行持久化,并返回ACK确认消息。只有当所有从节点都返回ACK确认消息时,主节点才会将该消息标记为已发送,并返回ACK确认消息。
由于RocketMQ采用主从同步方式,当主节点发送消息失败时,从节点可以接替主节点继续进行消息发送。这种机制可以保证消息的不丢失,并提高消息的可用性。
RocketMQ的消息确认机制通过ACK确认机制来实现。发送方发送消息后,会在一定时间内等待接收方的ACK确认消息。如果在指定的时间内未收到ACK确认消息,则会认为消息发送失败,并进行重发等操作。
消息确认机制可以有效保证消息的可靠性,但会对消息发送的性能产生一定的影响。在实际开发中,可以根据需求进行灵活的设置。
使用消息确认机制可以保证消息的可靠性,即只有在消息被Broker接收并成功处理后才会返回Ack应答,否则会进行消息重发,直到成功为止。
在RocketMQ中,消息确认机制有两种实现方式:同步和异步确认。
spring.rocketmq.producer.send-msg-async=false
rocketmq.producer.send-timeout=5000 # 超时时间
rocketmq.producer.retry-times-when-send-failed=2 #重试次数
@Service
public class MQProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void sendMsg(String topic, String message) throws RocketMQException {
Message msg = new Message(topic, message.getBytes());
SendResult result = rocketMQTemplate.syncSend(msg);
System.out.printf("消息:%s 发送状态:%s,消息ID:%s,队列:%s \n",
message, result.getSendStatus(), result.getMsgId(), result.getMessageQueue());
}
}
异步确认是指发送消息后,不会立即等待Broker返回Ack应答,而是注册一个回调函数,在收到Broker返回Ack应答后,立即执行回调函数。
可以通过RocketMQTemplate的asyncSend()方法实现。
rocketmq.producer.send-timeout=5000 # 超时时间
rocketmq.producer.retry-times-when-send-failed=2 #重试次数
示例代码如下:
Message msg = new Message("topic", "tag", "Hello World".getBytes());
rocketMQTemplate.asyncSend(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("Send success: " + sendResult);
}
@Override
public void onException(Throwable e) {
e.printStackTrace();
}
});
在上述代码中,我们使用了RocketMQTemplate的asyncSend()方法,该方法会将消息异步发送到Broker,并注册一个回调函数,在收到Broker返回Ack应答后,立即执行回调函数。如果发送成功,将会执行onSuccess()方法,打印发送结果;否则将会执行onException()方法,打印异常信息。
在Spring Boot整合RocketMQ中,我们可以通过配置RocketMQ的消息监听器来实现Ack应答。
rocketmq.producer.send-timeout=5000 # 超时时间
rocketmq.producer.retry-times-when-send-failed=2 #重试次数
假设你的业务处理成功,应该返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS来表示成功消费该消息并确认。如果你的业务处理失败,可以考虑返回ConsumeConcurrentlyStatus.RECONSUME_LATER来表示需要稍后再次尝试消费该消息。
@Component
public class RocketMQListener implements RocketMQListenerConcurrently {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
try {
// 处理消息
System.out.println("Received messages: " + msgs);
// 处理成功,返回ACK应答
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (Exception e) {
// 处理失败,稍后重新消费
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
}
RocketMQ的消息重试机制可以帮助开发者解决消息发送失败的问题。当消息发送失败时,RocketMQ会进行自动重发,直至消息发送成功或达到最大重试次数后放弃。
消息重试机制可以保证消息的可靠性,但也会对消息的处理产生一定的延迟。在实际开发中,需要适量使用消息重试机制,以平衡消息可靠性和处理效率。
综上所述,RocketMQ通过多种机制来保证消息的不丢失,具有高可靠性和高可用性。开发者需要根据实际需求,灵活使用这些机制,以达到更好的消息处理效果。
在SpringBoot中,可以使用RocketMQ提供的MessageListenerConcurrently接口来实现消费者。在重写MessageListenerConcurrently接口的onMessage()方法时,可以设置每秒从MQ获取10条消息,具体实现可以使用RateLimiter限流工具类进行控制。
以下是示例代码,演示如何实现每秒从MQ获取10条消息:
import com.google.common.util.concurrent.RateLimiter;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class MyConcurrentMessageListener implements MessageListenerConcurrently {
private RateLimiter rateLimiter = RateLimiter.create(10.0); // 每秒获取10条消息
@Override
public ConsumeConcurrentlyStatus onMessage(List<MessageExt> messages, ConsumeConcurrentlyContext context) {
if (rateLimiter.tryAcquire(messages.size())) { // 限流
for (MessageExt message : messages) {
String topic = message.getTopic();
String tags = message.getTags();
String msg = new String(message.getBody());
System.out.println("Received message: topic=" + topic + ", tags=" + tags + ", body=" + msg);
}
} else {
System.out.println("Too many messages, throttling...");
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
在以上示例中,使用了Guava提供的RateLimiter类来实现限流功能,可以设置每秒获取10条消息。在重写的onMessage()方法中,接收到消息后先使用tryAcquire()方法进行限流,并根据消息数量输出相关信息。
需要特别注意的是,在使用RateLimiter时需要合理设置流速,避免对RocketMQ服务器造成影响。推荐根据实际情况进行调整。