Spring RabbitMQ 解决 生产者丢失消息、消息列表丢失消息、消费者丢失消息问题 & @RabbitHandler方法参数详解

SpringBoot整合RabbitMQ

生产环境中使用RabbitMQ时需要保证消息的可靠传输,消息不可靠的情况可能是消息丢失,劫持等原因;

  • 丢失又分为:生产者丢失消息、消息列表丢失消息、消费者丢失消息
  • 本文主要展示如何解决上述3种问题

1.简单使用

1.1生产者

  1. 导入POM
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
  1. 配置交换机、队列、绑定
@Configuration
public class MyRabbitConfig {
    public static final String SECKILL_ROUTING_KEY = "seckill";

    public static final String DIRECT_QUEUE_SECKILL = "queue_seckill";

    public static final String DIRECT_EXCHANGE_SECKILL = "exchange_seckill";

    // 将发送的数据转为JSON
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    //队列 起名:DirectQueue
    @Bean
    public Queue DirectQueue() {
        return new Queue(DIRECT_QUEUE_SECKILL,true);
    }

    //Direct交换机 起名:DirectExchange
    @Bean
    DirectExchange DirectExchange() {
        return new DirectExchange(DIRECT_EXCHANGE_SECKILL);
    }

    //绑定  将队列和交换机绑定, 并设置用于匹配键:SECKILL_ROUTING_KEY
    @Bean
    Binding bindingDirect() {
        return BindingBuilder.bind(DirectQueue()).to(DirectExchange()).with(SECKILL_ROUTING_KEY);
    }

}
  1. 配置文件
