【消息队列 MQ 专栏】消息队列之 RocketMQ

【初探篇】初识消息队列 & RocketMQ 各位读者大家好,经过一段时间的筹备,我的新专栏《深入浅出RocketMQ设计思想》今天正式开始更文啦。 阅读详情

点击上方“芋道源码”,选择“置顶公众号”

技术文章第一时间送达!

源码精品专栏

 

简介

RocketMQ 特点

RocketMQ 是阿里巴巴在2012年开源的分布式消息中间件,目前已经捐赠给 Apache 软件基金会,并于2017年9月25日成为 Apache 的顶级项目。作为经历过多次阿里巴巴双十一这种“超级工程”的洗礼并有稳定出色表现的国产中间件,以其高性能、低延时和高可靠等特性近年来已经也被越来越多的国内企业使用。其主要特点有:

  1. 灵活可扩展性
    RocketMQ 天然支持集群,其核心四组件(Name Server、Broker、Producer、Consumer)每一个都可以在没有单点故障的情况下进行水平扩展。

  2. 海量消息堆积能力
    RocketMQ 采用零拷贝原理实现超大的消息的堆积能力,据说单机已可以支持亿级消息堆积,而且在堆积了这么多消息后依然保持写入低延迟。

  3. 支持顺序消息
    可以保证消息消费者按照消息发送的顺序对消息进行消费。顺序消息分为全局有序和局部有序,一般推荐使用局部有序,即生产者通过将某一类消息按顺序发送至同一个队列来实现。

  4. 多种消息过滤方式
    消息过滤分为在服务器端过滤和在消费端过滤。服务器端过滤时可以按照消息消费者的要求做过滤,优点是减少不必要消息传输,缺点是增加了消息服务器的负担,实现相对复杂。消费端过滤则完全由具体应用自定义实现,这种方式更加灵活,缺点是很多无用的消息会传输给消息消费者。

  5. 支持事务消息
    RocketMQ 除了支持普通消息,顺序消息之外还支持事务消息,这个特性对于分布式事务来说提供了又一种解决思路。

  6. 回溯消费
    回溯消费是指消费者已经消费成功的消息,由于业务上需求需要重新消费,RocketMQ 支持按照时间回溯消费,时间维度精确到毫秒,可以向前回溯,也可以向后回溯。

基本概念

下面是一张 RocketMQ 的部署结构图,里面涉及了 RocketMQ 核心的四大组件:Name Server、Broker、Producer、Consumer ,每个组件都可以部署成集群模式进行水平扩展。

img

部署结构图

生产者

生产者(Producer)负责产生消息,生产者向消息服务器发送由业务应用程序系统生成的消息。 RocketMQ 提供了三种方式发送消息:同步、异步和单向。

同步发送

同步发送指消息发送方发出数据后会在收到接收方发回响应之后才发下一个数据包。一般用于重要通知消息,例如重要通知邮件、营销短信。

异步发送

异步发送指发送方发出数据后,不等接收方发回响应,接着发送下个数据包,一般用于可能链路耗时较长而对响应时间敏感的业务场景,例如用户视频上传后通知启动转码服务。

单向发送

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

生产者组

生产者组(Producer Group)是一类 Producer 的集合,这类 Producer 通常发送一类消息并且发送逻辑一致,所以将这些 Producer 分组在一起。从部署结构上看生产者通过 Producer Group 的名字来标记自己是一个集群。

消费者

消费者(Consumer)负责消费消息,消费者从消息服务器拉取信息并将其输入用户应用程序。站在用户应用的角度消费者有两种类型:拉取型消费者、推送型消费者。

拉取型消费者

拉取型消费者(Pull Consumer)主动从消息服务器拉取信息,只要批量拉取到消息,用户应用就会启动消费过程,所以 Pull 称为主动消费型。

推送型消费者

推送型消费者(Push Consumer)封装了消息的拉取、消费进度和其他的内部维护工作,将消息到达时执行的回调接口留给用户应用程序来实现。所以 Push 称为被动消费类型,但从实现上看还是从消息服务器中拉取消息,不同于 Pull 的是 Push 首先要注册消费监听器,当监听器处触发后才开始消费消息。

消费者组

消费者组(Consumer Group)一类 Consumer 的集合名称,这类 Consumer 通常消费同一类消息并且消费逻辑一致,所以将这些 Consumer 分组在一起。消费者组与生产者组类似,都是将相同角色的分组在一起并命名,分组是个很精妙的概念设计,RocketMQ 正是通过这种分组机制,实现了天然的消息负载均衡。消费消息时通过 Consumer Group 实现了将消息分发到多个消费者服务器实例,比如某个 Topic 有9条消息,其中一个 Consumer Group 有3个实例(3个进程或3台机器),那么每个实例将均摊3条消息,这也意味着我们可以很方便的通过加机器来实现水平扩展。

