MQ高级

MQ高级

发送者可靠性

发送者重连

由于网络波动 可能会出现发送者连接失败MQ 导致消息未发送成功 通过配置可以开启后的失败重连机制

SpringAMQP的重连机制是阻塞执行 可以考虑异步线程执行发送消息的代码

发送者确认

SpringAMQP提供了Publisher ConfirmPublisher Return 两种确认机制 开启确认机制后 当发送者发送消息给MQ后 MQ会返回确认消息结果给发送者

  • 消息投递到MQ 但是路由失败 会通过PublisherReturn记录异常 然后返回ACK
  • 临时消息投递MQ 入队成功 返回ACK
  • 持久消息投递MQ 入队并完成持久化 返回ACK
  • 其他情况都返回NACK

开启确认机制

publisher-confirm-type有三种 none关闭确认机制 simple同步阻塞式等待MQ消息 **correlated **异步等待消息

  1. Publisher Return只需要配置一次即可
@Slf4j
@Configuration
@RequiredArgsConstructor
public class MQConfig {

    private final RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setReturnsCallback(returnedMessage -> {
            log.error("监听到了return callback");
            log.debug("exchange:{}",returnedMessage.getExchange());
            log.debug("routingKey:{}",returnedMessage.getRoutingKey());
            log.debug("message:{}",returnedMessage.getMessage());
            log.debug("replyCode:{}",returnedMessage.getReplyCode());
            log.debug("replyText:{}",returnedMessage.getReplyText());
        });
    }
}
  1. Publisher Confirm需要在每次发消息都配置
//创建correlation
        CorrelationData cd = new CorrelationData(UUID.randomUUID().toString());
        cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
            @Override
            public void onSuccess(CorrelationData.Confirm result) {
                if(result.isAck()){
                    log.debug("收到ConfirmCallback ACK 消息发送成功");
                }else {
                    log.error("收到ConfirmCallback NACK 消息发送失败,{}",result.getReason());
                }

            }

            @Override
            public void onFailure(Throwable ex) {
                log.error("spring amqp 内部处理异常",ex);
            }
        });
        String exchangeName = "hmall.direct";
        String message = "Hello 外币巴伯!";

        rabbitTemplate.convertAndSend(exchangeName,"blue1",message,cd);

MQ可靠性

默认情况下 RabbitMQ会把接收到的消息保存在内存中来降低消息收发的延迟 但是会导致两个问题

  • 一但MQ宕机 内存中的消息会丢失
  • 内存空间有限 当消费者故障或处理较慢 会导致消息积压 MQ阻塞

如果消息过多 然后MQ会尝试将一部分消息持久化 但是这个过程中是无法给MQ发消息的 从而导致数据丢失 这种被动持久化拉跨

数据持久化

实现数据持久化包括三个方面

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

都是点点控制台即可
SpringAMQP默认创建的三个都是持久化的 如果想发送非持久化的消息 需要自定义消息

Lazy Queue

3.6版本引入了Lazy Queue Lazy Queue特性如下

  • 接收到消息直接存入磁盘中 不写入内存
  • 消费者接收消息从磁盘中读取 但是如果读取速度过快mq会将一部分消息缓存到内存中 最多2048条

3.12版本之后都使用Lazy Queue且不能更改
控制台添加方式

代码添加方式 分别是Bena创建和注解

消费者可靠性

消费者确认机制

消费者确认机制(Consumer Acknowledgement) 就是让消费者处理完消息结束后 返回给MQ一个状态

  • ACK 成功处理消息 MQ删除队列中消息
  • NACK 消息处理失败 MQ再次投递消息
  • REJECT 消息处理失败并拒绝该消息 MQ删除队列中消息

NACK和REJECT就像前者是业务上出了问题 重试的话有机会能正常 而后者就是代码本身有问题 比如转换器格式不对等等
SpringAMQP实现了消息确认功能 可以通过配置文件实现

  • none 不处理 投递给消费者立即ack 不安全不建议
  • **manual **手动模式 自己在业务代码中调用api来发送ack或者reject 灵活但有代码侵入
  • auto 自动 SpringAMQP利用AOP对逻辑进行了环绕增强 业务正常返回ack 业务有问题返回nack 消息处理或者校验异常 返回reject

