RabbitMQ 应用问题

目录

前言

一、幂等性保障

[1. 幂等性概念](#1. 幂等性概念)

[2. MQ 幂等性介绍](#2. MQ 幂等性介绍)

[3. 幂等性的解决方法](#3. 幂等性的解决方法)

二、顺序性保障

[1. 顺序性介绍](#1. 顺序性介绍)

[2. 解决方案](#2. 解决方案)

三、消息积压

[1. 原因分析](#1. 原因分析)

[2. 解决方案](#2. 解决方案)

四、仲裁队列

总结


前言

本文介绍 RabbitMQ 在实际应用中,可能出现的一些应用问题,如幂等性保障,顺序性保障,消息积压等;以及针对这些问题,一些典型的解决方案。


一、幂等性保障

1. 幂等性概念

在计算机领域中,幂等性指的是同一个接口,请求或者操作,发起一次或重复发起 N 次,对系统资源或者数据状态,造成的影响完全相同,不会产生脏数据,重复扣款或者重复创建等问题。

核心本质:重复执行,结果一致

技术价值:解决消息重复,重试带来的数据错乱问题;

MQ 消息消费幂等:同一条消息重复消费 N 次,业务只生效一次

2. MQ 幂等性介绍

通常消息中间件的消息传输保障分为 3 个等级:

  1. at most once:最多一次,消息有可能会丢失,但绝不可能重复传输;
  2. at least once:最少一次,消息绝不会丢失,但是可能重复传输;
  3. exactly once: 恰好一次,每条消息只会被传输一次;

RabbitMQ 支持最多一次和最少一次;

通常业务可靠性要求比较高时,会使用"最少一次",防止消息丢失。但是最少一次,就会存在消息重复传输的问题;

如果不对重复消息进行幂等性处理,有可能会造成严重的问题。比如用户付款后,由于网络问题,业务没有进行处理,用户重复点击后,再次进行了扣款。

3. 幂等性的解决方法

1. 全局唯一 ID

1)为每条消息分配一个唯一 ID,比如 UUID 或者 MQ 中的 deliveryId;

2)消费者收到消息后,判断该 Id 是否已经消费过;

3)如果未消费过,则开始消费消息,并保存唯一 ID;如果消费过,则放弃处理;

2. 业务逻辑判断

处理消息之前,先检查相关业务状态,确保对应的操作尚未执行,再进行消息的处理;

二、顺序性保障

1. 顺序性介绍

消息的顺序性指的是:消费者消费消息的顺序和生产者发送消息的顺序是一致的

有些业务场景,消费者消费消息是不需要保障顺序的;比如不同用户的操作,即使顺序不一致,也不影响业务处理的结果;

有些业务场景,可能存在多个消息顺序处理的场景;比如同一个用户的操作,顺序不一致,可能会影响结果;

RabbitMQ 如果一个生产者只有一个消费者,借助队列先进先出的特性,可以保障消息的顺序;

打破 RabbitMQ 顺序性的场景:

    1. 多个消费者:多个消费者处理消息的速度不相同,不能保证消息的顺序性;
    1. 网络波动:消息确认丢失,导致消息重新入队,重新消费;
    1. 消息重试:消费者处理消息后没有及时确认,导致消息重试;
    1. 消息路由:消息根据路由键不同,被路由到不同的队列;
    1. 死信队列:消息消费时出现异常,消息被路由到死信队列;

2. 解决方案

消息的顺序保障分为:局部消息顺序保障和全局消息顺序保障

局部顺序保障:在单个队列中保证顺序;

全局顺序保障:在多个队列和对各消费者之间保障顺序;

常用策略:

  1. 单队列单消费者:利用队列的先进先出特性,保证消息的顺序;

  2. 分区消费:单个消费者的吞吐太低,当需要多个消费者提高处理速度时,可以使用分区消费,把一个队列分割成多个分区:每个分区由一个消费者进行处理,保证分区内消息的顺序性;

  3. 消息确认:消息确认,消费者按照顺序进行确认,每处理完一个消息,按照消息顺序发送确认;

  4. 业务逻辑控制:消息可能会乱序达到,由业务逻辑实现顺序控制;

三、消息积压

消息积压指的是,消费者消费消息的速度小于生产者生产消息的速度,导致消息在队列中越来越多;

1. 原因分析

消息积压通常有一下几种原因:

  1. 消息生产过快:消息生产的速度超过了消费的速度;

  2. 消费者能力不足:消费者能力不足,如消费逻辑复杂耗时,消费代码性能不足,系统资源限制,异常处理不当等;

  3. 网络问题:由于网络问题,消费者无法及时接收或者确认消息;

  4. RabbitMQ 服务器配置偏低;

2. 解决方案

出现消息积压的情况,首先要分析消息积压的原因,根据原因调整策略;

1. 提高消费者效率

1)增加消费者数量

2)优化业务逻辑,使用多线程的解决方案;

3)设置 prefetchCount,当一个消费者阻塞,分发消息到另一个消费者;

4)消息消费异常时,重试或者转发到死信队列;

