RabbitMQ~架构、能力、AMQP、工作模式、高可用、死信队列了、事务机制了解

RabbitMQ

RabbitMQ是使用Erlang编写的一个开源的消息中间件。它实现了AMQP(高级消息队列协议),并支持其他消息传递协议:例如STOMP(简单文本定向消息协议)和MQTT(物联网协议)。

支持多种客户端如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等。

用于在分布式系统中存储转发消息,在易用性、扩展性、高可用用性等方面表现不俗。

本身支持很多的协议:AMQP,MQTT,XMPP, SMTP, STOMP,也正是如此,使的它变的非常重量级

它同时实现了一个Broker构架,这意味着消息在发送给客户端时先在中心队列排队,对路由(Routing)、负载均衡(Load balance)或者数据持久化都有很好的支持。

所以最大的特点就是:灵活的路由 : 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能, RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起, 也可以通过插件机制来实现自己的交换器 。依据该特点,衍生出多种工作模式,但是因为集群模式限制吞吐量不行

架构

整体架构如下:

  • Producer(生产者) :生产消息的一方

  • Consumer(消费者) :消费消息的一方

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

  • Exchange(交换器) :在 RabbitMQ 中,消息并不是直接被投递到 Queue(消息队列) 中的,中间还必须经过 Exchange(交换器) 这一层,Exchange(交换器) 会把我们的消息分配到对应的 Queue(消息队列) 中。Exchange(交换器) 用来接收生产者发送的消息并将这些消息路由给服务器中的队列中 ,如果路由不到,或许会返回给 Producer(生产者) ,或许会被直接丢弃掉 。这里可以将 RabbitMQ 中的交换器看作一个简单的实体。RabbitMQ 的 Exchange(交换器) 有 4 种类型,不同的类型对应着不同的路由策略:direct(默认),fanout, topic, 和 headers,不同类型的 Exchange 转发消息的策略有所区别。核心要素就是中心化管理消息路由规则

  • Queue(消息队列): 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。RabbitMQ 中消息只能存储在 队列 中,这一点和 Kafka 这种消息中间件相反。Kafka 将消息存储在 topic(主题) 这个逻辑层面,而相对应的队列逻辑只是 topic 实际存储文件中的位移标识。 RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免消息被重复消费。

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

  • VHost:是RabbitMQ中虚拟主机的概念,它类似于操作系统中的命名空间,用于将RabbitMQ的资源进行隔离和分组。每个VHost拥有自己的交换器、队列、绑定和权限设置,不同VHost之间的资源相互独立,互不干扰。VHost可以用于将不同的应用或服务进行隔离,以防止彼此之间的消息冲突和资源竞争

  • Channel(信道):由于 TCP 链接的创建和销毁开销较大 ,且并发数受系统资源限制,会造成性能瓶颈,所以 RabbitMQ 使用信道的方式来传输数据 。信道(Channel)是生产者、消费者与 RabbitMQ 通信的渠道,信道是建立在 TCP 链接上的虚拟链接,且每条 TCP 链接上的信道数量没有限制 。就是说 RabbitMQ 在一条 TCP 链接上建立成百上千个信道来达到多个线程处理,这个 TCP 被多个线程共享,每个信道在 RabbitMQ 都有唯一的 ID,保证了信道私有性,每个信道对应一个线程使用。

Exchange类型

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

RabbitMQ 常用的 Exchange Type 有 fanout、direct、topic、headers 这四种(AMQP 规范里还提到两种 Exchange Type,分别为 system 与 自定义,这里不予以描述)。

  1. fanout:fanout 类型的 Exchange 路由规则非常简单,它会把所有发送到该 Exchange 的消息路由到所有与它绑定的 Queue 中 ,不需要做任何判断操作,所以 fanout 类型是所有的交换机类型里面速度最快的。fanout 类型常用来广播消息

  2. direct(默认):direct 类型的 Exchange 路由规则也很简单,它会把消息路由到那些 Bindingkey 与 RoutingKey 完全匹配的 Queue 中。

