RabbitMQ

序号内容
1基础面试题
2JVM面试题
3多线程面试题
4MySql面试题
5集合容器面试题
6设计模式面试题
7分布式面试题
8Spring面试题
9SpringBoot面试题
10SpringCloud面试题
11Redis面试题
12RabbitMQ面试题
13ES面试题
14Nginx、Cancal
15Mybatis面试题
16消息队列面试题
17网络面试题
18Linux、Kubenetes面试题
19Netty面试题

RabbitMq

生产者

TCP长连接connection

相当于连接池。channel相当于连接池中的连接。

虚拟通道channel

生产者产生消息,通过channel将消息发送到交换机(exchange),交换机(exchange)转到队列(queue)。消费者绑定指定的队列(queue)接收消息。

中间件(broker)

消息队列的服务器实体。是一个进程,可以有多个虚拟主机(virtual host)

例:broker相当于mysql服务器,负责运行服务器上的数据库。虚拟主机(virtual host)相对于数据库,queue相当于数据库中的表,queue中的消息相当于数据库表中的数据。

生产者发送消息

虚拟主机(virtual host)

一个逻辑隔离概念,允许一个服务器上有多个虚拟主机。如果没有虚拟主机,RabbitMQ服务器上仍会存在exchange 和 queue,它们可以在默认的虚拟主机“/”下运行。

交换机(exchange)

交换机不存储消息。生产者不会将消息直接发送到队列,而是先将消息发送到交换机中,再有交换机转发到具体的队列,队列再将消息以推送或者拉去的方式给消费者进行消费。

直连交换机(direct exchange)

将消息发送到binding的key对应的routing key 相同的队列上。发送的时候通过routing key 找到对应的queue。将消息发送到指定的queue中。

同一个routing key 可以绑定到不同的queue上,这样同一条消息可以推送到不同的queue。

如果routing key多的时候就不适合使用直连交换机了。

主题交换机

主题交换机可以更好的基于多个标准的路由发送消息给指定队列。

路由规则:routing key 必须是单词列表,单词之间已“.”号分割。使用通配符匹配,“#” 和 “*”。

#:代表匹配多个单词,用来表示任意数量的单词,可以是一个 或者多个。例如:beijing.# 可以匹配 beijing.queue.test,beijing.hebei.tianjin 中间 用”.“隔开。

星号:代表可以匹配一个单词。只能有一个,用“.”隔开。例如:beijing.* 可以匹配 beijing.tianjin ,beijing.hebei 。

头部交换机

类似主题交换机,但是头交换机使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。
此交换机有个重要参数:”x-match”

当”x-match”为“any”时,消息头的任意一个值被匹配就可以满足条件
当”x-match”设置为“all”的时候,就需要消息头的所有值都匹配成功

扇形交换机(Fanout exchange)

基本交换机类型,不需要绑定路由建(route key)。它做的事情是广播消息,会把接收到的消息全部发送给绑定在自己身上的队列。因为广播不需要“思考”,所以扇形交换机的处理消息的速度也是最快的。

@Configuration  
public class RabbitMqConfig {     
    @Bean  
    Queue queue() {  
        return new Queue("hello.queue", true); // 持久化队列  
    }  
    @Bean  
    DirectExchange exchange() {  
        return new DirectExchange("hello.exchange");  
    }  
  	// 交换机绑定queue,并指定路由到哪个queue的routing key
    @Bean  
    Binding binding(Queue queue, DirectExchange exchange) {  
        return BindingBuilder.bind(queue).to(exchange).with("hello.key");  
    }  
}
@Service  
public class MessageService {  
  	@Autowired
    private RabbitTemplate rabbitTemplate;  
    // 发送消息的时候指定交换机和routingkey,消息就能发送到指定的队列,消费者监听指定的队列,就能获取到消息。
    public void sendMessage(String message) {  
        rabbitTemplate.convertAndSend("hello.exchange", "hello.key", message);  
    }  
}
队列Queue
死信队列(DLX:dead letter exchange)

