看这篇就够了!RabbitMQ如何防止数据丢失,详细讲解

}

private Map<String, Object> getMessage(String msg) {

String msgId = UUID.randomUUID().toString().replace(“-”, “”).substring(0, 32);

CorrelationData correlationData = new CorrelationData(msgId);

String sendTime = sdf.format(new Date());

Map<String, Object> map = new HashMap<>();

map.put(“msgId”, msgId);

map.put(“sendTime”, sendTime);

map.put(“msg”, msg);

map.put(“correlationData”, correlationData);

return map;

}

}

复制代码

大功告成!接下来我们进行测试,发送一条消息,我们可以控制台:

详细讲解!RabbitMQ如何防止数据丢失,看这篇就够了

假设发送一条信息没有路由匹配到队列,可以看到如下信息:

详细讲解!RabbitMQ如何防止数据丢失,看这篇就够了

这就是confirm模式。它的作用是为了保障生产者投递消息到RabbitMQ不会出现消息丢失

3.2 事务机制(ACK)

=============

最开始的那张图已经讲过,消费者从队列中获取到消息后,会直接确认签收,假设消费者宕机或者程序出现异常,数据没有正常消费,这种情况就会出现数据丢失

所以关键在于把自动签收改成手动签收,正常消费则返回确认签收,如果出现异常,则返回拒绝签收重回队列。

详细讲解!RabbitMQ如何防止数据丢失,看这篇就够了

代码怎么实现呢,请看演示:

首先在消费者的application.yml文件中设置事务提交为manual手动模式:

spring:

rabbitmq:

listener:

simple:

acknowledge-mode: manual # 手动ack模式

concurrency: 1 # 最少消费者数量

max-concurrency: 10 # 最大消费者数量

复制代码

然后编写消费者的监听器:

@Component

public class RabbitDemoConsumer {

enum Action {

//处理成功

SUCCESS,

//可以重试的错误,消息重回队列

RETRY,

//无需重试的错误,拒绝消息,并从队列中删除

REJECT

}

@RabbitHandler

@RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC))

public void process(String msg, Message message, Channel channel) {

long tag = message.getMessageProperties().getDeliveryTag();

Action action = Action.SUCCESS;

try {

System.out.println(“消费者RabbitDemoConsumer从RabbitMQ服务端消费消息:” + msg);

if (“bad”.equals(msg)) {

throw new IllegalArgumentException(“测试:抛出可重回队列的异常”);

}

if (“error”.equals(msg)) {

throw new Exception(“测试:抛出无需重回队列的异常”);

}

} catch (IllegalArgumentException e1) {

e1.printStackTrace();

//根据异常的类型判断,设置action是可重试的,还是无需重试的

action = Action.RETRY;

} catch (Exception e2) {

//打印异常

e2.printStackTrace();

//根据异常的类型判断,设置action是可重试的,还是无需重试的

action = Action.REJECT;

} finally {

try {

if (action == Action.SUCCESS) {

//multiple 表示是否批量处理。true表示批量ack处理小于tag的所有消息。false则处理当前消息

channel.basicAck(tag, false);

} else if (action == Action.RETRY) {

//Nack,拒绝策略,消息重回队列

channel.basicNack(tag, false, true);

} else {

//Nack,拒绝策略,并且从队列中删除

channel.basicNack(tag, false, false);

}

channel.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

复制代码

解释一下上面的代码,如果没有异常,则手动确认回复RabbitMQ服务端basicAck(消费成功)。

如果抛出某些可以重回队列的异常,我们就回复basicNack并且设置重回队列。

如果是抛出不可重回队列的异常,就回复basicNack并且设置从RabbitMQ的队列中删除。

接下来进行测试,发送一条普通的消息"hello":

详细讲解!RabbitMQ如何防止数据丢失,看这篇就够了

解释一下ack返回的三个方法的意思。

①成功确认

void basicAck(long deliveryTag, boolean multiple) throws IOException;

复制代码

消费者成功处理后调用此方法对消息进行确认。

  • deliveryTag:该消息的index

  • multiple:是否批量.。true:将一次性ack所有小于deliveryTag的消息。

②失败确认

void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;

复制代码

  • deliveryTag:该消息的index。

  • multiple:是否批量。true:将一次性拒绝所有小于deliveryTag的消息。

  • requeue:被拒绝的是否重新入队列。

③失败确认

void basicReject(long deliveryTag, boolean requeue) throws IOException;

复制代码

  • deliveryTag:该消息的index。

  • requeue:被拒绝的是否重新入队列。

basicNack()和basicReject()的区别在于:basicNack()可以批量拒绝,basicReject()一次只能拒接一条消息

四、遇到的坑

======

4.1 启用nack机制后,导致的死循环

====================

上面的代码我故意写了一个bug。测试发送一条"bad",然后会抛出重回队列的异常。这就有个问题:重回队列后消费者又消费,消费抛出异常又重回队列,就造成了死循环。

详细讲解!RabbitMQ如何防止数据丢失,看这篇就够了

那怎么避免这种情况呢?

既然nack会造成死循环的话,我提供的一个思路是不使用basicNack(),把抛出异常的消息落库到一张表中,记录抛出的异常,消息体,消息Id。通过定时任务去处理

如果你有什么好的解决方案,也可以留言讨论~

4.2 double ack

==============

有的时候比较粗心,不小心开启了自动Ack模式,又手动回复了Ack。那就会报这个错误:

消费者RabbitDemoConsumer从RabbitMQ服务端消费消息:java技术爱好者

2020-08-02 22:52:42.148 ERROR 4880 — [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - unknown delivery tag 1, class-id=60, method-id=80)

2020-08-02 22:52:43.102 INFO 4880 — [cTaskExecutor-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer@f4a3a8d: tags=[{amq.ctag-8MJeQ7el_PNbVJxGOOw7Rw=rabbitmq.demo.topic}], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@127.0.0.1:5672/,5), conn: Proxy@782a1679 Shared Rabbit Connection: SimpleConnection@67c5b175 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 56938], acknowledgeMode=AUTO local queue size=0

复制代码

出现这个错误,可以检查一下yml文件是否添加了以下配置:

spring:

rabbitmq:

listener:

simple:

acknowledge-mode: manual

concurrency: 1

max-concurrency: 10

复制代码

如果上面这个配置已经添加了,还是报错,**有可能你使用@Configuration配置了

SimpleRabbitListenerContainerFactory,根据SpringBoot的特性,代码优于配置,代码的配置覆盖了yml的配置,并且忘记设置手动manual模式**:

@Bean

public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {

SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();

factory.setConnectionFactory(connectionFactory);

//设置手动ack模式

factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);

return factory;

}

复制代码

如果你还是有报错,那可能是写错地方了,写在生产者的项目了。以上的配置应该配置在消费者的项目。因为ack模式是针对消费者而言的。我就是写错了,写在生产者,折腾了几个小时,泪目~

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

总结

无论是哪家公司,都很重视高并发高可用的技术,重视基础,重视JVM。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。

最后我整理了一些面试真题资料,技术知识点剖析教程,还有和广大同仁一起交流学习共同进步,还有一些职业经验的分享。

面试了阿里,滴滴,网易,蚂蚁,最终有幸去了网易【面试题分享】

.(img-BHAucZTT-1710429151875)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-rhFbO1g4-1710429151876)]

总结

无论是哪家公司,都很重视高并发高可用的技术,重视基础,重视JVM。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。

最后我整理了一些面试真题资料,技术知识点剖析教程,还有和广大同仁一起交流学习共同进步,还有一些职业经验的分享。

[外链图片转存中…(img-LVnEvD9d-1710429151876)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值