以上图为例,如果发送消息的时候设置路由键为"warning",那么消息会路由到 Queue1 和 Queue2。如果在发送消息的时候设置路由键为"Info"或者"debug",消息只会路由到 Queue2。如果以其他的路由键发送消息,则消息不会路由到这两个队列中。
direct 类型常用在处理有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列

  1. topic:前面讲到 direct 类型的交换器路由规则是完全匹配 BindingKey 和 RoutingKey ,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。topic 类型的交换器在匹配规则上进行了扩展,它与 direct 类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定:RoutingKey 为一个点号"."分隔的字符串(被点号"."分隔开的每一段独立的字符串称为一个单词),如 "com.rabbitmq.client"、"java.util.concurrent"、"com.hidden.client";BindingKey 和 RoutingKey 一样也是点号"."分隔的字符串;BindingKey 中可以存在两种特殊字符串""和"#",用于做模糊匹配,其中" "用于匹配一个单词,"#"用于匹配多个单词(可以是零个)。
  2. headers(不推荐):headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时指定一组键值对,当发送消息到交换器时,RabbitMQ 会获取到该消息的 headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。

AMQP

RabbitMQ 中的交换器、交换器类型、队列、绑定、路由键等都是遵循的 AMQP 协议中相 应的概念。

目前 RabbitMQ 最新版本默认支持的是 AMQP 0-9-1。

AMQP 协议的三层:

  • Module Layer:协议最高层,主要定义了一些客户端调用的命令,客户端可以用这些命令实现自己的业务逻辑。
  • Session Layer:中间层,主要负责客户端命令发送给服务器,再将服务端应答返回客户端,提供可靠性同步机制和错误处理。
  • TransportLayer:最底层,主要传输二进制数据流,提供帧的处理、信道复用、错误检测和数据表示等。

AMQP 模型的三大组件:

  1. 交换器 (Exchange):消息代理服务器中用于把消息路由到队列的组件。
  2. 队列 (Queue):用来存储消息的数据结构,位于硬盘或内存中。
  3. 绑定 (Binding):一套规则,告知交换器消息应该将消息投递给哪个队列。

工作模式

RabbitMQ一共有6种工作模式(消息分发方式)分别是简!单模式、工作队列模式、发布订阅模式、路由模式、主题模式以及RPC模式。

  • 简单模式是最基本的工作模式,也是最简单的消息传递模式。在简单模式中,一个生产者将消息发送到一个队列中,一个消费者从队列中获取并处理消息。这种模式适用于单个生产者和单个消费者的简单场景,消息的处理是同步的。
  • 工作队列模式用于实现一个任务在多个消费者之间的并发处理。在工作队列模式中,一个生产者将消息发送到一个队列中,多个消费者从队列中获取并处理消息。每个消息只能被一个消费者处理。这种模式适用于多个消费者并发处理消息的情况,提高了系统的处理能力和吞吐量。
  • 发布/订阅模式用于实现一条消息被多个消费者同时接收和处理。在发布/订阅模式中,一个生产者将消息发送到交换器(Exchange)中,交换器将消息广播到所有绑定的队列,每个队列对应一个消费者。这种模式适用于消息需要被多个消费者同时接收和处理的广播场景,如事件通知等。
  • 路由模式用于实现根据消息的路由键(Routing Key)将消息路由到不同的队列中。在路由模式中,一个生产者将消息发送到交换器中,并指定消息的路由键,交换器根据路由健将消息路由到与之匹配的队列中。这种模式适用于根据不同的条件将消息发送到不同的队列中,以实现消息的筛选和分发。
  • 主题模式是一种更灵活的消息路由模式,它使用通配符匹配路由键,将消息路由到多个队列中。在主题模式中,一个生产者将消息发送到交换器中,并指定主题(Topic)作为路由键,交换器根据通配符匹配将消息路由到与之匹配的队列中。这种模式适用于消息的复杂路由需求,可以实现高高度灵活的消息筛选和分发。
  • RPC模式是一种用于实现分布式系统中远程调用的工作模式。指的是通过rabbitMQ来实现一种RPC的能力。

高可用部署

RabbitMQ有三种模式:单机模式、普通集群模式、镜像集群模式。

  • 单机模式

其中单机模式一般用于demo搭建,不适合在生产环境中使用。

  • 普通集群模式