死信队列又叫死信交换机,或者死信邮箱。

生产者生产消息,通过交换机将消息发送给队列。消息或者队列又设置超时时间,等过了超时时间后消息还没有被消费,这时候消息就进入死信交换机,在通过死信交换机转到死信队列。

在正常队列中设置参数“x-dead-lette-exchange” ,对应的值为 死信交换机的名称。例如:

@Bean
public Queue normalQueue(){
    Map<String,Object> argument = new HashMap<>();
    argument.put("x-message-ttl",5000);//队列的过期时间,设置为5秒
    rgument.put("x-dead-letter-wxchange","死信交换机名称");//设置队列消息过期后流向死信交换机
    rgument.put("x-dead-letter-routing-key","error");//这里设置的死信交换机连接死信队列的key必须要和初始化死信binding(绑定死信交换机和死信队列)的时候的routing key 一致。否则可能会导致找不到对应的死信队列。
    argument.put("x-max-length",5);//正常队列的最大长度,如果发送超过5条消息,则先进队列的消息先出队列,超几条出几条。队列的先进先出。
    return QueueBuilder.durable("正常队列名称").withArgument(argument).build();
}
生产者
交换机
队列
死信交换机
死信队列
消费者
延迟队列

rabbitmq 本身并不支持延迟队列,可以使用TTL(消息过期)和DLX(死信队列)的方式来实现消息的延迟投递。

把DLX(死信交换机)和某个队列绑定,到了指定时间后消息过期,消息就会通过DLX(死信交换机)路由到这个队列,消费者就可以从这个队列取走消费。

延迟队列实现流程图

生成者
交换机
正常队列
是否超时
交换机
死信队列
消费者
结束消费

使用同一个交换机,交换机在把消息发送给正常队列时使用一个routing key。正常队列设配置死信交换机为发送消息的这个交换机,死信routing key 为另外一个key,把当前这个交换机当成死信交换机,新的routing key 流转到死信队列。通过死信队列转给消费者。

延迟队列时间不一致的问题

在同一个消息队列中,由于消息是先进先出的,只有等前面的消息出去后,后面的消息才能从队列中被消费。如果后面的消息的过期时间小于前面的消息的过期时间,后面的消息已经过期,但是由于前面的消息还堵在队列前面,导致后面的消息无法出对。只有等前面的消息被消费或者过期时间到了出对后,后面的消息才能出对。

解决方法

1、想通过期时间的消息发送到相同的队列上,不要在同一个队列放不同过期时间的消息。

2、使用延迟插件。插件名称:rabbitmq_delayed_message_exchange,装完插件后需要初始化一个延迟交换机。

@Bean
public CustomExchange customExchange(){
    Map<String,Object> argument = new HashMap<>();
    argument.put("x-delayed-type","direct");//必须要设置这个参数
    //type:x-delayed-message,durable(持久化): true,autoDeleted:false
    return new CustomExchange("交换机名称""x-delayed-message",true,false,argumrnt);
}

使用插件就不用再使用死信交换机和死信队列。消息发送后持久化到了rabbitmq自带的数据库(mnesia)中。

发送消息的时候是通过设置header实现的,不在设置过期时间。

MessageProperties messageProperties =  new MessageProperties();
messageProperties.setHeader("x-delay",15000);
Binding

exchange 和 queue的桥梁,负责这两个的连接。通过绑定键将交换机与队列关联起来, 这样RabbitMQ就知道如何正确地将消息路由到队列。

消费者

**消费者不确认消息:**正常队列接收消息,但是对消息不进行确认,并且不对消息进行重新投递。此时消息进入死信队列。

消息确认

消息确认模式分为三种,自动确认(node),手动确认(manual),根据情况确认(auto)。RabbitMQ默认开始自动确认。

自动确认:如果消息不太重要,丢失也没有影响,这时采用自动ACK确认。可以有高吞吐量,但是可能会导致消息丢失(收到消息后,出了异常了)

