之前我们简单介绍了rabbitmq的功能。他的作用就是方便我们的消息解耦。紧接着问题就会暴露出来。解耦就设计到双方系统不稳定问题。在mq中有生产者、mq、消费者三个角色。其中一个角色down机或者重启后。就设计到消息的丢失问题。

因为MQ整个消息周期设计到上述的三个角色,所以我们从这个三个角色开始讨论丢失数据的情况。并如何解决

生产者丢失消息

  • 在生产数据程序中,消息已经处理好还未发送给MQ这个阶段,生产者因为意外情况中断了。这个时候生产者这条消息就会丢失。因为程序重启好之后可能不会再次生产该消息。

实际案列1

  • 购物商城中已经选购了商品提交到支付界面。在支付成功后我们的程序需要发送消息给商家。这个时候程序中断了。待重启后客户界面订单状态是付款成功的。但是这个订单就没有及时通知给商家。这会造成商家延迟发货。

实际案例2

  • 同样是购物支付,A客户先付款order1订单,支付成功后发送MQ前直线异常但并未导致程序中断。这个时候order1商家收不到通知,然后B客户对order2订单进行支付且整个过程正常。order2订单就会通知到对应的商家。整个周期order1订单就属于丢失

总结

  • 两种情况都是在发送消息是出现问题。第一种是程序中断,第二种是订单异常,第一种异常级别高会影响整个程序使用反而是好排查。第二种程序不异常。这种情况很难发现,只会是个别情况。

解决方案

  • 针对上述情况mq也提供了两种方法解决。
  • 1、开启rabbitmq事务(同步)
  • 2、开启confirm模式(异步)

代码模拟


Map<String, Object> resultMap = new HashMap<String, Object>(){
{
put("code", 200);
}
};
String msg = "";
Integer index = 0;
if (params.containsKey("msg")) {
msg = params.get("msg").toString();
}
if (params.containsKey("index")) {
index = Integer.valueOf(params.get("index").toString());
}
if (index != 0) {
//这里开始模拟异常出现。消息将会丢失
int i = 1 / 0;
}
rabbitTemplate.convertAndSend(RabbitConfig.TOPICEXCHANGE, "zxh", msg);
return resultMap;
  • 上述代码http://localhost:8282/rabbitmq/sendTopic?msg=test&index=1就会发生异常,这个时候数据丢失
  • http://localhost:8282/rabbitmq/sendTopic?msg=test可以正常发送。读者可以自行测试
  • 其实通过rabbitmq的事务并不能解决上面的丢失情况。但是加上事务会保证消息发送的可靠性。上面发送消息后出异常这时候我们就没法回退消息了。但是事务可以帮我们实现

事务


String msg = "trantest";
Connection connection = rabbitTemplate.getConnectionFactory().createConnection();
Channel channel = connection.createChannel(true);
try {
channel.basicPublish(RabbitConfig.TOPICEXCHANGE, "zxh", null, msg.getBytes());
int i = 1 / 0;
} catch (IOException e) {
channel.txRollback();
e.printStackTrace();
}
channel.txCommit();
connection.close();
  • 最终测试效果是mq没有收到消息的。

confirm模式确实


Connection connection = rabbitTemplate.getConnectionFactory().createConnection();
Channel channel = connection.createChannel(false);
channel.confirmSelect();
try {
channel.basicPublish(RabbitConfig.TOPICEXCHANGE, "zxh", null, msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
boolean b = channel.waitForConfirms();
if (b) {
System.out.println("mq接收消息成功");
Thread.sleep(1000*5);
}
System.out.println("end1");
channel.confirmSelect();
channel.basicPublish(RabbitConfig.TOPICEXCHANGE, "zxh", null, msg.getBytes());
channel.addConfirmListener(new ConfirmListener() {
@SneakyThrows
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("~~~~~消息成功发送到交换机");
Thread.sleep(1000 * 5);
} @Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("~~~~~消息发送到交换机失败");
}
});
System.out.println("end2");
channel.close();
connection.close();
  • 上面使用了两种确认方式,前者是同步确认,后者是异步确认。因为在同一个方法里。msg都是能获取到的。所以在ConfimListener中就没有返回消息。

