【RocketMQ】SpringBoot整合与使用RocketMQ(Windows)

本文详细介绍了如何在SpringBoot应用中集成和使用RocketMQ,包括生产者和消费者的配置、消息属性、运行逻辑、消息收发模式,以及事务消息和监听器的使用。同时,提到了消息流转模型和示例项目的获取与运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于是开发相关的文档,因此默认读者已经具备有IDE,编者使用的是IDEA 2022.1.3以该版本作为演示基础。

Spring引入RocketMQ

在适配Spring生态方面,RocketMQ由于本身就是Java语言开发并且开源,拥有Spring开发基础就能够参照源码进行调试,并且其本身也提供了可直接用于SpringBoot整合的父项目,pom.xml中通过如下配置即可引入该父项目。

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

学习SpringRocketMQ范例

RocketMQ对于Spring拥有良好的适配,并且拥有对应的参考项目,接下来我们借助该项目了解一下RocketMQ的架构及用法。

基本概念

首先,为了方便理解,我们需要了解一下RocketMQ中的一些基本概念、运行逻辑与名词,方便后续理解掌握。

该章节整理自文章 RocketMQ 简介-阿里云开发者社区 (aliyun.com) 原文更加详细,可通过链接了解详情。

专有名词

基本
  • NameServer:注册中心,相当于ZooKeeper,不过RocketMQ进行了另外的实现,比较轻便。
  • Broker:消息的管理中心,其负责管理Topic与保存传入的消息,并且会将自己注册到NameServer中供其调度,其由RocketMQ实现。
  • Producer:也称为消息发布者,负责产生消息,需要我们自行产生消息。
  • Consumer:也称为消息订阅者,负责接收消息并处理消息,需要我们自行处理消息。
消息属性相关
  • Topic:消息主题,一级消息类型,生产者向其发送消息,消费者从中取出消息进行消费。
  • Message:生产者向Topic发送并最终传送给消费者的数据消息体。
  • Message ID:消息的全局唯一标识,由消息队列RocketMQ系统自动生成,唯一标识某条消息。
  • Message Key:消息的业务标识,由消息生产者(Producer)自行设置,可作为标识符使用标识一类业务逻辑,也可不提供。
  • Tag:消息标签,二级消息类型,由消息生产者(Producer)自行设置,用来进一步区分某个Topic下的消息
运行逻辑相关
  • 分区:即Topic Partition,物理上的概念。每个Topic包含一个或多个分区,由RocketMQ实现。
  • 消费位点:每个Topic会有多个分区,每个分区会统计当前消息的总条数,这个称为最大位点MaxOffset;分区的起始位置对应的位置叫做起始位点MinOffset。
  • Group:对生产者或消费者进行分组,同一组的生产者或消费者通常仅生产或消费同一类消息,且消息发布或订阅的逻辑一致,因此通常需要保证同组中的生产者与生产者之间或者消费者与消费者之间业务逻辑一致。在 集群消费 模式下,该设计方式能够方便地对于生产者与消费者进行拓展。比如当某组中任务负载较大时,给组中额外增加多个生产者与消费者来分担该组的任务,降低负载。
  • Group ID:Group的唯一标识,用于定位Group,可以自行定制。
  • 队列:一个Topic下会由一到多个队列来存储消息,其由RocketMQ实现。
  • Exactly-Once投递语义:Exactly-Once投递语义是指发送到消息系统的消息只能被Consumer处理且仅处理一次,即使Producer重试消息发送导致某消息重复投递,该消息在Consumer也只被消费一次。
消息收发相关
  • 集群消费:一个Group ID所标识的所有Consumer平均分摊消费消息。例如某个Topic有9条消息,一个Group ID有3个Consumer实例,那么在集群消费模式下每个实例平均分摊,只消费其中的3条消息。
  • 广播消费:一个Group ID所标识的所有Consumer都会各自消费某条消息一次。例如某个Topic有9条消息,一个Group ID有3个Consumer实例,那么在广播消费模式下每个实例都会各自消费9条消息。
  • 定时消息:Producer将消息发送到消息队列RocketMQ服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到Consumer进行消费,该消息即定时消息。
  • 延时消息:Producer将消息发送到消息队列RocketMQ服务端,但并不期望这条消息立马投递,而是延迟一定时间后才投递到Consumer进行消费,该消息即延时消息。
  • 事务消息:RocketMQ提供类似X/Open XA的分布事务功能,通过消息队列RocketMQ的事务消息能达到分布式事务的最终一致。
  • 顺序消息:RocketMQ提供的一种按照顺序进行发布和消费的消息类型,分为全局顺序消息和分区顺序消息。
    • 全局顺序消息:对于指定的一个Topic,所有消息按照严格的先入先出(FIFO)的顺序进行发布和消费。
    • 分区顺序消息:对于指定的一个Topic,所有消息根据Sharding Key进行区块分区。同一个分区内的消息按照严格的FIFO顺序进行发布和消费。Sharding Key是顺序消息中用来区分不同分区的关键字段,和普通消息的Message Key是完全不同的概念。