手动确认:在消息非常重要的时候,采用手动确认。可以解决消息丢失的问题

rabbitmq:
	listener:
		simple:
			acknowledge-mode: manual #手动确认 和 注解使用一个就行,不用同时开启
手动确认消息
@RabbitListener(queues = "TopicFirstQueue", ackMode = AckMode.MANUAL)
public void testMsgListener(Message message,Channel channel){
    //通过注解手动确认
    // 第一个参数deliveryTag表示消息ID;
    // 第二个参数为false表示仅确认当前消息,否则一次性确认 delivery_tag 小于等于传入值的所有消息
    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
  
}
手动不确认消息
	/*
    // 第一个参数deliveryTag表示消息ID;
    // 第二个参数表示是否针对多条消息,如果是true则一次性把当前通道消息的delivery_tag小于等于当前这条消息的都拒绝确认,否则只针对当前这条消息;
    // 第三个参数表示是否重新入列,如果是false,不会被重新发送到队列继续等待消费,进入死信队列。
    // 需要配置死信交换机和设置x-deal-letter-routing-key 才能进入到死信交换机,进而进入到死信队列。
     */
    channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
拒绝消息
  /*
    // 第一个参数deliveryTag表示消息ID;
    // 第二个参数为true表示是否重新进入队列,如果是true则重新丢回队列里等待再次消费。不可以批量处理 
    // 如果没有配置死信交换机和死信队列,消息会被直接删除。
    // 在初始化队列的时候配置x-dead-letter-exchange和x-deal-letter-routing-key属性,消息被拒绝后会进入死信交换机。
    */
    channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);

过期消息TTL(Time To Live)

消息的过期时间,当消息达到存活时间后,还没有被消费,会自动被清除。可以对消息设置过期时间,也可以对队列设置过期时间。

消息可靠性

消息从生产者到达exchange会返回一个confirmCallback。

消息从exchange到达queue如果投递失败,会返回一个returnCallback,成功则不会调用。

confirm模式–确保消息的可靠性

生产者不知道消息是否真正到达broker。可以通过AMQP协议提供的事务机制实现消息确认,另一种是通过将channel设置为confirm模式确认消息。

需要先在配置文件配置开启确认配置

rabbit:
 publister-confirm-type: correlated #开启生产者确认模式,设置关联模式
实现方法
//在发送消息的类中实现确认,使用lambda表达实现
@Resource 
private RabbitTemplate rabbitTemplate;
//初始化comfirm
@PostConstruce 
public void init(){
    rabbitTemplate.setConfirmCallback(
        //可以去掉类型。
     (CorrelationData correlationData,boolean ack,String cause) ->{
            log.info("关联ID:{}",correlationData.getId());
            if (ack) {
                log.info("消息发送成功");
                return;
            }
            log.error("消息发送失败:{}",cause);
            
        }
    );
}

return模式–确保消息的可靠性

rabbit:
 publister-returns: true #开启return模式
实现方式
//在发送消息的类中实现确认,使用lambda表达实现
@Resource 
private RabbitTemplate rabbitTemplate;
//初始化comfirm
@PostConstruce 
public void init(){
    rabbitTemplate.setReturnCallback(
        //可以去掉类型。
    	returnMwssage ->{
            log.error("消息投递失败:{}",returnMwssage.getReplayText());
        }
    );
}

备用交换机

备用交换机一般都是使用Fanout(扇形)交换机。

作用:在消息无法被路由到任何一个队列的时候,将消息发送到备用交换机指定的队列中。可以用来处理一些异常消息,避免消息丢失,处理未被路由的消息。

@Bean
public DirectExchange orExchange() {
    Map<String, Object> args = new HashMap<>();
    args.put("alternate-exchange", "备用交换机名称");
    return ExchangeBuilder.directExchange(ORDINARY_EXCHANGE).withArguments(args).build();
}

消息、交换机、队列持久化

