RabbitMQ快速入门

文章目录

  • 1.概述
  • 2.消息队列
    • 2.1. JMS
    • 2.2.AMQP
    • 2.3. JMS vs AMQP
    • 2.4.常见MQ产品
  • 3.RabbitMQ
    • 3.1.核心概念
      • Producer 和 Consumer
      • Exchange(交换器)
      • Binding(绑定)
      • Queue(消息队列)
      • Broker(消息中间件的服务节点)
    • 3.2.概念整理表
    • 3.3.工作机制
  • 4.六种消息模型
    • 基本消息模型
    • work 消息模型
    • 订阅模型-Fanout
    • 订阅模型-Direct
    • 订阅模型-Topic
      • 拓展:交换器-headers
      • 持久化
      • 死信交换机
  • 5.Spring AMQP
    • AmqpTemplate
  • 6.实例
  • 7.总结

1.概述

看一个场景

  • 商品的原始数据保存在数据库中,增删改查都在数据库中完成。
  • 搜索服务使用ES,数据来源是索引库,如果数据库商品发生变化,索引库数据不能及时更新。
  • 商品详情通过 nginx 做了页面静态化,静态页面数据也不会随着数据库商品发生变化。

如果我们在后台修改了商品的价格,搜索页面和商品详情页显示的依然是旧的价格,这样显然不对。该如何解决?

这里有两种解决方案:

  • 方案1:每当后台对商品做增删改操作,同时要修改索引库数据及静态页面
  • 方案2:搜索服务和商品页面服务对外提供操作接口,后台在商品增删改后,调用接口

以上两种方式都有同一个严重问题:就是代码耦合,后台服务中需要嵌入搜索和商品页面服务,违背了微服务的独立原则。

所以,我们会通过另外一种方式来解决这个问题:消息队列

常用场景

在我们秒杀抢购商品的时候,系统会提醒我们稍等排队中,而不是像几年前一样页面卡死或报错给用户。

像这种排队结算就用到了消息队列机制,放入通道里面一个一个结算处理,而不是某个时间断突然涌入大批量的查询新增把数据库给搞宕机,所以RabbitMQ本质上起到的作用就是削峰填谷,为业务保驾护航。

2.消息队列

什么是消息队列?

消息队列,即MQ,Message Queue。参考

我们可以把消息队列比作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。

消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。

消息队列是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。

使用消息队列带来的一些问题

  • 系统可用性降低: 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了!
  • 系统复杂性提高: 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
  • 一致性问题: 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!

2.1. JMS

JMS(JAVA Message Service,java消息服务)是 java的消息服务,JMS的客户端之间可以通过 JMS 服务进行异步的消息传输。JMS(JAVA Message Service,Java消息服务)API是一个消息服务的标准或者说是规范,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。

ActiveMQ 就是基于 JMS 规范实现的。

JMS 两种消息模型

①点到点(P2P)模型

使用队列(Queue)作为消息通信载体;满足生产者与消费者模式,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。比如:我们生产者发送100条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。)

② 发布/订阅(Pub/Sub)模型

发布订阅模型(Pub/Sub) 使用主题(Topic)作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不到该条消息的

JMS 五种不同的消息正文格式

JMS定义了五种不同的消息正文格式,以及调用的消息类型,允许你发送并接收以一些不同形式的数据,提供现有消息格式的一些级别的兼容性。

  • StreamMessage – Java原始值的数据流
  • MapMessage–一套名称-值对
  • TextMessage–一个字符串对象
  • ObjectMessage–一个序列化的 Java对象
  • BytesMessage–一个字节的数据流

2.2.AMQP

AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 高级消息队列协议(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。

提供了五种消息模型:

  1. direct exchange;
  2. fanout exchange;
  3. topic change;
  4. headers exchange
  5. system exchange。

本质来讲,后四种和 JMS的pub/sub 模型没有太大差别,仅是在路由机制上做了更详细的划分;

稍后,我们会重点讲述

2.3. JMS vs AMQP

两者间的区别和联系:

  • JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
  • JMS限定了必须使用 Java 语言,对跨平台的支持较差;AMQP只是协议,不规定实现方式,因此是跨语言的
  • JMS 规定了两种消息模型;而 AMQP 的消息模型更加丰富
  • JMS 支持TextMessage、MapMessage 等复杂的消息类型;而 AMQP 仅支持 byte[] 消息类型(复杂的类型可序列化后发送)。
  • 由于Exchange 提供的路由算法,AMQP可以提供多样化的路由方式来传递消息到消息队列,而 JMS 仅支持 队列 和 主题/订阅 方式两种。

2.4.常见MQ产品

  • ActiveMQ:基于JMS
  • RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
  • RocketMQ:基于JMS,阿里巴巴产品,目前交由Apache基金会
  • Kafka:分布式消息系统,高吞吐量

具体使用差异

  • ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。
  • RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做erlang源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。
  • RocketMQ阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用 RocketMQ 挺好的
  • kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。

3.RabbitMQ

简介

RabbitMQ 是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。

RabbitMQ 发展到今天,被越来越多的人认可,这和它在易用性、扩展性、可靠性和高可用性等方面的卓著表现是分不开的。RabbitMQ 的具体特点可以概括为以下几点:

  • 可靠性: RabbitMQ使用一些机制来保证消息的可靠性,如持久化、传输确认及发布确认等。
  • 灵活的路由: 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。
  • 扩展性: 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
  • 高可用性: 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
  • 支持多种协议: RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP、MQTT 等多种消息中间件协议。
  • 多语言客户端: RabbitMQ几乎支持所有常用语言,比如 Java、Python、Ruby、PHP、C#、JavaScript等。
  • 易用的管理界面: RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。在安装 RabbitMQ 的时候会介绍到,安装好 RabbitMQ 就自带管理界面。
  • 插件机制: RabbitMQ 提供了许多插件,以实现从多方面进行扩展,当然也可以编写自己的插件。感觉这个有点类似 Dubbo 的 SPI机制。

3.1.核心概念

这里有点啰嗦,直接看3.2节的表吧

Producer 和 Consumer

  • Producer(生产者) :生产消息的一方(邮件投递者)
  • Consumer(消费者) :消费消息的一方(邮件收件人)

消息一般由 2 部分组成:消息头(或者说是标签 Label)和 消息体。消息体也可以称为 payLoad ,消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。生产者把消息交由 RabbitMQ 后,RabbitMQ 会根据消息头把消息发送给感兴趣的 Consumer(消费者)。

Exchange(交换器)

在 RabbitMQ 中,消息并不是直接被投递到 Queue(消息队列) 中的,中间还必须经过 Exchange(交换器) 这一层,Exchange(交换器) 会把我们的消息分配到对应的 Queue(消息队列) 中。

Exchange(交换器) 用来接收生产者发送的消息并将这些消息路由给服务器中的队列中,如果路由不到,或许会返回给 Producer(生产者) ,或许会被直接丢弃掉 。这里可以将RabbitMQ中的交换器看作一个简单的实体。

重要

Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

类型

RabbitMQ 的 Exchange(交换器) 有4种类型,不同的类型对应着不同的路由策略

  • direct(默认) 定向,把消息交给符合指定routing key 的队列
  • fanout 广播,将消息交给所有绑定到交换机的队列
  • topic 通配符,把消息交给符合 routing pattern(路由模式) 的队列
  • headers (不推荐) 根据发送的消息中的 headers 属性进行匹配一组键值对

不同类型的 Exchange 转发消息的策略有所区别。

Binding(绑定)

生产者将消息发给交换器的时候,一般会指定一个 RoutingKey(路由键),用来指定这个消息的路由规则,而这个 RoutingKey 需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效

RabbitMQ 中通过 Binding(绑定)Exchange(交换器)Queue(消息队列) 关联起来,在绑定的时候一般会指定一个 BindingKey(绑定建) ,这样 RabbitMQ 就知道如何正确将消息路由到队列了,如下图所示。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和 Queue 的绑定可以是多对多的关系。

  • 生产者将消息发送给交换器时,需要一个RoutingKey,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。
  • 在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的 BindingKey。BindingKey 并不是在所有的情况下都生效,它依赖于交换器类型,比如fanout类型的交换器就会无视,而是将消息路由到所有绑定到该交换器的队列中。

Queue(消息队列)

Queue(消息队列) 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

RabbitMQ 中消息只能存储在 队列 中,这一点和 Kafka 这种消息中间件相反。Kafka 将消息存储在 topic(主题) 这个逻辑层面,而相对应的队列逻辑只是topic实际存储文件中的位移标识。 RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。

多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免的消息被重复消费。

RabbitMQ 不支持队列层面的广播消费,如果有广播消费的需求,需要在其上进行二次开发,这样会很麻烦,不建议这样做。

Broker(消息中间件的服务节点)

对于 RabbitMQ 来说,一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点,或者RabbitMQ服务实例。大多数情况下也可以将一个 RabbitMQ Broker 看作一台 RabbitMQ 服务器。

3.2.概念整理表

给个表整理下

名词 介绍
Broker 简单来说就是消息队列服务器实体。
Exchange 消息交换机,它指定消息按什么规则,路由到哪个队列。
Queue 消息队列载体,每个消息都会被投入到一个或多个队列。
Binding 绑定,它的作用就是把exchange和queue按照路由规则绑定起来。
Routing Key 路由关键字,exchange根据这个关键字进行消息投递。
vhost 虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
producer 消息生产者,就是投递消息的程序。
consumer 消息消费者,就是接受消息的程序。
channel 消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

扯一句,为什么不通过TCP直接发送命令?

对于操作系统来说创建和销毁TCP会话是非常昂贵的开销,假设高峰期每秒有成千上万条连接,每个连接都要创建一条TCP会话,这就造成了TCP连接的巨大浪费,而且操作系统每秒能创建的TCP也是有限的,因此很快就会遇到系统瓶颈。

如果我们每个请求都使用一条TCP连接,既满足了性能的需要,又能确保每个连接的私密性,这就是引入信道概念的原因。

信道是创建在“真实”TCP上的虚拟连接,AMQP命令都是通过信道发送出去的,每个信道都会有一个唯一的ID,不论是发布消息,订阅队列或者介绍消息都是通过信道完成的。

3.3.工作机制

我们来总结一下工作机制:

RabbitMQ快速入门_第1张图片
AMQP模型中,消息在 producer 中产生,发送到 MQ 的 exchange 上,exchange 根据配置的路由方式发到相应的 Queue上,Queue又将消息发送给 consumer,消息从 queue 到 consumer 有 push 和 pull 两种方式。 消息队列的使用过程大概如下:

  1. 客户端连接到消息队列服务器,打开一个channel。
  2. 客户端声明一个exchange,并设置相关属性。
  3. 客户端声明一个queue,并设置相关属性。
  4. 客户端使用 routing key,在exchange和queue之间建立好绑定关系。
  5. 客户端投递消息到 exchange。

exchange接收到消息后,就根据消息的 key 和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。 exchange也有几个类型,完全根据 key进行投递的叫做Direct交换机,例如,绑定时设置了routing key为”abc”,那么客户端提交的消息,只有设置了key为”abc”的才会投递到队列。

4.六种消息模型

RabbitMQ提供了6种消息模型,但是第 6 种其实是 RPC,并不是MQ,因此不予学习。那么也就剩下5种。

但是其实3、4、5这三种都属于订阅模型,只不过进行路由的方式不同。

  • 参考官网
  • 参考博客,有介绍RPC实现

RabbitMQ快速入门_第2张图片

基本消息模型

RabbitMQ是一个消息代理:它接受和转发消息。 你可以把它想象成一个邮局:当你把邮件放在邮箱里时,你可以确定邮差先生最终会把邮件发送给你的收件人。 在这个比喻中,RabbitMQ是邮政信箱,邮局和邮递员。

RabbitMQ与邮局的主要区别是它不处理纸张,而是接受,存储和转发数据消息的二进制数据块。

RabbitMQ快速入门_第3张图片

生产者(P)将消息发送到队列,消费者(C)从队列中获取消息,队列(红色区域)是存储消息的缓冲区。

消息确认机制(ACK)

RabbitMQ怎么知道消息被接收了呢?

RabbitMQ有一个ACK 机制。当消费者获取消息后,会向RabbitMQ发送回执 ACK,告知消息已经被接收。

修改basicConsume方法参数,参考后面代码

  • 自动ACK:消息一旦被接收,消费者自动发送ACK
  • 手动ACK:消息接收后,不会发送ACK,需要手动调用

大家觉得哪种更好呢?

这需要看消息的重要性:

  • 如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便
  • 如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除。如果此时消费者宕机,那么消息就丢失了。

代码

生产者发送消息:图片可以看到参数列表,所以就不贴代码了

RabbitMQ快速入门_第4张图片

消费者获取消息:

RabbitMQ快速入门_第5张图片

work 消息模型

工作队列,又称任务队列。主要思想就是避免执行资源密集型任务时,必须等待它执行完成。相反我们稍后完成任务,我们将任务封装为消息并将其发送到队列。 在后台运行的工作进程将获取任务并最终执行作业。当你运行许多消费者时,任务将在他们之间共享,但是一个消息只能被一个消费者获取

面试题:避免消息堆积?

  • 采用 workqueue,多个消费者监听同一队列。
  • 接收到消息以后,而是通过线程池,异步消费。

接下来我们来模拟这个流程:

  • P:生产者:任务的发布者
  • C1:消费者,领取任务并且完成任务,假设完成速度较快
  • C2:消费者2:领取任务并完成任务,假设完成速度慢

RabbitMQ快速入门_第6张图片

能者多劳

我们可以使用 basicQos 方法和 prefetchCount = 1设置。消费者消费消息的多少,完全取决于消费者的处理能力,能者多劳,相当于消费者主动从mq中取消息,而不是被mq安排消息。

因此,如果消费者处理速度快,那么最终mq向它发送的消息就多,如果消费者处理的慢,mq向它发送的消息就少。

轮询分发

  • 打开自动通知,去掉手动通知,去掉消费端消费条数限制,就是轮询分发啦

代码

关键代码

RabbitMQ快速入门_第7张图片

订阅模型-Fanout

订阅模型

在之前的模式中,我们创建了一个工作队列。 工作队列背后的假设是:每个任务只被传递给一个工作人员。 在这一部分,我们将做一些完全不同的事情 - 我们将会传递一个信息给多个消费者。 这种模式被称为“发布/订阅”。

RabbitMQ快速入门_第8张图片

X(Exchanges):交换机一方面:接收生产者发送的消息。另一方面:知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型

回顾一下,Exchange类型有以下几种:

  • Fanout:广播,将消息交给所有绑定到交换机的队列
  • Direct:定向,把消息交给符合指定routing key 的队列
  • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

在广播模式下,消息发送流程是这样的:

  • 可以有多个消费者
  • 每个消费者有自己的queue(队列)
  • 每个队列都要绑定到Exchange(交换机)
  • 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
  • 交换机把消息发送给绑定过的所有队列
  • 队列的消费者都能拿到消息。实现一条消息被多个消费者消费

代码

生产者:

两个变化:

  • 声明Exchange,不再声明Queue
  • 发送消息到Exchange,不再发送到Queue

RabbitMQ快速入门_第9张图片

消费者:

要注意代码中:队列需要和交换机绑定

RabbitMQ快速入门_第10张图片

订阅模型-Direct

有选择性的接收消息

在订阅模式中,生产者发布消息,所有消费者都可以获取所有消息

在路由模式中,我们将添加一个功能 - 我们将只能订阅一部分消息。 例如,我们只能将重要的错误消息引导到日志文件(以节省磁盘空间),同时仍然能够在控制台上打印所有日志消息。

但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。

在Direct模型下,队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)

