消息中间件之RabbitMQ关键知识点总结

目录

 

一、什么是RabbitMQ

二、RabbitMQ整体架构

1、Exchange 交换器

三、RabbitMQ数据存储

四、RabbitMQ安装

五、RabbitMQ工作流程

1、生产者发送消息流程

2、消费者接收消息的过程

六、RabbitMQ工作模式

1、simple模式(即最简单的收发模式)

2、Work Queue(work工作模式,资源的竞争)

3、发布订阅(publish/fanout资源共享)

4 、路由模式(routing/direct)

5、 主题模式

七、精选面试题

1、RabbitMQ 概念里的 channel、exchange 和 queue 是什么?

2、如何确保消息正确地发送至 RabbitMQ?

3、如何确保消息接收方消费了消息?

4、如何避免消息重复投递或重复消费?

5、RabbitMQ 有几种消费模式?

6、RabbitMQ 如何保证消息的顺序性?


一、什么是RabbitMQ

      RabbitMQ是一个开源的消息代理和队列服务器,用来通过普通协议在不同的应用之间共享数据(跨平台跨语言)。RabbitMQ是使用Erlang语言编写,并且基于AMQP协议实现。

RabbitMQ,俗称“兔子MQ”(可见其轻巧,敏捷),是目前非常热门的一款开源消息中间件,不管是互联网行业还是传统行业都广泛使用(最早是为了解决电信行业系统之间的可靠通信而设计)。

  1. 可靠性(Reliability) RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
  2. 灵活的路由(Flexible Routing) 在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。
  3. 消息集群(Clustering) 多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。
  4. 高可用(Highly Available Queues) 队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。
  5. 多种协议(Multi-protocol) RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。
  6. 多语言客户端(Many Clients) RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby 等等。
  7. 管理界面(Management UI) RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方面。
  8. 跟踪机制(Tracing) 如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。
  9. 插件机制(Plugin System) RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件。

注:AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。

RabbitMQ具有很强大的插件扩展能力,官方和社区提供了非常丰富的插件可供选择: https://www.rabbitmq.com/community-plugins.html

 

二、RabbitMQ整体架构

1、Exchange 交换器

用来接收生产者发送的消息并将这些消息路由给服务器中的队列。

RabbitMQ常用的交换器类型有: fanout direct topic 、 headers 四种。

Fanout

会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中,如图:

每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。fanout 交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型转发消息是最快的。

Direct

direct类型的交换器路由规则很简单,它会把消息路由到那些BindingKey和RoutingKey完全匹配的队列中,如下图:

消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发 routing key 标记为“dog”的消息,不会转发“dog.puppy”,也不会转发“dog.guard”等等。它是完全匹配、单播的模式。

Topic

topic类型的交换器在direct匹配规则上进行了扩展,也是将消息路由到BindingKey和RoutingKey 相匹配的队列中,这里的匹配规则稍微不同,它约定:BindingKey和RoutingKey一样都是由"."分隔的字符串;BindingKey中可以存在两种特殊字符“*”和 “#”,用于模糊匹配,其中"*"用于匹配一个单词,"#"用于匹配多个单词(可以是0个)。

topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点(".")隔开。它同样也会识别两个通配符:符号“#”和“*”。#匹配0个或多个单词,"*"匹配不多不少一个单词。

Headers

headers类型的交换器不依赖于路由键的匹配规则来路由信息,而是根据发送的消息内容中的 headers属性进行匹配。在绑定队列和交换器时指定一组键值对,当发送的消息到交换器时, RabbitMQ会获取到该消息的headers,对比其中的键值对是否完全匹配队列和交换器绑定时指定的键 值对,如果匹配,消息就会路由到该队列。headers类型的交换器性能很差,不实用。

三、RabbitMQ数据存储

存储机制

   RabbitMQ消息有两种类型:

  • 1. 持久化消息和非持久化消息。
  • 2. 这两种消息都会被写入磁盘。

持久化消息在到达队列时写入磁盘,同时会内存中保存一份备份,当内存吃紧时,消息从内存中清 除。这会提高一定的性能。

非持久化消息一般只存于内存中,当内存压力大时数据刷盘处理,以节省内存空间。

RabbitMQ存储层包含两个部分:队列索引和消息存储。

四、RabbitMQ安装

《windows rabbitmq 安装》

《Linux环境RabbitMQ安装与常用操作命令》

五、RabbitMQ工作流程

1、生产者发送消息流程

  • 1. 生产者连接RabbitMQ,建立TCP连接( Connection),开启信道(Channel)
  • 2. 生产者声明一个Exchange(交换器),并设置相关属性,比如交换器类型、是否持久化等
  • 3. 生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等
  • 4. 生产者通过 routingKey (路由Key)将交换器和队列绑定( binding )起来
  • 5. 生产者发送消息至RabbitMQ Broker,其中包含 routingKey (路由键)、交换器等信息
  • 6. 相应的交换器根据接收到的 routingKey 查找相匹配的队列。
  • 7. 如果找到,则将从生产者发送过来的消息存入相应的队列中。
  • 8. 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者
  • 9. 关闭信道。
  • 10. 关闭连接。