//消息 消息配置默认持久化。
MessageProperties mp = new MessageProperties();
//交换机 初始化配置中设置交换机durable属性
ExchangeBuilder.directExchange("交换机名称").durable(true).build();
//队列 初始化配置中设置队列的durable属性为true
QueueBuilder.durable(true).build();

消息重复

幂等

通过使用redis分布式锁解决幂等问题。

RabbitMQ集群(高可用)

RabbitMQ中有三种模式,单机模式,普通集群模式,镜像集群模式。

单机模式:机器上启动一个RabbitMQ

普通集群模式:多台机器上启动多个RabbitMQ实例,每个机器启动一个。不同节点存储不同的数据,每个节点只负责自己的队列和消息。当消费者连接到集群时,它会连接到其中一个节点,并从该节点获取信息。如果这个节点没有想要的信息,这个节点会和其他节点通讯,以获取消息,或者重定向到有消费者需要的消息的节点。

镜像集群模式

镜像模式:把所有的队列数据完全同步,包括元数据信息和消息数据信息。对新能有一定影响,可靠性比较强。

集群中一般会包含一个主节点master和若干个从节点slave,如果master由于某种原因失效,那么按照slave加入的时间排序,"资历最老"的slave会被提升为新的master。镜像队列下,所有的消息只会向master发送,再由master将命令的执行结果广播给slave,所以master与slave节点的状态是相同的。如果消费者与slave建立了TCP连接并执行Basic.Get的操作,那么也是由slave将Basic.Get请求发往master,再由master准备好数据返回给slave,最后由slave投递给消费者。、

缺点

性能开销大,消息需要同步到所有机器上,导致网络带宽压力和消耗很重

非分布式,没有扩展性,如果 queue 的数据量大到这个机器上的容量无法容纳了,此时该方案就会出现问题了

为什么使用RabbitMQ

延时低,微妙级延时,社区活跃度高,bug 修复及时,而且提供了很友善的后台界面。

实现消费者和生产者之间的解耦

拥有持久化的机制,进程消息,队列中的信息也可以保存下来

在分布式系统下具备异步,削峰,负载均衡等一系列高级功能;

对AMQP协议的理解

RabbitMQ就是AMQP协议是erlang的实现,AMQP 的模型架构 和 RabbitMQ 的模型架构是一样的,生产者将消息发送给交换器,交换器和队列绑定。

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

AMQP协议3层

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

一条消息的流程

生产者发送消息

  • 生产者首先与RabbitMQ服务器建立TCP连接。

  • 在连接建立后,生产者开启一个通信信道(Channel)。

  • 生产者声明一个Exchange(交换器),并设置相关属性,如交换器类型、是否持久化等。

  • 生产者接着声明一个队列,并设置相关属性,如是否排他、是否持久化、是否自动删除等。

  • 生产者使用bindingKey(绑定键)将交换器和队列进行绑定。这样,当交换器接收到消息时,可以根据bindingKey将消息路由到相应的队列。

  • 生产者发送消息至RabbitMQ的Broker,消息中包含routingKey(路由键)和交换器等信息。

  • 相应的交换器根据接收到的routingKey查找相匹配的队列。如果找到匹配的队列,则将消息存入该队列中;如果没有找到,则根据生产者配置的属性选择丢弃消息或回退给生产者。

消费者接收消息

  • 消费者与RabbitMQ服务器建立连接,并创建通信信道。

  • 消费者声明需要监听的队列,并定义消费行为,如处理接收到的消息。

  • 消费者使用信道将自己与队列进行绑定,这样当队列中有新消息时,消费者可以接收到这些消息。

  • 消费者从队列中拉取消息,并进行处理。

消息如何路由(routing)

生产者在发布消息时,它需要在某个交换机中填写routing key和消息内容。交换机在接收到消息后,根据绑定的规则,来决定将消息发送到哪个队列。

常用的交换器主要分为以下三种广播模式

  • 在单播模式下,交换机根据Routing Key把消息派发到一个指定的队列。
  • 在广播模式下,交换机不考虑Routing Key,而是将消息派发给所有绑定了该交换机的队列。
  • 在topic模式下,交换机根据Routing Key的规则匹配,有选择性地进行广播,即只将消息派发给那些Routing Key与规则匹配的队列。