消息服务器

消息服务器(Broker)是消息存储中心,主要作用是接收来自 Producer 的消息并存储, Consumer 从这里取得消息。它还存储与消息相关的元数据,包括用户组、消费进度偏移量、队列信息等。从部署结构图中可以看出 Broker 有 Master 和 Slave 两种类型,Master 既可以写又可以读,Slave 不可以写只可以读。从物理结构上看 Broker 的集群部署方式有四种:单 Master 、多 Master 、多 Master 多 Slave(同步刷盘)、多 Master多 Slave(异步刷盘)。

单 Master

这种方式一旦 Broker 重启或宕机会导致整个服务不可用,这种方式风险较大,所以显然不建议线上环境使用。

多 Master

所有消息服务器都是 Master ,没有 Slave 。这种方式优点是配置简单,单个 Master 宕机或重启维护对应用无影响。缺点是单台机器宕机期间,该机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受影响。

多 Master 多 Slave(异步复制)

每个 Master 配置一个 Slave,所以有多对 Master-Slave,消息采用异步复制方式,主备之间有毫秒级消息延迟。这种方式优点是消息丢失的非常少,且消息实时性不会受影响,Master 宕机后消费者可以继续从 Slave 消费,中间的过程对用户应用程序透明,不需要人工干预,性能同多 Master 方式几乎一样。缺点是 Master 宕机时在磁盘损坏情况下会丢失极少量消息。

多 Master 多 Slave(同步双写)

每个 Master 配置一个 Slave,所以有多对 Master-Slave ,消息采用同步双写方式,主备都写成功才返回成功。这种方式优点是数据与服务都没有单点问题,Master 宕机时消息无延迟,服务与数据的可用性非常高。缺点是性能相对异步复制方式略低,发送消息的延迟会略高。

名称服务器

名称服务器(NameServer)用来保存 Broker 相关元信息并给 Producer 和 Consumer 查找 Broker 信息。NameServer 被设计成几乎无状态的,可以横向扩展,节点之间相互之间无通信,通过部署多台机器来标记自己是一个伪集群。每个 Broker 在启动的时候会到 NameServer 注册,Producer 在发送消息前会根据 Topic 到 NameServer 获取到 Broker 的路由信息,Consumer 也会定时获取 Topic 的路由信息。所以从功能上看应该是和 ZooKeeper 差不多,据说 RocketMQ 的早期版本确实是使用的 ZooKeeper ,后来改为了自己实现的 NameServer 。

消息

消息(Message)就是要传输的信息。一条消息必须有一个主题(Topic),主题可以看做是你的信件要邮寄的地址。一条消息也可以拥有一个可选的标签(Tag)和额处的键值对,它们可以用于设置一个业务 key 并在 Broker 上查找此消息以便在开发期间查找问题。

主题

主题(Topic)可以看做消息的规类,它是消息的第一级类型。比如一个电商系统可以分为:交易消息、物流消息等,一条消息必须有一个 Topic 。Topic 与生产者和消费者的关系非常松散,一个 Topic 可以有0个、1个、多个生产者向其发送消息,一个生产者也可以同时向不同的 Topic 发送消息。一个 Topic 也可以被 0个、1个、多个消费者订阅。

标签

标签(Tag)可以看作子主题,它是消息的第二级类型,用于为用户提供额外的灵活性。使用标签,同一业务模块不同目的的消息就可以用相同 Topic 而不同的 Tag 来标识。比如交易消息又可以分为:交易创建消息、交易完成消息等,一条消息可以没有 Tag 。标签有助于保持您的代码干净和连贯,并且还可以为 RocketMQ 提供的查询系统提供帮助。

消息队列

消息队列(Message Queue),主题被划分为一个或多个子主题,即消息队列。一个 Topic 下可以设置多个消息队列,发送消息时执行该消息的 Topic ,RocketMQ 会轮询该 Topic 下的所有队列将消息发出去。下图 Broker 内部消息情况:

img

Broker 内部消息

消息消费模式

消息消费模式有两种:集群消费(Clustering)和广播消费(Broadcasting)。默认情况下就是集群消费,该模式下一个消费者集群共同消费一个主题的多个队列,一个队列只会被一个消费者消费,如果某个消费者挂掉,分组内其它消费者会接替挂掉的消费者继续消费。而广播消费消息会发给消费者组中的每一个消费者进行消费。

消息顺序

消息顺序(Message Order)有两种:顺序消费(Orderly)和并行消费(Concurrently)。顺序消费表示消息消费的顺序同生产者为每个消息队列发送的顺序一致,所以如果正在处理全局顺序是强制性的场景,需要确保使用的主题只有一个消息队列。并行消费不再保证消息顺序,消费的最大并行数量受每个消费者客户端指定的线程池限制。

