MQ高级

55 阅读6分钟

一、消息可靠性

由生产者发送信息到MQ,最终被消费者正常消费完成,这就是消息可靠。

(一)在生产者发送消息到消费者可能会信息丢失的情况

  1. 生产者连接不上MQ
    • 可能因为网络原因,导致连接不上MQ
  2. 生产者发送消息未到达交换机
  3. 生产者发送消息到达交换机但没有找到正确的路由
  4. 消息发送到MQ但MQ宕机,消息丢失
  5. 消费者收到消息后未消费宕机

(二)如何保证信息不丢失?

1.生产者

生产者开启重试机制,保证消息正确到达MQ,如果重试次数使用完毕,将消息保存到数据库,交由人工处理

# 在生产者配置中添加如下内容开启重试策略
spring:
  rabbitmq:
    connection-timeout: 1s # 设置MQ的连接超时时间
    template:
      retry:
        enabled: true # 开启超时重试机制
        initial-interval: 1000ms # 失败后的初始等待时间
        multiplier: 1 # 失败后下次的等待时长倍数,下次等待时长 = 上次等待时长 * multiplier
        max-attempts: 3 # 总共尝试次数
  1. 开启ConfirmCallback回调机制,每个生产者独立配置,保证消息到达交换机
    • 到达交换机返回ack
    • 未到达交换机返回nack
  2. 开启ReturnCallback回调机制,每个RabbitTemplate只能配置一个,没到队列失败就会直接回调,保证消息正确到达队列
2.持久化操作

在创建交换机、队列、消息时设置持久化,将消息存储到磁盘,保证MQ宕机不会丢失(默认就是持久化)

3.消费者
  • 设置开启自动ack,并且设置本地重试策略,当重试策略用尽之后,失败的消息路由到错误队列
  • 然后监听错误队列,将错误队列中的数据写入到数据库,交由人工处理

image.png

二、消息重复消费问题

同一个消息被多次消费有什么问题?

需要看消费者的操作是否是一个幂等性的操作

什么是幂等性?
  • 多次执行同一个操作,最终的结果是一样的
  • 对于幂等性的操作,除了性能的影响的,其他的不会有任何问题,可以忽略
对于非幂等性的操作,多次消费消息,会造成数据一致性的问题,所以要保证重复消费的问题
  1. 数据库唯一约束
  2. 使用Redis来实现
    • 生产者发送消息时候,设置消息ID
    • 消费者先从Redis中获取消息ID,如果消息ID存在,则表示有消费者在消费消息,什么都不需要操作;如果不存在,表示此消息是第一次被消费,正常消费,并向Redis村粗key为消息ID的数据到Redis,同时设置过期的时间。
  3. 在业务的时候进行判断,有一个状态到另一个状态,必须要加条件是初始状态
  4. MQ可靠性保证并不是100%,所以又可能消费者没有收到MQ的消息,那么后面将不能执行,这样就需要主动发起查询去获取最新的消息,然后继续完成后续操作

三、保证消息消费顺序

多个消费者如何保证顺序消费?
  1. 消费者指定线程消费数为1
  2. MQ增加队列属性为x-single-active-consumer为true,表示当前队列,只能同时有一个消费者消费消息

四、消费积压问题

(一)产生的原因

由于生产者生产的速度远高于消费者消费消息的速度,这样就会导致消息积压在MQ

(二)解决方案

采用惰性队列

  • 提升消息的存储能力
  • 可以将消息持久化,先存入磁盘,后面在慢慢读取消费,来缓解队列消息积压的压力
(三)分析为什么会有消息积压

一般大概率都是消费者的原因,首先就要先修复消费者的代码,启动消费者程序,然后增加一些临时消费者程序节点,以倍速的形式去消费积压的消息,当积压的消息被处理的差不多的时候,在关闭临时消费者程序节点。

五、死信交换机

(一)死信交换机
1.概述

如果这个包含死信的队列配置了dead-letter-exchange属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机就会被称为死信交换机。

2.路由过程
  • 消费者拒绝消费消息
  • 队列绑定了死信交换机
  • 死信队列有绑定的死信交换机
(二)成为死信的几种情况
  1. 消费者使用basic.reject 或basic.nack声明消费失败,并且消息的requeue参数为false
  2. 消息设置了过期时间,或者消息存放的队列设置了过期时间,超时无人消费
  3. 要投递的队列消费满了,无法投递
  4. 这些就是死信,然后会通过路由规则经过交换机路由到一个队列,这个交换机就是死信交换机,队列就是死信队列
(三)TTL

超时未消费,消息变成死信的情况

  1. 消息所在的队列设置了超时时间

  2. 消息本身设置了超时时间

    如果两个都设置,一时间短的为先

(四)延迟消息
1.概述
  • 是一种实现消费者延迟收到消息的模式

  • 在RabbittMQ中,没有延迟队列的功能,可以使用TTL+死信队列的方式实现延迟队列

2.实现
  1. 声明死信交换机和死信队列(注解方式)

image.png 2. 声明普通交换机和普通队列,绑定关系(普通队列要把消息投递到私信交换机)

image.png 3. 发送消息

不指定消息的过期时间

-image.png

指定消息的过期时间

image.png

3.采用DelayExchange插件

就是将一个交换机声明为delayed类型

  • 执行过程
    • 接收消息
    • 判断消息是否具备x-delay属性
    • 如果有x-delay属性,说明是延迟消息,持久化到硬盘,读取x-delay值,作为延迟时间
    • 返回routing not found结果给消息发送者
    • x-delay时间到期后,重新投递消息到指定队列
(五)RabbitMQ如何实现延迟队列?
死信队列 + TTL
  • 由于队列的原因,会先消费队首的消息,如果后续的消息消费的时间比队首的要早,会导致消费无法消费。
装一个Delay插件
  • 没有队列,不会出现无法消费的问题

六、与Kafka的区别

(一)Kafka

1、优点

  • 吞吐量大
  • 功能强大:处理流媒体

2、缺点

  • 时效性差:消息延迟时间比MQ长

3、特点

  • 重Topic:收发消息都必须指定Topic
  • 拉模式:由消费者主动去Broker中拉取消息
  • 消息消费完后,不会删除,保留历史消息

(二)RabbitMQ

1、优点

  • 时效性强、可靠性高

2、缺点

  • 吞吐量比Kafka小

3、特点

  • 轻Topic:不同工作模式可以选择是否使用Topic
  • 推模式:由Broker主动把消息推送给消费者
  • 消息阅后即焚