消费者连接一个或者多个queue,并从queue中获取消息。当消费者连接到队列时,它会从队列中拉取特定的routing key 匹配的消息。

消息传输模式

  • 简单模式:是最简单的消息模式,它包含一个生产者、一个消费者和一个队列。生产者向队列里发送消息,消费者从队列中获取消息并消费。
  • 工作模式:是指向多个互相竞争的消费者发送消息的模式,它包含一个生产者、两个消费者和一个队列。两个消费者同时绑定到一个队列上去,当消费者获取消息处理耗时任务时,空闲的消费者从队列中获取并消费消息。
  • 发布/订阅模式:是指同时向多个消费者消费消息的模式(类似广播的形式),它包含一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的队列上去,两个队列绑定到交换机上去,生产者通过发送消息到交换机,所有消费者接收并消费消息。
  • 路由模式:是可以根据路由键选择性给多个消费者发送消息的模式,它包含一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的队列上去,两个队列通过绑定键绑定到交换机上去,生产者发送消息到交换机,交换机通过路由键转发到不同队列,队列绑定的消费者接收并消费消息。
  • 通配符模式: 是绑定键是某种泛泛的符号规则,如果传过来的消息路由键与绑定键匹配,就能传递到对应的消息队列上。特殊匹配符号 *:只能匹配一个单词;
    #:可以匹配零个或多个单词。

消息分发策略

  • 轮询分发:默认采用的轮训分发。当有两个消费者在处理任务时,其中有个消费者 处理任务的速度非常快,而另外一个消费者处理速度却很慢,这个时候我们还是采用轮训分发就会导致这处理速度快的这个消费者很大一部分时间处于空闲状态。
  • 不公平分发:通过设置参数 channel.basicQos(1);实现不公平分发策略使得能者多劳;
  • 预值分发:当消息被消费者接收后,但是没有确认,此时这里就存在一个未确认的消息缓冲区,用于存储非被确认的消息,该缓存区的大小是没有限制的。

消息的持久化

RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外crash掉,消息就会丢失。

当服务器重启后,RabbitMQ会经历一个启动和恢复过程。在这个过程中,RabbitMQ会检查磁盘上的持久化数据,包括之前创建的持久化队列。根据这些数据,RabbitMQ会重新创建这些队列和交换机,并恢复其状态,包括队列中的消息。

队列持久化

当声明队列时,通过将durable参数设置为true,可以确保队列在RabbitMQ服务重启后仍然存在,从而避免队列和其中的消息丢失。

是将队列及其数据保存到磁盘,以实现数据的持久保存。

消息持久化

发送消息时,通过设置发送模式为deliveryMode=2,可以将消息标记为持久化。为了确保消息在成功发送到队列后才从内存中删除,可以使用手动ACK()机制,这样能避免消息在处理过程中丢失。

息持久化是将消息保存到磁盘,每条消息在持久化时都会被赋予一个唯一的标识,这个标识可以帮助RabbitMQ在需要时从磁盘上准确地找到并恢复这条消息。当消费者请求消息时,RabbitMQ会根据消费者所连接的队列和相关的订阅规则,从磁盘上读取相应的持久化消息,并将其交付给消费者。

交换器持久化

在声明交换器时,通过将durable参数设置为true、

是将交换器保存到磁盘,以实现数据的持久保存。

可以对所有消息都持久化吗

不可以
持久化的操作是将数据写入磁盘中,效率上肯定比写入内存中要慢很多倍.而我们一般用mq会处理很多业务消息,若是所有消息都持久化,压力无疑是巨大的.所以持久化策略需要综合考虑,以及可能遇到的问题和解决方案,或者我们可以让一些必要数据持久化.

消息的应答机制(消息确认 ACK)

消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉,由于RabbitMQ 一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息,以及后续发送给该消费者的消息也无法接收到。