工程实例

Java 访问 RocketMQ 实例

RocketMQ 目前支持 Java、C++、Go 三种语言访问,按惯例以 Java 语言为例看下如何用 RocketMQ 来收发消息的。

引入依赖
  <dependency>
     <groupId>org.apache.rocketmq</groupId>
     <artifactId>rocketmq-client</artifactId>
     <version>4.2.0</version>
 </dependency>

添加 RocketMQ 客户端访问支持,具体版本和安装的 RocketMQ 版本一致即可。

消息生产者
package org.study.mq.rocketMQ.java;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
public class Producer {
   public static void main(String[] args) throws Exception {
       //创建一个消息生产者,并设置一个消息生产者组
       DefaultMQProducer producer = new DefaultMQProducer("niwei_producer_group");
       //指定 NameServer 地址
       producer.setNamesrvAddr("localhost:9876");
       //初始化 Producer,整个应用生命周期内只需要初始化一次
       producer.start();
       for (int i = 0; i < 100; i++) {
           //创建一条消息对象,指定其主题、标签和消息内容
           Message msg = new Message(
                   "topic_example_java" /* 消息主题名 */,
                   "TagA" /* 消息标签 */,
                   ("Hello Java demo RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* 消息内容 */
           );
           //发送消息并返回结果
           SendResult sendResult = producer.send(msg);
           System.out.printf("%s%n", sendResult);
       }
       // 一旦生产者实例不再被使用则将其关闭,包括清理资源,关闭网络连接等
       producer.shutdown();
   }
}

示例中用 DefaultMQProducer 类来创建一个消息生产者,通常一个应用创建一个 DefaultMQProducer 对象,所以一般由应用来维护生产者对象,可以其设置为全局对象或者单例。该类构造函数入参 producerGroup 是消息生产者组的名字,无论生产者还是消费者都必须给出 GroupName ,并保证该名字的唯一性,ProducerGroup 发送普通的消息时作用不大,后面介绍分布式事务消息时会用到。

接下来指定 NameServer 地址和调用 start 方法初始化,在整个应用生命周期内只需要调用一次 start 方法。

初始化完成后,调用 send 方法发送消息,示例中只是简单的构造了100条同样的消息发送,其实一个 Producer 对象可以发送多个主题多个标签的消息,消息对象的标签可以为空。send 方法是同步调用,只要不抛异常就标识成功。

最后应用退出时调用 shutdown 方法清理资源、关闭网络连接,从服务器上注销自己,通常建议应用在 JBOSS、Tomcat 等容器的退出钩子里调用 shutdown 方法。

消息消费者
package org.study.mq.rocketMQ.java;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;
public class Consumer {
   public static void main(String[] args) throws Exception {
       //创建一个消息消费者,并设置一个消息消费者组
       DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("niwei_consumer_group");
       //指定 NameServer 地址
       consumer.setNamesrvAddr("localhost:9876");
       //设置 Consumer 第一次启动时从队列头部开始消费还是队列尾部开始消费
       consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
       //订阅指定 Topic 下的所有消息
       consumer.subscribe("topic_example_java", "*");
       //注册消息监听器
       consumer.registerMessageListener(new MessageListenerConcurrently() {
           public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext context) {
               //默认 list 里只有一条消息,可以通过设置参数来批量接收消息
               if (list != null) {
                   for (MessageExt ext : list) {
                       try {
                           System.out.println(new Date() + new String(ext.getBody(), "UTF-8"));
                       } catch (UnsupportedEncodingException e) {
                           e.printStackTrace();
                       }
                   }
               }
               return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
           }
       });
       // 消费者对象在使用之前必须要调用 start 初始化
       consumer.start();
       System.out.println("消息消费者已启动");
   }
}

示例中用 DefaultMQPushConsumer 类来创建一个消息消费者,通生产者一样一个应用一般创建一个 DefaultMQPushConsumer 对象,该对象一般由应用来维护,可以其设置为全局对象或者单例。该类构造函数入参 consumerGroup 是消息消费者组的名字,需要保证该名字的唯一性。

接下来指定 NameServer 地址和设置消费者应用程序第一次启动时从队列头部开始消费还是队列尾部开始消费。

接着调用 subscribe 方法给消费者对象订阅指定主题下的消息,该方法第一个参数是主题名,第二个擦书是标签名,示例表示订阅了主题名 topic_example_java 下所有标签的消息。

最主要的是注册消息监听器才能消费消息,示例中用的是 Consumer Push 的方式,即设置监听器回调的方式消费消息,默认监听回调方法中 List 里只有一条消息,可以通过设置参数来批量接收消息。

