Rabbit

应用场景(优点)

异步(适合非主业务的处理)

场景说明:用户注册后,需要发注册邮件和注册短信。
传统做法:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。
在这里插入图片描述
引入消息队列,将不是必须的业务逻辑,异步处理。改造后的架构如下:
在这里插入图片描述
按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比正常提高了3倍

总结:

MQ,消息队列,消息可以理解为一个业务现场,而队列则是保存这个业务现场的容器,而B服务(业务)对消息的处理,则是一个对业务现场的异步处理。所以,消息队列的本质,就是将某个业务现场暂存下来,异步处理。

解耦

场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图:
在这里插入图片描述
传统模式的缺点:假如库存系统无法访问,则订单减库存将失败,从而导致订单失败,订单系统与库存系统耦合

如何解决以上问题呢?引入应用消息队列后的方案,如下图:
在这里插入图片描述
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功
库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作
假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦

流量消峰(最主要的优势!!!)

流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。
应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。
a、可以控制活动的人数
b、可以缓解短时间内高流量压垮应用
在这里插入图片描述
用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
秒杀业务根据消息队列中的请求信息,再做后续处理

总结:

因为MQ的本质就是业务的排队。所以,面对突然到来的高并发,MQ也可以不用慌忙,先排好队,不要着急,一个一个来。消峰的好处就是避免高并发压垮系统的关键组件,如某个核心服务或数据库等。

缺点

系统可用性降低:MQ若是挂了,容易引起整个服务挂掉
系统复杂性增加:要考虑多方面的问题,比如一致性问题,如何让保证消息不被重复消费,如何保证消息可靠传输

生产者

在这里插入图片描述
MQ消息发送上半场,即上图中的1-3

1,发送端MQ-client将消息发给服务端MQ-server

2,服务端MQ-server将消息落地

3,服务端MQ-server回ACK给发送端MQ-client

如果3丢失,发送端MQ-client超时后会重发消息,可能导致服务端MQ-server收到重复消息(生产者的消息发送和消息确认是俩个过程)。

此时重发是MQ-client发起的,消息的处理是MQ-server,为了避免步骤2落地重复的消息,对每条消息,MQ系统内部必须生成一个inner-msg-id,作为去重和幂等的依据,这个内部消息ID的特性是:

(1)全局唯一

(2)MQ生成,具备业务无关性,对消息发送方和消息接收方屏蔽

有了这个inner-msg-id,就能保证上半场重发,也只有1条消息落到MQ-server的DB中,实现上半场幂等。

消费者
在这里插入图片描述
MQ消息发送下半场,即上图中的4-6

4,服务端MQ-server将消息发给接收端MQ-client

5,接收端MQ-client回ACK给服务端

6,服务端MQ-server将落地消息删除

需要强调的是,接收端MQ-client回ACK给服务端MQ-server,是消息消费业务方的主动调用行为,不能由MQ-client自动发起,因为MQ系统不知道消费方什么时候真正消费成功。

如果5丢失,服务端MQ-server超时后会重发消息,可能导致MQ-client收到重复的消息(消费者的消息接受和消息确认是俩个过程)。

此时重发是MQ-server发起的,消息的处理是消息消费业务方,消息重发势必导致业务方重复消费(上例中的一次付款,重复发卡),为了保证业务幂等性,业务消息体中,必须有一个biz-id,作为去重和幂等的依据,这个业务ID的特性是:

(1)对于同一个业务场景,全局唯一

(2)由业务消息发送方生成,业务相关,对MQ透明

(3)由业务消息消费方负责判重,以保证幂等

最常见的业务ID有:支付ID,订单ID,帖子ID等。

具体到支付购卡场景,发送方必须将支付ID放到消息体中,消费方必须对同一个支付ID进行判重,保证购卡的幂等。

有了这个业务ID,才能够保证下半场消息消费业务方即使收到重复消息,也只有1条消息被消费,保证了幂等。

理论

ConnectionFactory、Connection、Channel

ConnectionFactory:ConnectionFactory为Connection的制造工厂。
Connection:Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑。
Channel(信道):信道是建立在“真实的”TCP连接上的虚拟连接,在一条TCP链接上创建多少条信道是没有限制的,把他想象成光纤就是可以了。它是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。

Queue(队列)

RabbitMQ中的消息只能存储在 Queue 中。生产者(下图中的P)生产消息并最终投递到Queue中,消费者(下图中的C)可以从Queue中获取消息并消费,消费者可以是一个或者多个。
在这里插入图片描述

Message acknowledgment(ack 消息的确认):

生产者ACK
1). 设置信道channel 为事务模式

通过channel.txSelect 开启事务,channel.txCommit 提交事务,channel.txRollback 用于事务回滚

