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

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

生产者丢失消息

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

实际案列1

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

实际案例2

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

总结

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

解决方案

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

代码模拟


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

事务


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

confirm模式确实


  1. Connection connection = rabbitTemplate.getConnectionFactory().createConnection();
  2. Channel channel = connection.createChannel(false);
  3. channel.confirmSelect();
  4. try {
  5. channel.basicPublish(RabbitConfig.TOPICEXCHANGE, "zxh", null, msg.getBytes());
  6. } catch (IOException e) {
  7. e.printStackTrace();
  8. }
  9. boolean b = channel.waitForConfirms();
  10. if (b) {
  11. System.out.println("mq接收消息成功");
  12. Thread.sleep(1000*5);
  13. }
  14. System.out.println("end1");
  15. channel.confirmSelect();
  16. channel.basicPublish(RabbitConfig.TOPICEXCHANGE, "zxh", null, msg.getBytes());
  17. channel.addConfirmListener(new ConfirmListener() {
  18. @SneakyThrows
  19. @Override
  20. public void handleAck(long deliveryTag, boolean multiple) throws IOException {
  21. System.out.println("~~~~~消息成功发送到交换机");
  22. Thread.sleep(1000 * 5);
  23. }
  24. @Override
  25. public void handleNack(long deliveryTag, boolean multiple) throws IOException {
  26. System.out.println("~~~~~消息发送到交换机失败");
  27. }
  28. });
  29. System.out.println("end2");
  30. channel.close();
  31. connection.close();
  • 上面使用了两种确认方式,前者是同步确认,后者是异步确认。因为在同一个方法里。msg都是能获取到的。所以在ConfimListener中就没有返回消息。

数据退回监听

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

  1. String msg = "Hello World!";
  2. Connection connection = rabbitTemplate.getConnectionFactory().createConnection();
  3. Channel channel = connection.createChannel(false);
  4. channel.confirmSelect();
  5. //return机制:监控交换机是否将消息分发到队列
  6. channel.addReturnListener(new ReturnListener() {
  7. @Override
  8. public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
  9. //如果交换机分发消息到队列失败,则会执行此方法(用来处理交换机分发消息到队列失败的情况)
  10. System.out.println("*****"+i); //标识
  11. System.out.println("*****"+s); //
  12. System.out.println("*****"+s1); //交换机名
  13. System.out.println("*****"+s2); //交换机对应的队列的key
  14. System.out.println("*****"+new String(bytes)); //发送的消息
  15. }
  16. });
  17. //发送消息
  18. //channel.basicPublish("ex2", "c", null, msg.getBytes());
  19. channel.basicPublish(RabbitConfig.DIRECTEXCHANGE, "c", true, null, msg.getBytes());
  20. channel.addConfirmListener(new ConfirmListener() {
  21. @SneakyThrows
  22. @Override
  23. public void handleAck(long deliveryTag, boolean multiple) throws IOException {
  24. System.out.println("~~~~~消息成功发送到交换机");
  25. Thread.sleep(1000 * 5);
  26. }
  27. @Override
  28. public void handleNack(long deliveryTag, boolean multiple) throws IOException {
  29. System.out.println("~~~~~消息发送到交换机失败");
  30. }
  31. });
  • 上面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. 【poj 2891】Strange Way to Express Integers(数论--拓展欧几里德 求解同余方程组 模版题)

    题意:Elina看一本刘汝佳的书(O_O*),里面介绍了一种奇怪的方法表示一个非负整数 m .也就是有 k 对 ( ai , ri ) 可以这样表示--m%ai=ri.问 m 的最小值. 解法:拓展欧 ...

  2. HDU 4289 Control(最大流+拆点,最小割点)

    题意: 有一群恐怖分子要从起点st到en城市集合,你要在路程中的城市阻止他们,使得他们全部都被抓到(当然st城市,en城市也可以抓捕).在每一个城市抓捕都有一个花费,你要找到花费最少是多少. 题解: ...

  3. LianLianKan HDU - 4272 状压dp

    题意:长度为n(n<=1000)的栈,栈顶元素可以与下面1~5个数中相同的元素消去,问最后能都完全消去. 题解: 比如这个序列12345678910112这个位置的最远可匹配位置能到11为什么呢 ...

  4. Dapr微服务应用开发系列3:服务调用构件块

    题记:这篇开始逐一深入介绍各个构件块,从服务调用开始 原理 所谓服务调用,就是通过这个构件块让你方便的通过HTTP或者gRPC协议同步调用其他服务的方法,这些方法也是通过HTTP或者gRPC来暴露的. ...

  5. Eureka详解系列(五)--Eureka Server部分的源码和配置

    简介 按照原定的计划,我将分三个部分来分析 Eureka 的源码: Eureka 的配置体系(已经写完,见Eureka详解系列(三)--探索Eureka强大的配置体系): Eureka Client ...

  6. 在程序中通过Process启动外部exe的方法及注意事项

    启动外部进程的方法: /// <summary> /// 启动外部进程 /// </summary> /// <param name="path"&g ...

  7. leetcode 22. 括号生成 dfs

    先思考符合要求的串是什么样子的 任意时刻,(数量大于),且最后(==)==n即可 考虑下一个加入string的字符时(或者)即可 dfs class Solution { public: vector ...

  8. URAL 1132 Square Root(二次剩余定理)题解

    题意: 求\(x^2 \equiv a \mod p\) 的所有整数解 思路: 二次剩余定理求解. 参考: 二次剩余Cipolla's algorithm学习笔记 板子: //二次剩余,p是奇质数 l ...

  9. sdut3562-求字典序最小的最短路 按顶点排序后spfa的反例

    首先我们可以这么搞...倒序建图,算出源点s附近的点距离终点的距离,然后判断一下,终点是否能跑到源点 能跑到的话呢,我们就判断s周围的点是否在最短路上,然后我们选编号最小的点就好了 代码 #inclu ...

  10. CVE-2018-8120 提权

    经验证,诸多版本的Windows系统均存在该漏洞,文末同时附带一份利用该漏洞制作的提权工具,以供学习.经测试该工具支持Win2003 x32/x64.WinXP x32.Win7 x32/x64, W ...