RabbitMQ系列(十二)RabbitMQ进阶-消息的可靠性投递之消息确认事务机制

RabbitMQ进阶-消息确认事务机制

1.RabbitMQ可靠性

如何保证消息成功发送?

  1. 当消息的生产者者发送消息后,消息到底有没有正确到达broker代理服务器呢?
  2. 什么情况下,能够让我们知道生产者生产的消息正确到达broker了?
  3. RabbitMQ如何保证消息的成功投递呢?

解决以上问题,RabbitMQ采用两种方式

  1. AMQP协议事务方式
  2. Channel配置Confirm模式

本篇文章,我们详细讲一下事务机制

2.RabbitMQ事务机制
2.1 RabbitMQ事务机制

AMQP协议提供的一种保证消息成功投递的方式,通过将信道开启 transactional 模式

利用信道 Channel 的三个方式来实现以事务方式发送消息,若发送失败,通过异常处理回滚事务,确保消息成功投递

  • channel.txSelect(): 开启事务
  • channel.txCommit() :提交事务
  • channel.txRollback() :回滚事务

下面是AMQP源码、我们可以通过信道开启事务,通过事务来控制提交还是回滚

 /**
    * Enables TX mode on this channel.
    * @see com.rabbitmq.client.AMQP.Tx.Select
    * @see com.rabbitmq.client.AMQP.Tx.SelectOk
    * @return a transaction-selection method to indicate the transaction was successfully initiated
    * @throws java.io.IOException if an error is encountered
    */
   Tx.SelectOk txSelect() throws IOException;

   /**
    * Commits a TX transaction on this channel.
    * @see com.rabbitmq.client.AMQP.Tx.Commit
    * @see com.rabbitmq.client.AMQP.Tx.CommitOk
    * @return a transaction-commit method to indicate the transaction was successfully committed
    * @throws java.io.IOException if an error is encountered
    */
   Tx.CommitOk txCommit() throws IOException;

   /**
    * Rolls back a TX transaction on this channel.
    * @see com.rabbitmq.client.AMQP.Tx.Rollback
    * @see com.rabbitmq.client.AMQP.Tx.RollbackOk
    * @return a transaction-rollback method to indicate the transaction was successfully rolled back
    * @throws java.io.IOException if an error is encountered
    */
   Tx.RollbackOk txRollback() throws IOException;
2.2 无事务实战

我们先看下如果RabbitMQ不开启事务情况,他的流程如何,此处我们通过WireShark抓包AMQP协议来看下交互过程

package transaction;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import conn.MqConnectUtil;
import subscrib3.ExchangeTypeEnum;

import java.time.LocalDate;
import java.time.LocalTime;


public class NoTransProducer {

    /**
     * 队列名称
     */
    private static final String QUEUE_TEST = "queue_test";

    /**
     * 队列RoutingKey
     */
    private static final String RK_QUEUE_TEST = "rk.queue_test";

    /**
     * 生产 Direct直连 交换机的MQ消息
     */
    public static void produceDirectMessage() throws Exception {
        // 获取到连接以及mq通道
        Connection connection = MqConnectUtil.getConnectionDefault();
        // 从连接中创建通道
        Channel channel = connection.createChannel();

        /*声明 直连交换机 交换机 String exchange,
         * 参数明细
         * 1、交换机名称
         * 2、交换机类型,direct
         */
        channel.exchangeDeclare(ExchangeTypeEnum.DIRECT.getName(), ExchangeTypeEnum.DIRECT.getType());

        /*声明队列
         * 参数明细:
         * 1、队列名称
         * 2、是否持久化
         * 3、是否独占此队列
         * 4、队列不用是否自动删除
         * 5、参数
         */
        channel.queueDeclare(QUEUE_TEST, true, false, false, null);


        /*交换机和队列绑定String queue, String exchange, String routingKey
         * 参数明细
         * 1、队列名称
         * 2、交换机名称
         * 3、路由key rk.subscribe_queue_direct
         */
        channel.queueBind(QUEUE_TEST, ExchangeTypeEnum.DIRECT.getName(), RK_QUEUE_TEST);


        //定义消息内容(发布多条消息)
        String message = "Hello World! Time:" + LocalDate.now() + " " + LocalTime.now();
        /* 发送消息 String exchange, String routingKey, BasicProperties props, byte[] body
         * exchange - 交换机  DirectExchange
         * queuename - 队列信息
         * props - 参数信息
         * message 消息体 byte[]类型
         */
        channel.basicPublish(ExchangeTypeEnum.DIRECT.getName(), RK_QUEUE_TEST, null, message.getBytes());

        //关闭通道和连接
        channel.close();
        connection.close();
    }

    public static void main(String[] args) throws Exception {
        produceDirectMessage();
    }

}

WireShark抓包如下
可以看到队列声明、队列绑定、交换机声明、通道开启、通道关闭等等协议消息
在这里插入图片描述
这是没有事务的,没有出现上面的三个事务标签 txSelect()、txCommit()、txRollback() ,下面我们开启事务看一下

2.3 正常RabbitMQ事务

