SpringBoot使用ActiveMQ

1,604 阅读7分钟

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

SpringBoot 中使用 ActiveMQ

一、消息队列中间件基本概念

『消息队列中间件』是分布式系统中重要的组件,主要解决应用耦合、异步消息、流量削锋等问题,实现高性能、高可用、可伸缩和最终一致性架构,是大型分布式系统不可缺少的中间件。

目前在生产环境中使用较多的消息队列有 ActiveMQ、RabbitMQ、Kafka 等。

1. 特性

  • 异步性:耗时的同步操作通过以发送消息的方式进行了异步化处理,减少了同步等待的时间
  • 松耦合:消息队列减少了服务之间的耦合性,不同的服务可以通过消息队列进行通信,而不用关心彼此的实现细节,只要定义好消息的格式就行。
  • 分布式:通过对消费者的横向扩展,降低了消息队列阻塞的风险,以及单个消费者产生生单点故障的可能性(当然消息队列本身也可以做成分布式集群)。
  • 可靠性:消息队列一般会把接收到的消息存储到本地硬盘上(当消息被处理完之后,存储信息根据不同的消息队列实现,有可能将其删除),这样即使应用挂掉或者消息队列本身挂掉,消息也能够重新加载。

2. JMS(Java Message Service) 规范

JMS 即『Java 消息服务』应用程序接口,是一个 Java 平台中关于面向消息中间件的 API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。

JMS 的消息机制有 2 种模型:

  • Point to Point:表现为队列的形式,发送的消息,只能被一个接收者取走;|
  • Topic:可以被多个订阅者订阅,类似于群发。|

ActiveMQ 是 JMS 的一个实现。

二、ActiveMQ 介绍

  1. 导入依赖
  2. 配置文件
  3. 创建队列
  4. 创建消息生产者(发消息的)
  5. 创建消息消费者(处理/接受消息)

ActiveMQ 是 Apache 软件基金下的一个开源软件,它遵循 JMS 1.1 规范,为企业消息传递提供高可用、出色性能、可扩展、稳定和安全保障的服务。

ActiveMQ 实现了 JMS 规范,并在此之上提供大量额外的特性。ActiveMQ 支持『队列』和『订阅』两种模式的消息发送方式。

Spring Boot 提供了 ActiveMQ 组件 spring-boot-starter-activemq,用来支持 ActiveMQ 在 Spring Boot 体系内使用。

1. 添加依赖

主要添加组件:spring-boot-starter-activemq

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

2. 配置文件

ActiveMQ有两种运行方式,第一种是单机版的外部消息队列。第二种是使用内存的消息队列

# 基于内存的 ActiveMQ,在开发/调试阶段建议使用这种方式
spring.activemq.in-memory=true
# 不使用连接池
spring.activemq.pool.enabled.false

# 独立安装的 ActiveMQ,在生产环境推荐使用这种
# spring:activemq:broker-url: tcp://192.168.0.1:61616
# spring:activemq:user: admin
# spring:activemq:password: admin

三、队列(Queue)

队列发送的消息,只能被一个消费者接收。

1. 创建队列

@Configuration
public class MqConfig {

    @Bean
    public Queue queue() {
        return new ActiveMQQueue("ben.queue");
    }
}

使用 @Configuration 注解在项目启动时,定义了一个队列 queue 命名为:ben.queue

2. 消息生产者

创建一个消息的生产者:

@Component
public class Producer {

    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    @Autowired
    private Queue queue;

    public void sendQueue(String msg) {
        System.out.println("send queue msg :" + msg);
        this.jmsMessagingTemplate.convertAndSend(this.queue, msg);
    }
}

JmsMessagingTemplate 是 Spring 提供发送消息的工具类,使用 JmsMessagingTemplate 和创建好的 queue 对消息进行发送。

3. 消息消费者

@Component
public class Consumer {

    @JmsListener(destination = "ben.queue")
    public void receiveQueue(String text) {
        System.out.println("Consumer queue msg : " + text);
    }
}

使用注解 @JmsListener(destination = "ben.queue"),表示此方法监控了名为 ben.queue 的队列。当队列 ben.queue 中有消息发送时会触发此方法的执行,text 为消息内容。

4. 测试

创建 SampleActiveMqTests 测试类,注入入创建好的消息生生产者。

@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleActiveMqTests {
    @Autowired
    private Producer producer;

    @Rule
    public OutputCapture outputCapture = new OutputCapture();
}

OutputCapture 是 Spring Boot 提供的一个测试类,它能捕获 System.out 和 System.err 的输出,我们可以利用这个特性来判断程序中的输出是否执行。

@Test
public void sendSimpleQueueMessage() throws InterruptedException {
    this.producer.sendQueue("Test queue message");
    Thread.sleep(1000L);
    assertTrue(this.outputCapture.toString().contains("Test queue"));
}

创建测试方式,使用 producer 发送消息,为了保证容器可以接收到消息,让测试方法等待 1 秒,最后使用 outputCapture 判断是否执行成功。

5. 测试多消费者

上面的案例只是一个生产者一个消费者,我们在模拟一个生产者和多个消费者队列的执行情况。我们复制上面的消费者 Consumer 重新命名为 Consumer2,并且将输出内容加上 2 的关键字,如下:

@Component
public class Consumer2 {
    @JmsListener(destination = "ben.queue")
    public void receiveQueue(String text) {
        System.out.println("Consumer2 queue msg : "+text);
    }
}

