Spring Cloud Alibaba(八) 消息驱动

文章目录

  • 一、MQ简介
    • (一)什么是MQ
    • (二)MQ的应用场景
      • 1、异步解耦
      • 2、流量削峰
      • 3、数据分发
    • (三)常见的MQ产品
      • 1、ZeroMQ
      • 2、RabbitMQ
      • 3、ActiveMQ
      • 4、RocketMQ
      • 5、Kafka
  • 二、RocketMQ入门
    • (一)RocketMQ的角色
    • (二)RocketMQ基本概念
    • (三)RocketMQ安装部署
      • 1、环境要求
      • 2、下载RocketMQ
      • 3、安装RocketMQ
      • 4、启动RocketMQ
      • 5、测试RocketMQ
      • 6、关闭RocketMQ
    • (二) RocketMQ的架构及概念
    • (三)RocketMQ控制台安装与启动
      • 1、下载并解压
      • 2、导入idea
      • 3、修改配置文件
      • 4、打成jar包,并启动,或者直接idea运行
      • 5、访问控制台
  • 三、springcloud集成rocketmq
    • (一)订单微服务-发送消息
      • 1、pom添加依赖
      • 2、application.yml配置
      • 3、controller发送消息
    • (二)用户微服务-订阅消息
      • 1、pom添加依赖
      • 2、application.yml配置
      • 3、消息接收服务
    • (三)控制台
  • 四、不同类型的消息发送与接收
    • (一)普通消息
      • 1、可靠同步发送
      • 2、可靠异步发送
      • 3、单向发送
      • 4、发消息代码案例
      • 5、三种发送方式的对比
    • (二)顺序消息
    • (三)广播模式
    • (四)延时消息
    • (五)批量消息
    • (六)过滤消息
      • 1、Tag过滤
      • 2、SQL属性过滤
    • (七)事务消息
    • (八)消息消费要注意的细节
    • (九)rocketMQ如何保证消息不丢失
      • 1. 消息复制
      • 2. 消息确认
        • 同步确认
        • 异步确认
        • 消息消费者
      • 3. 消息重试
    • (十)rocketMQ消息持久化
    • (十一)rocketMQ实现削峰填谷


参考文档:https://help.aliyun.com/document_detail/29543.html?spm=a2c4g.29543.0.0.24e6e4e86BFvl5#section-6p0-n6o-oty

一、MQ简介

(一)什么是MQ

MQ(Message Queue)是一种跨进程的通信机制,用于传递消息。通俗点说,就是一个先进先出的数据结构。
Spring Cloud Alibaba(八) 消息驱动_第1张图片
Spring Cloud Alibaba(八) 消息驱动_第2张图片

(二)MQ的应用场景

1、异步解耦

最常见的一个场景是用户注册后,需要发送注册邮件和短信通知,以告知用户注册成功。传统的做法如下:
Spring Cloud Alibaba(八) 消息驱动_第3张图片
此架构下注册、邮件、短信三个任务全部完成后,才返回注册结果到客户端,用户才能使用账号登录。
但是对于用户来说,注册功能实际只需要注册系统存储用户的账户信息后,该用户便可以登录,而后续的注册短信和邮件不是即时需要关注的步骤。
所以实际当数据写入注册系统后,注册系统就可以把其他的操作放入对应的消息队列 MQ 中然后马上返回用户结果,由消息队列 MQ 异步地进行这些操作。架构图如下:
Spring Cloud Alibaba(八) 消息驱动_第4张图片
异步解耦是消息队列 MQ 的主要特点,主要目的是减少请求响应时间和解耦。主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时,由于使用了消息队列MQ,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦合。
Spring Cloud Alibaba(八) 消息驱动_第5张图片

2、流量削峰

