rabbitmq-保证消费消息可靠行-方案
**消费消息(队列-消费端)** 总结:当消费端发送basicAck给MQ时,说明消息消费成功! 当消费端发送basicNack给MQ时,说明消息消费失败,消息回退至MQ 注意:当消费端未给MQ发送basicAck或basicNack,消息则会一直在mq里面进行堆积。 除非与mq断开或者mq宕机才会自动将消息回退至MQ 消费端信息消费确认、消息预取机制: 前提: 开启手动确认 simpleRabbitListenerContainerFactory 通过@Bean方式手动注入 simpleRabbitListenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL); 如果成功,我们手动给MQ发送basicAck,表示发送成功! 如果失败,我们手动给MQ发送basicNack,表示发送失败! 设置预取数量 // 最大数量支持2500,建议默认取值500 simpleRabbitListenerContainerFactory.setPrefetchCount(500); 代码如下: /** * 消费端开启手动确认 * 无论是basicAck还是basicNack操作,都必须给mq一个通知。 * 不然消息一直处于未确认状态,就会堆积在mq中。除非与mq断开后,消息会自动回到队列 * 消息预取(能者多劳) * 默认队列会将其所有数据一次性分发到多个消费端,然后消费者再进行消费,这样会存在一个问题, * 有些性能好的消费端很快就消费完了,处于空闲状态,然而有些性能差的消费端还在不停的消费中,处于忙碌状态 * 导致消费资源的分配不合理,采用消息预取来改善 * * 指定消费端一次从队列中拉取数据的个数。而不是一次性分发到多个消费端 * 当消费者消费完了当前拉取的数据时,才会继续向队列中拉取新的数据进行消费 * 这样性能好的消费端就能更多的从队列拉取数据 * 这样性能差的消费端就能更少的从队列拉取数据 * */ public class RabbitmqConsumer { private int count = 0; /** * simpleRabbitListenerContainerFactory 通过@Bean方式手动注入 * 设置消费端手动确认 * 设置消费消息预取 * // 将消息消费确认由自动改为手动确认 * simpleRabbitListenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL); * // 设置消息预取数量 * simpleRabbitListenerContainerFactory.setPrefetchCount(1); * @param message * @param channel * @throws IOException */ @RabbitListener(queues = QUEUE_NAME_1, ackMode = "MANUAL", containerFactory = "simpleRabbitListenerContainerFactory") public void consumer1(Message message, Channel channel) throws IOException { count++; if (isOrder()) { System.out.println(new String(message.getBody(), "UTF-8")); System.out.println("消费者1 -- 消费成功"); // 传入这条消息的标识, 这个标识由rabbitmq来维护 我们只需要从message中拿出来就可以 // 第二个boolean参数指定是不是批量处理的, true: 批量处理,false: 非批量处理 // channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); if (count == 491) { /** 如果前490条消费成功,第491消费失败时的情况: * 若设置了批量处理(true),则会将491条数据都回退到原队列或死信队列 * 若设置了单条处理(false),则只会将第491那条数据回退到原队列或死信队列 * 无论是哪种情况,回退的消息会等待消费者重新进行消费,这就可能会导致消息重复消费 * 可以分别设置true/false进行测试,然后在mq管理台查看队列消费状态 第三个参数: requeue为true时,消息回退到原队列 requeue为false时,消息丢弃或者回退到死信交换机(前提:当前队列配置了死信交换机) 配置代码如下: @Bean public Queue queue() { Map<String, Object> map = new HashMap<>(); // 设置消息的过期时间 单位毫秒 map.put("x-message-ttl", 10000); // 设置附带的死信交换机 map.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE_NAME); map.put("x-dead-letter-routing-key", DEAD_LETTER_KEY); Queue queue = new Queue(QUEUE_NAME_1, true, false, false, map); return queue; } */ System.out.println("第491条出错了,进行回退"); channel.basicNack(message.getMessageProperties().getDeliveryTag(), true, true); } if (count % 500 == 0) { // 当消费数量达到预取数量时,进行批量确认 channel.basicAck(message.getMessageProperties().getDeliveryTag(), true); } } else { //前两个参数 和上面的意义一样,最后一个参数 就是这条消息是返回到原队列 还是这条消息作废不退回到原队列了。 // true: 退回到原队列, false: 消息作废或者退回到死信队列 System.out.println("消费者1 -- 消费失败"); channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } }
依赖pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>