数据退回监听

  • 上面两种一个增加安全可靠性。一个增加确认机制。还有一种情况是数据回退。当交换机没有队列绑定是这个时候发送数据后如果设置了回退属性,那么消息会回退到监听器汇中的。channel中的mandatory表示是否检测分发到队列中。

String msg = "Hello World!";
Connection connection = rabbitTemplate.getConnectionFactory().createConnection();
Channel channel = connection.createChannel(false);
channel.confirmSelect();
//return机制:监控交换机是否将消息分发到队列
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
//如果交换机分发消息到队列失败,则会执行此方法(用来处理交换机分发消息到队列失败的情况)
System.out.println("*****"+i); //标识
System.out.println("*****"+s); //
System.out.println("*****"+s1); //交换机名
System.out.println("*****"+s2); //交换机对应的队列的key
System.out.println("*****"+new String(bytes)); //发送的消息
}
});
//发送消息
//channel.basicPublish("ex2", "c", null, msg.getBytes());
channel.basicPublish(RabbitConfig.DIRECTEXCHANGE, "c", true, null, msg.getBytes());
channel.addConfirmListener(new ConfirmListener() {
@SneakyThrows
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("~~~~~消息成功发送到交换机");
Thread.sleep(1000 * 5);
} @Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("~~~~~消息发送到交换机失败");
}
});
  • 上面ReturnListener就会被触发,这个时候confirm监听器也被触发认为成功接收的只不过被退回。

MQ事务相关软文推荐

mq事务开启分析

MQ丢失信息

  • 在发送消息到MQ时我们可以设置消息属性是否为可持久化。如果设置了直接就会存储在磁盘上。在内存可用时也会同步到内存中提高效率。如果消息属性中设置的是非持久化的话,就会直接存储在内存里,当内存不足是会将数据备份至磁盘上。

消费者丢失信息

  • 消费端如果没有单独设置的话默认就是MQ不管理。换句话说MQ只负责发送消息。mq为我们提供了三种模式

    NONE,

    MANUAL,

    AUTO; 默认的

  • 我们需要手动将连接工厂设置MANUAL后再接收到消息后我们需要手动确认,mq才会删除消息。否则会一直等待到消费端重启才会进行重新分发数据

  • channel.basicAck(long,boolean); 确认收到消息,消息将被队列移除,false只确认当前consumer一个消息收到,true确认所有consumer获得的消息。

  • channel.basicNack(long,boolean,boolean); 确认否定消息,第一个boolean表示一个consumer还是所有,第二个boolean表示requeue是否重新回到队列,true重新入队。

  • channel.basicReject(long,boolean); 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列。

当消息回滚到消息队列时,这条消息不会回到队列尾部,而是仍是在队列头部,这时消费者会又接收到这条消息,如果想消息进入队尾,须确认消息后再次发送消息。

