消息中间件---RabbitMQ

目录

1.消息队列

1.1 MQ相关概念

1.1.1 什么是MQ

1.1.2 MQ的功能

1.2 RabbitMQ

1.2.1 概念

1.2.2 四大核心概念

1.2.3 RabbitMQ核心部分

1.2.4 名词介绍

2. Work Queues工作模式

2.1 轮询发送消息

2.2 消息应答

2.2.1 概念

2.2.2 自动应答

2.2.3 消息应答的方法

2.2.4 Multiple的解释

2.2.5 消息自动重新入队

2.3 RabbitMQ持久化

2.3.1 概念

2.3.2 不公平分发

2.3.3 预取值

3.发布确认

3.1 发布确认原理

3.2 发布确认的策略

3.2.1 单个确认发布

3.2.2 批量确认发布

3.2.3 异步确认发布

3.2.4 如何处理异步未确认消息

4.交换机

4.1 Exchanges

4.1.1 概念

4.1.2 交换机类型

4.2 临时队列

4.3 绑定Bindings

4.4 Fanout扇出

4.4.1 介绍

4.5 Direct直接

4.5.1 介绍

4.5.2 多重绑定

4.6 Topics主题

4.6.1 介绍

4.6.2 格式要求

5.死信队列

5.1 死信的概念

5.2 死信的来源

5.3 结构图

6.延时队列

6.1 概念

6.2 RabbitMQ中的TTL

6.3 问题

7.发布确认高级

7.1 介绍

7.2 架构

7.3 回退消息

7.3.1 Mandatory参数

7.4 备份交换机

8.其他知识点

8.1 幂等性

8.1.1 概念

8.1.2 消费者重复消费问题

8.1.3 消费端的幂等性保障

8.2 优先级队列

8.3 惰性队列


1.消息队列

1.1 MQ相关概念

1.1.1 什么是MQ

        MQ(Message Queue),本质上是个FIFO先入先出的队列,只不过队列中存放的内容是message而已,还是一种跨进程的通信机制,用于上下游传递消息。

1.1.2 MQ的功能

  • 流量消峰。比如在一个系统的高峰期,很多订单同时过来,可能超出了系统下游的处理能力,此时可以利用消息队列做缓冲,把一秒内下的订单分散成一段时间来处理
  • 应用解藕。以电商应用为例,应用中有订单系统、库存系统、支付系统。用户创建订单后,如果耦合调用这些子系统,任何一个子系统出现了故障,都会造成下单操作异常;当转变成消息队列的方式后,系统间调用的问题会减少很多,比如物流系统出现了故障,需要几分钟来进行修复,这几分钟的时间内,物流系统要处理的消息被缓存在消息队列中,不影响其他子系统的使用,提升系统的可用性。
  • 异步处理。比如两个服务A和B,B服务需要花费很长时间执行,但是A需要知道B什么时候可以执行完,以前一般有两种方式,一种是A过一段时间去调用B的查询api查询。或者A提供一个callback api,B执行完之后调用api通知A服务。现在有一种更好的方式,使用消息总线,A调用B服务后,只需要监听B处理完成的消息,当B处理完成后,会发送一条消息给MQ,MQ会将此消息转发给A服务,这样A服务能够及时地得到异步处理成功的消息。

1.2 RabbitMQ

1.2.1 概念

        RabbitMQ是一个消息中间件:它接受并转发消息,接收、存储和转发消息数据

1.2.2 四大核心概念

  • 生产者:产生数据发送消息的程序。
  • 交换机:是RabbitMQ非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息推送到队列中,交换机必须确切知道如何处理它接收到的消息(处理逻辑)。
  • 队列:队列是RabbitMQ内部使用的一种数据结构,尽管消息流经RabbitMQ和应用程序,但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。许多生产者可以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。
  • 消费者:大多时候是一个等待接收消息的程序。注意消费者和消费中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又可以是消费者。

1.2.3 RabbitMQ核心部分

消息中间件---RabbitMQ_第1张图片

1.2.4 名词介绍

消息中间件---RabbitMQ_第2张图片

  • Broker接收和分发消息的应用,消息实体、MQ服务器。RabbitMQ Server就是Message Broker。
  • Virtual host:出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQ Server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange/queue等。一个Broker内有多个vhost,每个vhost内又有多个交换机
  • Connection:publisher/consumer和broker之间的TCP连接
  • Channel:如果每一次访问RabbitMQ都建立一个连接,在消息量大的时候建立TCP连接的开销是巨大的,效率也低。Channel是在connection内部建立的逻辑连接,Channel作为轻量级的connection极大减少了操作系统建立TCP连接的开销。Channel之间是完全隔离的
  • Exchange:message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。常用的类型:
    • direct:point-to-point
    • topic:publish-subscribe
    • fanout:multicast
  • Queue:消息最终被送到这里等待consumer取走。
  • Bindingexchange和queue之间的虚拟连接(绑定),binding中可以包含routing key,Binding信息被存储到exchange中的查询表中,用于message的分发依据。

