RabbitMQ事务和Confirm发送方消息确认——深入解读

RabbitMQ系列文章

  1. RabbitMQ在Ubuntu上的环境搭建
  2. 深入了解RabbitMQ工作原理及简单使用
  3. RabbitMQ交换器Exchange介绍与实践
  4. RabbitMQ事务和Confirm发送方消息确认——深入解读
  5. 使用Docker部署RabbitMQ集群
  6. 你不知道的RabbitMQ集群架构全解

引言

根据前面的知识(深入了解RabbitMQ工作原理及简单使用Rabbit的几种工作模式介绍与实践)我们知道,如果要保证消息的可靠性,需要对消息进行持久化处理,然而消息持久化除了需要代码的设置之外,还有一个重要步骤是至关重要的,那就是保证你的消息顺利进入Broker(代理服务器),如图所示:

正常情况下,如果消息经过交换器进入队列就可以完成消息的持久化,但如果消息在没有到达broker之前出现意外,那就造成消息丢失,有没有办法可以解决这个问题?

RabbitMQ有两种方式来解决这个问题:

  1. 通过AMQP提供的事务机制实现;
  2. 使用发送者确认模式实现;

一、事务使用

事务的实现主要是对信道(Channel)的设置,主要的方法有三个:

  1. channel.txSelect()声明启动事务模式;

  2. channel.txComment()提交事务;

  3. channel.txRollback()回滚事务;

从上面的可以看出事务都是以tx开头的,tx应该是transaction extend(事务扩展模块)的缩写,如果有准确的解释欢迎在博客下留言。