消息的发送方在向Exchange发送消息时,也必须指定消息的routing key。

流程

RabbitMQ快速入门_第11张图片

P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。

X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列

C1:消费者,其所在队列指定了需要routing key 为 error 的消息

C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息

代码

生产者:

此处我们模拟商品的增删改,发送消息的RoutingKey分别是:insert、update、delete

RabbitMQ快速入门_第12张图片

消费者:

我们此处假设消费者接收所有类型的消息:新增商品,更新商品和删除商品。

RabbitMQ快速入门_第13张图片

订阅模型-Topic

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符

Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

通配符规则:

  • #:匹配一个或多个词
  • *:匹配不多不少恰好1个词

举例:

  • audit.#:能够匹配audit.irs.corporate 或者 audit.irs
  • audit.*:只能匹配audit.irs

RabbitMQ快速入门_第14张图片

在这个例子中,我们将发送所有描述动物的消息。消息将使用由三个字(两个点)组成的routing key发送。路由关键字中的第一个单词将描述速度,第二个颜色和第三个种类:..

我们创建了三个绑定:Q1绑定了绑定键* .orange.*,Q2绑定了*.*.rabbitlazy.#

  • Q1 匹配所有的橙色动物。

  • Q2 匹配关于兔子以及懒惰动物的消息。

