对于消息中间件来讲,能解决我们实际场景中好多问题,但是也存在一些弊端:消息丢失与重发,rabbitmq自身提供了解决消息的丢失和重发,下来我们就来分析消息是怎么丢失和重发的。
1.消息未到达交换机
当我们发送一条消息的时候,消息首先被发到叫唤机,如未给当前队列设定交换机则发送到默认的交换机,假设此时发送失败,那么消息就丢失了。
2.消息未到达队列
发送的交换机未绑定队列,或者绑定的队列已满的情况下,交换机发送消息到队列会失败,此时消息丢失。
3.消费者消费失败
由于rabbitmq默认是自动ack,只要消息到达消费者就自动向队列返回ack,那么此时业务处理失败,我们可能需要重试操作,此时此条消息也不在队列。
解决以上问题,我们首先需要将rabbitmq设置成手动ack,以下是mq的配置
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
publisher-confirms: true #开启发送端确认机制
publisher-returns: true # 开启returns
template:
mandatory: true # 交换机没有匹配的队列时,会将消息返回给生产者,避免消息丢失
listener:
# 开启ACK
simple:
acknowledge-mode: manual #手动确认模式
prefetch: 1 #每次从队列中取一个,轮询分发,默认是公平分发
default-requeue-rejected: true #默认重新排队被拒绝
#失败后重试
retry:
enabled: true #开启
max-attempts: 5 #最大尝试次数
initial-interval: 3000 #初始间隔
怎么确认消息未到达交换机呢?我们需要实现ConfirmCallback的confirm方法,就可以确认,那么在发送失败的情况下,我们就可以做一些操作
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("===============ack=========" + JSONObject.toJSONString(correlationData) + " 是否成功:" + ack + " error "
+ cause);
if (ack) {
// 已发送到交换机
log.info("==========发送到交换机啦======");
} else {
// 未发送到交换机
log.info("==========发送到交换机失败啦======");
}
}
怎么确认消息未到达队列呢?同样我们可以实现ReturnCallback的returnedMessage方法,当消息未送达队列的时候,会调用此方法,我们可以将失败的消息存储起来定时补发。
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
/*消息到达交换机,没有路由到队列,根据订单号查询到订单数据,并将数据保存到异常消息表中,定时补发,并报警人工处理
* 1 交换机没有绑定队列
* 2 交换机根据路由键没有匹配到队列
* 3 队列消息已满
*/
// 交换机未发送到队列
log.info("==========发送到队列失败啦======");
log.info("replyCode =" + replyCode + " replyText = " + replyText + " exchange = " + exchange + " routingKey = "
+ routingKey);
}
当消费者消费失败的时候我们,我们往往不能就此罢休,可能需要做一些补救措施,延时执行或者重试机制,避免数据的不一致性。消费失败,
1.我们可以拒绝消息,不在入队,此时如果配置了死信队列,该消息将会进入死信队列。
2.也可以将消息重新入队消费。
@RabbitHandler
@RabbitListener(queues = QueueConstant.NO_QUEUE)
public void ackReceiver(Channel channel, Message message) {
try {
// 模拟执行任务
Thread.sleep(1000);
// 模拟异常
/*
* String is = null; is.toString();
*/
// 确认收到消息,false只确认当前consumer一个消息收到,true确认所有consumer获得的消息
//channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
if (message.getMessageProperties().getRedelivered()) {
log.info("消息已重复处理失败,拒绝再次接收" + "dd");
try {
// 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e1) {
e1.printStackTrace();
}
} else {
log.info("消息即将再次返回队列处理" + "dd");
try {
// requeue为是否重新回到队列,true重新入队
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
} catch (IOException e1) {
e1.printStackTrace();
}
}
// e.printStackTrace();
}
}
如有不明白的童鞋,可评论或者私信,如有不妥之处,请指出,共勉。