概念相关
  • 消息堆积:Producer已经将消息发送到消息队列RocketMQ的服务端,但由于Consumer消费能力有限,未能在短时间内将所有消息正确消费掉,此时在消息队列RocketMQ的服务端保存着未被消费的消息,该状态即消息堆积。
  • 消息过滤:Consumer可以根据消息标签(Tag)对消息进行过滤,确保Consumer最终只接收被过滤后的消息类型。消息过滤在消息队列RocketMQ的服务端完成。
  • 消息轨迹:在一条消息从Producer发出到Consumer消费处理过程中,由各个相关节点的时间、地点等数据汇聚而成的完整链路信息。通过消息轨迹,您能清晰定位消息从Producer发出,经由消息队列RocketMQ服务端,投递给Consumer的完整链路,方便定位排查问题。
  • 重置消费位点:以时间轴为坐标,在消息持久化存储的时间范围内(默认3天),重新设置Consumer对已订阅的Topic的消费进度,设置完成后Consumer将接收设定时间点之后由Producer发送到消息队列RocketMQ服务端的消息。
  • 死信队列:死信队列用于处理无法被正常消费的消息。当一条消息初次消费失败,消息队列RocketMQ会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明Consumer在正常情况下无法正确地消费该消息。此时,消息队列RocketMQ不会立刻将消息丢弃,而是将这条消息发送到该Consumer对应的特殊队列中。
    消息队列RocketMQ将这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),将存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。

消息流转模型

消息队列RocketMQ支持发布和订阅模型,消息生产者应用创建Topic并将消息发送到Topic。消费者应用创建对Topic的订阅以便从其接收消息。通信可以是一对多(扇出)、多对一(扇入)和多对多。具体通信如下图所示。

消息流转模型

  • 生产者集群:用来表示发送消息应用,一个生产者集群下包含多个生产者实例,可以是多台机器,也可以是一台机器的多个进程,或者一个进程的多个生产者对象。
    一个生产者集群可以发送多个Topic消息。发送分布式事务消息时,如果生产者中途意外宕机,消息队列RocketMQ服务端会主动回调生产者集群的任意一台机器来确认事务状态。
  • 消费者集群:用来表示消费消息应用,一个消费者集群下包含多个消费者实例,可以是多台机器,也可以是多个进程,或者是一个进程的多个消费者对象。
    一个消费者集群下的多个消费者以均摊方式消费消息。如果设置的是广播方式,那么这个消费者集群下的每个实例都消费全量数据。
    一个消费者集群对应一个Group ID,一个Group ID可以订阅多个Topic,如上图中的Group 2所示。Group和Topic的订阅关系可以通过直接在程序中设置即可。

获取范例项目

在安装文档中涉及到的GitHub - apache/rocketmq-externals项目ReadMe中也提及到了一个关联Spring的RocketMQ项目。
拓展项目

项目地址如下GitHub - apache/rocketmq-spring

拓展项目2

rocketmq-spring-boot-samples中则是基于Spring搭建的RocketMQ的范例项目,包含生产者与消费者demo,可以进行参考学习。

拓展项目3

由于该项目未依赖父项目,可以单独将其拉下来运行,也可以将该rocketmq-spring项目完整拉取下来使用。

单独拉取

项目拉取下来后使用IDE打开,IDEA会自动加载rocketmq-spring-boot-starter的依赖,此时可能会出现依赖版本问题。

下载样例项目

