一、消息可靠性
由生产者发送信息到MQ,最终被消费者正常消费完成,这就是消息可靠。
(一)在生产者发送消息到消费者可能会信息丢失的情况
- 生产者连接不上MQ
- 可能因为网络原因,导致连接不上MQ
- 生产者发送消息未到达交换机
- 生产者发送消息到达交换机但没有找到正确的路由
- 消息发送到MQ但MQ宕机,消息丢失
- 消费者收到消息后未消费宕机
(二)如何保证信息不丢失?
1.生产者
生产者开启重试机制,保证消息正确到达MQ,如果重试次数使用完毕,将消息保存到数据库,交由人工处理
# 在生产者配置中添加如下内容开启重试策略
spring:
rabbitmq:
connection-timeout: 1s # 设置MQ的连接超时时间
template:
retry:
enabled: true # 开启超时重试机制
initial-interval: 1000ms # 失败后的初始等待时间
multiplier: 1 # 失败后下次的等待时长倍数,下次等待时长 = 上次等待时长 * multiplier
max-attempts: 3 # 总共尝试次数
- 开启ConfirmCallback回调机制,每个生产者独立配置,保证消息到达交换机
- 到达交换机返回ack
- 未到达交换机返回nack
- 开启ReturnCallback回调机制,每个RabbitTemplate只能配置一个,没到队列失败就会直接回调,保证消息正确到达队列
2.持久化操作
在创建交换机、队列、消息时设置持久化,将消息存储到磁盘,保证MQ宕机不会丢失(默认就是持久化)
3.消费者
- 设置开启自动ack,并且设置本地重试策略,当重试策略用尽之后,失败的消息路由到错误队列
- 然后监听错误队列,将错误队列中的数据写入到数据库,交由人工处理
二、消息重复消费问题
同一个消息被多次消费有什么问题?
需要看消费者的操作是否是一个幂等性的操作
什么是幂等性?
- 多次执行同一个操作,最终的结果是一样的
- 对于幂等性的操作,除了性能的影响的,其他的不会有任何问题,可以忽略
对于非幂等性的操作,多次消费消息,会造成数据一致性的问题,所以要保证重复消费的问题
- 数据库唯一约束
- 使用Redis来实现
- 生产者发送消息时候,设置消息ID
- 消费者先从Redis中获取消息ID,如果消息ID存在,则表示有消费者在消费消息,什么都不需要操作;如果不存在,表示此消息是第一次被消费,正常消费,并向Redis村粗key为消息ID的数据到Redis,同时设置过期的时间。
- 在业务的时候进行判断,有一个状态到另一个状态,必须要加条件是初始状态
- MQ可靠性保证并不是100%,所以又可能消费者没有收到MQ的消息,那么后面将不能执行,这样就需要主动发起查询去获取最新的消息,然后继续完成后续操作
三、保证消息消费顺序
多个消费者如何保证顺序消费?
- 消费者指定线程消费数为1
- MQ增加队列属性为x-single-active-consumer为true,表示当前队列,只能同时有一个消费者消费消息
四、消费积压问题
(一)产生的原因
由于生产者生产的速度远高于消费者消费消息的速度,这样就会导致消息积压在MQ
(二)解决方案
采用惰性队列
- 提升消息的存储能力
- 可以将消息持久化,先存入磁盘,后面在慢慢读取消费,来缓解队列消息积压的压力
(三)分析为什么会有消息积压
一般大概率都是消费者的原因,首先就要先修复消费者的代码,启动消费者程序,然后增加一些临时消费者程序节点,以倍速的形式去消费积压的消息,当积压的消息被处理的差不多的时候,在关闭临时消费者程序节点。
五、死信交换机
(一)死信交换机
1.概述
如果这个包含死信的队列配置了dead-letter-exchange属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机就会被称为死信交换机。
2.路由过程
- 消费者拒绝消费消息
- 队列绑定了死信交换机
- 死信队列有绑定的死信交换机
(二)成为死信的几种情况
- 消费者使用basic.reject 或basic.nack声明消费失败,并且消息的requeue参数为false
- 消息设置了过期时间,或者消息存放的队列设置了过期时间,超时无人消费
- 要投递的队列消费满了,无法投递
- 这些就是死信,然后会通过路由规则经过交换机路由到一个队列,这个交换机就是死信交换机,队列就是死信队列
(三)TTL
超时未消费,消息变成死信的情况
-
消息所在的队列设置了超时时间
-
消息本身设置了超时时间
如果两个都设置,一时间短的为先
(四)延迟消息
1.概述
-
是一种实现消费者延迟收到消息的模式
-
在RabbittMQ中,没有延迟队列的功能,可以使用TTL+死信队列的方式实现延迟队列
2.实现
-
声明死信交换机和死信队列(注解方式)
2. 声明普通交换机和普通队列,绑定关系(普通队列要把消息投递到私信交换机)
3. 发送消息
不指定消息的过期时间
-
指定消息的过期时间
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主动把消息推送给消费者
- 消息阅后即焚