2. Work Queues工作模式

        工作队列的主要思想是避免立即执行资源密集型任务,我们把任务封装为消息并将其发送到队列,在后台运行的工作进程将弹出任务并最终执行任务。当有多个工作线程(即消费者)时,这些工作线程将一起处理这些任务。

消息中间件---RabbitMQ_第3张图片

        注意:处于同一队列的工作线程之间是竞争关系,同一个消息不能被重复消费

2.1 轮询发送消息

        消费者按照顺序有序地一个接收一次消息。

2.2 消息应答

2.2.1 概念

        消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它就挂掉了,会发生什么情况。RabbitMQ一旦向消费者传递了一条消息,便立即将该消息标记为删除。为了保证消息在发送过程中不丢失,RabbitMQ引入消息应答机制,即消费者在接收到消息并且处理该消息之后,告诉RabbitMQ它已经处理了,RabbitMQ可以把该消息删除了

2.2.2 自动应答

        消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收之前,消费者那边出现连接或者channel关闭,那么消息就丢失了;另一方面这种模式生产者那边可以传递过载的消息,没有对传递的消息数量进行限制,有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽。这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用。

2.2.3 消息应答的方法

  • Channel.basicAck(用于肯定确认)
    • RabbitMQ已知道该消息被接收并且成功的处理消息,可以将其丢弃了。
  • Channel.basicNack(用于否定确认)
  • Channel.basicReject(用于否定确认)
    • 与Channel.basicNack相比少一个参数Multiple,是否批量处理的参数
    • 不处理该消息了直接拒绝,可以将其丢弃了

2.2.4 Multiple的解释

        手动应答的好处是可以批量应答并且减少网络拥堵

Multiple的true和false含义:

  • true:代表批量应答channel上未应答的消息。比如说channel上有传送tag的消息5、6、7、8,当前tag是8,那么此时5-8这些还未应答的消息都会被确认收到消息应答
  • false:同上面相比,只会应答tag=8的消息,5、6、7这三个消息依然不会被确认收到消息应答。

2.2.5 消息自动重新入队

   如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或TCP连接丢失),导致消息未发送ACK确认,RabbitMQ将了解到消息未完全处理,并将其重新排队。如果此时其他消费者可以处理,它将很快将其分发给另一个消费者,这样即使某个消费者偶尔死亡,也确认不会丢失任何消息。

2.3 RabbitMQ持久化

2.3.1 概念

        如何保证党RabbitMQ服务停掉以后消息生产者发送过来的消息不丢失。默认情况下RabbitMQ退出或由于某种原因崩溃时,它忽视队列和消息,除非告知它不要这么做。确保消息不会丢失需要做两件事:需要将队列和消息都标记为持久化

  • 队列持久化
  • 消息持久化
    • 注意这里依然存在当消息刚准备存储在磁盘的时候,但是还没有存储完,消息还在缓存的一个间隔点,此时并没有真正写入磁盘,持久性保证并不强。

2.3.2 不公平分发

        采用轮询分发的话,在某些场景下并不好,比如有两个消费者在处理任务,其中有一个消费者1处理任务的速度非常快,另外一个消费者2处理速度很慢,这个时候如果采用轮询分发的话就会导致处理速度快的这个消费者很大一部分时间处于空闲状态,资源不能充分利用。

        避免上述情况,在消费者设置一个参数channel.basicQos(1); 意思是如果这个任务当前消费者还没有处理完成或者还没有应答,那么就先不给这个消费者分配,此时RabbitMQ就会把该任务分配给还没有那么忙的空闲消费者。相当于给消费者设置一个信息容量范围,仍旧是轮询每个channel,但是若此时容量满了,就会跳过该channel,实现了不公平分发。

2.3.3 预取值

        消息的发送是异步发送的,所以在任何时候,channel上肯定不止只有一个消息,另外消费者的手动确认也是异步的,因此这里就存在一个未确认的消息缓冲区,因此希望开发人员能限制此缓冲区的大小,以避免缓冲区里面无限制的未确认消息问题。

        通过使用basic.qos方法设置“预取计数”值来完成,该值定义通道上允许的未确认消息的最大数量,一旦数量达到配置的数量,RabbitMQ将停止在通道上传递更多消息。

        消息应答和Qos预取值对用户吞吐量有重大影响,通常,增加预取将提高向消费者传递消息的速度,虽然自动应答传输消息速率是最佳的,但是,在这种情况下已传递但尚未处理的消息的数量也会增加,从而增加了消费者的RAM消耗(随机存取存储器),应该小心使用具有无限预处理的自动确认模式或手动确认模式。

