RabbitMQ相关问题(1)

目录

一、可靠性传输

二、死信队列

1、死信队列概念:

2、死信来源

3、死信队列应用场景

三、延迟队列

1、概念:

2、应用场景:

3、实现方式:

四、幂等性问题

1、幂等性概念:

2、MQ的幂等性

3、保证幂等性的方案

五、顺序性保证

1、顺序性保证介绍

2、RabbitMQ默认难以保证顺序性

3、顺序性保证方案

六、消息积压问题

1、消息积压概念:

2、常见原因分析

3、解决方案

一、可靠性传输

可以从以下几个方面考虑

1、生产者------Broker

可能原因:网络问题等

解决办法:

producer------exchange:发送方确认-confirm模式(消息接收成功ack=true,消息接受失败ack=false)

2、Exchange------Queue

可能原因:代码或者配置错误,导致消息路由失败

解决办法:发送发确认-returns模式(出现错误退回给发送者,并且附带cause)

3、队列溢出:死信等

4、Broker内部出错

可能原因:消息到达RabbitMQ之后,RabbitMQ Server宕机导致消息丢失

解决办法:持久化操作

(1)交换机持久化

(2)队列持久化

(3)消息持久化

:消息持久化必须和队列持久化搭配,如果队列非持久化,那么就算消息是持久化的,消息也会丢失,只有队列和消息同时都是持久化,消息才不会丢失
1.交换机:持久化:durable(true) 数据不丢失

非持久化:durable(false) 数据丢失

2.队列持久化(队列不丢失):

消息持久化:durable(Constant.PRES_QUEUE)

MessageDeliveryMode.PERSISTENT 消息不丢失

消息非持久化:durable(Constant.PRES_QUEUE)

MessageDeliveryMode.NON_PERSISTENT 消息丢失

3.队列非持久化(队列元数据丢失):

消息持久化:nonDurable(Constant.PRES_QUEUE)

MessageDeliveryMode.PERSISTENT 消息丢失

消息非持久化:nonDurable(Constant.PRES_QUEUE)

MessageDeliveryMode.NON_PERSISTENT 消息丢失

5、消费者异常,导致消息丢失

可能原因:消息到达消费者,还没来得及消费,消费者宕机、消费者逻辑有问题

解决办法:消息确认(默认是自动确认,可以开启手动确认避免消息丢失)、重试机制

(1)自动确认

(2)手动确认

二、死信队列

1、死信队列概念:

死信队列是存储死信消息的队列。

死信(Dead Letter)是消息队列中一种特殊消息,他指的是无法被正常消费或处理的消息。

2、死信来源

(1)消息过期:消息在队列中存活时间超过了设定的TTL

eg:假如我们设置消息存活时间为10s,向normal_queue队列发送消息

等待10s之后观察到,消息进入到了死信队列

(2)消息被拒绝:消费者处理消息的时候,会因为一些原因(消息内容错误、处理逻辑异常)拒绝处理该消息,如果拒绝时指定不重新入队的话(requeue=false),就会变成死信消息

java 复制代码
public class DlxQueueListener {
    //指定监听的队列
    @RabbitListener(queues= Constant.NORMAL_QUEUE)
    public void listener(Message message, Channel channel) throws IOException {
        long deliveryTag=message.getMessageProperties().getDeliveryTag();
        try {
            System.out.printf("接收到消息:%s,deliveryTag: %d%n",
                    new String(message.getBody(),"UTF-8"),message.getMessageProperties().getDeliveryTag());
            int num = 3/0;
            System.out.println("处理完成");
            //手动确认,
            //basicAck需要两个参数,deliveryTag和multiple,是否批量
            channel.basicAck(deliveryTag,false);
        } catch (IOException e) {
            //出现异常就否定确认,三个参数,第三个是否重新入队,如果第三个参数设置为false,就会变成死信
            channel.basicNack(deliveryTag,false,false);
        }
    }
    //然后监听一下死信队列,查看死信队列到底能否收到这个消息
    @RabbitListener(queues = Constant.DL_QUEUE)
    public void dlListener(Message message, Channel channel) throws UnsupportedEncodingException {
        System.out.printf("接收到消息:%s,deliveryTag: %d%n",
                new String(message.getBody(),"UTF-8"),message.getMessageProperties().getDeliveryTag());
    }

}

(3)队列满了:队列达到最大长度,无法容纳新消息时,新来的消息会被处理成死信

