初识MQ
同步通信
概念
双方在同一个时钟信号的控制下,进行数据的接收和发送,来一个时钟,发送端发送,接收端接收,他们彼此之间的工作状态是一致的,例如直播、打电话。
同步优缺点
优点
时效性强,可以立即得到结果
缺点
耦合度高
每次加入新的需求,都要修改原来的代码
性能下降
调用者需要等待服务功能提供者响应,如果调用链过长则响应时间等于每次调用的时间之和
资源浪费
调用链中的每个服务在等待响应过程中,不能释放请求占用的资源,高并发场景下会极度浪费系统资
源
级联失败
如果服务功能提供者出现问题,所有调用方都会跟着出问题,如同多米诺骨牌一样,迅速导致整个服
务故障
异步通信
概念
异步通信在发送字符时,所发送的字符之间的时间间隔可以是任意的。例如微信聊天。在异步调用过程常见的实现就是**事件驱动模式,**系统中发生的事件会触发相应的事件处理器或监听器,从而实现特定的业务逻辑或功能。
异步优缺点
优点
耦合度低
性能提升
故障隔离
流量削峰
缺点
依赖于Broker的可靠性、安全性、性能提升能力
架构复杂了,业务没有明显的流程线,不好追踪管理
什么是MQ
MQ,中文消息队列(MessageQueue),字面来看就是存放消息的队列,也就是事件驱动中的Broker。
比较常见的MQ实现:
ActiveMQ
RabbitMQ
RocketMQ
Kafka
几种常见的MQ对比
技术对比
追求可用性(当需要处理数据时,资源处于可用状态的程度):Kafka、RocketMQ、RabbitMQ
追求可靠性:RocketMQ、RabbitMQ
追求吞吐能力(十万级别的):RocketMQ、Kafka
追求消息低延迟:RabbitMQ,Kafka
通过上述对比建议如下:
一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以不推荐用这个了;
后来大家开始用 RabbitMQ,由于是 erlang 语言阻止了大量的java,工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高;
不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黃掉的风险(目前 RocketMQ 已捐给Apache,但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。
所以中小型公司,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择,基础架构研发实力较强,很好的选择。
如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。
RabbitMQ快速入门
概述
RabbitMQ是基于Erlang语言开发的消息通信中间件,RabbitMQ的性能以及可用性较好,国内应用较为广泛,所以对RabbitMQ进行重点学习。
RabbitMQ的官网地址:https://www.rabbitmq.com
RabbitMQ的整体架构
RabbitMQ中的几个概念
RabbitMQ中的几个概念:
• Publisher:生产者(发布者)
• Consumer:消费者
• channel :表示通道,操作 MQ 的工具
• exchange :路由消息到队列中
• queue :队列,存储消息
• virtualHost :虚拟主机,隔离不同用户的exchange、queue、消息的隔离
将以上RabbitMQ基本结构归纳为以下四点
1.Publisher(生产者)会把消息发送给exchange(交换机)
2.交换机(exchange)将消息转发到与之绑定的queue(队列),queue负责暂存消息
3.消息消费者(Consumer)监听队列(queue),从队列中获取消息
4.将不同的队列(queue)和交换机划分成一组,称为虚拟主机(virtualHost)
常见的消息模型
RabbitMQ的官方文档中给出了5个MQ的Demo示例,对应了不同的消息模型:
基本消息队列(BasicQueue)
P(producer/ publisher):生产者,一个发送消息的用户应用程序。我们自己书写代码发送。
C(consumer):消费者,消费和接收有类似的意思,消费者是一个主要用来等待接收消息的用户应用程序。我们自己书写代码接收.
队列(红色区城):存在于rabbitmq内部。许多生产者可以发送消息到一个队列,许多消费者可以尝试从一个队列接收数据。
总之:生产者将消息发送到队列,消费者从队列中获取消息,队列是存储消息的缓冲区。
工作消息队列(WorkQueue)
工作消息队列是基本消息队列的增强版,具有多个消费者消费队列的消息。假设消息队列中积压了多个消息,那么此时可以使用多个
消费者来消费队列中的消息。效率要比基本消息队列模型高。
发布订阅(Publish、Subscribe)分三种
1、1个生产者,多个消费者
2、每一个消费者都有自己的一个队列
3、生产者没有将消息直接发送到队列,而是发送到了交换机
4、每个队列都要绑定到交换机
5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的
(Exchanges):
交换机一方面:接收生产者发送的消息。
另一方面:知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。
到底如何操作,取决于Exchange的类型。Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
Fanout Exchange:广播
将信息交给所有绑定到交换机的队列,生产者发送的消息只能发送到交换机,交换机来决定要发给那个队列,生产者无法决定。交换机把消息发送给绑定过的所与队列,队列的消费者都能拿到信息。实现一条信息被多个消费者消费。
Direct Exchange:路由
1.在广播模式中,生产者发布消息,所有消费者都可以获取所有消息。
2.在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。在Direct模型下,队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key),消息的发送方在向Exchange发送消息时,也必须指定消息的routing key.
3.P:生产者,向Exchange发送消息,发送消息时,会指定一个routingkey。
4.X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
Topic Exchange:主题
1.Topic类型的Exchange与Direct相比,都是可以根据Routing Key把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!
2.Routingkey 一般都是有一个或多个单词组成,多个单词之间以".分割,例如: item.insert
3.通配符规则:
#:匹配一个或多个词
*:匹配恰好1个词
SpringAMQP
介绍
SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。
说明:
1.spring AMQP 是对 spring 基于 AMQP 的消息收发解决方案,它是一个抽象层,不依赖于特定的 AMQP Broker 实现和客户端的抽象,所以可以很方便地替换。比如我们可以使用 spring-rabbit 来实现。
2.spring-rabbit 用于与RabbitMQ服务器交互的工具包
3.SpringAMQP提供了三个功能:
自动声明队列、交换机及其绑定关系
基于注解的监听器模式,异步接收消息
封装了RabbitTemplate工具,用于发送和接收消息
Basic Queue简单队列模型
演示如下
代码步骤
第一步:
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
第二步
首先要在yml配置文件中添加mq的连接信息
然后创建测试类,直接使用RabbitTemplate工具类发送消息
消息发送成功之后,我们会在simple.queue队列中发现这条消息
第三步
注意在此类上添加@Component注解,使其能被spring扫描到,然后用@RabbitListener注解声明要监听的是哪个队列
消息被成功接收,此时队列中就没有这条消息了,证明RabbitMQ是”阅后即焚“
注意:消息一旦消费就会从队列删除,RabbitMQ没有消息回溯功能
Work Queue工作队列
演示如下
代码步骤
发送消息,循环发送五十条
定义两个消费者接收消息
结果
消费者1处理的是偶数,消费者2处理的是奇数,平均分配
是因为RabbitMQ中存在消息预取机制
消息预取机制
消息预取机制(Prefetch Mechanism)是RabbitMQ中用于控制消息传递给消费者的一种机制。它定义了在一个信道上,消费者允许的最大未确认的消息数量。一旦未确认的消息数量达到了设置的预取值,RabbitMQ就会停止向该消费者发送更多消息,直到至少有一条未完成的消息得到了确认。
更改消费预取限制
修改application.yml文件,设置preFetch这个值,可以控制预取消息的上限:
发布订阅模型
发布订阅模式与之前案例的区别就是允许将同一消息发送给多个消费者。实现方式是加入了Exchange(交换机)
注意:Exchange负责消息路由,而不是存储,路由失败则消息丢失
发布订阅—Fanout Exchange
演示如下
代码步骤
在consumer服务的SpringRabbitListener类中,添加两个方法,分别监听队列1.2
在publisher服务发消息到FanoutExchange(交换机)
发布订阅—DirectExchange
演示如下
代码步骤
发布订阅—TopicExchange
演示如下
代码步骤
第一步
第二步
消息转换器
Spring会把你发送的消息序列化为字节发送给MQ,接收消息的时候,还会把字节反序列化为Java对象
只不过,默认情况下Spring采用的序列化方式是JDK序列化,而jdk序列化存在以下问题:
1.数据体积过大
2.有安全漏洞
3.可读性差
配置JSON转换器
JDK序列化方式并不合适,我们希望消息体的体积更小、可读性更高,因此可以使用json方式来序列化和反序列化。
【1】在生产者和消费者两个服务中都引入依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-datafoemat-xml</artifactId>
<version>2.9.10</version>
</dependency>
【2】在生产者方和消费者方配置消息转换器
//将json的消息转换器对象放到IOC容器中
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
【3】监听队列
@RabbitListener(queues = "object.queue")
public void listenObjectQueue(){
System.out.println("收到消息:" + msg);
}
【4】执行生产者代码,查看服务器内容
【5】查看托管平台
SpringAMQP中消息的序列化和反序列化是怎么实现的
利用MessageConverter实现的,默认是JDK的序列化
注意发送方与接收方必须使用相同的MessageConverter