ActiveMQ 高级特性:延迟消息与优先级队列实战(一)

引言

在当今的分布式系统开发中,消息中间件扮演着至关重要的角色,而 ActiveMQ 作为一款广泛使用的开源消息中间件,凭借其丰富的特性和良好的性能,深受开发者的青睐。它支持多种消息模型,如点对点和发布 / 订阅,并且具备消息持久化、事务支持等功能,能够满足不同场景下的消息通信需求。

延迟消息和优先级队列是 ActiveMQ 的两个高级特性,在实际应用中具有重要的价值。延迟消息允许消息在指定的时间后被投递,这在许多场景中都非常有用,比如订单超时处理,如果用户下单后一段时间内未支付,系统可以通过延迟消息自动取消订单;又如任务定时执行,某些任务需要在特定的时间点执行,延迟消息就能很好地实现这一需求。优先级队列则根据消息的优先级进行排序,优先处理高优先级的消息,这在任务调度系统中尤为重要,比如系统中有一些紧急任务和普通任务,通过优先级队列可以确保紧急任务优先得到处理,从而保证系统的高效运行。

本文将深入探讨 ActiveMQ 的延迟消息与优先级队列这两个高级特性,并通过实战示例详细介绍它们的使用方法和注意事项,帮助读者更好地掌握这两个特性,提升在分布式系统开发中运用 ActiveMQ 的能力。

一、ActiveMQ 基础回顾

1.1 简介

ActiveMQ 是 Apache 软件基金会所研发的开放源代码消息中间件,作为一种纯 Java 程序,只要操作系统支持 Java 虚拟机,它便可执行。它的设计目标是在尽可能多的平台和语言上提供标准的、消息驱动的应用集成 ,具备多种强大特性。

在语言支持方面,它允许使用 Java、C、C++、C#、Ruby、Perl、Python、PHP 等多种语言编写客户端,极大地拓宽了其适用范围,让不同技术栈的开发者都能轻松使用。在协议支持上,涵盖 OpenWire、Stomp REST、WS Notification、XMPP、AMQP 等,使其能适应各种复杂的网络环境和应用场景。

ActiveMQ 完全支持 JMS1.1 和 J2EE 1.4 规范,包括持久化、XA 消息、事务等功能,这使得它在企业级应用开发中表现出色,能够满足企业对于消息处理的严格要求。同时,它对 Spring 框架有着良好的支持,可以很容易地内嵌到使用 Spring 的系统里面去,并且支持 Spring2.0 的特性,方便了基于 Spring 框架的项目集成和开发 。此外,它还通过了常见 J2EE 服务器(如 Geronimo、JBoss 4、GlassFish、WebLogic)的测试,借助 JCA 1.5 resource adaptors 的配置,能够自动部署到任何兼容 J2EE 1.4 的商业服务器上 。

ActiveMQ 支持多种传送协议,如 in-VM、TCP、SSL、NIO、UDP、Jgroups、JXTA 等,为不同的应用场景提供了灵活的选择。在消息持久化方面,支持通过 JDBC 和 journal 提供高速的消息持久化,保证了消息在系统故障等情况下的可靠性。从设计架构上,它保证了高性能的集群、客户端 - 服务器、点对点等通信模式的实现,还支持 Ajax 以及与 Axis 的整合,并且可以很容易地调用内嵌 JMS provider 进行测试。

由于这些特性,ActiveMQ 被广泛应用于各种场景。在电商系统中,像京东、淘宝这类大型电商平台,在处理订单、库存、物流等模块间的通信时,ActiveMQ 可以发挥异步处理和应用解耦的作用,提高系统的响应速度和稳定性。在分布式系统开发中,它能实现不同服务之间的可靠消息传递,确保系统的各个部分能够高效协同工作。

