RabbitMQ进阶-消息确认事务机制
文章目录
1.RabbitMQ可靠性
如何保证消息成功发送?
- 当消息的生产者者发送消息后,消息到底有没有正确到达broker代理服务器呢?
- 什么情况下,能够让我们知道生产者生产的消息正确到达broker了?
- RabbitMQ如何保证消息的成功投递呢?
解决以上问题,RabbitMQ采用两种方式
- AMQP协议事务方式
- 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机制-生产者