rabbitmq学习篇和常见面试题

MQ

MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。

MQ优势

异步提速 - 相比于传统的串行、并行方式,提高了系统吞吐量。
在这里插入图片描述

使用MQ 后

在这里插入图片描述

应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。

流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。

举例:假设一个系统每秒最多能承担1000个请求,但是这时候,突发面临一秒5000个请求,这时候,这个系统肯定会崩。

​ 但是在两者之间加了MQ ,每次从MQ 拉取1000个请求就就可以了

使用了 MQ 之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在 MQ 中,高峰就被“削”掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做“填谷”。

使用MQ后,可以提高系统稳定性。

日志处理 - 解决大量日志传输。

消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。

rabbitmq概念

采用AMQP高级消息队列协议的一种消息队列技术,最大的特点就是消费并不需要确保提供方存在,实现了服务之间的高度解耦。

使用rabbitmq的场景。

答:

1、服务间异步通信

2、顺序消费

3、定时任务

4、请求削峰

RabbitMQ 中的相关概念

Broker: 简单来说就是消息队列服务器实体

Exchange: 消息交换机,它指定消息按什么规则,路由到哪个队列

Queue: 消息队列载体,每个消息都会被投入到一个或多个队列

Binding: 绑定,它的作用就是把exchange和queue按照路由规则绑定起来

Routing Key: 路由关键字,exchange根据这个关键字进行消息投递

VHost: vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。

其内部 均含有独立的 queue、exchange 和 binding 等,

但最最重要的是,其拥有独立的 权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度, vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同 的 vhost 中)。

Producer: 消息生产者,就是投递消息的程序

Consumer: 消息消费者,就是接受消息的程序

Channel: 消息通道,在客户端的每个连接里,可建立多个channel,每个 channel代表一个会话任务 由Exchange、Queue、RoutingKey三个才能决定一个从Exchange到Queue的 唯一的线路。
在这里插入图片描述

5大模式

简单队列模式

在这里插入图片描述

在上图的模型中,有以下概念:

lP:生产者,也就是要发送消息的程序

lC:消费者:消息的接收者,会一直等待消息到来

lqueue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息

工作队列模式模式

在这里插入图片描述

Work Queues:与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。消费者之间是竞争关系

l应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

例如:短信服务部署多个,只需要有一个节点成功发送即可。

发布订阅模式

在这里插入图片描述

在订阅模型中,多了一个 Exchange 角色,而且过程略有变化:

lP:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)

lC:消费者,消息的接收者,会一直等待消息到来

lQueue:消息队列,接收消息、缓存消息

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

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

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

交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。

发布订阅模式与工作队列模式的区别?

l工作队列模式不用定义交换机,而发布/订阅模式需要定义交换机

l发布/订阅模式的生产方是面向交换机发送消息,工作队列模式的生产方是面向队列发送消息(底层使用默认交换机)

l发布/订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将队列绑 定到默认的交换机

路由模式

在这里插入图片描述

工作流程:

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

l消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey

lExchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的Routingkey 与消息的 Routing key 完全一致,才会接收到消息

通配符模式

在这里插入图片描述

工作流程:

图解:

l红色 Queue:绑定的是 usa.# ,因此凡是以 usa. 开头的 routing key 都会被匹配到

l黄色 Queue:绑定的是 #.news ,因此凡是以 .news 结尾的 routing key 都会被匹配

l通配符规则:

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

例如:item.# 能够匹配 item.insert.abc 或者 item.insert,item.* 只能匹配 item.insertl通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc 或者 item.insert,item.* 只能匹配 item.insert

Rabbitmq 消息的确认的机制

由生产者发送到Broker,怎么确定信息是真正被投递到呢?

Rabbitmq 提供了监听器,来接收信息投递的状况

信息确认涉及两个状态:

Confirm 和return 

但是与消费者消费消息无关

Confirm

Confirm 代表生产者将信息送到Broker 产生的状态

也有两种,分别是:

ack

ack:代表Broker 已经将数据接受

nack

nack:代表Broker 拒绝信息:队列满,限流,IO 异常

Return