最后调用 start 方法初始化,在整个应用生命周期内只需要调用一次 start 方法。

启动 Name Server
nohup sh bin/mqnamesrv &
tail -f ~/logs/rocketmqlogs/namesrv.log

RocketMQ 核心的四大组件中 Name Server 和 Broker 都是由 RocketMQ 安装包提供的,所以要启动这两个应用才能提供消息服务。首先启动 Name Server,先确保你的机器中已经安装了与 RocketMQ 相匹配的 JDK ,并设置了环境变量 JAVA_HOME ,然后在 RocketMQ 的安装目录下执行 bin 目录下的 mqnamesrv ,默认会将该命令的执行情况输出到当前目录的 nohup.out 文件,最后跟踪日志文件查看 Name Server 的实际运行情况。

启动 Broker
nohup sh bin/mqbroker -n localhost:9876 &
tail -f ~/logs/rocketmqlogs/broker.log

同样也要确保你的机器中已经安装了与 RocketMQ 相匹配的 JDK ,并设置了环境变量 JAVA_HOME ,然后在 RocketMQ 的安装目录下执行 bin 目录下的 mqbroker ,默认会将该命令的执行情况输出到当前目录的 nohup.out 文件,最后跟踪日志文件查看 Broker 的实际运行情况。

运行 Consumer

先运行 Consumer 类,这样当生产者发送消息的时候能在消费者后端看到消息记录。配置没问题的话会看到在控制台打印出消息消费者已启动

运行 Producer

最后运行 Producer 类,在 Consumer 的控制台能看到接收的消息

img

消费者接收到消息

Spring 整合 RocketMQ

不同于 RabbitMQ、ActiveMQ、Kafka 等消息中间件,Spring 社区已经通过多种方式提供了对这些中间件产品集成,例如通过 spring-jms 整合 ActiveMQ、通过 Spring AMQP 项目下的 spring-rabbit 整合 RabbitMQ、通过 spring-kafka 整合 kafka ,通过他们可以在 Spring 项目中更方便使用其 API 。目前在 Spring 框架中集成 RocketMQ 有三种方式,一是将消息生产者和消费者定义成 bean 对象交由 Spring 容器管理,二是使用 RocketMQ 社区的外部项目 rocketmq-jms(https:http://github.com/apache/rocketmq-externals/tree/master/rocketmq-jms)然后通过 spring-jms 方式集成使用,三是如果你的应用是基于 spring-boot 的,可以使用 RocketMQ 的外部项目 rocketmq-spring-boot-starter(https:http://github.com/apache/rocketmq-externals/tree/master/rocketmq-spring-boot-starter)比较方便的收发消息。

总的来讲 rocketmq-jms 项目实现了 JMS 1.1 规范的部分内容,目前支持 JMS 中的发布/订阅模型收发消息。rocketmq-spring-boot-starter 项目目前已经支持同步发送、异步发送、单向发送、顺序消费、并行消费、集群消费、广播消费等特性,如果比较喜欢 Spring Boot 这种全家桶的快速开发框架并且现有特性已满足业务要求可以使用该项目。当然从 API 使用上最灵活的还是第一种方式,下面以第一种方式为例简单看下Spring 如何集成 RocketMQ 的。

消息生产者
package org.study.mq.rocketMQ.spring;
import org.apache.log4j.Logger;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
public class SpringProducer {
   private Logger logger = Logger.getLogger(getClass());
   private String producerGroupName;
   private String nameServerAddr;
   private DefaultMQProducer producer;
   public SpringProducer(String producerGroupName, String nameServerAddr) {
       this.producerGroupName = producerGroupName;
       this.nameServerAddr = nameServerAddr;
   }
   public void init() throws Exception {
       logger.info("开始启动消息生产者服务...");
       //创建一个消息生产者,并设置一个消息生产者组
       producer = new DefaultMQProducer(producerGroupName);
       //指定 NameServer 地址
       producer.setNamesrvAddr(nameServerAddr);
       //初始化 SpringProducer,整个应用生命周期内只需要初始化一次
       producer.start();
       logger.info("消息生产者服务启动成功.");
   }
   public void destroy() {
       logger.info("开始关闭消息生产者服务...");
       producer.shutdown();
       logger.info("消息生产者服务已关闭.");
   }
   public DefaultMQProducer getProducer() {
       return producer;
   }
}

消息生产者就是把生产者 DefaultMQProducer 对象的生命周期分成构造函数、init、destroy 三个方法,构造函数中将生产者组名、NameServer 地址作为变量由 Spring 容器在配置时提供,init 方法中实例化 DefaultMQProducer 对象、设置 NameServer 地址、初始化生产者对象,destroy 方法用于生产者对象销毁时清理资源。

消息消费者
package org.study.mq.rocketMQ.spring;
import org.apache.log4j.Logger;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
public class SpringConsumer {
   private Logger logger = Logger.getLogger(getClass());
   private String consumerGroupName;
   private String nameServerAddr;
   private String topicName;
   private DefaultMQPushConsumer consumer;
   private MessageListenerConcurrently messageListener;
   public SpringConsumer(String consumerGroupName, String nameServerAddr, String topicName, MessageListenerConcurrently messageListener) {
       this.consumerGroupName = consumerGroupName;
       this.nameServerAddr = nameServerAddr;
       this.topicName = topicName;
       this.messageListener = messageListener;
   }
   public void init() throws Exception {
       logger.info("开始启动消息消费者服务...");
       //创建一个消息消费者,并设置一个消息消费者组
       consumer = new DefaultMQPushConsumer(consumerGroupName);
       //指定 NameServer 地址
       consumer.setNamesrvAddr(nameServerAddr);
       //设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
       consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
       //订阅指定 Topic 下的所有消息
       consumer.subscribe(topicName, "*");
       //注册消息监听器
       consumer.registerMessageListener(messageListener);
       // 消费者对象在使用之前必须要调用 start 初始化
       consumer.start();
       logger.info("消息消费者服务启动成功.");
   }
   public void destroy(){
       logger.info("开始关闭消息消费者服务...");
       consumer.shutdown();
       logger.info("消息消费者服务已关闭.");
   }
   public DefaultMQPushConsumer getConsumer() {
       return consumer;
   }
}

同消息生产者类似,消息消费者是把生产者 DefaultMQPushConsumer 对象的生命周期分成构造函数、init、destroy 三个方法,具体含义在介绍 Java 访问 RocketMQ 实例时已经介绍过了,不再赘述。当然,有了消费者对象还需要消息监听器在接收到消息后执行具体的处理逻辑。

package org.study.mq.rocketMQ.spring;
import org.apache.log4j.Logger;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class MessageListener implements MessageListenerConcurrently {
   private Logger logger = Logger.getLogger(getClass());
   public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
       if (list != null) {
           for (MessageExt ext : list) {
               try {
                   logger.info("监听到消息 : " + new String(ext.getBody(), "UTF-8"));
               } catch (UnsupportedEncodingException e) {
                   e.printStackTrace();
               }
           }
       }
       return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
   }
}