2. 限制生产者速率

1)在消息生产者中实现流量控制;

2)限流,给生产者发送速率设置上限;

3)设置过期时间:过期消息进入死信队列;

3. 升级 RabbitMQ 服务器的配置

四、仲裁队列

RabbitMQ 的仲裁队列是一种基于 Raft 一致性算法实现的持久化,复制的 FIFO 队列;

仲裁队列提供队列的复制能力,保障数据的高可用和安全性;

仲裁队列可以在 RabbitMQ 节点之间进行队列数据的复制,从而实现一个节点宕机,其它节点依然可以提供服务;

Raft 协议下的消息复制:

每个仲裁队列都有多个副本,包含一个主副本和多个从副本,每个副本都在不同的 RabbitMQ 节点上;

客户端只会与主副本进行交互,主副本再将这些命令复制到从副本。当主副本所在的节点下线,另外一个从副本就会被选举为主副本,继续提供服务;

消息复制和主副本选举的操作,需要半数以上的副本同意,当生产者发送一条消息,需超过半数的队列副本,将消息写入磁盘以后,才会向生产者进行确认,这意味着少部分比较慢的副本不影响整个队列的性能;

交换机,队列及其绑定关系:

java 复制代码
public class Constants {
    public static final String QUORUM_QUEUE = "quorum.queue";
    public static final String QUORUM_EXCHANGE = "quorum.exchange";
    public static final String  CLUSTER_QUEUE = "cluster.queue";
    public static final String CLUSTER_EXCHANGE = "cluster.exchange";
}
java 复制代码
@Configuration
public class RabbitMQConfig {
    /**
     * 仲裁队列
     * @return
     */
    @Bean("quorumQueue")
    public Queue quorumQueue(){
        return QueueBuilder.durable(Constants.QUORUM_QUEUE).quorum().build();
    }
    @Bean("quorumExchange")
    public DirectExchange quorumExchange(){
        return ExchangeBuilder.directExchange(Constants.QUORUM_EXCHANGE).build();
    }
    @Bean("quorumBinding")
    public Binding quorumBinding(@Qualifier("quorumQueue") Queue queue,
                                 @Qualifier("quorumExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("quorum");
    }
}

生产者:

java 复制代码
@RestController
public class ProducerController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("quorum")
    public String quorum(){
        String message = "hello, quorum test...";
        rabbitTemplate.convertAndSend(Constants.QUORUM_EXCHANGE, "quorum", message);
        return "消息发送成功!";
    }
}

运行结果:

可以看到队列后面有两个镜像节点,都保存了仲裁队列的从副本;

停止主节点,观察仲裁队列:

可以看到队列中的消息仍然存在,没有丢失。原因是从副本重新选举为主副本。

当有多个仲裁队列时,主副本和从副本会分布在集群的不同节点上,每个节点可以承载多个主副本和从副本。


总结

本文仅仅简单介绍了 RabbitMQ 在实际应用中可能存在的一些问题,以及这些问题的常见的解决方案。

相关推荐
Maiko Star2 小时前
跑通第一个Spring AI 应用
java·后端·spring·springai
2501_913061342 小时前
网络原理知识(8)
java·网络·面试
我命由我123452 小时前
Android 广播 - 显式广播与隐式广播
android·java·开发语言·java-ee·kotlin·android studio·android-studio
SimonKing2 小时前
frontend-dev vs ui-ux-pro-max:谁才是Vibe Coding前端开发的“最强辅助”?
java·后端·程序员
小谢小哥2 小时前
57-数据同步方案详解
java·后端·架构
小谢小哥2 小时前
56-最终一致性方案详解
java·后端·架构
人道领域2 小时前
【Redis实战篇 | Day04】Lua原子性优化Redis分布式锁:解决线程安全问题
java·开发语言·redis·性能优化
2301_815279522 小时前
RabbitMQ - 在微服务架构中的落地实践:消息推送 / 解耦 / 削峰填谷
微服务·架构·rabbitmq
恋奴娇2 小时前
ubuntu 25 Nautilus 文件管理器不能以ROOT运行突破
java·数据库·ubuntu