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();
ConcurrentSkipListMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();
ConfirmCallback ackCallback = (sequenceNumber, multiple) -> {
if (multiple) {
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);
};
channel.addConfirmListener(ackCallback, nackCallback);
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
String message = "message:" + i;
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);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
};
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println(consumerTag + "消费者取消消费接口回调逻辑");
};
System.out.println("Consumer等待消费......");
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 {
@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;
@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 {
@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);
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();
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/82852d9440c6431982c2d3d313309b24.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-l5LmL5Li655-l,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)