代码

生产者:

使用topic类型的Exchange,发送消息的routing key有3种: item.isnertitem.updateitem.delete

RabbitMQ快速入门_第15张图片

消费者:

我们此处假设消费者接收所有类型的消息:新增商品,更新商品和删除商品。

RabbitMQ快速入门_第16张图片

拓展:交换器-headers

headers 类型的交换器不依赖于路由键的匹配规则来路由消息(忽略routingKey的一种路由方式),而是根据发送的消息内容中的 headers 属性进行匹配。参考博客

Headers是一个键值对,可以定义成Hashtable。发送者在发送的时候定义一些键值对,接收者也可以再绑定时候传入一些键值对,两者匹配的话,则对应的队列就可以收到消息。匹配有两种方式all和any。这两种方式是在接收端必须要用键值"x-mactch"来定义。

all代表定义的多个键值对都要满足,而any则代码只要满足一个就可以了。

fanout,direct,topic exchange的routingKey都需要要字符串形式的,而headers exchange则没有这个要求,因为键值对的值可以是任何类型

持久化

如何避免消息丢失?

  • 消费者的 ACK 机制。可以防止消费者丢失消息。
  • 但是,如果在消费者消费之前,MQ就宕机了,消息就没了。

是可以将消息进行持久化呢?

要将消息持久化,前提是:队列、Exchange都持久化