我们来看具体的代码实现:

  1. // 创建连接
  2. ConnectionFactory factory = new ConnectionFactory();
  3. factory.setUsername(config.UserName);
  4. factory.setPassword(config.Password);
  5. factory.setVirtualHost(config.VHost);
  6. factory.setHost(config.Host);
  7. factory.setPort(config.Port);
  8. Connection conn = factory.newConnection();
  9. // 创建信道
  10. Channel channel = conn.createChannel();
  11. // 声明队列
  12. channel.queueDeclare(_queueName, true, false, false, null);
  13. String message = String.format("时间 => %s", new Date().getTime());
  14. try {
  15. channel.txSelect(); // 声明事务
  16. // 发送消息
  17. channel.basicPublish("", _queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
  18. channel.txCommit(); // 提交事务
  19. } catch (Exception e) {
  20. channel.txRollback();
  21. } finally {
  22. channel.close();
  23. conn.close();
  24. }

注意:用户需把config.xx配置成自己Rabbit的信息。

从上面的代码我们可以看出,在发送消息之前的代码和之前介绍的都是一样的,只是在发送消息之前,需要声明channel为事务模式,提交或者回滚事务即可。

了解了事务的实现之后,那么事务究竟是怎么执行的,让我们来使用wireshark抓个包看看,如图所示:

输入ip.addr==rabbitip && amqp查看客户端和rabbit之间的通讯,可以看到交互流程:

  • 客户端发送给服务器Tx.Select(开启事务模式)
  • 服务器端返回Tx.Select-Ok(开启事务模式ok)
  • 推送消息
  • 客户端发送给事务提交Tx.Commit
  • 服务器端返回Tx.Commit-Ok

以上就完成了事务的交互流程,如果其中任意一个环节出现问题,就会抛出IoException移除,这样用户就可以拦截异常进行事务回滚,或决定要不要重复消息。

那么,既然已经有事务了,没什么还要使用发送方确认模式呢,原因是因为事务的性能是非常差的。事务性能测试

事务模式,结果如下:

  • 事务模式,发送1w条数据,执行花费时间:14197s
  • 事务模式,发送1w条数据,执行花费时间:13597s
  • 事务模式,发送1w条数据,执行花费时间:14216s

非事务模式,结果如下:

  • 非事务模式,发送1w条数据,执行花费时间:101s
  • 非事务模式,发送1w条数据,执行花费时间:77s
  • 非事务模式,发送1w条数据,执行花费时间:106s

从上面可以看出,非事务模式的性能是事务模式的性能高149倍,我的电脑测试是这样的结果,不同的电脑配置略有差异,但结论是一样的,事务模式的性能要差很多,那有没有既能保证消息的可靠性又能兼顾性能的解决方案呢?那就是接下来要讲的Confirm发送方确认模式。

扩展知识

我们知道,消费者可以使用消息自动或手动发送来确认消费消息,那如果我们在消费者模式中使用事务(当然如果使用了手动确认消息,完全用不到事务的),会发生什么呢?

消费者模式使用事务

假设消费者模式中使用了事务,并且在消息确认之后进行了事务回滚,那么RabbitMQ会产生什么样的变化?

结果分为两种情况:

  1. autoAck=false手动应对的时候是支持事务的,也就是说即使你已经手动确认了消息已经收到了,但在确认消息会等事务的返回解决之后,在做决定是确认消息还是重新放回队列,如果你手动确认现在之后,又回滚了事务,那么已事务回滚为主,此条消息会重新放回队列;
  2. autoAck=true如果自定确认为true的情况是不支持事务的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队列已经把消息移除了;

二、Confirm发送方确认模式

Confirm发送方确认模式使用和事务类似,也是通过设置Channel进行发送方确认的。

Confirm的三种实现方式:

方式一:channel.waitForConfirms()普通发送方确认模式;

方式二:channel.waitForConfirmsOrDie()批量确认模式;

方式三:channel.addConfirmListener()异步监听发送方确认模式;

方式一:普通Confirm模式

  1. // 创建连接
  2. ConnectionFactory factory = new ConnectionFactory();
  3. factory.setUsername(config.UserName);
  4. factory.setPassword(config.Password);
  5. factory.setVirtualHost(config.VHost);
  6. factory.setHost(config.Host);
  7. factory.setPort(config.Port);
  8. Connection conn = factory.newConnection();
  9. // 创建信道
  10. Channel channel = conn.createChannel();
  11. // 声明队列
  12. channel.queueDeclare(config.QueueName, false, false, false, null);
  13. // 开启发送方确认模式
  14. channel.confirmSelect();
  15. String message = String.format("时间 => %s", new Date().getTime());
  16. channel.basicPublish("", config.QueueName, null, message.getBytes("UTF-8"));
  17. if (channel.waitForConfirms()) {
  18. System.out.println("消息发送成功" );
  19. }

看代码可以知道,我们只需要在推送消息之前,channel.confirmSelect()声明开启发送方确认模式,再使用channel.waitForConfirms()等待消息被服务器确认即可。

方式二:批量Confirm模式

  1. // 创建连接
  2. ConnectionFactory factory = new ConnectionFactory();
  3. factory.setUsername(config.UserName);
  4. factory.setPassword(config.Password);
  5. factory.setVirtualHost(config.VHost);
  6. factory.setHost(config.Host);
  7. factory.setPort(config.Port);
  8. Connection conn = factory.newConnection();
  9. // 创建信道
  10. Channel channel = conn.createChannel();
  11. // 声明队列
  12. channel.queueDeclare(config.QueueName, false, false, false, null);
  13. // 开启发送方确认模式
  14. channel.confirmSelect();
  15. for (int i = 0; i < 10; i++) {
  16. String message = String.format("时间 => %s", new Date().getTime());
  17. channel.basicPublish("", config.QueueName, null, message.getBytes("UTF-8"));
  18. }
  19. channel.waitForConfirmsOrDie(); //直到所有信息都发布,只要有一个未确认就会IOException
  20. System.out.println("全部执行完成");

以上代码可以看出来channel.waitForConfirmsOrDie(),使用同步方式等所有的消息发送之后才会执行后面代码,只要有一个消息未被确认就会抛出IOException异常。

方式三:异步Confirm模式

  1. // 创建连接
  2. ConnectionFactory factory = new ConnectionFactory();
  3. factory.setUsername(config.UserName);
  4. factory.setPassword(config.Password);
  5. factory.setVirtualHost(config.VHost);
  6. factory.setHost(config.Host);
  7. factory.setPort(config.Port);
  8. Connection conn = factory.newConnection();
  9. // 创建信道
  10. Channel channel = conn.createChannel();
  11. // 声明队列
  12. channel.queueDeclare(config.QueueName, false, false, false, null);
  13. // 开启发送方确认模式
  14. channel.confirmSelect();
  15. for (int i = 0; i < 10; i++) {
  16. String message = String.format("时间 => %s", new Date().getTime());
  17. channel.basicPublish("", config.QueueName, null, message.getBytes("UTF-8"));
  18. }
  19. //异步监听确认和未确认的消息
  20. channel.addConfirmListener(new ConfirmListener() {
  21. @Override
  22. public void handleNack(long deliveryTag, boolean multiple) throws IOException {
  23. System.out.println("未确认消息,标识:" + deliveryTag);
  24. }
  25. @Override
  26. public void handleAck(long deliveryTag, boolean multiple) throws IOException {
  27. System.out.println(String.format("已确认消息,标识:%d,多个消息:%b", deliveryTag, multiple));
  28. }
  29. });

异步模式的优点,就是执行效率高,不需要等待消息执行完,只需要监听消息即可,以上异步返回的信息如下:

可以看出,代码是异步执行的,消息确认有可能是批量确认的,是否批量确认在于返回的multiple的参数,此参数为bool值,如果true表示批量执行了deliveryTag这个值以前的所有消息,如果为false的话表示单条确认。

Confirm性能测试

测试前提:与事务一样,我们发送1w条消息。

方式一:Confirm普通模式

  • 执行花费时间:2253s
  • 执行花费时间:2018s
  • 执行花费时间:2043s

方式二:Confirm批量模式

  • 执行花费时间:1576s
  • 执行花费时间:1400s
  • 执行花费时间:1374s

方式三:Confirm异步监听方式

  • 执行花费时间:1498s
  • 执行花费时间:1368s
  • 执行花费时间:1363s

总结

综合总体测试情况来看:Confirm批量确定和Confirm异步模式性能相差不大,Confirm模式要比事务快10倍左右。

RabbitMQ系列(四)RabbitMQ事务和Confirm发送方消息确认——深入解读的更多相关文章

  1. RabbitMQ事务和Confirm发送方消息确认

    RabbitMQ事务和Confirm发送方消息确认——深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...

  2. 消息中间件系列三:使用RabbitMq原生Java客户端进行消息通信(消费者(接收方)自动确认模式、消费者(接收方)自行确认模式、生产者(发送方)确认模式)

    准备工作: 1)安装RabbitMQ,参考文章:消息中间件系列二:RabbitMQ入门(基本概念.RabbitMQ的安装和运行) 2.)分别新建名为OriginalRabbitMQProducer和O ...

  3. RabbitMQ系列(四)--消息如何保证可靠性传输以及幂等性

    一.消息如何保证可靠性传输 1.1.可能出现消息丢失的情况 1.Producer在把Message发送Broker的过程中,因为网络问题等发生丢失,或者Message到了Broker,但是出了问题,没 ...

  4. rabbitmq系列四 之路由

    1.路由 在上一个的教程中,我们构建了一个简单的日志记录系统.我们能够向许多接收者广播日志消息. 在本次教程中,我们向该系统添加一些特性,比如,我只需要严重错误(erroe级别)的部分日志打印到磁盘文 ...

  5. Python操作rabbitmq系列(二):多个接收端消费消息

    今天,我们要逐步开始讨论rabbitmq稍微高级点的耍法了.了解这一步,对我们设计高并发的系统非常有用.当然,还可以使用kafka.不过还是算了,有几个硬性条件不支持,还是用rabbitmq吧. 循环 ...

  6. RabbitMQ系列之RabbitMQ单机安装

    安装epel源 rpm -ivh http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm #ht ...

  7. RabbitMQ系列之RabbitMQ集群

    本文的集群是RabbitMq 自身的Cluster搭建 . #hosts准备: vim /etc/hosts #172.16.3.79 s1 #172.16.3.105 s2 172.16.2.10 ...

  8. Python操作rabbitmq系列(三):多个接收端消费消息

    接着上一章.这一章,我们要将同一个消息发给多个客户端.这就是发布订阅模式.直接看代码: 发送端: import pikaimport sys connection = pika.BlockingCon ...

  9. RabbitMQ系列(三)RabbitMQ交换器Exchange介绍与实践

    RabbitMQ交换器Exchange介绍与实践 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchang ...