1.2 核心组件与工作原理

  • Broker:Broker 是 ActiveMQ 的核心组件,负责接收、存储和传递消息 ,可以将其看作是一个消息服务器。它可以运行在单机上,也可以分布在多个节点上组成集群,以实现高可用性和负载均衡。比如在一个大型的分布式电商系统中,可能会部署多个 Broker 节点,共同承担处理大量订单消息、库存更新消息等任务,防止单个节点因负载过高而出现性能瓶颈。
  • Connection:Connection 是生产者和消费者与 Broker 之间的通信链路 ,就像是一条连接客户端(生产者或消费者)和消息服务器(Broker)的桥梁。它可以是 TCP 连接、SSL 连接或者 HTTP 连接等。当一个订单系统(生产者)要向 ActiveMQ 发送订单消息时,首先需要建立一个到 Broker 的 Connection,通过这个连接来传输消息。
  • Session:Session 是生产者和消费者之间的通信会话 ,它可以是同步会话,也可以是异步会话。在 Session 中可以创建消息的生产者(MessageProducer)、消费者(MessageConsumer)以及消息的目的地(Destination,如队列 Queue 或主题 Topic)。例如,在一个物流系统(消费者)从 ActiveMQ 接收订单配送消息的过程中,会在一个 Session 内创建 MessageConsumer 来接收消息。
  • Destination:Destination 是消息的接收端 ,分为队列(Queue)和主题(Topic)两种类型。队列是一种先进先出(FIFO)的数据结构,生产者将消息发送到队列,消费者从队列中按照先进先出的顺序取消息进行处理,适用于一对一的消息通信场景,比如订单系统发送订单消息到队列,库存系统从队列中获取消息更新库存。主题则采用发布 / 订阅模式,一个生产者可以向主题发送消息,多个消费者可以订阅该主题并接收到相同的消息,适用于一对多的广播通信场景,比如系统发布公告消息到主题,多个相关的业务模块都可以订阅该主题获取公告内容。
  • MessageProducer:消息生产者,负责将消息发送到 Destination 。比如电商系统中的订单模块,在用户下单后,订单模块作为 MessageProducer 将订单相关消息发送到指定的 Queue 或 Topic。
  • MessageConsumer:消息消费者,负责从 Destination 接收消息 。如电商系统中的物流模块,作为 MessageConsumer 从对应的 Queue 或 Topic 中接收订单消息,从而安排后续的物流配送任务。

消息的生产和消费过程如下:

  1. 消息生产:生产者首先创建一个 ConnectionFactory,通过它创建与 Broker 的 Connection。接着,在这个 Connection 上创建 Session。在 Session 中创建 Destination(Queue 或 Topic)以及 MessageProducer。生产者创建消息对象(如 TextMessage、ObjectMessage 等),设置好消息的内容和属性后,通过 MessageProducer 将消息发送到指定的 Destination。例如,在一个在线教育平台中,当用户购买课程后,订单系统作为生产者,创建一个包含订单信息(课程 ID、用户 ID、购买时间等)的 TextMessage,通过 MessageProducer 发送到名为 "orderQueue" 的队列中。
  1. 消息消费:消费者同样先创建 ConnectionFactory 和 Connection,然后在 Connection 上创建 Session,并在 Session 中创建与生产者发送消息时相同的 Destination 以及 MessageConsumer。消费者通过 MessageConsumer 接收消息,可以采用同步阻塞方式(如Message message = consumer.receive();)等待接收消息,也可以设置消息监听器(consumer.setMessageListener(new MessageListener() {... });)以异步方式接收消息。当接收到消息后,消费者对消息进行处理。比如课程管理系统作为消费者,从 "orderQueue" 队列中接收到订单消息后,解析消息内容,更新课程的销售记录等。

二、延迟消息实战

2.1 延迟消息概念与应用场景

延迟消息是指消息在发送后,并不会立即被投递到消费者,而是在经过指定的时间延迟后才被投递 。这种特性在许多实际应用场景中都非常有用。

在电商系统中,订单超时处理是一个常见的场景。当用户下单后,如果在规定的时间内(如 30 分钟)未完成支付,系统需要自动取消订单并释放库存。通过发送延迟消息,在用户下单时,向消息队列发送一条延迟 30 分钟的消息,消息内容包含订单信息。30 分钟后,消息被投递,消费者接收到消息后检查订单状态,如果订单仍未支付,则执行取消订单和释放库存的操作。