当前版本的pom,此处给rocketmq-spring-boot-starter配置的是2.2.3快照版本,但是在maven仓库中已经修改2.2.3为正式的版本号,rocketmq-spring-boot-starter源码版本也已经定义为2.2.4-SNAPSHOT,此处应该是版本升级的疏漏。

下载样例项目2

对应修改一下版本号即可,修改为2.2.3直接重新运行一下maven导入。

下载样例项目3

子项目均被解析为maven项目(项目图标右下角带蓝色小方格即表示已被解析为maven项目,且其中的文件结构不再是普通的文件夹结构,而是已包结构排列,并且特定的文件夹具有特定图标,如java、resources、test等),并且依赖项不报红,代表项目导入成功。

下载样例项目4

完整拉取

项目拉取下来之后使用IDE打开,等待其安装依赖项,防止依赖项安装失败,或者找不到依赖项可以使用阿里的maven镜像。

<mirrors>
    <mirror>
          <id>alimaven</id>
          <name>aliyun maven</name>
          <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
          <mirrorOf>central</mirrorOf>        
    </mirror>
</mirrors>

项目导入成功。

下载样例项目5

编者此处samples并没有被解析为maven,是由于在根目录的pom.xml中,该模块被默认注释掉了。

下载样例项目6

将module标签处的注释放开然后重新跑maven一遍,maven就会去解析rocketmq-spring-boot-samples下的pom.xml文件。此时可能会遇到与上文 单独拉取 时相同的情况,进入到rocketmq-spring-boot-samples下的pom中采用相同方式解决即可。

补充说明

此处补充一些范例项目中出现的不常见的且不涉及RocketMQ逻辑的代码的解析,方便对范例项目进行理解。

CommandLineRunner

在生产者与消费者项目中都使用了CommandLineRunner接口。这里来探究以下该接口的效果。

在启动spring应用时,会使用到如下的基本的spring启动代码SpringApplication.run(ProducerApplication.class, args),进入到该方法中可以看到在经过多个默认的run方法后最终调用到了一个run方法中。

run

我们可以看到在该方法中spring进行了一系列环境的初始化操作,将spring环境运行起来。这部分的逻辑都不重要。

在try代码段的末尾,我们可以看到其调用了一段callRunners(context, applicationArguments)方法,传入了spring容器对象以及通过启动参数args构造出来的ApplicationArguments参数对象。

进入到该方法中可以看到如下方法体。

run2

可以推出该方法的逻辑是将ApplicationRunner.class与CommandLineRunner.class构造出来的Bean添加到runners列表中,排序后依次将运行参数传入然后进行调用。

简而言之,在启动Spring应用后,会在应用装配完毕时,运行CommandLineRunner与ApplicationRunner中的代码。并且该运行时机是要晚于InitializingBean的,能够确保所有Bean均已加载完毕。可以用来作为Spring应用启动后的初始化数据的方案,因为bean已装填完毕,可以正常使用Spring的功能。

需要注意的是此处的代码是影响到Spring主线程的,如果出现异常会打断Spring应用的启动过程。

在demo中的效果就是,对应的方法仅仅在启动时运行一遍,里面涉及到的就是一些演示代码。

理解范例项目

生产者(Producer)

@ExtRocketMQTemplateConfiguration

该类是一个注解类,其携带了Spring中的@Component类,因此被该注解修饰的类会被Spring解析为一个bean组件,可以直接通过spring bean的一些方式来使用。

很容易就能看出,该注解是用来修饰RocketMQTemplate及其拓展类,并且主要修饰发送方,即生产者端的RocketMQTemplate。该注解能够为RocketMQTemplate及其拓展类规定一些默认的运行参数,生产者demo中就使用了其中的nameServer参数用于指定目标NameServer地址,使用了tlsEnable用于指定是否开启tls加密,使用instanceName指定实例名称。

RocketMQTemplate

在生产者demo中,使用了该类中非常多种的发送方法,并且该类大多数方法都是用于生产者发送消息。因此将该类放到生产者部分讲解。需要注意的是对于每一类功能逻辑而言,应该单独拓展一个RocketMQTemplate子类,因为当使用事务消息时,事务监听器是直接与RocketMQTemplate类进行绑定的,因此为了防止事务状态紊乱出错,推荐每个功能模块单独拓展一个RocketMQTemplate子类,后续会进行详述。因为该类中的方法众多,为了节省篇幅且便于拓展理解,这里对其进行大概的分类解释,便于自行了解这些方法的含义。

