一、知识结构图
我是按照黑马的笔记大概整理了一个结构图,用于总体回顾和学习一下,https://b11et3un53m.feishu.cn/wiki/OQH4weMbcimUSLkIzD6cCpN0nvc这是黑马的笔记地址,有需要的同学可以看一下
二、消息队列的类型
以下是当前常见的消息队列类型及其对比分析,涵盖核心类型、代表实现、关键特性及适用场景:
1.主流消息队列实现对比
维度 | RabbitMQ | Kafka | RocketMQ | Pulsar | ActiveMQ |
---|---|---|---|---|---|
消息模型 | P2P、发布订阅(基于Exchange) | 发布订阅(分区Topic) | 发布订阅(Topic+Tag) | 发布订阅(支持多租户Topic) | P2P、发布订阅(JMS标准) |
底层协议 | AMQP、MQTT、STOMP | 自定义二进制协议 | 自定义协议(支持HTTP/2扩展) | Pulsar协议(支持HTTP/2、MQTT) | JMS、AMQP、MQTT、OpenWire |
吞吐量 | 中低(万级TPS) | 极高(百万级TPS,依赖分区数) | 高(十万级TPS,顺序写入优化) | 高(分层存储架构,支持多租户隔离) | 低(单节点瓶颈,适合小规模) |
延迟 | 低(毫秒级,适合实时场景) | 低(毫秒级,批量处理时稍高) | 低(毫秒级,优化网络和存储) | 低(基于BookKeeper的高速存储) | 中低(单节点下延迟可控) |
持久化 | 磁盘持久化(支持内存队列) | 磁盘顺序写入(日志分段存储,支持长期保留) | 磁盘顺序写入(CommitLog机制) | 分层存储(内存+BookKeeper持久化) | 磁盘/内存持久化(JMS标准实现) |
集群能力 | 分布式集群(Erlang节点,支持镜像队列) | 分布式分区集群(ZooKeeper/Kafka Raft) | 分布式集群(主从/DLedger Raft) | 分布式集群(分层架构,无中心节点) | 简单集群(主从/静态集群,扩展性有限) |
生态系统 | 丰富(插件生态,支持多语言客户端) | 成熟(Kafka Connect、Streams生态) | 完善(阿里生态,支持Spring Cloud) | 新兴(与Kafka生态兼容,Function支持) | 传统(JMS生态,兼容性强但更新慢) |
典型场景 | 企业级解耦、微服务通信、实时通知 | 日志收集、实时流处理、大数据管道 | 电商订单处理、分布式事务、顺序消息 | 多租户云原生场景、边缘计算、流处理 | 遗留系统集成、小规模企业应用 |
优势 | 轻量级、协议支持全、社区活跃 | 高吞吐量、可扩展性、消息回溯 | 事务消息、顺序消息、高可用性 | 云原生架构、分层存储、跨地域复制 | 兼容性强、易于部署(早期市场主流) |
劣势 | 高并发下内存开销大、集群管理较复杂 | 消息顺序仅保证分区内、运维成本较高 | 生态依赖阿里、客户端语言支持较少 | 生态成熟度低于Kafka、学习曲线较陡 | 性能瓶颈明显、社区活跃度下降 |
2.选型建议
- 中小规模业务/低延迟场景:选RabbitMQ(协议灵活)或RocketMQ(轻量高效)。
- 高吞吐量/流处理场景:选Kafka(大数据生态适配)或Pulsar(云原生架构)。
- 复杂业务逻辑/事务需求:选RocketMQ(事务消息、顺序消息)。
- 遗留系统集成/简单解耦:选ActiveMQ(兼容性强,但需注意性能瓶颈)。
根据实际需求(如吞吐量、延迟、可靠性、生态适配)选择合适的类型和实现,分布式消息队列已成为主流,尤其在微服务、实时数据处理和云原生架构中优势显著。
三、收发消息
1.交换机(Exchanges)
1.1Fanout交换机
①流程
Fanout,英文翻译是扇出,在MQ中叫广播更合适。
在广播模式下,消息发送流程是这样的:
②总结交换机的作用
- 接收publisher发送的消息
- 将消息按照规则路由到与之绑定的队列
- 不能缓存消息,路由失败,消息丢失
- FanoutExchange的会将消息路由到每个绑定的队列
1.2Direct交换机
①流程
在Direct模型下:
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
- 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey。
- Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息
②总结(描述下Direct交换机与Fanout交换机的差异?)
- Fanout交换机将消息路由给每一个与之绑定的队列
- Direct交换机根据RoutingKey判断路由给哪个队列
- 如果多个队列具有相同的RoutingKey,则与Fanout功能类似
1.3Topic交换机
①流程
Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。
只不过Topic类型Exchange可以让队列在绑定BindingKey 的时候使用通配符!
BindingKey 一般都是有一个或多个单词组成,多个单词之间以.分割,例如: item.insert
通配符规则:
- #:匹配一个或多个词
- *:匹配不多不少恰好1个词
举例:
- item.#:能够匹配item.spu.insert 或者 item.spu
- item.*:只能匹配item.spu
②总结(描述下Direct交换机与Topic交换机的差异?)
- Topic交换机接收的消息RoutingKey必须是多个单词,以 . 分割
- Topic交换机与队列绑定时的bindingKey可以指定通配符
- #:代表0个或多个词
- *:代表1个词
四、SpringAMQP
1.功能
- 自动声明队列、交换机及其绑定关系
- 基于注解的监听器模式,异步接收消息
- 封装了RabbitTemplate工具,用于发送消息
2.需要的依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.声明队列和交换机
3.1基于bean声明
direct模式由于要绑定多个KEY,会非常麻烦,每一个Key都要编写一个binding
package com.itheima.consumer.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectConfig {
/**
* 声明交换机
* @return Direct类型交换机
*/
@Bean
public DirectExchange directExchange(){
return ExchangeBuilder.directExchange("hmall.direct").build();
}
/**
* 第1个队列
*/
@Bean
public Queue directQueue1(){
return new Queue("direct.queue1");
}
/**
* 绑定队列和交换机
*/
@Bean
public Binding bindingQueue1WithRed(Queue directQueue1, DirectExchange directExchange){
return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
}
/**
* 绑定队列和交换机
*/
@Bean
public Binding bindingQueue1WithBlue(Queue directQueue1, DirectExchange directExchange){
return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");
}
/**
* 第2个队列
*/
@Bean
public Queue directQueue2(){
return new Queue("direct.queue2");
}
/**
* 绑定队列和交换机
*/
@Bean
public Binding bindingQueue2WithRed(Queue directQueue2, DirectExchange directExchange){
return BindingBuilder.bind(directQueue2).to(directExchange).with("red");
}
/**
* 绑定队列和交换机
*/
@Bean
public Binding bindingQueue2WithYellow(Queue directQueue2, DirectExchange directExchange){
return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");
}
}
3.2基于注解声明
基于@Bean的方式声明队列和交换机比较麻烦,Spring还提供了基于注解方式来声明。
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
key = {"red", "blue"}
))
public void listenDirectQueue1(String msg){
System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg){
System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}
4.消息转换器
4.1默认消息转换器
Spring的消息发送代码接收的消息体是一个Object:
而在数据传输时,它会把你发送的消息序列化为字节发送给MQ,接收消息的时候,还会把字节反序列化为Java对象。
只不过,默认情况下Spring采用的序列化方式是JDK序列化。众所周知,JDK序列化存在下列问题:
- 数据体积过大
- 有安全漏洞
- 可读性差
4.2配置JSON转换器
JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。
①引入依赖(注意,如果项目中引入了spring-boot-starter-web依赖,则无需再次引入Jackson依赖。)
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
②在配置类中添加一个bean
@Bean
public MessageConverter messageConverter(){
// 1.定义消息转换器
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
// 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
jackson2JsonMessageConverter.setCreateMessageIds(true);
return jackson2JsonMessageConverter;
}
五、WorkQueues模型
Work queues,任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息
Work模型的使用:
- 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
- 通过设置prefetch来控制消费者预取的消息数量(默认是平均分配消息给每个队列)