eg:我们设置队列最大长度为10L,向队列中发送消息超过10条消息,可以看到超过队列长度的消息都进入了死信队列

3、死信队列应用场景

异常情况下,消息进入死信队列,应用程序可以在启动一个消费者,去消费死信队列里面的内容来分析当时遇到的异常情况,进而改善和优化系统。

消息重试:将死信消息重新发送到原队列或另一个队列重试处理;

消息丢弃:直接丢弃这些无法处理的消息,以免占用资源;

日志收集:将死信消息作为日志收集起来,用于后续分析和问题定位;

三、延迟队列

1、概念:

延迟队列是指:消息发送之后,并不会立即给消费者,而是等待特定的时间,才发送给消费者。

2、应用场景:

(1)订单在规定时间内未支付会自动取消;

(2)用户注册之后,3天后发送调查问卷;

(3)用户发起退款,24小时之后商家未处理,则默认同意,自动退款

3、实现方式:

(1)TTL+死信队列实现:

优点:灵活不需要安装插件即可使用

缺点:存在消息顺序问题(比如先发送30s的TTL消息,在发送10s的TTL消息,那么10s的TTL消息,会在30s过期之后才进行处理);需要额外的逻辑来处理死信队列的消息,增加系统的复杂性

(2)基于插件实现的延迟队列

优点:通过插件可以直接创建延迟队列,简化延迟消息的实现;避免了DLX的时序问题

缺点:需要依赖特定的插件,有运维工作;只适用特定版本

四、幂等性问题

1、幂等性概念:

简单来说,幂等性就是一个操作,无论你执行多少次,产生的影响和只执行一次的影响是一样的。

举个例子:

select操作 :select执行一次或者多次对数据状态的最终影响都是一样的,**这里要注意,select操作的查询结果可能不一样,因为上一次查询和下一次查询之间如果对数据进行别的操作,那返回的结果也不一样,但是这个结果的不一样,不是select造成的,**所以select操作是幂等性的;

i++操作:i++是非幂等性的,因为每执行一次i++,都会使结果改变一次

2、MQ的幂等性

核心矛盾: RabbitMQ 为了确保消息绝不丢失,提供了 "至少一次" 的保障。这意味着,它可能会因为各种原因(如网络抖动、消费者处理成功但返回确认信号失败)把同一条消息发送多次给消费者。

一般消息中间件的消息传输保障分为三个层级:

最多一次:消息可能丢失,但绝不会重复传输

至少一次:消息绝不会丢失,但可能会重复传输

恰好一次:每条消息肯定会被传输一次且只传输一次

带来的问题:如果消费者处理的是一个非幂等操作(比如扣款、核销优惠券、减少库存),那么同一条消息被处理多次,就会造成严重事故(比如扣两次款)。

因此,MQ的幂等性指的是: 即使消费者收到了多条相同的消息,整个系统(尤其是数据库)的最终状态也只会被改变一次,就像只收到一条消息一样。

3、保证幂等性的方案

全局唯一ID(最常用、最有效):

(1)生产端:在生产者发送消息时,给每条消息加一个全局唯一的ID(比如UUID)

(2)消费端:消费者收到消息之后,在处理业务逻辑之前,先拿着这个ID查一下(可以在读写很快的数据库,如Redis上查询)

(3)判断处理:如果查不到ID,说明是新消息,就进行业务处理,处理完将这个ID标记为已处理(写入Redis中);如果查到这个ID在Redis已经存在,说明之前处理过,那就直接丢弃,不进行业务处理

使用Redis的setnx(key,value)来写入,因为这个操作具有原子性,只有key不存在的时候才可以设置成功

业务逻辑判断

这个方法不依赖额外的ID,而是在进行业务处理之前,先去数据库里面查询是否已经存在相关数据记录(和前面全局ID查询不同,这里查的是业务数据表,业务状态和约束)或者使用乐观锁机制避免更新已被其他事务更改的数据。

五、顺序性保证

1、顺序性保证介绍

一句话概括: 生产者按照顺序 消息A -> 消息B -> 消息C 发送消息,消费者也严格按照 消息A -> 消息B -> 消息C 的顺序进行消费和处理。

2、RabbitMQ默认难以保证顺序性

(1)多个消费者:这是打破顺序性的最主要原因。配置了多个消费者时,消息可能会被不同的消费者并行处理,从而导致消息处理的顺序性无法保证;