意思就是在多台机器上启动多个RabbitMQ实例,每个机器居动一个。你创建的queue,只会放在一个RabbitMQ实例上 ,但是每个实例都同步queue的元数据(元数据可以认为是queue的一些配置信息,通过元数据,可以找到queue所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从queue所在实例上拉取数据过来。这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个queue的读写操作。
在这种模式下,我们创建的Queue,它的元数据(配置信息)会在集群中的所有实例间进行同步,但是队列中的消息只会存在于一个RabbitMQ实例上,而不会同步到其他队列。

当我们消费消息的时候,如果消费者连接到了未保存消息的实例,那么那个实例会通过元数据定位到消息所在的实例,拉取数据过来发送给消费者进行消费。

消息的发送也是一样的,当发送者连接到了一个不保存消息的实例时,也会被转发到保存消息的实例上进行写操作。
这种集群模式下,每一个实例中的元数据是一样的,大家都是完整的数据。但是队列中的消息数据,在不同的实例上保存的是不一样的。这样通过增加实例的方式就可以提升整个个集群的消息存储量,以及性能

这种方式在高可用上有一定的帮助,不至于一个节点挂了就全都挂了。但是也还有缺点,至少这个实例上的数据是没办法被读写了。

  • 镜像集群模式

这种模式,才是所谓的RabbitMQ的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上 ,就是说,每个RabbitMQ节点都有这个queue的一个完整镜像,包含queue的全部数据的意思。然后每次你写消息到queue的时候,都会自动把消息同步到多个实例的queue上。RabbitMQ有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。

这样的好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个queue的完整数据,别的consumer都可以到其它节点上去消费数据。

坏处在于,第一,这个性能开销也太大了吧 ,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!

RabbitMQ每个queue的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个queue的完整数据。

如何保证消息可靠性

  • 生产者到 RabbitMQ:事务机制和 Confirm 机制,注意:事务机制和 Confirm 机制是互斥的,两者不能共存,会导致 RabbitMQ 报错。
  • RabbitMQ 自身:持久化、集群、普通模式、镜像模式。
  • RabbitMQ 到消费者:basicAck 机制、死信队列、消息补偿机制。

死信队列

DLX,全称为 Dead-Letter-Exchange,死信交换器,死信邮箱。当消息在一个队列中变成死信 (dead message) 之后,它能被重新发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。

导致的死信的几种原因:

  1. 消息被拒(Basic.Reject /Basic.Nack) 且 requeue = false。
  2. 消息 TTL 过期。
  3. 队列满了,无法再添加。

事务机制,保证消息发送成功

一种是通过conffirm机制,另外一种就是通过事务机制。

RabbitMQ的事务机制,允许生产者将一组操作打包成一个原子事务单元,要么全部执行成功,要么全部失败 。事务提供了一种确保消息完整性的方法,但需要谨慎使用,因为它们对性能有一定的影响。

RabbitMQ是基于AMQP协议实现的,RabbitMQ中,事务是通过在通道(Channel)上启用的,与事务机制有关的方法有三个:

  • txSelect():将当前channel设置成transaction模式。
  • txCommit():提交事务。
  • txRollback():回滚事务。

我们需要先通过txSelect开启事务,然后就可以发布消息给MQ了,如果txCommit提交成功了,则消息一定到达了RabbitMQ,如果在txCommit执行之前RabbitMQ实例异常崩溃或者抛出异常,那我们就可以捕获这个异常然后执行txRollback进行回滚事务。

相关推荐
优秀的颜2 小时前
计算机基础知识(第五篇)
java·开发语言·分布式
打码人的日常分享8 小时前
物联网智慧医院建设方案(PPT)
大数据·物联网·架构·流程图·智慧城市·制造
白水baishui9 小时前
搭建强化推荐的决策服务架构
架构·推荐系统·强化学习·决策服务·服务架构
何双新9 小时前
第23讲、Odoo18 邮件系统整体架构
ai·架构
雪碧聊技术9 小时前
将单体架构项目拆分成微服务时的两种工程结构
微服务·架构·module·project·工程结构
棠十一9 小时前
Rabbitmq
分布式·docker·rabbitmq
陈暗暗9 小时前
rabbitMQ初入门
rabbitmq
懒虫虫~9 小时前
基于SpringBoot解决RabbitMQ消息丢失问题
spring boot·rabbitmq
从零开始学习人工智能9 小时前
Doris 数据库深度解析:架构、原理与实战应用
数据库·架构
Lansonli9 小时前
大数据Spark(六十一):Spark基于Standalone提交任务流程
大数据·分布式·spark