消息确认模式分为,自动确认(node),手动确认(manual)。RabbitMQ默认开始自动确认。

自动确认:如果消息不太重要,丢失也没有影响,这时采用自动ACK确认。可以有高吞吐量,但是可能会导致消息丢失(收到消息后,出了异常了)

手动确认:在消息非常重要的时候,采用手动确认。可以解决消息丢失的问题

发送方确认发送成功:

@Service  
public class MessagingService {  
    @Autowired  
    private final RabbitTemplate rabbitTemplate;  
  
    @Autowired  
    public MessagingService(RabbitTemplate rabbitTemplate) {  
      
        // 设置发送方确认回调  
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {  
            if (ack) {  
                System.out.println("Message sent successfully: " + correlationData.getId());  
            } else {  
                System.out.println("Failed to send message: " + correlationData.getId() + ", cause: " + cause);  
            }  
        });  
    }  
  
    public void sendMessage(String exchange, String routingKey, String message) {  
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());  
        rabbitTemplate.convertAndSend(exchange, routingKey, message, correlationData);  
    }  
}

接收方确认接收成功:

@Service  
public class MessageReceiver {  
    @RabbitListener(queues = "your.queue.name")  
    public void receiveMessage(String message, Message messageAmqp) throws Exception {  
        try {  
            // 处理消息...  
            System.out.println("Received message: " + message);  
            // 手动确认消息  
            messageAmqp.getMessageProperties().getDeliveryTag();  
            Channel channel = messageAmqp.getMessageProperties().getChannel();  
            long deliveryTag = messageAmqp.getMessageProperties().getDeliveryTag();  
            channel.basicAck(deliveryTag, false);  
        } catch (Exception e) {  
            // 处理异常...  
            // 可能需要重新入队或记录错误  
            throw e;  
        }  
    }  
}

对死信队列的理解

死信队列,绑定死信交换机的队列就称之为死信队列,当消息成为死信消息后,如果配置了死信队列消息,那么该消息就会被丢进死信队列中,否则被丢弃

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

如何成死信队列

  • 消息被消费者reject或者返回nack。
  • 消息超时未消费
  • 消息队列中的消息已经超过最大队列长度。

如何设置超时属性

  • 给队列设置ttl属性,进入队列后超过ttl时间的消息变为死信
  • 给消息设置ttl属性,队列接收到消息超过ttl时间后变为死信
  • 两者共存时,以时间短的ttl为准

如何保证消息可靠性

1、保证消息不被重复消费

2、保证消息不丢失

  • 发送方丢失:发送方在发送消息的过程中,出现网络问题等导致mq接收不到消息,导致了消息丢失。以采用事务机制,在发送消息的时候实现事务机制,若是出现发送失败的情况,可以进行回滚,而让消息重新被发送。消息多了会导致性能下降。另外就是采用消息确认机制,发送方在发送消息的时候必须要保证要收到一个确认消息,如果没有收到或者收到失败的确认消息,就说明消息发送失败,要重新进行发送,确认机制是可以采用异步进行的,
  • mq方丢失:如果mq没有进行持久化,出现了宕机关机等情况,消息就会丢失。持久化消息或者队列,交换机。
  • 消费方丢失:采用确认机制。

某些原因导致消费者消费消息的时间很长,超出了mq的等待时间。这时候mq会在发送一次消息,导致重复消费。

保证消息不丢失可能会产生幂等问题,可以通过redis分布式锁解决幂等问题。

如何保证消息不被重复消费

让每个消息携带一个全局的唯一ID,即可保证消息的幂等性,具体消费过程为:

消费者获取到消息后先根据id去查询库中查询是否存在该消息

如果不存在,则正常消费,消费完毕后把该消息写入数据库

如果存在,则证明消息被消费过,直接丢弃

如何解决消息堆积的问题

