RabbitMQ 消息确认

1.生成者不知道消息是否真正到达broker(confirm模式)

(1)普通confirm模式:同步确认发布,publish一条消息后,等待服务器端confirm,如果服务端返回false或者超时时间内未返回,客户端进行消息重传
channel.confirmSelect();//开启发布确认
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
if(!channel.waitForConfirms()){
	System.out.println("send message failed.");
}

(2)批量confirm模式:同步确认发布,每发送一批消息后,调用waitForConfirms()方法,等待服务器端confirm
channel.confirmSelect();
for(int i=0;i<batchCount;i++){
	channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
}
if(!channel.waitForConfirms()){
	System.out.println("send message failed.");
}
批量极大提升confirm效率,但是问题在于一旦出现confirm返回false或者超时的情况时,客户端需要将这一批次的消息全部重发,这会带来明显的重复消息数量,并且,当消息经常丢失时,批量confirm性能应该是不升反降的

(3)异步confirm模式(★最佳★)
public static void publishMessageAsync() throws Exception {
    try (Channel channel = ConnectionUtil.getConnection().createChannel()) {
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName, false, false, false, null);
        //开启发布确认
        channel.confirmSelect();
        /**
         * 线程安全有序的一个哈希表,适用于高并发的情况
         * 1.轻松的将序号与消息进行关联
         * 2.轻松批量删除条目 只要给到序列号
         * 3.支持并发访问
         */
        ConcurrentSkipListMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();
        /**
         * 确认收到消息的回调
         * 1.消息序列号
         * 2.true可以确认小于等于当前序列号的消息 false确认当前序列号消息
         */
        ConfirmCallback ackCallback = (sequenceNumber, multiple) -> {
            if (multiple) {
                //返回的是小于等于当前序列号的未确认消息 是一个 map
                ConcurrentNavigableMap<Long, String> confirmed = outstandingConfirms.headMap(sequenceNumber, true);
                //清除该部分未确认消息
                confirmed.clear();
            } else {
                //只清除当前序列号的消息
                outstandingConfirms.remove(sequenceNumber);
            }
        };

        ConfirmCallback nackCallback = (sequenceNumber, multiple) -> {
            String message = outstandingConfirms.get(sequenceNumber);
            System.out.println("发布的消息" + message + "未被确认,序列号" + sequenceNumber);
        };

        /**
         * 添加一个异步确认的监听器
         * 1.确认收到消息的回调
         * 2.未收到消息的回调
         */
        channel.addConfirmListener(ackCallback, nackCallback);

        long begin = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            String message = "message:" + i;
            /**
             * channel.getNextPublishSeqNo()获取下一个消息的序列号
             * 通过序列号与消息体进行一个关联
             * 全部都是未确认的消息体
             */
            outstandingConfirms.put(channel.getNextPublishSeqNo(), message);
            channel.basicPublish("", queueName, null, message.getBytes());
        }
        long end = System.currentTimeMillis();
        System.out.println("发布" + 1000 + "个异步确认消息,耗时" + (end - begin) + "ms");
    }
}

2.保证消息从队列可靠地到达消费者(关闭自动消息确认,进行手动ack),
public class Consumer {
    private final static String QUEUE_NAME = "TEST_QUEUE";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String receivedMessage = new String(delivery.getBody());
            System.out.println("接收到消息:" + receivedMessage);
			//手动ack
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        };
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费接口回调逻辑");
        };
        System.out.println("Consumer等待消费......");
        //关闭自动消息确认 autoAck = false;
        channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
    }
}
 发布确认高级:
1.投递到交换机失败,投递失败后应该通知消息生产者(确认回调)
2.交换机投递成功但是消息路由到队列失败(回退回调)

springboot版本:
(1)配置文件
spring.rabbitmq.publisher-confirm-type=correlated
NONE:禁用发布确认模式,是默认值
CORRELATED:发布消息成功到交换器后会触发回调方法
SIMPLE:略

spring.rabbitmq.host=192.168.196.129
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123
spring.rabbitmq.publisher-confirm-type=correlated

(2)交换机、队列配置类
@Configuration
public class ConfirmConfig {
    public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";

    //声明交换机
    @Bean("confirmExchange")
    public DirectExchange confirmExchange() {
        return new DirectExchange(CONFIRM_EXCHANGE_NAME);
    }

    //声明队列
    @Bean("confirmQueue")
    public Queue confirmQueue() {
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    //绑定交换机与队列
    @Bean
    public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
                                @Qualifier("confirmExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("key");
    }
}

(3)实现确认回调ConfirmCallback 
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback {
    /**
     * 交换机不管是否收到消息的一个回调方法
     * CorrelationData消息相关数据
     * ack交换机是否收到消息
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (ack) {
            log.info("交换机已经收到 id 为:{}的消息", id);
        } else {
            log.info("交换机还未收到 id 为:{}消息,由于原因:{}", id, cause);
        }
    }
}

(4)生产者
@RestController
@RequestMapping("/confirm")
@Slf4j
public class ProducerController {
    public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private MyCallBack myCallBack;

    //依赖注入rabbitTemplate并设置它的回调对象
    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback(myCallBack);
    }

    @GetMapping("sendMessage/{message}")
    public void sendMessage(@PathVariable String message) {
        rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME, "key", message, new CorrelationData("1"));
    }
}

(5)消费者
@Component
@Slf4j
public class ConfirmConsumer {
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";

    @RabbitListener(queues = CONFIRM_QUEUE_NAME)
    public void receiveMsg(Message message) {
        log.info("接受到队列 confirm.queue 消息:{}", new String(message.getBody()));
    }
}

测试:
http://localhost:8080/confirm/sendMessage/你好鸭

开启了生产者确认机制的情况下,交换机接收到消息后,发现该消息不可路由,那么消息会被直接丢弃
解决办法:使用ReturnsCallback回调
(1)配置文件
添加:spring.rabbitmq.publisher-returns=true
spring.rabbitmq.host=192.168.196.129
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123
spring.rabbitmq.publisher-confirm-type=correlated
spring.rabbitmq.publisher-returns=true
(2)实现回退回调ReturnsCallback
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {
    /**
     * 交换机不管是否收到消息都会回调
     * CorrelationData 消息相关数据
     * ack 交换机是否收到消息
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (ack) {
            log.info("交换机已经收到 id 为:{}的消息", id);
        } else {
            log.info("交换机还未收到 id 为:{}消息,由于原因:{}", id, cause);
        }
    }

    /**
     * 未路由到队列回调,只在路由失败时回调
     */
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        System.err.println("ReturnedMessage: " + returned);
    }
}
(3)依赖注入rabbitTemplate并设置它的回调对象
@PostConstruct
public void init() {
    //设置确认回调
    rabbitTemplate.setConfirmCallback(myCallBack);
    /**
     * true:交换机无法将消息进行路由时,会将该消息返回给生产者
     * false:如果发现消息无法进行路由,则直接丢弃
     */
    rabbitTemplate.setMandatory(true);
    //设置回退消息交给谁处理
    rabbitTemplate.setReturnsCallback(myCallBack);
}

备份交换机:如果回退回调与备份交换机同时使用,备份交换机优先级高
当消息路由失败时,可以在该交换机处设置备份交换机,将路由失败的消息路由到备份交换机上
//声明交换机并指明其备份交换机
@Bean("confirmExchange")
public DirectExchange confirmExchange() {
    ExchangeBuilder exchangeBuilder =
            ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME)
                    .durable(true)
                    .withArgument("alternate-exchange", BACKUP_EXCHANGE_NAME);//指明备份交换机
    return (DirectExchange) exchangeBuilder.build();
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值