3.发布确认

  • 消息应答是消费者与RabbitMQ之间
  • 发布确认是生产者与RabbitMQ之间

3.1 发布确认原理

        生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出。

        confirm模式最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢弃,就会发送一条nack消息,生产者应用程序同样可以在回调函数中处理该nack消息。

3.2 发布确认的策略

3.2.1 单个确认发布

        这是一种简单的确认模式,它是一种同步确认发布的方式,也就是发布一个消息之后只有它被确认发布,后续的消息才能继续发布,如果在指定时间范围内这个消息没有被确认那么它将抛出异常。这种确认方式有一个最大的缺点就是发布速度特别的慢,因为如果没有确认发布的消息就会阻塞所有后续消息的发布,这种方式最多提供每秒不超过数百条发布消息的吞吐量。

3.2.2 批量确认发布

        发布一批消息然后一起确认,可以极大地提高吞吐量,但是也有缺点,当发生故障导致发布出现问题时,不知道是哪个消息出现问题了,我们必须将整个批处理保存在内存中,以记录重要的信息而后重新发布消息。这种方式也是同步的,也会阻塞消息的发布。

3.2.3 异步确认发布

        异步确认虽然编程逻辑比上两个要复杂,但是性价比最高,无论是可靠性还是效率都没得说,它是利用回调函数来达到消息可靠性传递的,通过函数回调来保证是否投递成功。

3.2.4 如何处理异步未确认消息

        最好的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列

4.交换机

        发布/订阅模式

消息中间件---RabbitMQ_第4张图片

        消息能够由路由发送到队列中其实是由routingkey(bindingkey)绑定key指定的。不同的routingkey代表不同的路由方式。

4.1 Exchanges

4.1.1 概念

        RabbitMQ消息传递模型的核心思想是生产者生产的消息从不直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递到了哪些队列中。相反,生产者只能将消息发送到交换机exchange,交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将它们推入队列,交换机必须确切知道如何处理收到的消息,是应该把这些消息放到特定队列,还是说把它们放到许多队列中,还是说丢弃它们,这就由交换机的类型来决定。

4.1.2 交换机类型

  • 直接direct
  • 主题topic
  • 标题headers
  • 扇出fanout

4.2 临时队列

        每当我们连接到RabbitMQ时,我们都需要一个全新的空队列,为此我们可以创建一个具有随机名称的队列,或者能让服务器为我们选择一个随机队列名称那就更好了,其次一旦我们断开了消费者的连接,队列将被自动删除(不带有持久化)

4.3 绑定Bindings

        binding其实是exchange和queue之间的桥梁,它告诉我们exchange和哪个队列进行了绑定关系。

消息中间件---RabbitMQ_第5张图片

4.4 Fanout扇出

4.4.1 介绍

        Fanout这种类型非常简单,它是将接收到的所有消息广播到它知道的所有队列中。Fanout是广播模式,无论routingkey是否相同,消费者都会收到来自同一个交换机发出的消息,即把交换机里的消息发给所有绑定该交换机的队列,忽略routingkey

4.5 Direct直接

4.5.1 介绍

        direct类型的工作方式是消息只去到它绑定的routingkey队列中去

消息中间件---RabbitMQ_第6张图片

4.5.2 多重绑定

        如果exchange的类型是direct,但是它绑定的多个队列的routingkey都相同,在这种情况下,虽然绑定类型是direct,但是它表现的就和fanout有点类似了,和广播差不多。

4.6 Topics主题

4.6.1 介绍

        topics的工作方式是消息只去到它能匹配的topic的队列中去。

4.6.2 格式要求

        发送到类型是topic交换机的消息的routingkey是不能随便写的,必须满足一定的要求,它必须是一个单词列表,以点号.分开,这个单词列表最多不能超过255个字节。

在这个规则列表,有两个替换符:

  • *:可以代替一个单词
  • #:可以代替零个或多个单词

消息中间件---RabbitMQ_第7张图片

  • 当一个队列绑定键是#,那么这个队列将接收所有数据,类似fanout
  • 当一个队列绑定键当中没有*和#出现,那么该队列绑定类型就类似direct 

5.死信队列

5.1 死信的概念

        死信,顾名思义就是无法被消费的消息,某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就成了死信,自然就有了对应的死信队列。

        应用场景:为了保证订单业务的消息数据不丢失,需要使用到死信队列机制,当消息消费发生异常时,将消息投入死信队列中;比如说用户在商场下单成功并点击去支付后在指定时间未支付时自动失效。

5.2 死信的来源

  • 消息TTL过期
  • 队列达到最大长度(队列满了,无法再添加数据到mq中)
  • 消息被拒绝(basic.reject或basic.nack)并且不再重新放回队列requeue=false