常见参数
  • destination:用来定位当前参数需要发送到哪个Topic,以及携带什么Tag,标准格式为topicName:tags(文档注释中此处为tags,但是据查生产者是无法为消息指定多个tag的)。
    例如:“test:tag1”,表示当前消息将会发送到名为test的Topic中,并且tag为tag1。

  • payload:消息载体,可以接收任何类对象,在发送前一般都会经过MessageBuilder.withPayload(payload)转化为Message对象。

  • message/messages:一般是Message类对象,发送动作主要就是通过该类对象来进行,可以通过MessageBuilder获得

  • timeout:发送超时时间,单位毫秒,当不指定时一般默认为3秒。

  • delayLevel:延时级别,该参数不支持任意时间精度,仅支持特定的 level,例如定时 5s,10s,1m等。其中level=0级表示不延时,level=1 表示 1 级延时,level=2 表示2级延时,以此类推。
    延时级别的配置在broker配置(ROCKETMQ根路径/conf/broker.conf)中messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h。时间单位支持:s、m、h、d,分别表示秒、分、时、天。

  • delayTime:延时时间,可以详细指定延时时间,默认单位为毫秒。

  • deliverTimeMills:交付时间,可以详细指定延时时间,默认单位为毫秒。

  • mode:与delayTime或deliverTimeMills同时出现,接收DelayMode枚举对象。具有三种值,分别是DELAY_SECONDS,DELAY_MILLISECONDS,DELIVER_TIME_MILLISECONDS。

    • 单独使用 deliverTimeMills时,mode默认为DelayMode.DELIVER_TIME_MILLISECONDS
    • 单独使用delayTime时,默认mode为DELAY_MILLISECONDS

    未查到二者的详细说明可能为新增api,判断二者区别可能在于延时投递的方式不同。

  • hashKey:用于为消息进行定位和排序。

  • type:一般出现于同步消息时,用于规定返回消息的类型。

  • sendCallback/rocketMQLocalRequestCallback:用于异步消息时的回调。

    • sendCallback:为SendCallback对象,其中接收返回值时使用的是SendResult对象,内部以byte数组保存信息。
    • rocketMQLocalRequestCallback:为RocketMQLocalRequestCallback<T>对象,返回值可以直接用泛型T接收,内部会自动转换成T。
同步与异步
  • 带sync的方法:表示同步方法,调用该方法之后会阻塞当前线程,直达该方法成功完成、超时或抛错。
    例如:

    SendResult sendResult = rocketMQTemplate.syncSend(springTopic, "Hello, World!");
    System.out.printf("syncSend1 to topic %s sendResult=%s %n", springTopic, sendResult);
    

    这里必须要等到syncSend返回了SendResult后才会来到下方的printf。

  • 带async的方法:表示异步方法,其中需要定义对应的回调,即SendCallback。调用该方法后并不会阻塞当前线程,当该方法处理成功或者失败是则会自行运行SendCallback中对应的代码块。

    rocketMQTemplate.asyncSend(orderPaidTopic, new OrderPaidEvent("T_001", new BigDecimal("88.00")), new SendCallback() {
        @Override
        public void onSuccess(SendResult var1) {
            System.out.printf("async onSucess SendResult=%s %n", var1);
        }
    
        @Override
        public void onException(Throwable var1) {
            System.out.printf("async onException Throwable=%s %n", var1);
        }
    
    });
    

    这里该方法并没有返回值,该async方法会启用另外一个线程去发送消息,开始发送后放行主线程,由该线程完成剩下的接收并回调SendCallback操作。

发送模式
  • send:普通的发送方法,发送一条普通消息并通过SendResult接收返回消息,其中的数据形式为序列化后的byte数组形式,不可直接使用。

  • convertAndSend:来自Spring的默认实现AbstractMessageSendingTemplate提供的方法,该方法会调用配置好的MessageConverter为消息载体进行转化,转化成Message对象后再进行发送,效果上与send并无不同。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hqpotiei-1685347564221)(images\1685007173971.png)]

  • sendAndReceive:发送并接收远端的响应,同步与异步方式均可用。该可以将远端的响应反序列化成指定类型的对象,同步方式下需要提供Type对象来表明返回值类型,而异步方式下则需要提供RocketMQLocalRequestCallback<T>对象用来异步回调,该对象回调时则是通过其所提供的泛型来确定返回值类型。

  • oneWay:发送消息,但是不追踪其返回,也就无法判断是否发送成功,拥有较高的运行性能,但是通过该方式发送可能会出现消息丢失的情况。

  • delay:发送延时消息,需要提供一个延时,其他与普通消息一致,基本逻辑同上方对 延时消息 的表述一致。

  • orderly:发送顺序消息,需要为消息提供一个hashKey,用于对消息的排序,其他与普通消息一致,基本逻辑同上方对 顺序消息 的表述一致。