在任务定时执行场景中,比如每天凌晨需要执行数据备份任务,或者每周一需要发送周报提醒。可以在系统中设置定时任务,在合适的时间点发送延迟消息,消息到达消费者后触发相应的任务执行逻辑 。例如,在每天凌晨 0 点,系统发送一条延迟 1 分钟的消息,消费者接收到消息后启动数据备份任务,这样可以避免在整点时刻系统负载过高时执行备份任务,影响其他业务的正常运行。

在物流系统中,当货物到达目的地后,可能需要在一段时间后(如 2 小时)自动确认收货并更新物流状态。通过延迟消息,在货物到达目的地时发送一条延迟 2 小时的消息,消息包含物流单号等信息,2 小时后消息被投递,消费者根据消息内容确认收货并更新物流状态,提高物流处理的自动化程度 。

2.2 ActiveMQ 实现延迟消息的方式

2.2.1 使用 TimeToLive 属性

通过设置消息的 TimeToLive(TTL)属性可以实现延迟投递。TTL 属性表示消息在队列中的存活时间,单位为毫秒。当生产者发送消息时,设置 TTL 属性为一个大于 0 的值,消息并不会立即被投递,而是在经过 TTL 时间后才会被投递到消费者 。

例如,以下是使用 Java 代码通过设置 TimeToLive 属性实现延迟消息发送的生产者示例:

复制代码

import javax.jms.*;

import org.apache.activemq.ActiveMQConnectionFactory;

public class DelayedMessageProducer {

private static final String BROKER_URL = "tcp://localhost:61616";

private static final String QUEUE_NAME = "DelayedMessageQueue";

public static void main(String[] args) throws JMSException {

// 创建连接工厂

ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(BROKER_URL);

// 创建连接

Connection connection = connectionFactory.createConnection();

// 启动连接

connection.start();

// 创建会话

Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// 创建队列

Destination destination = session.createQueue(QUEUE_NAME);

// 创建消息生产者

MessageProducer producer = session.createProducer(destination);

// 创建文本消息

TextMessage message = session.createTextMessage("这是一条延迟消息");

// 设置消息的TimeToLive属性为5000毫秒,即5秒后投递

message.setJMSExpiration(System.currentTimeMillis() + 5000);

// 发送消息

producer.send(message);

System.out.println("延迟消息已发送");

// 关闭资源

producer.close();

session.close();

connection.close();

}

}

消费者代码示例如下:

复制代码

import javax.jms.*;

import org.apache.activemq.ActiveMQConnectionFactory;

public class DelayedMessageConsumer {

private static final String BROKER_URL = "tcp://localhost:61616";

private static final String QUEUE_NAME = "DelayedMessageQueue";

public static void main(String[] args) throws JMSException {

// 创建连接工厂

ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(BROKER_URL);

// 创建连接

Connection connection = connectionFactory.createConnection();

// 启动连接

connection.start();

// 创建会话

Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// 创建队列

Destination destination = session.createQueue(QUEUE_NAME);

// 创建消息消费者

MessageConsumer consumer = session.createConsumer(destination);

// 接收消息,这里采用同步阻塞方式接收消息

Message message = consumer.receive();

if (message != null && message instanceof TextMessage) {

TextMessage textMessage = (TextMessage) message;

System.out.println("接收到延迟消息:" + textMessage.getText());

}

// 关闭资源

consumer.close();

session.close();

connection.close();

}

}

在上述代码中,生产者创建了一条文本消息,并设置了消息的JMSExpiration属性(即 TTL)为当前时间加上 5000 毫秒,这样消息会在 5 秒后被投递。消费者通过receive方法接收消息,当消息被投递时,消费者可以接收到并处理该消息。

2.2.2 使用 Scheduled Message 机制

ActiveMQ 的 Scheduled Message 机制提供了更灵活的延迟消息设置方式 。它允许通过设置消息的属性来实现延迟投递、定时重复投递等功能。

主要涉及以下几个属性:

  • AMQ_SCHEDULED_DELAY:消息延迟发送的时间,单位为毫秒 。例如,设置该属性为 60000,表示消息将在 60 秒后发送。
  • AMQ_SCHEDULED_PERIOD:每次重新发送该消息的时间间隔,单位为毫秒 。如果设置了该属性,消息在延迟发送后,会按照这个时间间隔重复发送。
  • AMQ_SCHEDULED_REPEAT:重新发送该消息的次数 。结合AMQ_SCHEDULED_PERIOD属性,可以实现消息的多次重复发送。
  • AMQ_SCHEDULED_CRON:使用 Cron 表达式设置发送该消息的时机 。通过 Cron 表达式,可以实现更复杂的定时任务,比如每天凌晨 3 点发送消息。