随机推荐

  1. 配置Tomcat时遇到的问题

    今天准备开始JavaWeb的学习,先配置tomcat,前期一切顺利,可当我打开startup.bat,访问localhost:8080时,却显示localhost 拒绝了我们的连接请求. 于是我开始在 ...

  2. Linux 第十四天

    6)Bash常用快捷键 快捷键 作用 ctr1+ a 把光标移动到命令行开头.如果我们输入的命令过长,想要把光标移| 动到命令行开头时使用. ctr1+e 把光标移动到命令行结尾. ctr1+c 强制 ...

  3. anemometer安装

    1.背景介绍:nginx:1.9.3 安装路径/data/nginxphp:5.5.27 安装路径 /data/phpmysql:5.7.18 安装路径/usr/local/mysql软件下载目录 / ...

  4. Codeforces Round #485 (Div. 2) D. Fair

    Codeforces Round #485 (Div. 2) D. Fair 题目连接: http://codeforces.com/contest/987/problem/D Description ...

  5. python列表和字符串的三种逆序遍历方式

    python列表和字符串的三种逆序遍历方式 列表的逆序遍历 a = [1,3,6,8,9] print("通过下标逆序遍历1:") for i in a[::-1]: print( ...

  6. Geomystery现已上架!

    欢迎体验由王涵.程立智.郑昊.蔡镇泽.李明伦.温志成同学开发的几何解谜游戏Geomystery,现已上架Microsoft官方商城! https://www.microsoft.com/zh-cn/s ...

  7. java面试一、1.1基础

    免责声明:     本文内容多来自网络文章,转载为个人收藏,分享知识,如有侵权,请联系博主进行删除. 基础篇 1.1Java基础 面向对象的特征:继承.封装和多态 三大特性是:封装,继承,多态 所谓封 ...

  8. sublime text3如何在浏览器预览?

    插件: view-in-browser CTRL + ALT + V 打开浏览器 默认打开firefox,settings里面可修改. Sublime Text - View In Browser V ...

  9. OC重写init方法

    在创建一个对象的时候我们经常会用到init方法,单单是init只能是初始化,当我们在初始化的时候想要给这个对象加上默认的东西的时候, 系统提供的init方法就不能满足我们的需要,这时,就需要我们自己去 ...

  10. Exp4 恶意代码分析 ——20164325王晓蕊

    1.实践目标 监控你自己系统的运行状态,看有没有可疑的程序在运行. 分析一个恶意软件,就分析Exp2或Exp3中生成后门软件:分析工具尽量使用原生指令或sysinternals,systracer套件 ...