流量削峰也是消息队列 MQ 的常用场景,一般在秒杀或团队抢购(高并发)活动中使用广泛。在秒杀或团队抢购活动中,由于用户请求量较大,导致流量暴增,秒杀的应用在处理如此大量的访问流量后,下游的通知系统无法承载海量的调用量,甚至会导致系统崩溃等问题而发生漏通知的情况。为解决这些问题,可在应用和下游通知系统之间加入消息队列 MQ。
Spring Cloud Alibaba(八) 消息驱动_第6张图片
秒杀处理流程如下所述:

  • 用户发起海量秒杀请求到秒杀业务处理系统。
  • 秒杀处理系统按照秒杀处理逻辑将满足秒杀条件的请求发送至消息队列 MQ。
  • 下游的通知系统订阅消息队列 MQ 的秒杀相关消息,再将秒杀成功的消息发送到相应用户。
  • 用户收到秒杀成功的通知。
    Spring Cloud Alibaba(八) 消息驱动_第7张图片

3、数据分发

Spring Cloud Alibaba(八) 消息驱动_第8张图片

(三)常见的MQ产品

1、ZeroMQ

号称最快的消息队列系统,尤其针对大吞吐量的需求场景。扩展性好,开发比较灵活,采用C语言实现,实际上只是一个socket库的重新封装,如果做为消息队列使用,需要开发大量的代码。ZeroMQ仅提供非持久性的队列,也就是说如果down机,数据将会丢失。

2、RabbitMQ

使用erlang语言开发,性能较好,适合于企业级的开发。但是不利于做二次开发和维护。

3、ActiveMQ

历史悠久的Apache开源项目。已经在很多产品中得到应用,实现了JMS1.1规范,可以和spring-jms轻松融合,实现了多种协议,支持持久化到数据库,对队列数较多的情况支持不好。

4、RocketMQ

阿里巴巴的MQ中间件,由java语言开发,性能非常好,能够撑住双十一的大流量,而且使用起来很简单。

5、Kafka

Kafka是Apache下的一个子项目,是一个高性能跨语言分布式Publish/Subscribe消息队列系统,相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。

二、RocketMQ入门

RocketMQ是阿里巴巴开源的分布式消息中间件,现在是Apache的一个顶级项目。在阿里内部使用非常广泛,已经经过了"双11"这种万亿级的消息流转。

(一)RocketMQ的角色

Spring Cloud Alibaba(八) 消息驱动_第9张图片

(二)RocketMQ基本概念

Spring Cloud Alibaba(八) 消息驱动_第10张图片

(三)RocketMQ安装部署

接下来我们先在linux平台下安装一个RocketMQ的服务

1、环境要求

  • Linux 64位操作系统
  • 64bit JDK 1.8+

2、下载RocketMQ

https://rocketmq.apache.org/release-notes/2023/03/26/4.9.5
Spring Cloud Alibaba(八) 消息驱动_第11张图片
下载时要注意与springcloudalibaba版本匹配
版本说名链接地址
Spring Cloud Alibaba(八) 消息驱动_第12张图片

3、安装RocketMQ

  1. 上传文件到Linux系统
  2. 解压到安装目录