Return 代表已经被Broker 接受(ack)后,但是Broker 没有对消息进行投递,消息被退回给消费者。

消息的可靠投递

问题:作为消息发送方希望杜绝任何消息丢失或者投递失败?

l消息从 producer 到 exchange 则会返回一个 confirmCallback 。

消息从 exchange–>queue 投递失败则会返回一个 returnCallback

我们将利用这两个 callback 控制消息的可靠性投递

流程:

先配置

#设置此属性配置可以确保消息成功发送到交换器
spring.rabbitmq.publisher-confirms=true

使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发送失败,需要处理。


##设置
ConnectionFactory的publisher-returns="true" 开启 退回模式。

使用rabbitTemplate.setReturnCallback设置退回函数,当消息从exchange路由到queue失败后,如果设置了rabbitTemplate.setMandatory(true)参数,则会将消息退回给producer。并执行回调函数returnedMessage。

ack

ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。

确认方式

•自动确认:acknowledge=“none”

•手动确认:acknowledge=“manual”

•根据异常情况确认:acknowledge=“auto”

在yml文件配置

server:
  port: 8021
spring:
  #给项目来个名字
  application:
    name: rabbitmq-test
  #配置rabbitMq 服务器
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: need
    password: 123456
    #虚拟host 可以不设置,使用server默认host
    virtual-host: /testhostserver:
  port: 8021
spring:
  #给项目来个名字
  application:
    name: rabbitmq-test
  #配置rabbitMq 服务器
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: need
    password: 123456
    #虚拟host 可以不设置,使用server默认host
    virtual-host: /testhost
#ack 确认方式
listener:
  simple:
    acknowledge-mode: manual
  direct:
    acknowledge-mode: manual其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。

但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。

如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

Ø在rabbit:listener-container标签中设置acknowledge属性,设置ack方式 none:自动确认,manual:手动确认

Ø

Ø如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息

Ø

如果出现异常,则在catch中调用 basicNack或 basicReject,拒绝消息,让MQ重新发送消息。

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;

@Component
public class DirectReceiver_1  {
@RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue
public void process(Message message, Channel channel) throws IOException {
 
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
 
    try {
        String msgbody = new String(message.getBody());
        //1.接收转换消息
        System.out.println("DirectReceiver消费者 1 收到消息  : " +msgbody+" 编号: "+deliveryTag);
 
        //2. 处理业务逻辑
        System.out.println("处理业务逻辑...");
        //模拟出现错误
        System.out.println(500/Double.valueOf(msgbody));
        //3. 手动签收
        channel.basicAck(deliveryTag,true);
    } catch (Exception e) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException interruptedException) {
            interruptedException.printStackTrace();
        }
        //e.printStackTrace();
 
        //4.拒绝签收
        /*
        第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
         */
        channel.basicNack(deliveryTag,true,true);
        //channel.basicReject(deliveryTag,true);
    }
}

消费端限流

在rabbit:listener-container 中配置 prefetch属性设置消费端一次拉取多少消息

消费端的确认模式一定为手动确认。acknowledge=“manual”

server:
  port: 2002
 
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: LeoLee
    password: lyl512240816
    virtual-host: /LeoLee
    listener:
      simple:
        acknowledge-mode: manual #消费者端确认模式:none自动确认 manual手动确认 auto通过抛出异常的类型,来做响应的处理
        concurrency: 1 #当前监听的数量
        max-concurrency: 5 #最大监听数量
        retry:
          enabled: true #是否支持重试
          max-attempts: 4 #最大重试次数,默认为3
        prefetch: 2 #消费端限流5 每个消费者未确认的未处理消息的最大数量
      direct:
        acknowledge-mode: manual    #acknowledgeMode设置为手动模式

TTL

ØTTL 全称 Time To Live(存活时间/过期时间)。

Ø当消息到达存活时间后,还没有被消费,会被自动清除。

RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间

特性区别

设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。

设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断这一消息是否过期。

如果两者都进行了设置,以时间短的为准。

死信队列

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message(死亡信息)后,可以被重新发送到另一个交换机,这个交换机就是DLX(死信交换机)
在这里插入图片描述
在这里插入图片描述

  1. 死信交换机和死信队列和普通的没有区别