要使用 Scheduled Message 机制,需要在activemq.xml配置文件中开启 schedulerSupport属性,即在broker节点上添加schedulerSupport="true",如下所示:

复制代码

<broker xmlns="http://activemq.apache.org/schema/core" schedulerSupport="true">

<!-- 其他配置 -->

</broker>

以下是基于 Spring Boot 的代码示例,展示如何使用 Scheduled Message 机制发送延迟消息:

首先,在pom.xml文件中添加 ActiveMQ 和 Spring JMS 的依赖:

复制代码

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-activemq</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-jms</artifactId>

</dependency>

</dependencies>

然后,在application.yml文件中配置 ActiveMQ 的连接信息:

复制代码

spring:

activemq:

broker-url: tcp://localhost:61616

user: admin

password: admin

接着,创建消息发送服务类:

复制代码

import org.apache.activemq.ScheduledMessage;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jms.core.JmsTemplate;

import org.springframework.stereotype.Service;

@Service

public class ScheduledMessageService {

@Autowired

private JmsTemplate jmsTemplate;

public void sendScheduledMessage(String destinationName, String messageContent, long delay, long period, int repeat) {

jmsTemplate.send(destinationName, session -> {

// 创建文本消息

javax.jms.TextMessage textMessage = session.createTextMessage(messageContent);

// 设置延迟发送时间

textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delay);

// 设置重复发送时间间隔

if (period > 0) {

textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, period);

}

// 设置重复发送次数

if (repeat > 0) {

textMessage.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);

}

return textMessage;

});

}

}

最后,在测试类中调用发送方法:

复制代码

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest

public class ScheduledMessageTest {

@Autowired

private ScheduledMessageService scheduledMessageService;

@Test

public void testSendScheduledMessage() {

// 发送延迟消息,延迟60秒发送,不重复发送

scheduledMessageService.sendScheduledMessage("ScheduledMessageQueue", "这是一条定时延迟消息", 60000, 0, 0);

}

}

在上述代码中,通过ScheduledMessageService类的sendScheduledMessage方法发送延迟消息,设置了消息的延迟时间为 60 秒,不设置重复发送时间间隔和次数。在测试类中调用该方法,即可发送延迟消息 。消费者的代码与前面使用 TimeToLive 属性时的消费者代码类似,通过监听队列接收消息并处理。

2.3 实战案例与代码实现

2.3.1 案例背景与需求分析

假设我们正在开发一个电商系统,其中订单模块需要实现订单支付超时取消的功能。当用户下单后,系统生成订单并发送到消息队列,同时设置订单的支付超时时间为 30 分钟。如果在 30 分钟内用户未完成支付,系统需要自动取消订单,并将订单状态更新为 "已取消",同时释放订单中占用的库存 。

为了实现这个功能,我们可以利用 ActiveMQ 的延迟消息特性。在用户下单时,向 ActiveMQ 发送一条包含订单信息的延迟消息,延迟时间设置为 30 分钟。30 分钟后,消息被投递到消费者,消费者接收到消息后检查订单的支付状态,如果订单未支付,则执行取消订单和释放库存的操作 。

2.3.2 代码实现与配置

首先,创建一个 Spring Boot 项目,并在pom.xml文件中添加 ActiveMQ 和 Spring JMS 的依赖:

复制代码

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-activemq</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-jms</artifactId>

</dependency>

</dependencies>

然后,在application.yml文件中配置 ActiveMQ 的连接信息:

复制代码

spring:

activemq:

broker-url: tcp://localhost:61616

user: admin

password: admin

接下来,创建订单实体类Order:

复制代码

import java.io.Serializable;

public class Order implements Serializable {

private static final long serialVersionUID = 1L;

private Long orderId;

private String orderNo;

private String status;

// 其他订单相关属性和getter、setter方法

}

创建消息发送服务类OrderMessageSender:

复制代码

import org.apache.activemq.ScheduledMessage;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jms.core.JmsTemplate;

import org.springframework.stereotype.Service;

@Service

public class OrderMessageSender {

@Autowired

private JmsTemplate jmsTemplate;

public void sendOrderMessage(Order order, long delay) {

jmsTemplate.send("OrderQueue", session -> {

// 创建对象消息,因为要传递订单对象

javax.jms.ObjectMessage objectMessage = session.createObjectMessage(order);

// 设置延迟发送时间,单位毫秒

objectMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delay);

return objectMessage;

});

}

}

创建消息接收服务类OrderMessageReceiver:

复制代码

import org.springframework.jms.annotation.JmsListener;

import org.springframework.stereotype.Service;

@Service

public class OrderMessageReceiver {

@JmsListener(destination = "OrderQueue")

public void receiveOrderMessage(Order order) {

// 检查订单支付状态,这里假设订单实体中有支付状态字段,实际应用中需要从数据库查询

if ("未支付".equals(order.getStatus())) {

// 执行取消订单操作,更新订单状态到数据库

order.setStatus("已取消");

// 模拟释放库存操作

System.out.println("订单 " + order.getOrderNo() + " 支付超时,已取消,库存已释放");

} else {

System.out.println("订单 " + order.getOrderNo() + " 已支付,无需取消");

}

}

}

在ActiveMQ的配置文件activemq.xml中,确保开启了 schedulerSupport属性:

复制代码

<broker xmlns="http://activemq.apache.org/schema/core" schedulerSupport="true">

<!-- 其他配置 -->

</broker>

在订单创建的业务逻辑中,调用消息发送服务发送延迟消息:

复制代码

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RestController;

@RestController

public class OrderController {

@Autowired

private OrderMessageSender orderMessageSender;

@PostMapping("/orders")

public String createOrder(@RequestBody Order order) {

// 模拟订单创建,设置订单编号和初始状态

order.setOrderNo("20240101001");

order.setStatus("未支付");

// 发送延迟消息,延迟30分钟,单位毫秒

orderMessageSender.sendOrderMessage(order, 30 * 60 * 1000);

return "订单已创建,等待支付";

}

}

2.3.3 测试与验证

启动 ActiveMQ 服务和 Spring Boot 应用。使用 Postman 等工具向/orders接口发送 POST 请求,请求体中包含订单信息,模拟用户下单操作 。

发送请求后,查看控制台输出,确认订单消息已发送。等待 30 分钟后,再次查看控制台输出,应该能看到订单支付超时取消的相关信息,如 "订单 20240101001 支付超时,已取消,库存已释放" 。

为了更准确地验证,可以在数据库中查看订单状态是否已更新为 "已取消",以及库存相关数据是否已恢复到订单创建前的状态 。通过以上测试,可以验证延迟消息是否按照预期时间投递和消费,以及订单支付超时取消功能是否正常工作。

相关推荐
你想考研啊11 天前
Linux下搭建Activemq的Master-Slave(共享文件模式)
linux·运维·activemq
好玩的Matlab(NCEPU)15 天前
消息队列RabbitMQ、Kafka、ActiveMQ 、Redis、 ZeroMQ、Apache Pulsar对比和如何使用
kafka·rabbitmq·activemq
埃泽漫笔20 天前
Kafka、ActiveMQ、RabbitMQ、RocketMQ 对比
kafka·rabbitmq·activemq
小池先生1 个月前
activemq延迟消息变成实时收到了?
linux·数据库·activemq
clownAdam2 个月前
ActiveMQ classic ,artemis ,artemis console ,nms clients,cms client详解
activemq
百思可瑞教育2 个月前
ActiveMQ、RocketMQ、RabbitMQ、Kafka 的全面对比分析
vue.js·分布式·rabbitmq·rocketmq·activemq·北京百思可瑞教育·百思可瑞教育
Zhang.jialei3 个月前
HiveMQ 2024.9 设计与开发文档
hive·物联网·activemq
学习HCIA的小白5 个月前
ActiveMQ
activemq
代码的余温5 个月前
ActiveMQ多消费者负载均衡优化指南
java·后端·负载均衡·activemq