2、消费者接收消息的过程

  • 1. 消费者连接到RabbitMQ Broker ,建立一个连接(Connection ) ,开启一个信道(Channel) 。
  • 2. 消费者向RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数, 以及 做一些准备工作
  • 3. 等待RabbitMQ Broker 回应并投递相应队列中的消息, 消费者接收消息。
  • 4. 消费者确认( ack) 接收到的消息。
  • 5. RabbitMQ 从队列中删除相应己经被确认的消息。
  • 6. 关闭信道。
  • 7. 关闭连接。

六、RabbitMQ工作模式

简单的实现可参考《RabbitMQ的五种工作模式的简单实现》

1、simple模式(即最简单的收发模式)

  • 1个生产者将消息交给默认的交换机(AMQP default)
  • 2 交换机获取消息后交给绑定这个生产者的队列(关系是通过队列名称完成)
  • 3 监听当前队列的消费者获取消息,执行消费逻辑
  • 应用场景:短信,聊天

2、Work Queue(work工作模式,资源的竞争)

生产者发消息,启动多个消费者实例来消费消息,每个消费者仅消费部分信息,可达到负载均衡的效果。

  • 1 生产者将消息交个交换机
  • 2 交换机交给绑定的队列
  • 3 队列由多个消费者同时监听,只有其中一个能够获取这一条消息,形成了资源的争抢,谁的资源空闲大,争抢到的可能越大;
  • 应用场景:抢红包,大型系统的资源调度

3、发布订阅(publish/fanout资源共享)

  • 1 生产者扔给交换机消息
  • 2 交换机根据自身的类型(fanout)将会把所有消息复制同步到所有与其绑定的队列
  • 3 每个队列可以有一个消费者,接收消息进行消费逻辑
  • 应用场景:邮件群发,广告

4 、路由模式(routing/direct)

  • 1 生产者还是将消息发送给交换机,消息携带具体的路由key(routingKey)
  • 2 交换机类型direct,将接收到的消息中的routingKey,比对与之绑定的队列的routingKey
  • 3 消费者监听一个队列,获取消息,执行消费逻辑
  • 应用场景:根据生产者的要求发送给特定的一个或者一批队列;错误的通报;

5、 主题模式

  • 1 生产端发送消息,消息携带具体的路由key
  • 2 交换机的类型topic
  • 3 队列绑定交换机不在使用具体的路由key而是一个范围值
  • .orange. : haha.orange.haha,haha.haha.orange.haha
  • lazy.# : haha.lazy.haha.haha,layz.alsdhfsh(sh9ou)N0
  • *表示一个字符串(不能携带特殊符号) 例如 *表示 haha,item,update
  • #表示任意字符串

topic主题模式和路由模式区别:

路由模式中的queue绑定携带的是具体的key值,路由细化划分
topic主题模式queue携带的是范围的匹配,某一类的消息获取

七、精选面试题

1、RabbitMQ 概念里的 channel、exchange 和 queue 是什么?

  • queue 具有自己的 erlang 进程;
  • exchange 内部实现为保存 binding 关系的查找表;
  • channel 是实际进行路由工作的实体,即负责按照 routing_key 将 message 投递给 queue 。

2、如何确保消息正确地发送至 RabbitMQ?

RabbitMQ 使用发送方确认模式,确保消息正确地发送到 RabbitMQ。

  • 发送方确认模式:将信道设置成 confirm 模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的 ID 。一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。如果 RabbitMQ 发生内部错误从而导致消息丢失,会发送一条 nack(not acknowledged,未确认)消息。
  • 发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

3、如何确保消息接收方消费了消息?

RabbitMQ 使用接收方消息确认机制,确保消息接收方消费了消息。

  • 接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ 才能安全地把消息从队列中删除。

这里并没有用到超时机制,RabbitMQ 仅通过 Consumer 的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ 给了 Consumer 足够长的时间来处理消息。

下面罗列几种特殊情况:

  • 如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ 会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要根据 bizId 去重)
  • 如果消费者接收到消息却没有确认消息,连接也未断开,则 RabbitMQ 认为该消费者繁忙,将不会给该消费者分发更多的消息。

4、如何避免消息重复投递或重复消费?

在消息生产时,MQ 内部针对每条生产者发送的消息生成一个 inner-msg-id ,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列

在消息消费时,要求消息体中必须要有一个 bizId(对于同一业务全局唯一,如支付 ID、订单 ID、帖子 ID 等)作为去重和幂等的依据,避免同一条消息被重复消费。

5、RabbitMQ 有几种消费模式?

RabbitMQ 有 pull 和 push 两种消费模式

6、RabbitMQ 如何保证消息的顺序性?

和 Kafka 与 RocketMQ 不同,Kafka 不存在类似类似 Topic 的概念,而是真正的一条一条队列,并且每个队列可以被多个 Consumer 拉取消息。这个,是非常大的一个差异。

先看看 RabbitMQ 顺序错乱的场景

一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者 2 先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。 也就是说,乱序消费的问题。

解决方案

方案一,拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点。

方案二,或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。

实际上,我们会发现上述的两个方案,前提都是一个 queue 只能启动一个 consumer 对应

 

参考文章

拉勾教育讲义

《消息队列之 RabbitMQ》

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页