spring:
  rabbitmq:
    host: 192.168.79.129
    port: 5672
    virtual-host: /
  1. 使用(@EnableRabbit
RabbitTemplate 发送消息

1.2消费者

  1. 导包
  2. 配置文件
  3. 使用
    • 使用@RabbitListener 指定消费的队列
    • 使用@RabbitHandler 指定处理方法
@Service
@RabbitListener(queues = RabbitConfig.DIRECT_QUEUE_SECKILL)
public class RabbitConsumer {

    AtomicInteger count = new AtomicInteger(0);

    @Resource
    GoodsService goodsService;

    // 单线程处理
    @RabbitHandler
    public void handle(String seckillDTOJson) {
        Gson gson = new Gson();
        SeckillDTO seckillDTO = gson.fromJson(seckillDTOJson, SeckillDTO.class);
        goodsService.payOrder(seckillDTO.getUserId(), seckillDTO.getGoodsId());
        System.out.println("处理:" + seckillDTO + count.incrementAndGet());
    
}
  1. 使用(@EnableRabbit

2.生产者发布确认

修改RabbitTemplate 配置

  • rabbitTemplate.setConfirmCallback():设置发布确认处理函数

  • 实现接口:RabbitTemplate.ConfirmCallback

  • rabbitTemplate.setMandatory(true) + rabbitTemplate.setReturnCallback():设置消息回退处理函数

  • 实现接口:RabbitTemplate.ReturnCallback

@Service
public class RabbitService implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    // todo 如何知道消费者正确消费了消息?
    @Resource
    RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init() {
        //  交换机发布确认
        rabbitTemplate.setConfirmCallback(this);
        // 消息回退
        rabbitTemplate.setMandatory(true);
        // 消息回退处理
        rabbitTemplate.setReturnCallback(this);

    }

    public String sendSeckillMessage(SeckillDTO seckillDTO) {

        Gson gson = new Gson();
        String toJson = gson.toJson(seckillDTO);
        rabbitTemplate.convertAndSend(
                MyRabbitConfig.DIRECT_EXCHANGE_SECKILL, MyRabbitConfig.SECKILL_ROUTING_KEY, toJson);
        return "ok";
    }

    /**
     * @param correlationData 发送消息时我们所绑定的相关数据【rabbitTemplate.convertAndSend中发送】
     * @param ack             是否成功投递到交换机
     * @param cause           原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {

    }

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {

    }
}

3.设置消息持久化

3.1 队列持久化

队列持久化在创建(new)队列时指定

参数:

  • name:队列名

  • durable:是否持久化

  • exclusive:表示队列是否为排他的(独占的)。如果设置为true,则该队列只能被当前连接使用,并且在连接断开后自动删除。此特性适用于一个客户端需要独享队列的场景,比如用于临时队列或者测试环境。

  • autoDelete表示队列是否在不再被使用时自动删除。如果设置为true,一旦最后一个消费者取消订阅,队列就会被删除。这个特性对于临时队列特别有用,可以避免不必要的资源占用。

  • arguments允许用户自定义一些额外的参数,这些参数可以用来设置队列的特定行为或特性,比如死信队列的配置、消息过期时间等。

public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, 
             Map<String, Object> arguments) {
}

3.2 消息持久化

消息持久化在消息创建时指定

// 指定消息属性的对象
MessageProperties messageProperties = new MessageProperties();
// 设置消息交付模式
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
// 创建消息
Message message = new Message("消息体".getBytes(StandardCharsets.UTF_8), messageProperties);
// ... 发送消息

4.@RabbitHandler的传入参数

@RabbitHandler注解标记的方法可以接收多种类型的参数,具体取决于消息的内容以及配置的MessageConverter

以下是一些常见的参数类型:(主要是Message,其他的内容可以中Message中自己取)
1. Message对象:这是最原始的形式,你可以直接接收整个org.springframework.amqp.core.Message对象,它包含了消息的所有元数据(如消息ID、路由键、交换机等)以及消息体。通过Message.getBody()方法可以获得消息的实际内容,内容通常是字节数组
- message.getBody():获取消息体
- message.getMessageProperties():获取消息元数据
2. Channel对象:获取该消费者与对应队列的通道并进行一系列重要操作,详情见 [Channel详情](# 5. Channel对象)

  1. 自定义类型对象:
    如果你在发送消息时发送的是一个对象,并且配置了相应的MessageConverter(如Jackson2JsonMessageConverter或SimpleMessageConverter),那么接收端的@RabbitHandler方法可以直接接收该对象类型作为参数。例如,如果你发送的是一个User对象,那么处理方法就可以声明一个User类型的参数。

  2. @Payload注解的使用:
    当消息体需要被直接映射到方法参数时,可以使用@Payload注解来明确指定哪个参数应该接收消息体。这在方法接收多个参数时特别有用。

  3. 其他附加信息参数:(@Headers对应的Map我们可以在发送消息时配置

    1. 可以使用**@Header**注解来获取消息头中的特定信息,如消息ID、时间戳等(建议搭配AmqpHeaders常量类)。
    2. @Headers注解可以用来接收所有消息头作为一个Map<String, Object>。
    3. @SendTo和@SendToUser注解:
      虽然这不是参数类型,但是与@RabbitHandler相关的注解,它们用于指示处理方法处理消息后应发送响应到哪个队列或目的地。
@Component
public class MyMessageReceiver {
    
    
    @RabbitHandler
    public void handleMessage(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
        try {
            // 处理消息的逻辑
            System.out.println("Received message: " + new String(message.getBody());
            // 成功处理完消息后,手动发送确认
            channel.basicAck(tag, false); // 第二个参数false表示只确认当前消息
        } catch (Exception e) {
            // 如果消息处理失败,可以选择拒绝消息(requeue参数决定是否重新入队)
            channel.basicNack(tag, false, true); // 第三个参数true表示重新入队列
            // 或者也可以使用basicReject方法,效果类似但不支持批量确认
            // channel.basicReject(tag, true);
        }
    }

    @RabbitHandler
    public void handleUserMessage(User user) {
        // 直接处理User对象
        System.out.println("Received User: " + user);
    }

    @RabbitHandler
    public void handleMessageWithDetails(Message message) {
        // 处理原始Message对象
        byte[] body = message.getBody();
        String routingKey = message.getMessageProperties().getReceivedRoutingKey();
        System.out.println("Received Message with routingKey: " + routingKey);
    }

    @RabbitHandler
    public void handleMessageWithHeader(@Payload String content, @Headers Map<String, Object> headers) {
        // 获取消息体和消息头
        System.out.println("Content: " + content);
        System.out.println("Headers: " + headers);
    }
}

5. Channel对象

在RabbitMQ和Spring AMQP的上下文中,Channel对象是一个非常重要的概念,它提供了与RabbitMQ服务器交互的基础功能。在你的@RabbitHandler方法签名中,Channel参数允许你直接执行一些低级别的AMQP操作,如消息确认、拒绝或 nack。以下是Channel对象的一些关键功能:

long deliveryTag 可以在发送时手动指定,也可以自动生成

  1. 消息确认(Acknowledgment):
    如之前讨论的,Channel可以用来手动确认消息已经被成功处理,通过basicAck(long deliveryTag, boolean multiple)方法。deliveryTag是消息的唯一标识multiple标志决定是否同时确认之前的消息(批量确认)(如果为true)。

  2. 消息拒绝(Reject)与重新入队(Requeue):
    如果消息处理失败,可以通过basicReject(long deliveryTag, boolean requeue)方法拒绝消息。requeue参数指示RabbitMQ是否应该尝试重新将消息放入队列以供后续重试。

  3. 消息的Nack(Negative Acknowledgment):
    basicNack(long deliveryTag, boolean multiple, boolean requeue)方法类似于拒绝,但它允许批量操作(当multiple为true时),可以一次性拒绝或重新入队多个消息。

  4. 发布消息:
    虽然通常我们使用RabbitTemplate来发送消息,但Channel也提供了直接发送消息的能力,如通过

    basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)方法。

  5. 事务管理:
    如果需要在消息发布和确认之间保持原子性,可以使用Channel.txSelect()开始事务,然后在一系列操作后调用Channel.txCommit()提交,或者在遇到错误时调用Channel.txRollback()回滚。

  6. 队列、交换机和绑定操作:
    虽然这些操作通常在应用启动时通过配置完成,但Channel也提供了创建、删除队列、交换机以及绑定等操作的API,如queueDeclare, exchangeDeclare, queueBind等。

  7. 流控(Flow Control):
    通过basicQos(int prefetchSize, int prefetchCount, boolean global)方法,可以设置预取限制(prefetch count),控制消费者从队列中获取消息的速度,以实现流量控制。

    综上所述,Channel是RabbitMQ客户端与服务端交互的核心接口,它提供了丰富的功能来控制消息的发送、接收、确认、拒绝等操作,是实现消息可靠传输和高级消息处理逻辑的基础。

6.消费者手动确认

在RabbitMQ中,消息的删除通常是自动进行的,并且这一行为依赖于消息的确认模式。RabbitMQ提供了两种消息确认模式来确保消息被正确处理并决定何时可以从队列中删除消息:

  • 自动确认(Automatic Acknowledgment): 默认情况下,RabbitMQ使用自动确认模式。在这种模式下,消费者接收到消息后,RabbitMQ会立即将消息从队列中移除,而不需要消费者明确发送确认。这种方式简单但风险在于,如果消费者处理消息过程中崩溃,消息将会丢失。
  • 手动确认(Manual Acknowledgment): 若要更精确地控制消息的删除时机,可以使用手动确认模式。在这种模式下,消费者在成功处理完一条消息后,需主动发送一个确认(ack)给RabbitMQ,告知消息已经被处理,RabbitMQ接收到确认后才会将消息从队列中删除。
@Component
@RabbitListener(queues = "your_queue_name")
public class YourConsumer {

    @RabbitHandler
    public void handleMessage(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
        try {
            // 处理消息的逻辑
            System.out.println("Received message: " + new String(message.getBody());
            // 成功处理完消息后,手动发送确认
            channel.basicAck(tag, false); // 第二个参数false表示只确认当前消息
        } catch (Exception e) {
            // 如果消息处理失败,可以选择拒绝消息(requeue参数决定是否重新入队)
            channel.basicNack(tag, false, true); // 第三个参数true表示重新入队列
            // 或者也可以使用basicReject方法,效果类似但不支持批量确认
            // channel.basicReject(tag, true);
        }
    }
}
  • 32
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RabbitMQ 中,可以通过以下方式保证生产者消费者之间的消息传输的可靠性: 1. 消息持久化:生产者可以将消息标记为持久化,确保即使在服务器重启后,消息也不会丢失。在发布消息时,可以设置消息的 delivery mode 为2。 ```java channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes()); ``` 2. 消息确认机制:使用消息确认机制可以确保消息已经成功发送到 RabbitMQ 服务器。生产者发送消息后,等待服务器返回确认消息后再继续发送下一条消息。 ```java channel.confirmSelect(); channel.basicPublish(exchange, routingKey, null, message.getBytes()); if (channel.waitForConfirms()) { // 消息发送成功 } else { // 消息发送失败 } ``` 3. 消费者确认机制:消费者在接收到消息后,需要发送确认信号给 RabbitMQ 服务器,告知已经成功处理该消息。只有当消费者发送确认信号后,RabbitMQ 才会将该消息从队列中删除。 ```java channel.basicConsume(queueName, false, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { // 处理消息 channel.basicAck(envelope.getDeliveryTag(), false); // 发送确认信号 } }); ``` 通过以上机制的组合使用,可以确保消息生产者消费者之间的可靠传输。同时,RabbitMQ 也提供了备份交换器、队列镜像等高可用性机制,以进一步增加消息传输的可靠性。 希望这能解答你的问题!如果还有疑问,请随时追问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值