发送事务消息

sendMessageInTransaction用于发送事务消息,在构造消息时,可以通过setHeader(RocketMQHeaders.TRANSACTION_ID, [事务id])的方式向header中设置事务id用作事务标识,后续也可以从header中取出来使用。

事务监听器

在使用事务前,我们需要为事务实现一个事务监听器,节省篇幅,我们这里使用demo中较简单的监听器示例来讲解。

@RocketMQTransactionListener(rocketMQTemplateBeanName = "extRocketMQTemplate")
class ExtTransactionListenerImpl implements RocketMQLocalTransactionListener {
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        System.out.printf("ExtTransactionListenerImpl executeLocalTransaction and return UNKNOWN. \n");
        return RocketMQLocalTransactionState.UNKNOWN;
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        System.out.printf("ExtTransactionListenerImpl checkLocalTransaction and return COMMIT. \n");
        return RocketMQLocalTransactionState.COMMIT;
    }
}

实现事务监听器后,需要通过@RocketMQTransactionListener注解将监听器与rocketMQTemplate的bean进行关联,参数rocketMQTemplateBeanName用来确定需要关联的RocketMQTemplate的bean的名称,其默认值为rocketMQTemplate。其与RocketMQTemplate是一一对应的关系,这也就是为什么上文提到当使用事务消息时,最好单独拓展一个rocketMQTemplate。

并且该监听器也会被注册为bean,并通过org.apache.rocketmq.spring.autoconfigure.RocketMQTransactionConfiguration类中的以下方法将bean对象与对应的rocketMQTemplate的bean对象进行关联。

@Override
public void afterSingletonsInstantiated() {
    Map<String, Object> beans = this.applicationContext.getBeansWithAnnotation(RocketMQTransactionListener.class)
        .entrySet().stream().filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

    beans.forEach(this::registerTransactionListener);
}

private void registerTransactionListener(String beanName, Object bean) {
    Class<?> clazz = AopProxyUtils.ultimateTargetClass(bean);

    if (!RocketMQLocalTransactionListener.class.isAssignableFrom(bean.getClass())) {
        throw new IllegalStateException(clazz + " is not instance of " + RocketMQLocalTransactionListener.class.getName());
    }
    RocketMQTransactionListener annotation = clazz.getAnnotation(RocketMQTransactionListener.class);
    RocketMQTemplate rocketMQTemplate = (RocketMQTemplate) applicationContext.getBean(annotation.rocketMQTemplateBeanName());
    if (((TransactionMQProducer) rocketMQTemplate.getProducer()).getTransactionListener() != null) {
        throw new IllegalStateException(annotation.rocketMQTemplateBeanName() + " already exists RocketMQLocalTransactionListener");
    }
    ((TransactionMQProducer) rocketMQTemplate.getProducer()).setExecutorService(new ThreadPoolExecutor(annotation.corePoolSize(), annotation.maximumPoolSize(),
                                                                                                       annotation.keepAliveTime(), annotation.keepAliveTimeUnit(), new LinkedBlockingDeque<>(annotation.blockingQueueSize())));
    ((TransactionMQProducer) rocketMQTemplate.getProducer()).setTransactionListener(RocketMQUtil.convert((RocketMQLocalTransactionListener) bean));
    log.debug("RocketMQLocalTransactionListener {} register to {} success", clazz.getName(), annotation.rocketMQTemplateBeanName());
}

也就是说最终事务中使用到的监听器,是注入到spring容器中的监听器bean,并不是新创建的,设计上需要注意。

监听器的用处

在事务监听器中包含两个方法,分别是executeLocalTransactioncheckLocalTransaction。,从事务的运行逻辑顺序入手,来探究一下这些方法的作用。以下给出事务的运行逻辑。