(2)网络波动:在消息传递过程中,如果出现网络波动或异常,可能会导致消息确认(ACK)丢失,从而使得消息被重新入队和重新消费,造成顺序性问题;

(3)消息重试:如果消费者在处理消息后未能及时发送确认,或者确认消息在传输过程中丢失,那么MQ可能会认为消息未被成功消费而进行重试,这也可能导致消息处理的顺序性问题.

(4)消息路由问题:在复杂的路由场景中,消息可能会根据路由键被发送到不同的队列,从而无法保证全局的顺序性;

(5)死信队列:消息因为某些原因(如消费端拒绝消息)被放入死信队列,死信队列被消费时,无法保证消息的顺序和生产者发送消息的顺序一致

3、顺序性保证方案

单队列单消费者:由于只有一个消费者,消息会严格按照FIFO(先进先出)的顺序被取出和处理,前一条消息处理完,后一条才会被处理。

优点:实现简单、保证顺序

缺点:吞吐量低,无法利用并发优势,容易成为系统瓶颈

适用场景:消息量不大,但对顺序要求极高的场景

分区消费:这是前一个方案的扩展,兼顾顺序性和吞吐量,将需要保证顺序的消息进行分区,确保同一组内消息由同一个消费者顺序处理(实现分区可以借助业务逻辑或者spring-cloud-stream 来实现)

消息确认机制:使用手动消息确认机制,消费者在统一处理完一条消息后,显式地发送确认,这样RabbitMQ才会移除并继续发送下一条消息

业务逻辑控制:当无法在MQ层面实现绝对顺序时,可能在消费端通过业务逻辑来保障最终结果的正确性,比如在消息中嵌入序列号,并在消费时根据信息来处理

六、消息积压问题

1、消息积压概念:

消息积压指的是:待处理消息的数量超过了消费者的处理能力,导致消息在RabbitMQ的队列中不断堆积,无法被及时消费的情况。

2、常见原因分析

消息生产过快:业务高峰期、定时任务集中出发、程序bug导致循环发送消息,这使得消息的入口流量瞬间或持续远高于系统处理能力

消费者处理能力不足

(1)业务逻辑复杂:消息处理中包含耗时的CPU密集计算或频繁的I/O操作,导致消费者跟不上生产者的速度;

(2)消费者实例不足:部署的消费者服务实例数量太少,无法对应上涨的流量;

(3)代码bug或异常处理不当:消费代码存在bug导致消息处理失败,从而触发无限重试,或者未设置合理的重试次数和死信队列,失败的消息始终堆积在主队列中

(4)资源瓶颈:消费者服务站所在的资源(CPU\内存、磁盘I/O、网络带宽)耗尽,导致服本身处理缓慢

网络问题:由于网络延迟或不稳定,消费者无法及时接收或确认消息,最终导致消息积压

3、解决方案

提高消费者效率

增加消费者实例数量;

优化业务逻辑;

设置prefetchCount,当一个消费者阻塞时,消息转发到其他未阻塞的消费者;

消息发生异常时,设置合适的重试机制,或者转入死信队列里面

限制生产者速率

流量控制:在消息生产者中实现流量控制逻辑,根据消费者处理能力动态调整发送频率;

限流:使用限流工具,为消息发送速率设置一个上限;

设置过期时间:如果消息过期未消费,可以配置死信队列,避免消息丢失

资源配置优化

升级服务器的硬件,调整RabbitMQ的配置参数等

相关推荐
海南java第二人4 小时前
Spring Boot Starters深度解析:简化依赖管理的核心利器
java·spring boot·后端
captain3764 小时前
Java-链表
java·开发语言·链表
tqs_123454 小时前
跳出多层循环的方式
java·开发语言
东方轧线4 小时前
突破锁竞争的性能枷锁:深度剖析 C++ 内存模型与无锁编程在超大规模并行 AI 系统中的极致应用实践
java·c++·人工智能
风清云淡_A5 小时前
【JPA】spring集成jpa实战之数据增删改查入门教程(二)
java
让我上个超影吧5 小时前
天机学堂——播放进度方案优化
java·spring boot·redis·spring cloud
月空MoonSky5 小时前
解决使用Notepad++出现异型字或者繁体字体问题
java·javascript·notepad++
Filotimo_5 小时前
JWT的概念
java·开发语言·python
min1811234565 小时前
软件升级全流程步骤详解
android·java·服务器