rabbitmq如何保证消息可靠性不丢失的更多相关文章

  1. RabbitMQ消息丢失问题和保证消息可靠性-消费端不丢消息和HA(二)

    继续上篇文章解决RabbitMQ消息丢失问题和保证消息可靠性(一) 未完成部分,我们聊聊MQ Server端的高可用和消费端如何保证消息不丢的问题? 回归上篇的内容,我们知道消息从生产端到服务端,为了 ...

  2. 解决RabbitMQ消息丢失问题和保证消息可靠性(一)

    原文链接(作者一个人):https://juejin.im/post/5d468591f265da03b810427e 工作中经常用到消息中间件来解决系统间的解耦问题或者高并发消峰问题,但是消息的可靠 ...

  3. RabbitMQ高级之如何保证消息可靠性?

    人生终将是场单人旅途,孤独之前是迷茫,孤独过后是成长. 楔子 本篇是消息队列RabbitMQ的第四弹. RabbitMQ我已经写了三篇了,基础的收发消息和基础的概念我都已经写了,学任何东西都是这样,先 ...

  4. springboot + rabbitmq发送邮件(保证消息100%投递成功并被消费)

    前言: RabbitMQ相关知识请参考: https://www.jianshu.com/p/cc3d2017e7b3 Linux安装RabbitMQ请参考: https://www.jianshu. ...

  5. (转载)springboot + rabbitmq发送邮件(保证消息100%投递成功并被消费)

    转载自https://www.jianshu.com/p/dca01aad6bc8 一.先扔一张图   image.png 说明: 本文涵盖了关于RabbitMQ很多方面的知识点, 如: 消息发送确认 ...

  6. RabbitMQ 如何保证消息不丢失?

    RabbitMQ一般情况很少丢失,但是不能排除意外,为了保证我们自己系统高可用,我们必须作出更好完善措施,保证系统的稳定性. 下面来介绍下,如何保证消息的绝对不丢失的问题,下面分享的绝对干货,都是在知 ...

  7. (转)RabbitMQ学习之消息可靠性及特性

    http://blog.csdn.net/zhu_tianwei/article/details/53971296 下面主要从队列.消息发送.消息接收方面了解消息传递过的一些可靠性处理. 1.队列 消 ...

  8. RabbitMQ如何保证消息99.99%被发送成功?

    1. 本篇概要 RabbitMQ针对这个问题,提供了以下几个机制来解决: 生产者确认 持久化 手动Ack 本篇博客我们先讲解下生产者确认机制,剩余的机制后续单独写博客进行讲解. 2. 生产者确认 要想 ...

  9. RabbitMQ 和 Kafka 的消息可靠性对比

    RabbitMQ和Kafka都提供持久的消息保证.两者都提供至少一次和至多一次的保证,另外,Kafka在某些限定情况下可以提供精确的一次(exactly-once)保证. 让我们首先理解一下上述术语的 ...

随机推荐

  1. Java的awt包的使用实例和Java的一些提示框

    一.awt的一些组件 Label l1=new Label("姓名:"); //标签 Label l2=new Label("密码:"); TextField ...

  2. UVA-11019 二维哈希算法

    UVA-11019 题意: 就是给你AB两个字符矩阵,问你B矩阵在A矩阵中的出现次数. 题解:  参考链接:https://blog.csdn.net/qq_38891827/java/article ...

  3. Codeforces Round #529 (Div. 3) D. Circular Dance (思维)

    题意:有\(n\)个熊小孩,绕着树转圈圈,编号\(i\)的小孩可以记住\(a_{i,1}\)和\(a_{i,2}\)两个小孩,这两个小孩是顺时针相邻的,但谁前谁后不一定.现在给你每个小孩的\(a_{i ...

  4. [Python] Pandas 中 Series 和 DataFrame 的用法笔记

    目录 1. Series对象 自定义元素的行标签 使用Series对象定义基于字典创建数据结构 2. DataFrame对象 自定义行标签和列标签 使用DataFrame对象可以基于字典创建数据结构 ...

  5. Splunk监控软件操作

    一.  Splunk公司与产品 美国Splunk公司,成立于2004年,2012年纳斯达克上市,第一家大数据上市公司,荣获众多奖项和殊荣.总部位于美国旧金山,伦敦为国际总部,香港设有亚太支持中心,上海 ...

  6. Win7环境下mysql报错1045:Access denied for user root@localhost错误解决方法

    产生场景:在涉及到连接数据库操作的时候,会报1045:Access denied for user root@localhost. 比如:本地建立的数据库连接不上,在IDEA等开发工具中使用反向生成. ...

  7. Ubuntu16安装Caffe+Python3缺少libboost

    如果在/usr/lib/x86_64-linux-gnu中找到libboost_python-py3.5.so, 则 sudo ln -s libboost_python-py3.5.so libbo ...

  8. element-ui dialog loading

    element-ui dialog loading 指令方式 服务方式 v-loading 当使用指令方式时,全屏遮罩需要添加fullscreen修饰符(遮罩会插入至 body 上),此时若需要锁定屏 ...

  9. how to write a node cli tool

    how to write a node cli tool https://www.google.com/search?q=how+to+node+cli+tool&oq=how+to+node ...

  10. 加州金融专访NGK,就NGK DeFi+展开讨论

    近日,加利福尼亚金融日报联合数家知名媒体就DeFi+行业专访了NGK团队代表特德惠斯基. 首先,加利福尼亚金融日报专栏记者迈尔斯表示,目前区块链领域,去中心化金融(DeFi+)的概念是目前市场上面最火 ...