交换机持久化

RabbitMQ快速入门_第17张图片

队列持久化

RabbitMQ快速入门_第18张图片

消息持久化

GDUWuD.png

死信交换机

场景:如果没有返回给原队列 那么这条消息就被作废了?超时订单?

在创建队列的时候 可以给这个队列附带一个交换机, 那么这个队列作废的消息就会被重新发到附带的交换机,然后让这个交换机重新路由这条消息。

满足什么条件?

  • 消息被消费者使用 basic.reject 或 basic.nack 方法并且 requeue 参数值设置为 false 的方式进行消息确认(negatively acknowledged)
  • 消息由于消息有效期(per-message TTL)过期
  • 消息由于队列超过其长度限制而被丢弃
@Bean
public Queue queue() {
    Map<String,Object> map = new HashMap<>();
    //设置消息的过期时间 单位毫秒
    map.put("x-message-ttl",10000);
    //设置附带的死信交换机
    map.put("x-dead-letter-exchange","exchange.dlx");
    //指定重定向的路由建 消息作废之后可以决定需不需要更改他的路由建 如果需要 就在这里指定
    map.put("x-dead-letter-routing-key","dead.order");
    return new Queue("testQueue", true,false,false,map);
}

5.Spring AMQP

Spring AMQP 是对 AMQP 协议的抽象实现,而 spring-rabbit 是对协议的具体实现,也是目前的唯一实现。底层使用的就是RabbitMQ。Spring AMQP的页面

快速使用

  1. 导入依赖
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-amqpartifactId>
dependency>
  1. application.yml中添加 RabbitMQ 地址:
spring:
   rabbitmq:
    host: 192.168.229.130  # RabbitMQ的配置
    virtual-host: /leyou
    username: leyou
    password: leyou
  1. 监听者

在 SpringAmqp 中,对消息的消费者进行了封装和抽象,一个普通的 JavaBean 中的普通方法,只要通过简单的注解,就可以成为一个消费者。