5.3 结构图

消息中间件---RabbitMQ_第8张图片

6.延时队列

6.1 概念

        延时队列,队列内部是有序的,最重要的特性体现在它的延时属性上,延时队列中的元素是希望在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列。

使用场景:需要在某个事件发生之后或者之前的指定时间点完成某一项任务。

  • 订单在十分钟之内未支付则自动取消
  • 用户发起退款,如果三天内没有登陆则进行短信提醒
  • 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

6.2 RabbitMQ中的TTL

        TTL是RabbitMQ中一个消息或者队列的属性,表明一条消息或者该队列中的所有消息的最大存活时间,单位是毫秒。换句话说,如果一条消息设置了TTL属性或者进入了设置TTL属性的队列,那么这条消息如果在TTL设置的时间内没有被消费,则会成为死信。如果同时配置了队列的TTL和消息的TTL,那么较小的那个值将会被使用。

        如果不设置TTL,表示消息永远不会过期;如果设置TTL为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。

两种方式设置TTL:

  • 消息设置TTL:针对每条消息设置TTL
  • 队列设置TTL:在创建队列的时候设置队列的“x-message-ttl”属性

两者的区别:

  • 如果设置了队列的TTL属性,那么一旦消息过期,就会被队列丢弃(如果配置了死信队列被丢到死信队列中)
  • 如果是设置消息的TTL属性,消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间

消息中间件---RabbitMQ_第9张图片

6.3 问题

        如果使用在消息属性上设置TTL的方式,消息可能并不会按时死亡,因为RabbitMQ只会检查第一个消息是否过期,如果过期则丢到死信队列,如果第一消息的延时时长很长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行,因为队列的先进先出原则。

        解决方法:利用RabbitMQ插件rabbitmq_delayed_message_exchange实现延时队列。 此时不再由队列或消息来延时,而是由交换机来延时,消息停留在交换机中,而不是在队列中,就不用再满足先入先出原则。

7.发布确认高级

7.1 介绍

        前面介绍的发布确认是在服务器正常工作下消息持久化成功之后给交换机一个确认(消费方的确认应答,重新入队);这里介绍的是在服务器挂了,即交换机、队列不能正常工作的环境下(生产方的确认应答)。两者本质上都是为了防止消息丢失。

        在生产环节中由于一些不明原因,导致RabbitMQ重启,在重启期间生产者消息投递失败,导致消息丢失,需要手动处理和恢复。

7.2 架构

消息中间件---RabbitMQ_第10张图片

7.3 回退消息

7.3.1 Mandatory参数

        在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个时事件的,通过Mandatory参数可以在当消息传递过程中不可达目的地时将消息返回给生产者

7.4 备份交换机

        当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理,通常备份交换机的类型为Fanout,这样就能把所有消息都投递到与其绑定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都进入这个队列了。也可以建立一个报警队列,用独立的消费者来进行监测和报警。

消息中间件---RabbitMQ_第11张图片

        mandatory参数与备份交换机可以一起使用,如果两者同时开启,备份交换机的优先级更高

8.其他知识点

8.1 幂等性

8.1.1 概念

        用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

8.1.2 消费者重复消费问题

        消费者在消费MQ中的消息时,MQ已经把消息发送给消费者,消费者在给MQ返回ack时网络中断,故MQ未收到确认消息,该条消息会重新发给其他的消费者,或者在网络重连后再次发送给该消费者,但实际上该消费者已成功消费了该条消息,造成消费者重复消费的问题。

8.1.3 消费端的幂等性保障

两者方式:

  • 唯一ID+指纹码机制,利用数据库主键去重:我们的一些规则或者时间戳加别的服务给到的唯一信息码,它并不一定是我们系统生成的,基本都是由我们的业务规则拼接而来,但是一定要保证唯一性,然后就利用查询语句进行判断这个id是否存在数据库中。优势就是简单就一个拼接,然后查询判断重复;劣势就是在高并发时,如果是单个数据库就有写入性能瓶颈,不过可以采用分库表提升性能,但不是最推荐的方式。
  • redis的原子性:利用redis执行setnx命令,天然具有幂等性。

8.2 优先级队列

        将队列设计为优先级队列,消息需要设置消息的优先级,消费者需要等待消息已经发送到队列中才去消费因为需要对消息进行排序。先把消息放到队列中,排完序后再让消费者消费。

8.3 惰性队列

        惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储。当消费者由于各种各样的原因(比如消费者下线、宕机亦或是由于维护而关闭等)而致使更长时间内不能消费造成堆积时,惰性队列就很需要了。惰性队列占的内存很小,因为内存中存放的是一些索引,通过索引到磁盘上找消息。

PS:根据尚硅谷课程进行整理,如有侵权,联系删除。

你可能感兴趣的:(消息中间件,rabbitmq)