消息监听器类就是把前面 Java 示例中注册消息监听器时声明的匿名内部类代码抽取出来定义成单独一个类而已。

Spring 配置文件

因为只使用 Spring 框架集成,所以除了 Sping 框架核心 jar 包外不需要额外添加依赖包了。本例中将消息生产者和消息消费者分成两个配置文件,这样能更好的演示收发消息的效果。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
   <bean id="producer" class="org.study.mq.rocketMQ.spring.SpringProducer" init-method="init" destroy-method="destroy">
       <constructor-arg name="nameServerAddr" value="localhost:9876"/>
       <constructor-arg name="producerGroupName" value="spring_producer_group"/>
   </bean>
</beans>

消息生产者配置很简单,定义了一个消息生产者对象,该对象初始化时调用 init 方法,对象销毁前执行 destroy 方法,将 Name Server 地址和生产者组配置好。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
   <bean id="messageListener" class="org.study.mq.rocketMQ.spring.MessageListener" />
   <bean id="consumer" class="org.study.mq.rocketMQ.spring.SpringConsumer" init-method="init" destroy-method="destroy">
       <constructor-arg name="nameServerAddr" value="localhost:9876"/>
       <constructor-arg name="consumerGroupName" value="spring_consumer_group"/>
       <constructor-arg name="topicName" value="spring-rocketMQ-topic" />
       <constructor-arg name="messageListener" ref="messageListener" />
   </bean>
</beans>

消息消费者同消息生产者配置类似,多了一个消息监听器对象的定义和绑定。

运行实例程序

按前述步骤 启动 Name Server 和 Broker,接着运行消息生产者和消息消费者程序,简化起见我们用两个单元测试类模拟这两个程序:

package org.study.mq.rocketMQ.spring;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringProducerTest {
   private ApplicationContext container;
   @Before
   public void setup() {
       container = new ClassPathXmlApplicationContext("classpath:spring-producer.xml");
   }
   @Test
   public void sendMessage() throws Exception {
       SpringProducer producer = container.getBean(SpringProducer.class);
       for (int i = 0; i < 20; i++) {
           //创建一条消息对象,指定其主题、标签和消息内容
           Message msg = new Message(
                   "spring-rocketMQ-topic",
                   null,
                   ("Spring RocketMQ demo " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* 消息内容 */
           );
           //发送消息并返回结果
           SendResult sendResult = producer.getProducer().send(msg);
           System.out.printf("%s%n", sendResult);
       }
   }
}

SpringProducerTest 类模拟消息生产者发送消息。

package org.study.mq.rocketMQ.spring;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringConsumerTest {
   private ApplicationContext container;
   @Before
   public void setup() {
       container = new ClassPathXmlApplicationContext("classpath:spring-consumer.xml");
   }
   @Test
   public void consume() throws Exception {
       SpringConsumer consumer = container.getBean(SpringConsumer.class);
       Thread.sleep(200 * 1000);
       consumer.destroy();
   }
}

SpringConsumerTest 类模拟消息消费者者接收消息,在 consume 方法返回之前需要让当前线程睡眠一段时间,使消费者程序继续存活才能监听到生产者发送的消息。

分别运行 SpringProducerTest 类 和 SpringConsumerTest 类,在 SpringConsumerTest 的控制台能看到接收的消息:

img

消费者接收到消息

假如启动两个 SpringConsumerTest 类进程,因为它们属于同一消费者组,在 SpringConsumerTest 的控制台能看到它们均摊到了消息:

img

消费者1

img

消费者2



知识星球

目前在知识星球(https://t.zsxq.com/2VbiaEu)更新了如下 Dubbo 源码解析如下:

01. 调试环境搭建
02. 项目结构一览
03. API 配置(一)之应用
04. API 配置(二)之服务提供者
05. API 配置(三)之服务消费者
06. 属性配置
07. XML 配置
08. 核心流程一览

09. 拓展机制 SPI

10. 线程池

...
一共 60 篇++

RocketMQ【什么是MQ、MQ的应用场景(应用解耦、流量削峰)、RocketMQ的发展历程、RocketMQ概念术语 】(一)-全面详解(学习总结---从入门到深化) RocketMQ【什么是MQ、MQ的应用场景(应用解耦、流量削峰)、RocketMQ的发展历程、RocketMQ概念术语】(一)-全面详解(学习总结---从入门到深化) 阅读详情

相关推荐

倾向得分匹配的stata命令_政策评估方法引荐|广义倾向得分匹配(GPSM)

和大家分享一下最近学习的进展,在痛苦读完stata journal和练习OA程序,已经可以基本完成广义倾向得分的估计了,在国内这一方法还没有形成广泛运用,因此学习资源也是有点稀缺的,在文章最后给各位提供数据和代码以供练习。有问题可以随时文末留言或者联系我哦。人文社会科学中一般无法在实验室中控制样本个体的特征,我们一般采用“准自然实验”的研究路径,也就是不随机安排样本接受处理与否。但放弃随机分组假定...

weixin_39550258的博客 1万+

消息队列之 RocketMQ

简介 RocketMQ 特点 RocketMQ 是阿里巴巴在2012年开源的分布式消息中间件,目前已经捐赠给 Apache 软件基金会,并于2017年9月25日成为 Apache 的顶级项目。作为经历过多次阿里巴巴双十一这种“超级工程”的洗礼并有稳定出色表现的国产中间件,以其高性能、低延时和高可靠等特性近年来已经也被越来越多的国内企业使用。其主要特点有: 1. 灵活可扩展性 RocketMQ 天然...

weixin_34072458的博客 436

手把手教你用.NET Reactor 7.3.0保护跨平台.NET应用(含macOS/Rider配置)

本文详细介绍了如何使用.NET Reactor 7.3.0为跨平台.NET应用提供代码保护。文章重点涵盖了在macOS环境下的GUI工具安装、与JetBrains Rider的深度集成配置,并针对Avalonia UI框架提供了专项保护策略和避坑指南,帮助开发者有效防止代码被反编译和破解。

coffee的博客 596

全面升级 —— Apache RocketMQ 5.0 SDK 的新面貌

引言Aliware长久以来,RocketMQ 易于部署、高性能、高可用的架构,支撑了数十年来集团内外海量的业务场景。时至今日,为了迎接如今云原生时代的新挑战,我们重磅推出了 RocketM...

阿里巴巴中间件 541

MDT部署实战:除了Office,这些“装机必备”软件也能一键静默安装(含静默参数)

本文详细介绍了如何利用Microsoft Deployment Toolkit(MDT)实现企业级软件的静默安装,包括浏览器、开发工具和系统增强工具等“装机必备”软件的部署方案。通过命令行参数和MSI属性,IT运维团队可以高效构建标准化工作环境,提升部署效率并确保版本一致性。文章还提供了安装顺序控制、结果验证和常见问题排错等高级技巧。

weixin_26985923的博客 172

如何点亮一颗sensor

要点亮一个sensor需要从,时钟、sensorI2C,供电,reset脚,mipi的配置几个方面一一确认。 reset脚拉高 给sensor的时钟有没有。(目前用到的常为24MHZ) 供电 查看sensor的数据手册,看有几个电源,用示波器量一下是否为手册规定的电压 比如:DVDD 数字电压: AVDD模拟电压 DOVDD芯片引脚出来的电压等为多少。 sensor的I2C能否抓到 将I2C的口对应上,用示波器量取i2c的波形。 检查有应答并且数据正确。读写地址是否正...

weixin_40787463的博客 3652

RocketMQ console监控

开发十年,就只剩下这套Java开发体系了 &gt;&gt;&gt;    ...

幽静净土的技术分享 398

手把手教你用Apple Developer App完成苹果开发者账号注册(含证件验证失败解决方案)

本文详细指导如何通过Apple Developer App完成苹果开发者账号注册,特别针对证件验证失败问题提供解决方案。从准备工作到注册流程,再到验证失败的处理方法,帮助开发者顺利完成注册,避免常见错误。

moss5的博客 1161

记录一次RocketMq消费不均的解决方案

>背景:xxx系统有一个打包操作,打包时需要把课件里面所有视频、音频、图片、试题、文本等内容下载下来,视频、图片根据不同需求需要压缩或者转码,然后再上传到服务器,所以根据课件内容不同打包操作需要的时间几十秒到十几分钟不等。我们当时是用RocketMQ做的限流,然而,在2020年12月31日翻车了…,翻车原因是消息堆积太多,造成老师课件不能及时打包,发现问题后第一时间紧急处理解决了,经验很值得分享,跨年解决的。 >1. 线上问题分析 1.1 当时堆积状态 ![在这里插入图片描述](https://

WKzhangliang123的博客 3772

龙腾CMS downloadFile接口任意文件读取漏洞复现 [附POC]

大连网龙网络科技有限公司专注于在信息科技领域中向客户提供商业解决方案,是专业的互联网服务解决方案提供商和网络营销服务专家,为用户提供一体化的网络营销整体解决方案,龙腾CMS downloadFile接口存在任意文件读取漏洞。

薛定谔嘚喵博客 720

下采样优化实战:用ADown显著提升YOLOv11的多尺度特征保持能力

ADown 以“可编程梯度信息+多分支特征融合”打破了传统下采样的性能壁垒,为 YOLOv11 带来了多尺度特征的精准传递能力。从原理到代码集成,本教程已为你铺就了一条“小目标检测精度跃升”的实战路径。现在就动手尝试吧——当你看到模型对极小目标也能精准识别,且特征图细节完整时,你会明白这份技术探索的价值。我很乐意在你实践过程中帮你解决任何问题,让我们一起推动目标检测在下采样领域的边界。

FJN110的博客 424

「RocketMQ 技术专题」帮你梳理 RocketMQ 相关的消费问题以及原理分析总结

消息重复消费的问题 消息重复消费是各个 MQ 都会发生的常见问题之一,在一些比较敏感的场景下,重复消费会造成比较严重的后果,比如重复扣款等。 消息重复消费场景及解决办法 在什么情况下会发生 RocketMQ 的消息重复消费呢? 生产者重复发送场景 当系统的调用链路比较长的时候,比如,系统 A 调用系统 B,系统 B 再把消息发送到 RocketMQ 中,在系统 A 调用系统 B 的时候。 如果系统 B 处理成功,但是迟迟没有将调用成功的结果返回给系统 A 的时候,系统 A 就会尝..

gp16674213804的博客 2105

扩充两个三维体积:ITK实现

在医学图像处理中,经常需要进行 3D 体积的操作,比如合并、分割等。而 ITK (Insight Segmentation and Registration Toolkit) 是一个常用的开源库,可以用于实现各种医学图像处理算法。这篇文章将介绍如何使用 ITK 实现扩充两个三维体积的操作。不过以上代码已经足够简单明了地演示了如何使用 ITK 实现扩充两个三维体积的操作。首先,我们需要准备两个三维体积的数据。这里假设这两个体积大小相同,并且已经加载到内存中了。来读取和保存体积数据,使用了。

CodeVorter的博客 205

rocketmq支持最大消息_【消息队列 MQ 专栏】消息队列之 RocketMQ

RocketMQ 特点RocketMQ 是阿里巴巴在2012年开源的分布式消息中间件,目前已经捐赠给 Apache 软件基金会,并于2017年9月25日成为 Apache 的顶级项目。作为经历过多次阿里巴巴双十一这种“超级工程”的洗礼并有稳定出色表现的国产中间件,以其高性能、低延时和高可靠等特性近年来已经也被越来越多的国内企业使用。其主要特点有:灵活可扩展性RocketMQ 天然支持集群,其核心四...

weixin_36290719的博客 581

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

KisFlow-Golang流式实时计算案例(四)-KisFlow在消息队列MQ中的应用

Golang框架实战-KisFlow流式计算框架(1)-概述 Golang框架实战-KisFlow流式计算框架(2)-项目构建/基础模块-(上) Golang框架实战-KisFlow流式计算框架(3)-项目构建/基础模块-(下) Golang框架实战-KisFlow流式计算框架(4)-数据流 Golang框架实战-KisFlow流式计算框架(5)-Function调度 Golang框架实战-KisFlow流式计算框架(6)-Connector Golang框架实战-KisFlow流式计算框架(7)-配置导入

rita_0567的博客 824

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

【消息中间件】详解三大MQ:RabbitMQ、RocketMQ、Kafka

一文详解三大MQ,RabbitMQ、RocketMQ、Kafka,各自的使用、原理和适用场景。

BugMan的博客 2万+

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

【MQ】消息队列概述

消息队列:一般我们简称为MQ(Message Queue)Message Queue :消息队列中间件,很多初学者认为,MQ通过消息的发送和接受来实现程序的异步和解耦。mq主要用于异步操作,这个并不是mq的真正目的,这只是mq的应用,mq的真正目的是为了通讯。

m0_62645012的博客 1789

【专栏RocketMq】简单介绍

MQ

孤刺 2361

【面朝大厂】面试官:详细说说对MQ消息队列的理解以及主流MQ的优缺点...

点击上方“Java基基”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏原创 | Java 2020超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网...

weixin_44421461的博客 266

【Kafka基础篇】RabbitMQ、RocketMQ、Kafka怎么选?3种主流MQ核心差异实测解析

本文深入探讨了消息队列的核心价值与主流MQ对比,重点解析Kafka从消息队列到分布式流处理平台的演进历程。文章首先回顾消息队列的解耦、削峰和异步通信三大核心价值,以及点对点与发布订阅两大通信模型。随后对比RabbitMQ、RocketMQ和Kafka的架构特性,指出Kafka的高吞吐量源于日志式存储和批量处理机制。最后详细阐述Kafka通过引入流处理API和生态集成,实现了从基础消息传递到完整流处理闭环的升级,成为大数据领域的核心组件。全文为开发者提供了MQ选型指导和技术演进洞察。

m0_58782205的博客 1744

【MQ篇】初识MQ!

想象一下,你开了一家网店,顾客下单后,你需要通知仓库发货、更新库存、给用户发送短信等等。如果没有MQ,你的系统可能就要像这样:顾客下单 -> 调用仓库系统 -> 等待仓库响应 -> 调用库存系统 -> 等待库存响应 -> 调用短信服务 -> 等待短信服务响应 -> 订单完成这就像让快递员直接跑遍所有环节,效率低下不说,万一哪个环节出了问题(比如仓库系统崩了),整个下单流程就卡住了!😱而MQ就像一个“快递中转站”。

qq_56158663的博客 1424

【MQ系列】RocketMQ高级功能

分布式队列因为有高可靠性的要求,所以数据要进行持久化存储。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dYPu0wnd-1685247485249)(http://qinyingjie.top/blogImg/%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F.png)]目前业界较为常用的几款产品(RocketMQ/Kafka/RabbitMQ)均采用的是消息刷盘至所部署虚拟机/物理机的文件系统来做持久化(刷盘一般可

优快云站内信: https://i.youkuaiyun.com/#/msg/chat/qyj19920704 3万+

rocket mq 监听端口_微服务异步架构——MQ之RocketMQ

欢迎关注专栏《Java架构筑基》——专注于Java技术的研究与分享!Java架构筑基​zhuanlan.zhihu.comJava架构筑基——专注于Java技术的研究与分享!后续文章将首发此专栏!欢迎各位Java工程师朋友投稿和关注Java架构师进阶之路<常用资料分享>“我们大家都知道把一个微服务架构变成一个异步架构只需要加一个MQ,现在市面上有很多MQ的开源框架。到底选择哪一个MQ的...

weixin_39979948的博客 350

【RocketMQ系列一】初识RocketMQ

RocketMQ是阿里巴巴开源的一款消息队列,它具有低延迟,高吞吐量,高可靠性等特点。目前,RocketMQ在阿里巴巴内部有着广泛的应用,经历多次双十一的洗礼,RocketMQ的可靠性非常高。

码农飞哥 576
上一篇: 数据库中间件 Sharding-JDBC 源码分析 —— SQL 路由(一)之分库分表配置
下一篇: 分布式消息队列 RocketMQ 源码分析 —— Message 发送与接收
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值