在刚才的测试类中添加一个 send100QueueMessage() 方法,模式发送 100 条消息时,两个消费者是如何消费消息的。

@Test
public void send100QueueMessage() throws InterruptedException {
    for (int i = 0; i < 100; i++) {
        this.producer.sendQueue("Test queue message" + i);
    }

    Thread.sleep(1000L);
}

控制台输出结果:

Consumer queue msg : Test queue message0
Consumer2 queue msg : Test queue message1
Consumer queue msg : Test queue message2
Consumer2 queue msg : Test queue message3
...

根据控制台输出的消息可以看出,当有多个消费者监听一个队列时,消费者会自动均衡负载的接收消息,并且每个消息只能有一个消费者所接收。

注意:控制台输出 javax.jms.JMSException: peer (vm://localhost#1) stopped. 报错信息可以忽略,这是 Info 级别的错误,是 ActiveMQ 的一个 bug。

四、广播(Topic)

广播发送的消息,可以被多个消费者接收。

实现广播模式需要修改如下配置:

# 默认值为 false,表示 queue 模式
spring.jms.pub-sub-domain = true  

1. 创建 Topic

@Configuration
public class MqConfig {

    @Bean
    public Topic topic() {
        return new ActiveMQTopic("ben.topic");
    }
}

使用 @Configuration 注解在项目启动时,定义了一个广播 Topic 命名为:ben.topic

2. 消息生产者

创建一个消息的生产者:

@Component
public class Producer {

    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    @Autowired
    private Topic topic;
    public void sendTopic(String msg) {
        System.out.println("send topic msg :" + msg);
        this.jmsMessagingTemplate.convertAndSend(this.topic, msg);
    }
}

和上面的生产者对比只是 convertAndSend() 方法传入的第一个参数变成了 Topic 。

3. 消息消费者

@Component
public class Consumer {

    @JmsListener(destination = "ben.topic")
    public void receiveTopic(String text) {
        System.out.println("Consumer topic msg : " + text);
    }
}

消费者也没有变化,只是监听的名改为上面的 ben.topic,因为模拟多个消费者,复制一份 Consumer 命名为 Consumer2,代码相同在输出中标明来自 Consumer2 。

4. 测试

创建 SampleActiveMqTests 测试类,注入创建好的消息生产者。

@Test
public void sendSimpleTopicMessage() throws InterruptedException {
    this.producer.sendTopic("Test Topic message");
    Thread.sleep(1000L);
}

测试方法执行成功后,会看到控制台输出信息,如下:

send topic msg :Test Topic message
Consumer topic msg : Test Topic message
Consumer2 topic msg : Test Topic message

可以看出两个消费者都收到了发送的消息,从而验证广播(Topic)是一个发送者多个消费者的模式。

五、同时支持队列(Queue)和广播(Topic)

Spring Boot 集成 ActiveMQ 的项目默认只支持队列或者广播中的一种,通过配置项 spring.jms.pub-sub-domain 的值来控制,true 为广播模式,false 为队列模式,默认情况下支持队列模式。

如果需要在同一项目中既支持队列模式也支持广播模式,可以通过 DefaultJmsListenerContainerFactory 创建自定义的 JmsListenerContainerFactory 实例,之后在 @JmsListener 注解中通过 containerFactory 属性引用它。

分别创建两个自定义的 JmsListenerContainerFactory 实例,通过 pubSubDomain 来控制是支持队列模式还是广播模式。

@Configuration
@EnableJms
public class ActiveMQConfig {

    @Bean("queueListenerFactory")
    public JmsListenerContainerFactory<?> queueListenerFactory(ConnectionFactory connectionFactory) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setPubSubDomain(false);
        return factory;
    }

    @Bean("topicListenerFactory")
    public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setPubSubDomain(true);
        return factory;
    }
}

然后在消费者接收的方法中,指明使用 containerFactory 接收消息。

@Component
public class Consumer {
    @JmsListener(destination = "ben.queue", containerFactory = "queueListenerFactory")
    public void receiveQueue(String text) {
        System.out.println("Consumer queue msg : " + text);
    }

    @JmsListener(destination = "ben.topic", containerFactory = "topicListenerFactory")
        public void receiveTopic(String text) {
        System.out.println("Consumer topic msg : " + text);
    }
}

改造完成之后,再次执行队列和广播的测试方法,就会发现项目同时支持了两种类型的消息收发。

ActiveMQ 在 windows 环境下的安装启动

ActiveMQ 5.14.5 下载地址:activemq.apache.org/activemq-51…

下载后加压 zip 到自己的路径(例如:D:\Program Files\apache-activemq-5.14.5),解压后得到的目录结构如下:

Note:启动 activemq 之前,首先要确保本地环境已经配置好了 jdk,而且要保证下载的 activemq 的版本适配于 jdk 的版本。

一切准备就绪后,进入 apache-activemq-5.14.5\bin ,得到如下目录:

根据自己的操作系统,选择进入那个文件夹就可以,我是 64 位操作系统,就进入 win64 目录,双击 activemq.bat 这时 activemq 就启动了,如果黑色窗口一闪而过,那么就是 jdk 的环境变量没有配置的原因。

然后打开浏览器访问 http://localhost:8161/admin/ 弹出登录窗口

用户名密码配置在 apache-activemq-5.14.5\conf\jetty-realm.properties 文件,格式是用户名 : 密码, 角色名

用户名权限配置信息记录在 apache-activemq-5.14.5\conf\jetty.xml

登录成功后,进入 activeMQ 的欢迎页