步骤:当消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列

消息成为死信的三种情况:

  1. 队列消息长度到达限制;

  2. 消费者拒接消费消息,并且不重回队列;

  3. 原队列存在消息过期设置,消息到达超时时间未被消费;

延迟队列

概念:即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。

需求

1️⃣ 下单后,30分钟未支付,取消订单,回滚库存。

2️⃣ 新用户注册成功7天后,发送短信问候。

实现方式

定时器

延迟队列

流程图

在这里插入图片描述

⭐️ 可惜,在RabbitMQ中并未提供延迟队列功能。

但是可以使:TTL+死信队列

rabbitmq流程图

在这里插入图片描述

消息幂等性保障

幂等性

概念:幂等性指一次和多次请求某一个资源,对于资源本身应该具有同样的结果。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。在MQ中指,消费多条相同的消息,得到与消费该消息一次相同的结果

消息幂等性保障–乐观锁机制

在这里插入图片描述

消息积压

1.消费者宕机积压

2.消费者消费能力不足积压

3.发送者发流量太大

🌟 解决方案:上线更多的消费者,进行正常消费上线专门的队列消费服务,将消息先批量取出来,记录数据库,再慢慢处理

面试题

如何确保消息不丢失如何确保消息不丢失

🛑 消息持久化,当然前提是队列必须持久化

RabbitMQ确保持久性消息能从服务器重启中恢复的方式是,将它们写入磁盘上的一个持久化日志文件,当发布一条持久性消息到持久交换器上时,Rabbitmq会在消息提交到日志文件后才发送响应。

一旦消费者从持久队列中消费了一条持久化消息,RabbitMQ会在持久化日志中把这条消息标记为等待垃圾收集。如果持久化消息在被消费之前RabbitMQ重启,那么Rabbit会自动重建交换器和队列(以及绑定),并重新发布持久化日志文件中的消息到合适的队列

消息怎么路由?

答:

消息提供方->路由->一至多个队列

消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设定。

通过队列路由键,可以把队列绑定到交换器上。

消息到达交换器后,RabbitMQ 会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则);

消息基于什么传输?

答:

由于TCP连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ使用信道的方式来传输数据。信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制消息基于什么传输?

如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,怎么办?

消息积压处理办法:临时紧急扩容:

先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉。

新建一个topic,partition是原来的10倍,临时建立好原先10倍的queue数量。

然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue。

接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据。这种做法相当于是临时将 queue 资源和consumer资源扩大10倍,以正常的10倍速度来消费数据。

等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的consumer机器来消费消息。

**MQ中消息失效:**假设你用的是RabbitMQ,RabbtiMQ是可以设置过期时间的,也就是 TTL。如果消息在queue中积压超过一定的时间就会被RabbitMQ给清理掉,这个数据就没了。那这就是第二个坑了。

这就不是说数据会大量积压在mq里,而是大量的数据会直接搞丢。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,
然后重新灌入mq里面去,把白天丢的数据给他补回来。也只能是这样了。假设1万个订单积压在mq里面,没有处理,其中 1000个订单都丢了,你只能手动写程序把那1000个订单给查出来,手动发到mq里去再补一次。

mq消息队列块满了:如果消息积压在mq里,你很长时间都没有处理掉,此时导致mq都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。

快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的consumer机器来消费消息。

**MQ中消息失效:**假设你用的是RabbitMQ,RabbtiMQ是可以设置过期时间的,也就是 TTL。如果消息在queue中积压超过一定的时间就会被RabbitMQ给清理掉,这个数据就没了。那这就是第二个坑了。

这就不是说数据会大量积压在mq里,而是大量的数据会直接搞丢。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,
然后重新灌入mq里面去,把白天丢的数据给他补回来。也只能是这样了。假设1万个订单积压在mq里面,没有处理,其中 1000个订单都丢了,你只能手动写程序把那1000个订单给查出来,手动发到mq里去再补一次。

mq消息队列块满了:如果消息积压在mq里,你很长时间都没有处理掉,此时导致mq都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值