1.消息中间件
消息中间件是一种软件系统,它在分布式系统中的不同组件或服务之间传递消息。它提供了一种异步的数据传输方式,允许不同系统或服务之间进行通信,而不需要直接的网络连接。消息中间件通常基于消息队列(Message Queue)或主题(Topic)来实现消息的发布和订阅。
1.1.消息中间件主要特点
消息中间件的主要特点包括:
- 异步通信:发送者(生产者)发送消息后可以立即继续处理其他任务,而接收者(消费者)在消息到达时再进行处理。
- 解耦:发送者和接收者不需要同时在线,也不需要知道对方的具体位置或状态。
- 可靠性:消息中间件通常提供持久化机制,确保消息不会因为系统故障而丢失。
- 扩展性:可以根据需要轻松地增加发送者和接收者的数量。
- 灵活性:支持点对点和发布/订阅等多种消息传递模式。
1.2.常用消息中间件
2.RabbitMQ
2.1.RabbitMQ概述
RabbitMQ是一个开源的消息代理和消息队列服务器,它基于AMQP(Advanced Message Queuing Protocol,高级消息队列协议)协议实现。RabbitMQ提供了一个可靠的消息传递机制,支持多种消息协议和开发工具。
RabbitMQ的主要特点包括:
- 多种协议支持:除了AMQP,RabbitMQ还支持STOMP、MQTT和其他消息协议。
- 多种消息交换类型:支持直接(direct)、主题(topic)、扇形(fanout)、头部(header)和延迟(delayed)等多种消息交换类型。
- 持久化:支持将消息持久化到磁盘,确保消息不会因为RabbitMQ服务重启而丢失。
- 高可用性:支持集群部署,提供高可用性和负载均衡。
- 灵活的路由:支持复杂的路由逻辑,可以根据消息属性将消息路由到不同的队列。
- 插件系统:提供了丰富的插件系统,可以扩展RabbitMQ的功能,如支持不同的认证机制、监控工具等。
- 管理界面:提供了一个易于使用的Web管理界面,用于监控和管理消息队列。
RabbitMQ的应用场景包括:
- 任务队列:用于分布式任务处理,提高系统响应速度和吞吐量。
- 解耦系统组件:在微服务架构中,不同服务之间通过消息队列进行通信,实现服务解耦。
- 事件驱动架构:在事件驱动的系统中,事件生产者发布事件,事件消费者订阅并响应事件。
- 消息传递:在需要可靠消息传递的系统中,确保消息的可靠传输和处理。
为什么选择RabbitMQ
-
与ActiveMQ相比:
- ActiveMQ的API设计非常完善,适合中小型项目和互联网公司使用。然而,在高并发场景下,ActiveMQ的性能表现可能不尽如人意,这使得它在面对大规模分布式系统时稍显不足。因此,在需要处理高吞吐量消息的场合,ActiveMQ可能不是最佳选择。
-
与Kafka相比:
- Kafka以其卓越的性能而闻名,特别适合用于日志收集和大数据处理场景。然而,如果业务需求强调消息传递的可靠性和一致性,Kafka可能不是最佳选择,因为它更注重消息的吞吐量而非消息的精确传递。
- Kafka的高吞吐量和水平扩展能力使其在处理大量数据流时表现出色,但在需要严格的消息确认和持久化机制的场景中,RabbitMQ可能更适合。
-
与RocketMQ相比:
- RocketMQ是一个高性能、高可靠性的消息队列系统,支持分布式事务、水平扩展,并且能够处理亿级别的消息堆积。它几乎具备了现代消息队列系统所需的所有特性。
- 然而,RocketMQ的一个主要缺点是其商业版功能不完全开放,这意味着一些高级特性可能需要付费才能使用。这可能限制了它在某些项目中的应用。
2.2.RabbitMQ工作原理介绍
2.2.1.MQ的基本结构
RabbitMQ中的一些角色:
-
publisher:生产者
-
consumer:消费者
-
exchange个:交换机,负责消息路由
-
queue:队列,存储消息
-
virtualHost:虚拟主机,隔离不同租户的exchange、queue、消息的隔离
2.2.2.RabbitMQ 交换机类型
-
Direct Exchange(直连交换机):
- 直连交换机根据完全匹配的路由键(Routing Key)将消息路由到队列 。
- 使用场景:适用于精确匹配场景,通常用于单播或点对点消息传递 。
- 工作原理:队列在绑定到直连交换机时,会指定一个绑定键(Binding Key)。只有当消息的路由键和某个队列的绑定键完全一致时,消息才会被路由到这个队列 。
-
Fanout Exchange(广播交换机):
- 广播交换机将消息广播到所有与该交换机绑定的队列,忽略路由键 。
- 使用场景:适用于需要将同一消息发送到多个消费者的场景,通常用于广播消息 。
- 工作原理:每当有消息发布到广播交换机时,它会将消息复制到所有绑定的队列 。
-
Topic Exchange(主题交换机):
- 主题交换机根据路由键和队列绑定的路由模式之间的匹配关系将消息路由到一个或多个队列 。
- 使用场景:适用于需要基于模式匹配路由消息的场景,通常用于多播或发布/订阅消息传递 。
- 工作原理:队列在绑定到主题交换机时,会使用一个模式(可能包含通配符)作为绑定键。消息的路由键如果与这个模式匹配,就会被路由到相应的队列 。
2.2.3.RabbitMQ消息模型
RabbitMQ官方提供了5个不同的Demo示例,对应了不同的消息模型:
2.3.RabbitMQ使用
2.3.1.SpringAMQP
SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。
SpringAMQP提供了三个功能:
-
自动声明队列、交换机及其绑定关系
-
基于注解的监听器模式,异步接收消息
-
封装了RabbitTemplate工具,用于发送消息
举一个简单例子以Fanout交换机为例 (发布订阅的模型如图):
可以看到,在订阅模型中,多了一个exchange角色,而且过程略有变化:
-
Publisher:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
-
Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有以下3种类型:
-
Fanout:广播,将消息交给所有绑定到交换机的队列
-
Direct:定向,把消息交给符合指定routing key 的队列
-
Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
-
-
Consumer:消费者,与以前一样,订阅队列,没有变化
-
Queue:消息队列也与以前一样,接收消息、缓存消息。
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
在父工程中引入依赖:
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
首先配置MQ地址,在publisher服务的application.yml中添加配置、在consumer服务的application.yml中添加配置: :
spring:
rabbitmq:
host: 192.168.150.101 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: itcast # 用户名
password: 123321 # 密码
2.3.2.声明队列和交换机
Spring提供了一个接口Exchange,来表示所有不同类型的交换机:
在consumer中创建一个类,声明队列和交换机:
@Configuration
public class FanoutConfig {
/**
* 声明交换机
* @return Fanout类型交换机
*/
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itcast.fanout");
}
/**
* 第1个队列
*/
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
/**
* 绑定队列和交换机
*/
@Bean
public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
/**
* 第2个队列
*/
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
/**
* 绑定队列和交换机
*/
@Bean
public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
2.3.3.消息发送
@Test
public void testFanoutExchange() {
// 队列名称
String exchangeName = "itcast.fanout";
// 消息
String message = "hello, everyone!";
rabbitTemplate.convertAndSend(exchangeName, "", message);
}
2.3.4.消息接收
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
}
2.3.5.消息转换器
在RabbitMQ中,消息转换器用于在生产者发送消息时将Java对象转换为RabbitMQ可以理解的字节流,以及在消费者接收消息时将字节流转换回Java对象 。这个转换过程是自动的,由Spring框架在发送和接收消息时应用 。
只不过,默认情况下Spring采用的序列化方式是JDK序列化。众所周知,JDK序列化存在下列问题:
消息转换器的类型
RabbitMQ提供了多种消息转换器,包括:
-
SimpleMessageConverter:
- 默认的消息转换器,使用Java的序列化机制,但存在安全风险、消息体积大、可读性差的问题 。
-
Jackson2JsonMessageConverter:
- 使用JSON序列化,相比默认的JDK序列化,JSON序列化更安全、消息体积小、可读性好 。
-
MarshallingMessageConverter:
- 使用Java的序列化机制,但提供了更多的配置选项。
测试默认转换器
@Test
public void testSendMap() throws InterruptedException {
// 准备消息
Map<String,Object> msg = new HashMap<>();
msg.put("name", "Jack");
msg.put("age", 21);
// 发送消息
rabbitTemplate.convertAndSend("simple.queue","", msg);
}
配置JSON转换器
显然,JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。
在publisher和consumer两个服务中都引入依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
之后在启动类中添加一个Bean:
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
PS:此文章只做参考学习使用,记录巩固所学知识内容,有错误请指正理解。