目录
什么是MQ
MQ 是“消息队列”(Message Queue)的缩写,是一种用于异步通信的中间件系统。它允许不同应用程序或服务之间通过消息传递进行数据交换,而不需要它们直接连接或实时交互。MQ 的核心功能包括消息的存储、路由和分发,使得消息生产者和消费者可以解耦,从而提高系统的可伸缩性和可靠性。常见的 MQ 系统包括 RabbitMQ、Apache Kafka 和 ActiveMQ等。
几种常见MQ的对比:
RabbitMQ | ActiveMQ | RocketMQ | Kafka | |
公司/社区 | Rabbit | Apache | 阿里 | Apache |
开发语言 | Erlang | Java | Java | Scala&Java |
协议支持 | AMQP,XMPP,SMTP,STOMP | OpenWire,STOMP,REST,XMPP,AMQP | 自定义协议 | 自定义协议 |
可用性 | 高 | 一般 | 高 | 高 |
单机吞吐量 | 一般 | 差 | 高 | 非常高 |
消息延迟 | 微秒级 | 毫秒级 | 毫秒级 | 毫秒以内 |
消息可靠性 | 高 | 一般 | 高 | 一般 |
- 可用性:Kafka、RocketMQ 和 RabbitMQ 都具有较高的可用性,能满足大规模系统的需求。
- 可靠性:RabbitMQ 和 RocketMQ 在消息可靠性方面表现较好,尤其是在持久化和事务处理上。
- 吞吐能力:RocketMQ 和 Kafka 在吞吐量上表现突出,适合需要处理大量消息的场景。
- 消息低延迟:RabbitMQ 和 Kafka 能提供较低的消息延迟,适用于需要实时响应的应用。
据统计,目前国内消息队列使用最多的还是RabbitMQ,再加上其各方面都比较均衡,稳定性也好,
什么是RabbitMQ
RabbitMQ 是一个开源的消息代理软件,使用 Erlang 编写,支持多种消息传递协议,如 AMQP、STOMP 和 MQTT。它允许应用程序以异步的方式进行通信,通过在消息队列中发送和接收消息来实现解耦。
RabbitMQ对应的架构如图:
其中包含几个概念:
-
publisher
:生产者,也就是发送消息的一方 -
consumer
:消费者,也就是消费消息的一方 -
queue
:队列,存储消息。生产者投递的消息会暂存在消息队列中,等待消费者处理 -
exchange
:交换机,负责消息路由。生产者发送的消息由交换机决定投递到哪个队列。 -
virtual host
:虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue
上述这些东西都可以在RabbitMQ的管理控制台来管理,
RabbitMQ 使用场景
1. 服务解耦
问题:服务之间的紧密耦合会导致代码难以维护,特别是当系统扩展到多个服务时,每个服务都需要直接调用其他服务进行数据传输。
解决方案:使用 RabbitMQ,可以将服务之间的直接调用转换为消息传递。服务 A 只需将消息发布到 RabbitMQ,而服务 B、C、D 等只需订阅相关的消息队列。这样,服务 A 不需要知道具体的服务 B、C、D,也无需关心它们的状态或是否存在,这样可以显著降低服务之间的耦合度,提高系统的可维护性。
2. 流量削峰
问题:在高峰期,系统会面临瞬时的流量激增,这可能导致服务器过载,资源浪费,或者需要对资源进行不必要的扩展。
解决方案:RabbitMQ 可以作为一个缓冲区来处理流量峰值。高峰期间,系统中的大量请求可以被发送到 RabbitMQ 队列中,而不是直接处理。然后,后台服务可以从消息队列中逐步处理这些请求,这样可以平滑流量,避免瞬时高峰对系统的冲击,同时减少资源浪费。
3. 异步调用
问题:某些操作(如寻找外卖小哥)可能非常耗时,如果在调用链中进行同步处理,会导致整体响应时间变长,影响用户体验。
解决方案:利用 RabbitMQ 的异步处理能力,可以将耗时的操作从主调用链中分离出来。订单系统可以立即将订单消息发送到 RabbitMQ,并快速返回响应。后台服务可以异步处理这些订单,寻找外卖小哥的过程不会阻塞主应用的响应流程。这样,系统的主响应时间会显著缩短,同时后台服务可以在不影响主流程的情况下完成复杂的操作。
RabbitMQ 基本概念
RabbitMQ 是一个消息中间件,用于异步消息处理,是分布式系统标准的配置。其基本组件包括:
- Exchange:接收生产者的消息,并根据预设的路由规则(如 direct、fanout、topic)将消息分发到相应的队列。
- Message Queue:存储消息,直到消费者处理它们。
- Binding Key:连接 Exchange 和队列的关键字,定义消息路由规则。
- Routing Key:生产者在发送消息时指定的关键字,用于确定消息的路由方式。
RabbitMQ 的配置和这些组件的配合,使得系统能够高效、灵活地处理消息。
RabbitMQ 安装
RabbitMQ Web界面管理
默认情况下,是没有安装web端的客户端插件,需要安装插件才可以生效。执行命令:
# 进入rabbitmq的sbin目录
# 启动rabbitmq_managemen是管理后台的插件、我们要开启这个插件才能通过浏览器访问登录页面
rabbitmq-plugins enable rabbitmq_management
# 启动rabbitmq
rabbitmq-server start
#访问登录页面,RabbitMQ默认的管理界面账号和密码通常是:guest
http://localhost:15672
RabbitMQ六种工作模式
简单模式
RabbitMQ 可以用一个简单的比喻来解释,它就像是一个邮局,处理和传递消息。
生产者(Producer):
- 生产者就像是一个发信人,它们创建消息并将其发送到 RabbitMQ。
- 你可以把生产者想象成一个放信件的邮件箱。
队列(Queue):
- 队列像是一个邮箱或邮局的储存区,用来暂时存储消息。
- 消息在队列中被保留,直到被消费者取走。
- 多个生产者可以将消息发送到同一个队列,同时多个消费者可以从同一个队列中取走消息。
消费者(Consumer):
- 消费者是接收和处理消息的程序。
- 消费者就像是一个取信的人员,它从队列中取出消息并处理它们。
工作流程
-
生产者发送消息:生产者将消息发送到 RabbitMQ 中的某个队列。
-
消息存储在队列中:消息存储在队列中,直到消费者来取。
-
消费者从队列中接收消息:消费者连接到队列并接收消息进行处理。
SpringBoot+RabbitMQ实战
1、添加依赖
在 pom.xml 文件中添加 RabbitMQ 相关的依赖:
<!--rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2、配置 RabbitMQ
在 application.properties文件中配置 RabbitMQ 的连接信息:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
3、创建消息生产者
创建一个 MessageProducer 类用于发送消息:
package com.example.demo.producer;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* MessageProducer类用于发送消息到MQ队列中
*/
@Component
public class MessageProducer {
/**
* amqpTemplate是用于发送消息的核心模板类
*/
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 发送消息到指定的队列中
*
* @param message 要发送的消息内容
*/
public void sendMessage(String message) {
amqpTemplate.convertAndSend("myQueue", message);
}
}
4、创建消息消费者
创建一个 MessageConsumer 类用于接收消息:
package com.example.demo.consumer;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* MessageConsumer类用于监听和处理来自RabbitMQ的消息
*/
@Component
public class MessageConsumer {
/**
* 监听指定队列中的消息
*
* @param message 接收到的消息内容
*/
@RabbitListener(queues = "myQueue")
public void receiveMessage(String message) {
System.out.println("收到的消息: " + message);
}
}
5、配置 RabbitMQ 队列、交换机和绑定
创建一个配置类 RabbitMQConfig:
package com.example.demo.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置RabbitMQ相关的设置
*/
@Configuration
public class RabbitMQConfig {
/**
* 声明一个队列
*
* @return Queue对象,表示一个名为"myQueue"的队列,该队列是持久的
*/
@Bean
public Queue myQueue() {
return new Queue("myQueue", false);
}
}
6. 启动 Spring Boot 应用
7. 测试生产者和消费者
在 Spring Boot 应用启动后,通过注入 MessageProducer 来发送消息:
package com.example.demo.runner;
import com.example.demo.producer.MessageProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* Runner类用于在Spring Boot应用启动时运行特定的任务
*/
@Component
public class Runner implements CommandLineRunner {
// 注入消息生产者,用于发送消息到RabbitMQ
@Autowired
private MessageProducer messageProducer;
/**
* 在应用启动时运行的方法
* @param args 命令行参数,本实现中未使用
* @throws Exception 如果执行过程中发生异常则抛出
*/
@Override
public void run(String... args) throws Exception {
// 启动时发送一条消息到RabbitMQ
messageProducer.sendMessage("Hello, RabbitMQ!");
}
}
总结
RabbitMQ 使得消息的传递变得简单而高效。生产者将消息放入队列,消费者从队列中取出并处理消息。这个过程就像是将信件放入邮箱,然后邮递员将其送到目的地。通过这种方式,可以实现生产者与消费者之间的解耦,提升系统的灵活性和可扩展性。
工作模式
工作队列的核心思想是将任务异步处理,通过将任务封装为消息并发送到队列,避免了立即执行带来的负担。这样,后台的工作进程可以在适当的时候处理这些任务。多个消费者可以并行工作,从而提高处理能力。随着任务量的增加,可以通过增加更多的工作进程来扩展系统的处理能力,确保任务高效地分配和执行。
SpringBoot+RabbitMQ实战
RabbitMQ 配置
package com.example.demo.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置RabbitMQ的相关设置
*/
@Configuration
public class RabbitMQConfig {
/**
* 声明一个持久化的任务队列
*
* @return Queue对象,表示名为"taskQueue"的持久化队列
*/
@Bean
public Queue taskQueue() {
return new Queue("taskQueue", true);
}
}
生产者(发送任务)
package com.example.demo.producer;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* TaskProducer类用于生产消息,将任务发送到消息队列中
*/
@Component
public class TaskProducer {
/**
* amqpTemplate是用于发送消息的模板对象
*/
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 发送任务到指定的消息队列
*
* @param task 要发送的任务信息
*/
public void sendTask(String task) {
amqpTemplate.convertAndSend("taskQueue", task);
}
}
消费者(处理任务)
package com.example.demo.consumer;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* TaskConsumer类用于消费消息队列中的任务
* 通过使用RabbitListener注解,监听特定的队列,并处理接收到的消息
*/
@Component
public class TaskConsumer {
/**
* 接收并处理来自任务队列的消息
*
* @param task 从队列中接收到的任务消息
* 该方法使用RabbitListener注解,指定监听的队列为"taskQueue"
* 接收到消息后,先打印处理任务的信息,模拟任务处理耗时操作后,打印任务完成的信息
*/
@RabbitListener(queues = "taskQueue")
public void receiveTask(String task) {
System.out.println("Processing task: " + task);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Task completed: " + task);
}
}
任务发送测试
package com.example.demo.runner;
import com.example.demo.producer.TaskProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* TaskRunner类用于在应用程序启动时运行任务它实现了CommandLineRunner接口,
* 允许在Spring Boot应用程序启动完成后执行自定义的运行逻辑
*/
@Component
public class TaskRunner implements CommandLineRunner {
// 注入TaskProducer的实例,用于发送任务消息
@Autowired
private TaskProducer taskProducer;
/**
* 当Spring Boot应用程序启动后运行此方法
* 它会发送5个任务消息到消息队列
*
* @param args 命令行参数,本例中未使用
* @throws Exception 如果任务发送过程中发生异常,则抛出异常
*/
@Override
public void run(String... args) throws Exception {
// 循环发送5个任务消息
for (int i = 1; i <= 5; i++) {
taskProducer.sendTask("Task " + i);
}
}
}
发布订阅模式
发布/订阅模式是一种常见的消息传递模式,它允许消息被广播到多个消费者。这个模式的主要优势在于它的灵活性和扩展性,使得一个消息可以被多个消费者同时接收和处理。
RabbitMQ中的Exchange
在RabbitMQ中,生产者不直接将消息发送到队列。相反,生产者将消息发送到交换机(Exchange)。交换机的职责是决定消息的去向,将消息路由到一个或多个队列中。RabbitMQ支持几种不同类型的交换机,每种交换机根据不同的路由规则来决定如何处理消息:
- Direct Exchange: 根据路由键将消息路由到匹配的队列。
- Topic Exchange: 根据主题模式将消息路由到匹配的队列。
- Header Exchange: 根据消息的头部信息进行路由。
- Fanout Exchange: 将收到的消息广播到所有绑定的队列。
对于发布/订阅模式,fanout 交换机是最适合的,因为它会将消息广播到所有绑定的队列中,不关心路由键或消息的内容。
创建和配置Fanout Exchange
在使用fanout交换机之前,需要在RabbitMQ中进行配置。以下是如何在RabbitMQ中创建一个fanout类型的交换机:
channel.exchangeDeclare("logs", "fanout");
这条命令创建了一个名为“logs”的fanout交换机。所有发送到这个交换机的消息都会被广播到所有与该交换机绑定的队列中。
创建队列并绑定到Exchange
在发布/订阅模式中,每个消费者都会有一个队列,用来接收消息。为了确保每个消费者都能接收到消息,我们需要为每个消费者创建一个新的队列,并将这个队列绑定到交换机上:
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, "logs", "");
这里,queueDeclare()创建了一个具有随机名称的临时队列,这个队列在断开连接后会自动删除。queueBind()将这个队列与之前创建的fanout交换机绑定。
SpringBoot+RabbitMQ实战
配置RabbitMQ
创建一个配置类来定义RabbitMQ的Exchange、Queue和Binding:
package com.example.demo.config;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置RabbitMQ的Fanout交换机和队列以及它们之间的绑定关系
*/
@Configuration
public class RabbitConfig {
/**
* 声明一个Fanout类型的交换机
* Fanout交换机会把消息分发到所有绑定到它的队列中
*
* @return FanoutExchange 返回一个名为"logs"的FanoutExchange对象
*/
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("logs");
}
/**
* 声明一个持久化的队列queue1
* 队列被声明为持久化确保RabbitMQ重启后队列会被重新创建
*
* @return Queue 返回一个名为"queue1"的Queue对象
*/
@Bean
public Queue queue1() {
return new Queue("queue1", true);
}
/**
* 声明一个持久化的队列queue2
*
* @return Queue 返回一个名为"queue2"的Queue对象
*/
@Bean
public Queue queue2() {
return new Queue("queue2", true);
}
/**
* 将队列queue1绑定到Fanout交换机
* 绑定操作使得Fanout交换机可以将收到的消息分发到queue1
*
* @param fanoutExchange Fanout交换机对象
* @param queue1 需要绑定的队列queue1对象
* @return Binding 返回一个表示queue1与Fanout交换机之间绑定关系的Binding对象
*/
@Bean
public Binding binding1(FanoutExchange fanoutExchange, Queue queue1) {
return BindingBuilder.bind(queue1).to(fanoutExchange);
}
/**
* 将队列queue2绑定到Fanout交换机
*
* @param fanoutExchange Fanout交换机对象
* @param queue2 需要绑定的队列queue2对象
* @return Binding 返回一个表示queue2与Fanout交换机之间绑定关系的Binding对象
*/
@Bean
public Binding binding2(FanoutExchange fanoutExchange, Queue queue2) {
return BindingBuilder.bind(queue2).to(fanoutExchange);
}
}
创建生产者
编写一个服务类用于发送消息到RabbitMQ:
package com.example.demo.producer;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 消息发布者服务类
* 用于发送消息到RabbitMQ的Fanout交换器
*/
@Service
public class MessagePublisher {
// 注入RabbitMQ模板,用于消息的发送
@Autowired
private RabbitTemplate rabbitTemplate;
// 注入Fanout交换器,用于广播消息
@Autowired
private FanoutExchange fanoutExchange;
/**
* 发送消息方法
*
* @param message 要发送的消息内容
* 通过Fanout交换器广播消息,没有任何路由键,将消息发送到所有绑定到该交换器的队列中
*/
public void sendMessage(String message) {
rabbitTemplate.convertAndSend(fanoutExchange.getName(), "", message);
}
}
创建消费者
编写一个消息监听器类来接收和处理来自RabbitMQ的消息:
package com.example.demo.consumer;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 消息监听器,用于处理来自不同队列的消息
*/
@Component
public class MessageListener {
/**
* 监听队列queue1的消息
* 当队列中有消息时,此方法将被调用
*
* @param message 队列queue1中的消息
*/
@RabbitListener(queues = "queue1")
public void receiveFromQueue1(String message) {
System.out.println("Received from queue1: " + message);
}
/**
* 监听队列queue2的消息
* 当队列中有消息时,此方法将被调用
*
* @param message 队列queue2中的消息
*/
@RabbitListener(queues = "queue2")
public void receiveFromQueue2(String message) {
System.out.println("Received from queue2: " + message);
}
}
创建一个控制器
package com.example.demo.Controller;
import com.example.demo.producer.MessagePublisher;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 消息控制器,处理消息相关的HTTP请求
*/
@RestController
@RequestMapping("/api/messages")
public class MessageController {
/**
* 注入的消息发布者,用于发送消息到消息队列
*/
@Autowired
private MessagePublisher messagePublisher;
/**
* 处理POST请求,发送消息到消息队列
*
* @param message 要发送的消息内容,通过请求体传递
* @return
*/
@PostMapping
public String sendMessage(@RequestBody String message) {
messagePublisher.sendMessage(message);
return "1";
}
}
postman测HTTP接口
总结
在发布/订阅模式中,fanout交换机非常适合于广播消息的场景。每个消费者都通过创建和绑定队列来接收消息,从而确保所有消费者都能够收到相同的消息。通过这种方式,可以轻松地扩展系统,添加更多的消费者,或者在不同的处理节点中处理消息。
路由模式
在这节中,我们将学习如何使用直连交换机(Direct exchange)来实现更精确的消息路由。这个模式允许根据消息的路由键(routingKey)将消息发送到特定的队列,从而实现更细粒度的消息过滤和处理。
绑定 (Bindings)
在之前的示例中,我们使用了简单的绑定,将队列和交换机连接在一起。绑定可以用来指定队列对来自交换机的消息感兴趣。
绑定键(Binding Key) 是一个用于在交换机和队列之间建立关系的标识符。在使用直连交换机时,绑定键非常重要,因为它决定了消息的路由规则。与基本的 basic_publish 方法中的 routingKey 不同,我们通常称之为 bindingKey,它在直连交换机中用于匹配消息的路由键。
ch.queueBind(queueName, "logs", "black");
直连交换机(Direct exchange)
直连交换机使用简单的路由算法:消息被发送到所有绑定了匹配路由键的队列。比如:
交换机 X 有两个队列绑定:队列 Q1 绑定了 orange,队列 Q2 绑定了 black 和 green。如果一条消息的路由键是 orange,它会被发送到 Q1。如果一条消息的路由键是 black,它会被发送到 Q2。
如果路由键不匹配任何绑定键,消息将被丢弃。
多重绑定 (Multiple bindings)
可以将同一个绑定键绑定到多个队列,这样消息会被发送到所有匹配的队列。举个例子,如果将绑定键 black 绑定到队列 Q1 和 Q2,那么路由键为 black 的消息会被同时发送到这两个队列。
SpringBoot+RabbitMQ实战
配置 Direct Exchange、队列和绑定
package com.example.demo.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置RabbitMQ的Direct Exchange路由模式
* 本类用于设置RabbitMQ的消息队列、直连交换机以及它们之间的绑定关系
*/
@Configuration
public class RabbitMQConfig {
// 定义交换机名称常量
public static final String EXCHANGE_NAME = "direct_logs";
// 定义信息队列名称常量
public static final String INFO_QUEUE = "info_queue";
// 定义警告队列名称常量
public static final String WARNING_QUEUE = "warning_queue";
// 定义错误队列名称常量
public static final String ERROR_QUEUE = "error_queue";
/**
* 声明一个直连交换机
*
* @return DirectExchange 返回创建的直连交换机实例
*/
@Bean
DirectExchange directExchange() {
return new DirectExchange(EXCHANGE_NAME);
}
/**
* 声明一个持久化的信息队列
*
* @return Queue 返回创建的队列实例
*/
@Bean
Queue infoQueue() {
return QueueBuilder.durable(INFO_QUEUE).build();
}
/**
* 声明一个持久化的警告队列
*
* @return Queue 返回创建的队列实例
*/
@Bean
Queue warningQueue() {
return QueueBuilder.durable(WARNING_QUEUE).build();
}
/**
* 声明一个持久化的错误队列
*
* @return Queue 返回创建的队列实例
*/
@Bean
Queue errorQueue() {
return QueueBuilder.durable(ERROR_QUEUE).build();
}
/**
* 将信息队列与直连交换机按"info"路由键进行绑定
*
* @param infoQueue 信息队列实例
* @param directExchange 直连交换机实例
* @return Binding 返回创建的绑定实例
*/
@Bean
Binding infoBinding(Queue infoQueue, DirectExchange directExchange) {
return BindingBuilder.bind(infoQueue).to(directExchange).with("info");
}
/**
* 将警告队列与直连交换机按"warning"路由键进行绑定
*
* @param warningQueue 警告队列实例
* @param directExchange 直连交换机实例
* @return Binding 返回创建的绑定实例
*/
@Bean
Binding warningBinding(Queue warningQueue, DirectExchange directExchange) {
return BindingBuilder.bind(warningQueue).to(directExchange).with("warning");
}
/**
* 将错误队列与直连交换机按"error"路由键进行绑定
*
* @param errorQueue 错误队列实例
* @param directExchange 直连交换机实例
* @return Binding 返回创建的绑定实例
*/
@Bean
Binding errorBinding(Queue errorQueue, DirectExchange directExchange) {
return BindingBuilder.bind(errorQueue).to(directExchange).with("error");
}
}
发送消息
package com.example.demo.Service;
import com.example.demo.config.RabbitMQConfig;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 消息发送服务类
* 该类提供了发送不同类型消息的方法,并通过RabbitMQ进行传输
*/
@Service
public class MessageSender {
// 注入AmqpTemplate实例,用于发送消息到RabbitMQ
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 发送信息类型的消息
*
* @param message 要发送的消息内容
* 使用RabbitMQ的发布确认机制,将消息发送到指定的交换机,routing key为"info"
*/
public void sendInfoMessage(String message) {
amqpTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "info", message);
System.out.println("Sent info: " + message);
}
/**
* 发送警告类型的消息
*
* @param message 要发送的消息内容
* 使用RabbitMQ的发布确认机制,将消息发送到指定的交换机,routing key为"warning"
*/
public void sendWarningMessage(String message) {
amqpTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "warning", message);
System.out.println("Sent warning: " + message);
}
/**
* 发送错误类型的消息
*
* @param message 要发送的消息内容
* 使用RabbitMQ的发布确认机制,将消息发送到指定的交换机,routing key为"error"
*/
public void sendErrorMessage(String message) {
amqpTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "error", message);
System.out.println("Sent error: " + message);
}
}
接收消息
package com.example.demo.Service;
import com.example.demo.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
/**
* 消息接收服务,用于从RabbitMQ中接收不同类型的消
* 息并处理它们。
*/
@Service
public class MessageReceiver {
/**
* 接收并处理信息类型的消息。
*
* @param message 从RabbitMQ队列中接收到的消息。
*/
@RabbitListener(queues = RabbitMQConfig.INFO_QUEUE)
public void receiveInfoMessage(String message) {
System.out.println("Received info: " + message);
}
/**
* 接收并处理警告类型的消息。
*
* @param message 从RabbitMQ队列中接收到的消息。
*/
@RabbitListener(queues = RabbitMQConfig.WARNING_QUEUE)
public void receiveWarningMessage(String message) {
System.out.println("Received warning: " + message);
}
/**
* 接收并处理错误类型的消息。
*
* @param message 从RabbitMQ队列中接收到的消息。
*/
@RabbitListener(queues = RabbitMQConfig.ERROR_QUEUE)
public void receiveErrorMessage(String message) {
System.out.println("Received error: " + message);
}
}
启动应用
package com.example.demo;
import com.example.demo.Service.MessageSender;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
/**
* 应用程序的主入口类。
*/
@SpringBootApplication
public class Demo1Application {
/**
* 主方法,应用程序的入口点。
* @param args 命令行参数,在此应用中未使用。
*/
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
/**
* 创建并配置一个 CommandLineRunner Bean。
* 此 Bean 在应用程序上下载入后运行,并发送不同类型的消息。
* @param sender MessageSender 实例,用于发送不同类型的消息。
* @return 返回一个 CommandLineRunner 实例。
*/
@Bean
CommandLineRunner demo(MessageSender sender) {
// 返回一个 Lambda 函数,执行时发送三种类型的消息
return (args) -> {
sender.sendInfoMessage("Info message");
sender.sendWarningMessage("Warning message");
sender.sendErrorMessage("Error message");
};
}
}
主题模式
使用Topic交换机可以显著提升消息路由的灵活性。
Topic交换机概述
Topic交换机 允许基于更复杂的路由模式将消息传递到队列。它的核心在于 routingKey 和 bindingKey 的匹配规则,这些规则支持通配符,以提供灵活的路由选项。
- routingKey 是发送消息时指定的路由关键字,可以包含多个由点分隔的单词。例如:"quick.orange.rabbit"。
- bindingKey 是队列与交换机之间的绑定模式,也可以使用通配符匹配多个单词。
通配符规则
- 单个单词的通配符(*):匹配一个单词。例如,*.orange.* 可以匹配 quick.orange.rabbit 或 lazy.orange.elephant。
- 多个单词的通配符(#):匹配零个或多个单词。例如,lazy.# 可以匹配 lazy.orange.rabbit、lazy.brown.fox 和 lazy.orange.male.rabbit 等。
消息路由的行为
- 消息 "quick.orange.rabbit":会匹配 Q1 和 Q2 的绑定键,因此这条消息将被传递到两个队列。
- 消息 "lazy.orange.elephant":匹配 Q1 的绑定键和 Q2 的 lazy.# 绑定键,所以也会被传递到两个队列。
- 消息 "quick.orange.fox":只会匹配 Q1 的绑定键,不会匹配 Q2 的任何绑定键,因此只会传递到 Q1。
- 消息 "lazy.brown.fox":只会匹配 Q2 的 lazy.# 绑定键,因此只会传递到 Q2。
- 消息 "lazy.pink.rabbit":会匹配 Q2 的 *.*.rabbit 和 lazy.# 绑定键,因此将传递到 Q2,但只会传递一次。
- 消息 "quick.brown.fox":不匹配任何绑定键,因此将被丢弃。
错误和异常
- 消息 "orange" 或 "quick.orange.male.rabbit":这些消息会因为不匹配任何现有的 bindingKey 而被丢弃。
- 消息 "lazy.orange.male.rabbit":尽管有四个单词,但 lazy.# 绑定键仍然能匹配,因此将被传递到 Q2。
SpringBoot+RabbitMQ实战
配置RabbitMQ组件
package com.example.demo.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置RabbitMQ的交换机和队列
*/
@Configuration
public class RabbitMQConfig {
// 定义交换机的名称
public static final String EXCHANGE_NAME = "topicExchange";
// 定义第一个队列的名称
public static final String QUEUE_ONE_NAME = "queueOne";
// 定义第二个队列的名称
public static final String QUEUE_TWO_NAME = "queueTwo";
// 定义第一个路由键的模式
public static final String ROUTING_KEY_ONE = "key.one.#";
// 定义第二个路由键的模式
public static final String ROUTING_KEY_TWO = "key.two.#";
/**
* 创建一个主题交换机
*
* @return 返回创建的主题交换机实例
*/
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(EXCHANGE_NAME);
}
/**
* 创建第一个队列
*
* @return 返回创建的队列实例,非持久化
*/
@Bean
public Queue queueOne() {
return new Queue(QUEUE_ONE_NAME, false);
}
/**
* 创建第二个队列
*
* @return 返回创建的队列实例,非持久化
*/
@Bean
public Queue queueTwo() {
return new Queue(QUEUE_TWO_NAME, false);
}
/**
* 将第一个队列绑定到交换机上
*
* @param queueOne 第一个队列
* @param topicExchange 主题交换机
* @return 返回绑定对象,将队列与交换机通过指定的路由键绑定
*/
@Bean
public Binding bindingOne(Queue queueOne, TopicExchange topicExchange) {
return BindingBuilder.bind(queueOne).to(topicExchange).with(ROUTING_KEY_ONE);
}
/**
* 将第二个队列绑定到交换机上
*
* @param queueTwo 第二个队列
* @param topicExchange 主题交换机
* @return 返回绑定对象,将队列与交换机通过指定的路由键绑定
*/
@Bean
public Binding bindingTwo(Queue queueTwo, TopicExchange topicExchange) {
return BindingBuilder.bind(queueTwo).to(topicExchange).with(ROUTING_KEY_TWO);
}
}
发送消息
package com.example.demo.Service;
import com.example.demo.config.RabbitMQConfig;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* MessageProducer类负责通过RabbitMQ发送消息
*/
@Service
public class MessageProducer {
/**
* AmqpTemplate用于执行RabbitMQ操作
*/
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 发送消息到指定的routingKey
*
* @param routingKey the routing key for the message
* @param message the message to be sent
*/
public void sendMessage(String routingKey, String message) {
amqpTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, routingKey, message);
}
}
接收消息
package com.example.demo.Service;
import com.example.demo.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 消息监听器类,用于处理来自不同队列的消息
*/
@Component
public class MessageListener {
/**
* 接收并处理来自队列一的消息
*
* @param message 队列一中接收到的消息
*/
@RabbitListener(queues = RabbitMQConfig.QUEUE_ONE_NAME)
public void receiveMessageFromQueueOne(String message) {
System.out.println("Queue One Received: " + message);
}
/**
* 接收并处理来自队列二的消息
*
* @param message 队列二中接收到的消息
*/
@RabbitListener(queues = RabbitMQConfig.QUEUE_TWO_NAME)
public void receiveMessageFromQueueTwo(String message) {
System.out.println("Queue Two Received: " + message);
}
}
RPC模式
要实现RPC模式中的远程过程调用,需要两个主要组件:客户端和服务器。客户端发送请求到服务器,并且等待服务器的响应。以下是如何搭建一个简单的RPC系统的步骤:
- 客户端:定义一个RPCClient类,其call()方法会发送一个RPC请求,并等待响应。
- 服务器:处理请求并将结果发送回客户端。使用RabbitMQ的队列机制来处理请求和响应。
- 回调队列:客户端会在发送请求时创建一个回调队列,用于接收响应。消息的replyTo属性指定了回调队列,correlationId用来关联请求和响应。