消息堆积往往是生产者的生产速度与消费者的消费速度不匹配导致的。有可能就是消费者消费能力弱,渐渐地消息就积压了,也有可能是因为消息消费失败反复复重试造成的,也有可能是消费端出了问题,导致不消费了或者消费极其慢。比如,消费端每次消费之后要写mysql,结果mysql挂了,消费端hang住了不动了,或者消费者本地依赖的一个东西挂了,导致消费者挂了。

所以如果是 bug 则处理 bug;如果是因为本身消费能力较弱,则优化消费逻辑,比如优化前是一条一条消息消费处理的,那么就可以批量处理进行优化。

临时扩容,快速处理积压的消息:

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

(2)临时创建原先 N 倍数量的 queue ,然后写一个临时分发数据的消费者程序,将该程序部署上去消费队列中积压的数据,消费之后不做任何耗时处理,直接均匀轮询写入临时建立好的 N 倍数量的 queue 中;

(3)接着,临时征用 N 倍的机器来部署 consumer,每个 consumer 消费一个临时 queue 的数据

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

这种做法相当于临时将 queue 资源和 consumer 资源扩大 N 倍,以正常 N 倍速度消费。

MQ长时间未处理导致MQ写满的情况如何处理:

如果消息积压在MQ里,并且长时间都没处理掉,导致MQ都快写满了,这种情况肯定是临时扩容方案执行太慢,这种时候只好采用 “丢弃+批量重导” 的方式来解决了。首先,临时写个程序,连接到mq里面消费数据,消费一个丢弃一个,快速消费掉积压的消息,降低MQ的压力,然后在流量低峰期时去手动查询重导丢失的这部分数据

如何保证消息的顺序消费

1、将原来的一个queue拆分成多个queue,每个queue都有一个自己的consumer。该种方案的核心是生产者在投递消息的时候根据业务数据关键值(例如订单ID哈希值对订单队列数取模)来将需要保证先后顺序的同一类数据(同一个订单的数据) 发送到同一个queue当中。

2、个queue就一个consumer,在consumer中维护多个内存队列,根据业务数据关键值(例如订单ID哈希值对内存队列数取模)将消息加入到不同的内存队列中,然后多个真正负责处理消息的线程去各自对应的内存队列当中获取消息进行消费。

消息的事务

当消息的发布者在将消息发送出去之后,消息到底有没有正确到达broker代理服务器呢?如果不进行特殊配置的话,默认情况下发布操作是不会返回任何信息给生产者的,也就是默认情况下我们的生产者是不知道消息有没有正确到达broker的,如果在消息到达broker之前已经丢失的话,持久化操作也解决不了这个问题,因为消息根本就没到达代理服务器。

1、确认机制

通过AMQP事务机制实现,这也是AMQP协议层面提供的解决方案;通过将channel设置成confirm模式来实现;

2、事务

bbitMQ中与事务机制有关的方法有三个:txSelect(), txCommit()以及txRollback(), txSelect用于将当前channel设置成transaction模式,txCommit用于提交事务,txRollback用于回滚事务,在通过txSelect开启事务之后,我们便可以发布消息给broker代理服务器了,如果txCommit提交成功了,则消息一定到达了broker了,如果在txCommit执行之前broker异常崩溃或者由于其他原因抛出异常,这个时候我们便可以捕获异常通过txRollback回滚事务了。

消息确认机制和返回机制有什么区别

confirm模式和return模式的区别

消息确认:监听生产者的消息是否已经发送到了 RabbitMQ消息队列中;如果消息没有被发送到 RabbitMQ消息队列中,则消息确认机制不会给生产端返回任何确认应答,也就是没有发送成功。相反,如果消息被成功发送到了 RabbitMQ消息队列中,则消息确认机制会给生产端返回一个确认应答,以通知生产者,消息已经发送到了 RabbitMQ消息队列。
返回模式:监听生产端发动到RabbitMQ消息队列中的消息是否可达,如果消息不可达,则返回一个信号通知生产端,相反,如果消息可达,则不会返回任何信号

RabbitMQ的架构

生成者,TCP连接(channel),borker(virtual host(exchange,queue)),消费者

你如何设计一个MQ

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值