开启事务 txSelect,然后休眠20s提交事务,看消息什么时候发送,是basicPublish的时候发送,还是说 commit提交事务后发送的

package transaction;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import conn.MqConnectUtil;
import subscrib3.ExchangeTypeEnum;

import java.time.LocalDate;
import java.time.LocalTime;


public class TransProducer {

    /**
     * 队列名称
     */
    private static final String QUEUE_TEST = "queue_test";

    /**
     * 队列RoutingKey
     */
    private static final String RK_QUEUE_TEST = "rk.queue_test";

    /**
     * 生产 Direct直连 交换机的MQ消息
     */
    public static void produceDirectMessage() throws Exception {
        // 获取到连接以及mq通道
        Connection connection = MqConnectUtil.getConnectionDefault();
        // 从连接中创建通道
        Channel channel = connection.createChannel();

        /*声明 直连交换机 交换机 String exchange,
         * 参数明细
         * 1、交换机名称
         * 2、交换机类型,direct
         */
        channel.exchangeDeclare(ExchangeTypeEnum.DIRECT.getName(), ExchangeTypeEnum.DIRECT.getType());

        /*声明队列
         * 参数明细:
         * 1、队列名称
         * 2、是否持久化
         * 3、是否独占此队列
         * 4、队列不用是否自动删除
         * 5、参数
         */
        channel.queueDeclare(QUEUE_TEST, true, false, false, null);


        /*交换机和队列绑定String queue, String exchange, String routingKey
         * 参数明细
         * 1、队列名称
         * 2、交换机名称
         * 3、路由key rk.subscribe_queue_direct
         */
        channel.queueBind(QUEUE_TEST, ExchangeTypeEnum.DIRECT.getName(), RK_QUEUE_TEST);


        //定义消息内容(发布多条消息)
        String message = "Hello World! Time:" + LocalDate.now() + " " + LocalTime.now();
        /* 发送消息 String exchange, String routingKey, BasicProperties props, byte[] body
         * exchange - 交换机  DirectExchange
         * queuename - 队列信息
         * props - 参数信息
         * message 消息体 byte[]类型
         */
        try {
            //开启事务
            channel.txSelect();
            //发送消息
            channel.basicPublish(ExchangeTypeEnum.DIRECT.getName(), RK_QUEUE_TEST, null, message.getBytes());
            System.out.println(LocalTime.now() + " 发送消息:" + message);
            Thread.sleep(30000);
            channel.txCommit();
            System.out.println(LocalTime.now() + " 提交消息: " + message);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("异常、事务回滚,未成功发送消息");
            channel.txRollback();
        }
        //关闭通道和连接
        channel.close();
        connection.close();
    }

    public static void main(String[] args) throws Exception {
        produceDirectMessage();
    }
}

basicPublish代码执行发送完毕后,未提交事务时,队列中依旧没有消息
在这里插入图片描述
tx.Commit事务提交完毕后,队列中才出现消息,不提交事务,是没有消息的
在这里插入图片描述
WireShark 看下AMQP协议包,事务提交过程,可以明显看到,AMQP协议多了 Tx.Select、Tx.Select-OK、Tx.Commit、Tx.Commit-OK 这几个事务的过程
!!!我们看到Tx.Commit和Tx.Commit-OK 之间从 1889-2902花费了 1003ms,由此可见事务还是比较耗时的
在这里插入图片描述

2.4 异常RabbitMQ事务回滚

我们把上面代码中的 事务提交核心代码换一下 int result = 100 / 0; 使之抛出异常,从而触发回滚,看下消息处理是什么流程

回滚事务核心代码

        try {
            //开启事务
            channel.txSelect();
            //发送消息
            channel.basicPublish(ExchangeTypeEnum.DIRECT.getName(), RK_QUEUE_TEST, null, message.getBytes());
            System.out.println(LocalTime.now() + " 发送消息:" + message);
            //此处 抛出异常,不再提交事务,catchException后,触发事务回滚
            int result = 100 / 0;
            channel.txCommit();
            System.out.println(LocalTime.now() + " 提交消息: " + message);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("异常、事务回滚,未成功发送消息");
            channel.txRollback();
        }

执行异常事务代码,看下消息
在这里插入图片描述
可以看到,事务开启Tx.Select后,过程出现异常、并未提交,消息也并没有发送成功,队列中一直没有消息
而且 触发回滚,执行了Tx.Rollback 回滚操作

我们看下WireShark 抓包看下AMQP协议消息流程
在这里插入图片描述
!!!可以看到 事务Tx.Rollback 和Tx.Rollback-OK之间,从628-1041花费了713ms,事务的回滚也是相当耗时的

事务确实能够解决producer与broker之间消息确认的问题

只有消息成功被broker接受,事务提交才能成功,否则我们便可以在捕获异常进行事务回滚操作同时进行消息重发

但是使用事务机制的话会降低RabbitMQ的性能


下篇文章我们讲一下 channel信道设置成confirm模式,这种模式既能保证可靠性、又能提高RabbitMQ的性能
RabbitMQ系列(十三)RabbitMQ进阶-消息确认机制之Confirm机制-生产者

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值