图片引用自文章RocketMQ事务消息机制_rocketmqtransactionlistener

引用图片

  1. 在调用Transaction相关方法后,会先将消息发送出去,此时采用的是同步方式,即当前线程会被阻塞,直到获取到远端返回过来的发送结果。此时来到远端broker中的事务消息并不是一个可用消息,而是一个半消息,是无法被消费者消费的**(此处对应图中第1步)**。
  2. 根据发送结果的状态来进行,最终会提供一个LocalTransactionState来作为最终事务处理的依据:
    1. 当状态为SEND_OK时**(此处对应图中第2步),就会来到executeLocalTransaction方法将此前发送的消息传入作为参数传入,由其进行本地事务的后续处理,然后提供一个事务状态RocketMQLocalTransactionState枚举。其中包含三种值并最终与LocalTransactionState相对应(此处对应图中第3步)**。
      • COMMIT对应LocalTransactionState的提交
      • ROLLBACK对应LocalTransactionState的回滚
      • UNKNOWN对应LocalTransactionState的未知态。
    2. 当状态为FLUSH_DISK_TIMEOUT,FLUSH_SLAVE_TIMEOUT或者SLAVE_NOT_AVAILABLE时,LocalTransactionState将被置为回滚态。
    3. 其他情况下,状态默认为未知态。
  3. 最后根据LocalTransactionState来对事务进行处理:
    • 提交,告知Broker将当前事务消息置为可用,可以提供给消费者消费**(此处对应图中第4步Commit)**。
    • 回滚,告知Broker回滚当前事务,删除对应的事务消息**(此处对应图中第4步Rollback)**。
    • 未知,可能当前事务应故(网络断开,回传消息受阻等)未能获取到消息的发送状态,或者由executeLocalTransaction提供了UNKNOWN的状态,接下来等待固定的时间后,broker发起对事务的回查请求回查事务状态**(此处对应图中第5步)
      消费者此时正常响应后,会进入到事务监听器的checkLocalTransaction中来给出一个事务状态,判断事务接下来的处理方式
      (此处对应图中第6步)。最后发送给broker进行响应处理(此处对应图中第7步)**

可见监听器的主要用处,就是让开发者能够自行决定事务的提交和回滚,自定义实现消息的事务流程。发送事务消息的默认实现来自于org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendMessageInTransaction方法,可以自行跟踪查看。

demo理解
@RocketMQTransactionListener(rocketMQTemplateBeanName = "extRocketMQTemplate")
class ExtTransactionListenerImpl implements RocketMQLocalTransactionListener {
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        System.out.printf("ExtTransactionListenerImpl executeLocalTransaction and return UNKNOWN. \n");
        return RocketMQLocalTransactionState.UNKNOWN;
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        System.out.printf("ExtTransactionListenerImpl checkLocalTransaction and return COMMIT. \n");
        return RocketMQLocalTransactionState.COMMIT;
    }
}

再看demo中的监听器梳理逻辑,就不难看出,该监听器关联名称为extRocketMQTemplate的bean组件,当我们通过使用该组件的sendMessageInTransaction方法发送事务消息时。

  1. 消息发送成功后首先进入到executeLocalTransaction方法中,此处返回UNKNOWN,也就是此时broker并不能确定当前事务需要如何处理,因此broker仍然将消息置为半消息状态,保存消息但是该消息不能被访问。
  2. 再一段时间后,broker再次尝试回查事务状态,此时来到checkLocalTransaction方法,此处返回COMMIT,表示可以提交消息了,于是broker将消息置为可用状态。

消费者(Consumer)

对于消费者而言,消费消息具有两种模式,一种是PUSH模式,即当队列中存在消息,则RocketMQ服务端便将该消息push给对应消费该消息的消费者,即消费者被动消费消息,一种是PULL模式,即消费者通过PULL主动从RocketMQ服务端中获取未被消费的所有消息进行消费,即消费者主动消费。接下来讲解一下这两种模式的实现。

PUSH模式

push模式下比较简单,消费者被动消费消息,只需要实现对应的监听器即可,由RocketMQ自行管理推送的逻辑。

@RocketMQMessageListener

该类是一个注解类 ,用于添加到消费者监听器上,为该监听器指定一些对应的运行参数。例如

