目录
简要概述
消息队列的起源
-
进程通信:早期的计算机操作系统和应用程序通过共享内存或直接的函数调用进行通信。然而,这种方式有时会导致耦合性高、可维护性差的问题。
-
消息传递系统:在操作系统和分布式系统的发展中,出现了一些消息传递系统,例如DEC的消息系统(DECmessage),它允许不同进程之间通过消息传递进行通信,从而降低了耦合性。
-
IBM的MQService:在1992年,IBM发布了MQSeries(现在称为IBM MQ),这是商用消息队列系统的开创性产品之一。MQSeries允许分布式应用程序通过消息传递进行通信,提供了可靠性、持久性和事务支持。
-
开源的消息队列系统:随着开源软件的兴起,出现了许多开源消息队列系统,如Apache ActiveMQ、RabbitMQ、Apache Kafka等。这些系统提供了强大的消息传递功能,适用于不同的应用场景。
消息队列的特点
消息队列,又叫消息中间件。是指用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。主要的特点:
1.本身是一个独立运行的服务。生产者发送消息,消费者接收消息,都需要先与服务器建立连接。
2.采用队列作为数据结构,有先进先出的优点。
3.具有发布订阅的模型,消费者可以获取自己需要的信息。
4.可靠性:消息队列提供持久化选项,确保消息在发送后不会丢失。即使接收者离线,也会在消费者恢复时传递。
5.顺序性:消息队列保持消息的顺序性。消息按照发送的顺序到达,并按顺序处理。
消息队列的优点
消息队列在实现异步通信、业务解耦、流量削峰方面发挥着重要作用:
-
解耦:
-
解耦生产者和消费者:消息队列将生产者和消费者解耦,使它们不需要直接通信。生产者将消息发送到队列,而消费者从队列中接收消息,它们不需要知道彼此的存在。
-
解耦组件和系统:消息队列允许不同组件或微服务之间以松散耦合的方式通信。组件可以独立开发、部署和维护,只需遵循共同的消息协议。
-
-
异步通信:
-
提高响应速度:消息队列支持异步通信,允许发送者和接收者独立工作,而不需要实时互动。这提高了系统的响应速度,因为发送者不需要等待接收者立即处理消息。
-
处理长时间任务:消息队列可用于将长时间运行的任务委托给后台工作者,以减轻应用程序的负担。例如,可以使用消息队列来处理图像处理、数据导入等任务。
-
-
削峰:
-
平滑负载:消息队列允许消息在队列中排队,而不是立即处理。这有助于平滑处理负载,防止系统在突发流量时崩溃。
-
优化资源利用:通过消息队列,系统可以有效地分配资源,以处理峰值流量。这减少了资源浪费,同时保持了系统的可用性。
-
RabbitMQ的特点
RabbitMQ是一个由Erlang语言开发的AMQP的开源实现。
AMQP:Advanced Message Queue Protocol,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。
RabbitMQ的具体特点包括:
-
可靠性:RabbitMQ使用持久化、传输确认、发布确认等机制保证可靠性。
-
灵活的路由:在消息进入队列之前,通过Exchange路由消息。
-
消息集群:多个RabbitMQ服务器组成一个集群,形成一个逻辑broker。
-
高可用性:队列可以在集群中的机器上进行镜像,保证部分节点挂掉后仍然可用。
-
多协议:RabbitMQ支持多种通信协议,例如STOMP、MQTT。
-
多语言:RabbitMQ几乎支持所有的常用语言,例如Java、.NET、Ruby等等。
-
可视化界面:提供了一个易用的用户界面,监控和管理消息broker。
-
跟踪机制:RabbitMQ提供消息跟踪确保消息异常的情况下,使用者方便定位原因。
-
插件机制:提供了插件扩展,有许多第三方插件可以选择。
-
死信队列:支持死信队列机制,无法处理的消息由专门的队列进行处理。
-
优先队列:支持消息优先级,允许定义哪些消息应该首先被处理。
RabbitMQ架构分析
-
Broker(代理/中介):安装RabbitMQ服务的服务器。RabbitMQ默认端口5672。
-
Connection(连接):无论生产者还是消费者,都需要和broker建立一个连接,这是一个TCP的长链接。
-
Channel(信道):如果所有生产者和消费者与broker建立连接都使用TCP长链接的话,性能损耗和资源浪费严重,所以在TCP的长链接里面创建和释放channel可以大大减少资源损耗。
-
Queue(队列):存放消息的独立进程,有自己的数据库(Mnesia),支持Push和Pull模式存取消息。
-
Exchange(交换机):绑定列表的形式,用来查找匹配的绑定关系,根据生产者发送的消息路由键(Routing Key)决定将消息路由到哪些队列;交换机和队列是多对多的关系,队列和消费者也是多对多关系。
-
VirtualHots(虚拟主机):表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。就是可以通过设置不同的用户权限控制对交换机和消息队列的访问。
路由方式
生产者发送消息时会携带一个路由键,交换机根据绑定规则将消息路由到对应的队列中。目前共四种类型:direct、fanout、 topic、headers 。
headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:
-
Direct(直接):路由键与绑定键完全匹配,单播模式。
-
Fanout(广播):不需要绑定建和路由键,广播模式,交换机会给每个消息队列发送。
-
Topic(主题):正则匹配。两种通配符"#"(0或多个单词)和"*"(仅一个单词)。举例:
假设channel.basicPublish("MY_TOPIC_EXCHANGE", "haha.heihei" ,"msg"),那么A和C都能收到msg。
简单示例
1.安装 RabbitMQ
首先,确保你已经安装了RabbitMQ服务器,并启动了它。
2.添加依赖
在你的Java项目中,你需要添加RabbitMQ的Java客户端库的依赖。你可以使用Maven或Gradle进行依赖管理。以下是Maven的依赖:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.12.0</version> <!-- 使用最新版本 -->
</dependency>
3.发送消息(生产者)
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception {
// 创建连接和信道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost"); // RabbitMQ 服务器地址
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 声明一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello, RabbitMQ!";
// 发送消息到队列
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("Sent message: " + message);
}
}
}
4.接收消息(消费者)
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
public class Consumer {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost"); // RabbitMQ 服务器地址
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 声明一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println("Waiting for messages. To exit, press Ctrl+C");
// 创建一个回调函数来处理接收到的消息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("Received message: " + message);
};
// 消费队列
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
}
5.运行示例
运行Producer来发送一条消息,然后运行Consumer来接收消息。你应该看到生产者发送的消息在消费者端被接收。