@Component
public class Listener {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "spring.test.queue", durable = "true"),
            exchange = @Exchange(
                    value = "spring.test.exchange",
                    ignoreDeclarationExceptions = "true",
                    type = ExchangeTypes.TOPIC
            ),
            key = {"#.#"}))
    public void listen(String msg){
        System.out.println("接收到消息:" + msg);
    }
}
  • @Componet:类上的注解,注册到 Spring 容器
  • @RabbitListener:方法上的注解,声明这个方法是一个消费者方法,需要指定下面的属性:
    • bindings:指定绑定关系,可以有多个。值是@QueueBinding的数组。@QueueBinding包含下面属性:
      • value:这个消费者关联的队列。值是@Queue,代表一个队列
      • exchange:队列所绑定的交换机,值是@Exchange类型
      • key:队列和交换机绑定的RoutingKey

类似 listen 这样的方法在一个类中可以写多个,就代表多个消费者。

AmqpTemplate

Spring 最擅长的事情就是封装,把他人的框架进行封装和整合。

Spring为AMQP提供了统一的消息处理模板:AmqpTemplate,非常方便的发送消息,其发送方法:

RabbitMQ快速入门_第19张图片

红框圈起来的是比较常用的3个方法,分别是:

  • 指定交换机、RoutingKey和消息体
  • 指定消息
  • 指定RoutingKey和消息,会向默认的交换机发送消息

6.实例

发送消息

  1. 配置文件
  • template:有关AmqpTemplate的配置
    • exchange:缺省的交换机名称,此处配置后,发送消息如果不指定交换机就会使用这个
  • publisher-confirms:生产者确认机制,确保消息会正确发送,如果发送失败会有错误回执,从而触发重试
spring:
    rabbitmq:
        host: 192.168.229.130  # RabbitMQ的配置
        virtual-host: /leyou
        username: leyou
        password: leyou
        template:
          exchange: LEYOU.ITEM.EXCHANGE # 配置默认交换机
        publisher-confirms: true  # 生产者确认机制
  1. 在Service中封装一个发送消息到mq的方法:(需要注入AmqpTemplate模板)
private void sendMessage(Long id, String type){
    // 发送消息
    try {
        this.amqpTemplate.convertAndSend("item." + type, id);
    } catch (Exception e) {
        logger.error("{}商品消息发送异常,商品id:{}", type, id, e);
    }
}

供其它 service 方法调用,发送消息到队列

RabbitMQ快速入门_第20张图片

接收消息

  1. 配置文件

这里只是接收消息而不发送,所以不用配置template相关内容。

spring:
  rabbitmq:
    host: 192.168.229.130  # RabbitMQ的配置
    virtual-host: /leyou
    username: leyou
    password: leyou
  1. 编写监听器

搜索服务接收到消息后要做的事情:添加或者修改 索引库数据(这里用到了ES)

@Component
public class GoodsListener {

    @Autowired
    private SearchService searchService;

    /**
     * 处理insert和update的消息
     *
     * @param id
     * @throws Exception
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "leyou.create.index.queue", durable = "true"),
            exchange = @Exchange(
                    value = "leyou.item.exchange",
                    ignoreDeclarationExceptions = "true",
                    type = ExchangeTypes.TOPIC),
            key = {"item.insert", "item.update"}))
    public void listenCreate(Long id) throws Exception {
        if (id == null) {
            return;
        }
        // 创建或更新索引
        this.searchService.createIndex(id);
    }

}

7.总结

需掌握

  • 工作机制
  • RabbitMQ六工作模式
    • 五种即可
  • 四种交换器
    • Fanout
    • Direct
    • Topic
    • Headers
  • 如何避免消息丢失(ACK、持久化)
  • Spring 整合 RibbitMQ,发送接收消息

消息模型小结

Exchange(交换机)只负责转发消息,不具备存储消息的能力

总的来说可以分为三类:

  • 点对点
  • 工作队列
  • 订阅模式

使用场合

  • simple 简单队列,应用场景极少
  • workqueue 常用于避免消息堆积问题,每个任务只被传递给一个工作人员
  • 订阅模型-Fanout,队列的消费者都能拿到消息
  • 订阅模型-Direct,指定一个RoutingKey,不同的消息被指定的队列消费
  • 订阅模型-Topic,让队列在绑定Routing key 的时候使用通配符

你可能感兴趣的:(学习笔记)