@RocketMQMessageListener(nameServer = "${demo.rocketmq.myNameServer}", topic = "${demo.rocketmq.topic.user}", consumerGroup = "user_consumer")

这里为监听器指定了NameServer地址,并且指定了对应订阅的topic,同时也可以通过selectorType(默认值为SelectorType.TAG)与selectorExpression为监听器指定对应的tag等分类信息,consumerGroup为消费者指定了对应的消费者组

需要注意的是,该注解并不包含Component注解,因此不会将对应的监听器注册为Spring bean组件,需要自己另行注册或添加对应注解。

RocketMQListener

该类为一个接口,是作为消费者需要实现的监听器接口之一,其接口代码为:

public interface RocketMQListener<T> {
    void onMessage(T message);
}

该接口接受一个泛型参数,该泛型参数用于指定接受到的消息类型,当接受到对应的消息时,对应的消息内容会被转换为T所指定的类型传入到参数message中。

demo样例:

@RocketMQMessageListener(nameServer = "${demo.rocketmq.myNameServer}", topic = "${demo.rocketmq.topic.user}", consumerGroup = "user_consumer")
public class UserConsumer implements RocketMQListener<User> {
    @Override
    public void onMessage(User message) {
        System.out.printf("######## user_consumer received: %s ; age: %s ; name: %s \n", message, message.getUserAge(), message.getUserName());
    }
}

该监听器会自动接收配置项demo.rocketmq.topic.user中的消息,当接收到消息时,会将消息中的二进制码部分反序列化为User对象作为message传入然后进行消费。

RocketMQReplyListener

该类为一个接口,是作为消费者需要实现的监听器接口之一,其接口代码为:

public interface RocketMQReplyListener<T, R> {
    R onMessage(T message);
}

实现该接口的监听器就支持对传入的消息回传进行对应的响应结果。该类接受的两个类型泛型T、R中,T表示接受到的消息类型,效果同RocketMQListener中的T;R表示回传的响应数据的类型。

demo样例:

@RocketMQMessageListener(topic = "${demo.rocketmq.objectRequestTopic}", consumerGroup = "${demo.rocketmq.objectRequestConsumer}", selectorExpression = "${demo.rocketmq.tag}")
public class ObjectConsumerWithReplyUser implements RocketMQReplyListener<User, User> {
    @Override
    public User onMessage(User user) {
        System.out.printf("------- ObjectConsumerWithReplyUser received: %s \n", user);
        User replyUser = new User();
        replyUser.setUserAge((byte) 10);
        replyUser.setUserName("replyUserName");
        return replyUser;
    }
}

该监听器能够接收配置项demo.rocketmq.objectRequestTopic指定的topic中具有由配置项demo.rocketmq.tag指定的tag的消息,当接收到消息时,会将消息中的二进制码部分反序列化为User对象作为message传入然后进行消费,完毕后构造了一个新的User对象作为响应返回,RocketMQ将会将该返回值回传给消费者。

PULL模式

PULL模式需要使用到此前提到的RocketMQTemplate类中的receive方法。

@ExtRocketMQConsumerConfiguration

该类是一个注解类,同@ExtRocketMQTemplateConfiguration注解类似,作用于RocketMQTemplate子类上,不过主要用于提供给消费者主动拉取消息。例如

@ExtRocketMQConsumerConfiguration(topic = "${demo.rocketmq.topic}", group = "string_consumer", tlsEnable = "${demo.ext.consumer.tlsEnable}")

这里为RocketMQTemplate指定了拉取的topic,并指定了当前的消费者组,指定了是否开启tls加密。
ser对象作为响应返回,RocketMQ将会将该返回值回传给消费者。

PULL模式

PULL模式需要使用到此前提到的RocketMQTemplate类中的receive方法。

@ExtRocketMQConsumerConfiguration

该类是一个注解类,同@ExtRocketMQTemplateConfiguration注解类似,作用于RocketMQTemplate子类上,不过主要用于提供给消费者主动拉取消息。例如

@ExtRocketMQConsumerConfiguration(topic = "${demo.rocketmq.topic}", group = "string_consumer", tlsEnable = "${demo.ext.consumer.tlsEnable}")

这里为RocketMQTemplate指定了拉取的topic,并指定了当前的消费者组,指定了是否开启tls加密。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值