[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
  1. 修改RocketMQ启动配置
    bin 下的 3 个配置文件不然会报insufficient memory:
  • runserver.sh
  • runbroker.sh
  • tools.sh

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

4、启动RocketMQ

  1. 启动NameServer
    执行命令启动NameServer
## 创建日志目录 
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 环境变量

  1. 启动Broker
    同样进入 RocketMQ 安装目录下的 /bin目录进行操作
    执行启动命令,并且常驻内存,注意ip地址要配置成为服务的ip地址,保证地址以及端口能够访问。
# 启动命令,并且常驻内存:注意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 &

5、测试RocketMQ

发送/接收消息之前,需要告诉客户端(Producer、Consumer)名称服务器的位置,RocketMQ 提供了多种方法来实现这一点.

  • 编程方式,如:producer.setNamesrvAddr(“ip:port”)
  • Java 选项,如:rocketmq.namesrv.addr
  • 环境变量,如:NAMESRV_ADDR
  • HTTP 端点

  1. 测试消息发送
[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
  1. 测试消息接收
[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'}]] 

消息发送完毕之后就会退出,在同一窗口中可以使用消费者类来进行接收消息,消费是多线程的。

6、关闭RocketMQ

与启动顺序相反进行关闭,先关闭 broker、在关闭 nameserv

./mqshutdown broker
./mqshutdown namesrv

(二) RocketMQ的架构及概念

Spring Cloud Alibaba(八) 消息驱动_第13张图片如上图所示,整体可以分成4个角色,分别是:NameServer,Broker,Producer,Consumer。

  • Broker(邮递员):Broker是RocketMQ的核心,负责消息的接收,存储,投递等功能
  • NameServer(邮局):消息队列的协调者,Broker向它注册路由信息,同时Producer和Consumer向其获取路由信息Producer(寄件人)消息的生产者,需要从NameServer获取Broker信息,然后与Broker建立连接,向Broker发送消息
  • Consumer(收件人)
    :消息的消费者,需要从NameServer获取Broker信息,然后与Broker建立连接,从Broker获取消息
  • Topic(地区):用来区分不同类型的消息,发送和接收消息前都需要先创建Topic,针对Topic来发送和接收消息MessageQueue(邮件)为了提高性能和吞吐量,引入了Message Queue,一个Topic可以设置一个或多个MessageQueue,这样消息就可以并行往各个Message Queue发送消息,消费者也可以并行的从多个Message Queue读取消息
  • Message:Message 是消息的载体。
  • Producer Group:生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组。
  • Consumer Group:消费者组,消费同一类消息的多个 consumer 实例组成一个消费者组。

(三)RocketMQ控制台安装与启动

1、下载并解压

下载地址:https://github.com/apache/rocketmq-externals/tags

Spring Cloud Alibaba(八) 消息驱动_第14张图片

下载rocketmq-console-1.0.0:https://codeload.github.com/apache/rocketmq-externals/zip/refs/tags/rocketmq-console-1.0.0

在这里插入图片描述

2、导入idea

解压后按照
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>

3、修改配置文件

使用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端口

Spring Cloud Alibaba(八) 消息驱动_第15张图片

4、打成jar包,并启动,或者直接idea运行

\# 进入控制台项目,将工程打成jar包
mvn clean package -Dmaven.test.skip=true
\# 启动控制台
java -jar target/rocketmq-console-ng-1.0.0.jar

5、访问控制台

http://127.0.0.1:8080

Spring Cloud Alibaba(八) 消息驱动_第16张图片

三、springcloud集成rocketmq

接下来我们模拟一种场景: 下单成功之后,向下单用户发送短信。设计图如下:
在这里插入图片描述

(一)订单微服务-发送消息

1、pom添加依赖

        <dependency>
            <groupId>org.apache.rocketmqgroupId>
            <artifactId>rocketmq-spring-boot-starterartifactId>
            <version>2.2.2version>
        dependency>

2、application.yml配置

rocketmq:
  name-server: 192.168.109.149:9876
  producer:
    group: reposotory-server # 消息分组

3、controller发送消息

@RestController
@RequestMapping("/mq")
public class RocketMQController {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping("/send")
    public void send(){
        rocketMQTemplate.convertAndSend("repository-topic", "hello world from repository!!");
    }
}

(二)用户微服务-订阅消息

1、pom添加依赖

        <dependency>
            <groupId>org.apache.rocketmqgroupId>
            <artifactId>rocketmq-spring-boot-starterartifactId>
            <version>2.2.2version>
        dependency>

2、application.yml配置

rocketmq:
  name-server: 192.168.109.149:9876

3、消息接收服务

@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接口的泛型一致即可。

(三)控制台

在控制台可以查看到主题,可以在控制台发送消息
Spring Cloud Alibaba(八) 消息驱动_第17张图片

四、不同类型的消息发送与接收

(一)普通消息

RocketMQ提供三种方式来发送普通消息:可靠同步发送、可靠异步发送和单向发送。
Spring Cloud Alibaba(八) 消息驱动_第18张图片

1、可靠同步发送

同步发送是指消息发送方发出数据后,会在收到接收方发回响应之后才发下一个数据包的通讯方式。
此种方式应用场景非常广泛,例如重要通知邮件、报名短信通知、营销短信系统等可靠性要求高的的数据或者需要实时响应的数据。

2、可靠异步发送

异步发送是指发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。发送方通过回调接口接收服务器响应,并对响应结果进行处理。
异步发送一般用于链路耗时较长,对 RT 响应时间较为敏感的业务场景,例如用户视频上传后通知启动转码服务,转码完成后通知推送转码结果等。

3、单向发送

单向发送是指发送方只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答。
适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。

4、发消息代码案例

@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", "这是一条单向消息");
	}
}

5、三种发送方式的对比

Spring Cloud Alibaba(八) 消息驱动_第19张图片

接收异步、同步消息可以使用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会启动多个消费线程来处理消息,需要确保消息处理的线程安全性。

(二)顺序消息

顺序消息是消息队列提供的一种严格按照顺序来发布和消费的消息类型
Spring Cloud Alibaba(八) 消息驱动_第20张图片
//同步顺序消息[异步顺序 单向顺序写法类似]

public void testSyncSendOrderly()
{
	//第三个参数用于队列的选择
	rocketMQTemplate.syncSendOrderly("test-topic-1", "这是一条异步顺序消息",
	"xxxx");
}

(三)广播模式

在RocketMQ中,消息可以以广播模式发送,广播模式的消息会被所有订阅了该topic的消费者都接收到。
在消费者端设置消费组的名称,消费者监听以该名称为标识的消费组,这样同一消费组内的消费者将不会重复消费相同的消息。
下面是一个简单的广播模式下消息的发送和接收的示例:

  1. 发送广播模式的消息
@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方法实现对广播消息的同步发送。广播模式需要注意消息发送后的消费状态。

  1. 接收广播模式的消息
@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实现类,来将消息转换为指定的类型。

(六)过滤消息

1、Tag过滤

生产者:

@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为准。

2、SQL属性过滤

(七)事务消息

RocketMQ提供了事务消息,通过事务消息就能达到分布式事务的最终一致。
事务消息交互流程:
Spring Cloud Alibaba(八) 消息驱动_第21张图片

两个概念:

  • 半事务消息:暂不能投递的消息,发送方已经成功地将消息发送到了RocketMQ服务端,但是服务端未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,处于该种状态下的消息即半事务消息。
  • 消息回查:由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,RocketMQ服务端通过扫描发现某条消息长期处于“半事务消息”时,需要主动向消息生产者询问该消息的最终状态(Commit
    或是 Rollback),该询问过程即消息回查。

事务消息发送步骤:

  1. 发送方将半事务消息发送至RocketMQ服务端。
  2. RocketMQ服务端将消息持久化之后,向发送方返回Ack确认消息已经发送成功,此时消息为半事务消息。
  3. 发送方开始执行本地事务逻辑。
  4. 发送方根据本地事务执行结果向服务端提交二次确认(Commit 或是 Rollback),服务端收到Commit 状态则将半事务消息标记为可投递,订阅方最终将收到该消息;服务端收到 Rollback 状态则删除半事务消息,订阅方将不会接受该消息。

事务消息回查步骤:

  1. 在断网或者是应用重启的特殊情况下,上述步骤4提交的二次确认最终未到达服务端,经过固定时间后服务端将对该消息发起消息回查。
  2. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  3. 发送方根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行操作。
//事物日志
@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设计之初就考虑了消息的可靠性传输,提供了多种方式来保证消息的不丢失。具体的实现机制包括消息复制、消息确认和消息重试等。

1. 消息复制

RocketMQ的消息复制机制采用主从同步方式,主节点负责将消息发送给所有的从节点,从节点接收到消息后进行持久化,并返回ACK确认消息。只有当所有从节点都返回ACK确认消息时,主节点才会将该消息标记为已发送,并返回ACK确认消息。

由于RocketMQ采用主从同步方式,当主节点发送消息失败时,从节点可以接替主节点继续进行消息发送。这种机制可以保证消息的不丢失,并提高消息的可用性。

2. 消息确认

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;
        }
    }
}

3. 消息重试

RocketMQ的消息重试机制可以帮助开发者解决消息发送失败的问题。当消息发送失败时,RocketMQ会进行自动重发,直至消息发送成功或达到最大重试次数后放弃。

消息重试机制可以保证消息的可靠性,但也会对消息的处理产生一定的延迟。在实际开发中,需要适量使用消息重试机制,以平衡消息可靠性和处理效率。

综上所述,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服务器造成影响。推荐根据实际情况进行调整。

你可能感兴趣的:(python,开发语言)