spring.rabbitmq.listener.simple.acknowledge-mode=auto

失败重试机制

如果不开启失败重试 就会一致重新投递 占用CPU 如果开启失败重试机制 默认是重试三次 会减少消耗

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true # 开启消费者失败重试
          initial-interval: 1000ms # 初识的失败等待时长为1秒
          multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 3 # 最大重试次数
          stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false

开启重试模式后 如果次数耗尽 消息还在失败 就有MessageRecoverer接口来处理 包含三种不同的实现

  • RejectAndDontRequeueRecoverer 重试耗尽后 直接reject 丢弃消息 默认采用
  • ImmediateRequeueMessageRecoverer 重试耗尽采用NACK 消息重新入队
  • RepublishMessageRecoverer 重试耗尽把失败消息投递到指定的交换机

一般都是配置RepublishMessageRecoverer 然后指定一个交换机 指定一个队列 来让重试三次之后直接发到错误队列中

业务幂等性

幂等业务 就是一个业务 执行一次和执行多次 结果是一样的 比如查询商品 无论执行几次 都是一样的
实现方法

  • 唯一消息ID 给每一个消息一个唯一ID 利用ID来区分是否是重复消息 类似于乐观锁
    • 每一条消息带有唯一ID 然后一起投递给消费者
    • 消费者收到后处理业务 然后处理成功之后把消息ID存到数据库
    • 如果下次又收到相同消息 先从数据库中查 如果已经存在相同ID 则代表已经处理过

可以通过设置转换器来让MQ自动为消息带上唯一ID

  • 业务判断 结合业务逻辑 来对非幂等性业务加上逻辑判断
    比如余额业务 当用户支付成功之后 把订单状态改为已支付 然后发送ack给MQ 但是此时网络波动 导致没发过去 然后用户改成了已退款 但是这时候mq又发来一条改成已支付的订单消息 就会重复修改 非幂等性 因此加以业务就是在更改订单之前先查询判断 如果状态是未支付 就修改 如果不是未支付 就不修改

如何确保支付服务和交易服务的状态一致性

  • 首先 支付服务在用户支付成功之后会通过mq消息通知交易服务 完成订单同步 采取异步通知的方式
  • 其次 为了确保消息的可靠性 采用了生产者确认机制 消费者确认机制 消费者失败重试策略 确保消息投递和处理的可靠性 也开启了MQ的持久化 避免因服务器宕机而导致消息丢失 确保消息至少被投递一次
  • 最后 还在交易服务更新订单状态时做了业务幂等的判断 避免因消息重复投递导致订单状态异常

延迟消息

延迟消息 发送者发送消息时指定一个时间 消费者不会立刻收到消息 而是在执行消息之后才收到
延迟任务 设置在一定时间之后才执行的任务

死信交换机

当一个队列中的消息满足下列情况时 就会成为死信(dead letter)

  • 消费者声明reject或nack 表示消费失败 并且消息的requeue设置为false
  • 消息是一个过期消息 超时无人消费
  • 要投递的队列消息堆积满了 最早的消息可能称为死信

如果队列通过dead-letter-exchange 指定了一个交换机 那么队列中的死信就会投递到这个交换机中 这个交换机就是死信交换机(Dead Letter Exchange) 简称DLX

延迟消息插件

功能就是将普通交换机改造为支持延迟消息的交换机 当消息投递到交换机时暂存一定时间 到期后再投递给队列 从而达到延迟消息功能
声明交换机只需要把delayed属性改为true即可

然后发送的时候通过setDelayed来设置延迟消息到期时间 message.getMessageProperties().setDelay(10000);
尽可能把延迟消息设置时间短点 避免因消息过多导致时钟过多然后CPU压力过大

取消超时订单

posted @ 2025-08-13 16:12  big4mart  阅读(6)  评论(0)    收藏  举报