如果在还没有提交事务之前,RabbitMQ抛出异常,我们可以 将其捕获,然后进行事务回滚。缺点是 事务模式会极大的消耗RabbitMQ的性能。

2). 设置信道confirm 模式

通过confirm.select开启confirm模式,如果设置了no_wait为 false的话,那么broker会返回confirm.select_ok,表示broker同意将信道设置为confirm模式。 但是这个优点是发送方确认是异步的,发送方可以不等到确认就发送下一条消息。当消息被brocker接收,broker

会发送basic.ack,生产者可以通过回调函数确认这个消息,如果因为RabbitMQ本身问题导致消息丢失,broker会发送basic.nack,生产者通过回调方法接收到nack后,可以考虑消息重发

要注意的是 事务模式 和confirm模式不能共存,是互斥的。
消费者ACK
为了保证消息正常的到达消费者,RabbitMQ 提供了消息 acknowledgement来确认消息。

默认autoAck=None isAutoAck=false 来确认消息, autoAck = false 是表示,需要等到消费者发送显示的回复确认信号之后,消息才从内存中移除,但是如果acknowledgeMode设置了Auto ,那么isAutoAck= true ,这个其实是不安全的,fire-And-forget, 消息发送出去之后,但是可能还没到消费者,TCP连接就断了(由于配置了auto,只要消费发出去了,就删掉了),那么TCP连接断开了,这部分消息就丢失了,是不安全的,但是对应能一直keepup连接的,是可以提高吞吐量的。

还可以设置autoAck = manual ,isAutoack= false,那么就是 队列每次向消费者发送消息之后,需要消费者手动确认basc.ack,basic,nack等,rabbitmq才可以将消息删除或者重新入队。 isAutoack=false 情况下 ,一致没有收到 消费者的 basic.ack, RabbitMQ如果检测到 和消费者端口连接 端口,会重新发这条消息。

还有注意如果basic.nack basic.reject 只是简单的拒绝 ,而不是重新 requeue的话,那么消息是不会重新入队的

Message durability(消息的持久化)

如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。但依然解决不了小概率丢失事件的发生(比如RabbitMQ服务器已经接收到生产者的消息,但还没来得及持久化该消息时RabbitMQ服务器就断电了),如果我们需要对这种小概率事件也要管理起来,那么我们要用到事务。由于这里仅为RabbitMQ的简单介绍,所以这里将不讲解RabbitMQ相关的事务。具体可以参考上面的 RabbitMQ消息确认机制(事务+Confirm)

Prefetch count(每次向消费者发送消息的总数)

前面我们讲到如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。
在这里插入图片描述

Exchange(交换器)

Exchange生产者将消息发送到 Exchange(交换器,下图中的X),由 Exchange 根据一定的规则将消息路由到一个或多个 Queue 中(或者丢弃)。
在这里插入图片描述

Routing key(路由key)

生产者在将消息发送给 Exchange 的时候,一般会指定一个 routing key,来指定这个消息的路由规则。 Exchange 会根据 routing key 和 Exchange Type(交换器类型) 以及 Binding key 的匹配情况来决定把消息路由到哪个 Queue。RabbitMQ为routing key设定的长度限制为255 bytes。

Binding(绑定)

RabbitMQ中通过 Binding 将 Exchange 与 Queue 关联起来。
在这里插入图片描述

Binding key

在绑定(Binding) Exchange 与 Queue 关系的同时,一般会指定一个 binding key。

Exchange Types (交换器类型)

RabbitMQ常用的Exchange Type有 Fanout、 Direct、 Topic、 Headers 这四种。
Fanout
这种类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,这时 Routing key 不起作用。
在这里插入图片描述
Direct
这种类型的Exchange路由规则也很简单,它会把消息路由到那些 binding key 与 routing key完全匹配的Queue中。
在这里插入图片描述
在这个设置中,我们可以看到两个队列Q1、Q2直接绑定到了交换器X上。 第一个队列用绑定key橙色(orange)绑定,第二个队列有两个绑定,一个绑定key为黑色(black),另一个为绿色(green)。

在这种设置中,通过路由键橙色发布到交换器的消息将被路由到队列Q1。 带有黑色或绿色的路由键的消息将进入Q2。 所有其他消息将被丢弃。
Topic
这种类型的Exchange的路由规则支持 binding key 和 routing key 的模糊匹配,会把消息路由到满足条件的Queue。 binding key 中可以存在两种特殊字符 *与 #,用于做模糊匹配,其中 * 用于匹配一个单词,# 用于匹配0个或多个单词,单词以符号“.”为分隔符。
在这里插入图片描述
以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1与Q2,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。
Headers
这种类型的Exchange不依赖于 routing key 与 binding key 的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值