消息队列的主要功能是系统间解耦,实现流量的削峰填谷。主流的消息队列一般有三个核心操作:消费者发送消息,Broker 保存消息,消费者消费消息。如下图:
图片
对于一个完整的事务消息,可以理解为生产者生产消息和消费者消费消息,这两个操作要不全部成功要不全部失败。但事实上,很难有消息队列来实现生产消息和消费消息的事务特性。
1 事务消息实现
1.1 生产者事务
一些消息队列在生产端实现事务消息,对生产的一批消息要不全部发送成功,要不全部发送失败。
下图是 RabbitMQ 的事务消息:
图片
RabbitMQ 的事务消息实现的就是生产者的事务消息,发送消息的时候,把 Channel 设置为事务模式,这样这一批消息就会缓存在 Channel,等提交事务时才会提交到 Broker。可以参考下面代码:
ConnectionFactory factory=new ConnectionFactory();
cnotallow=factory.newConnection();
Channel channel=connection.createChannel();
//开启事务
channel.txSelect();
channel.basicPublish("directTransactionExchange","transactionRoutingKey",null,message.getBytes("utf-8"));
//提交事务 或者 channel.txRollback()回滚事务
channel.txCommit();
Kafka 的事务消息类似,指定一个事务 id,集群中不同的生产者都可以使用这个事务 id 来实现事务消息。看下面代码:
String transactionId = "xxxx123yyy"
Properties properties = new Properties();
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, transactionId);
KafkaProducer producer = new KafkaProducer(properties);
//开启事务消息
producer.initTransactions();
producer.beginTransaction();
try {
//发送消息
String topic = "testTransactionId"
producer.send(new ProducerRecord(topic, "msg1"));
producer.send(new ProducerRecord(topic, "msg2"));
producer.send(new ProducerRecord(topic, "msg3"));
// 提交事务
producer.commitTransaction();
} catch (ProducerFencedException e) {
producer.abortTransaction();
}
producer.close();
使用同一个事务 id 后,集群中的多个生产者可以实现对不同 Topic、不同分区的原子性写入。在提交事务之前,这些消息是不可见的,事务提交之后,这些消息才能变为可见。
1.2 生产者+本地事务
生产者加本地事务也是消息队列事务消息的一种实现方式。这种事务是指本地事务和生产者生产消息是一个原子性操作,要不都成功,要不都失败。如下图所示:
图片
RocketMQ 的事务消息是首先发送 half 消息到 Broker,然后执行本地事务,最后执行 commit/rollback 操作。执行了 commit 操作后,消费者就可以拉取到这条消息了。如下图:
图片
RocketMQ 的 half 消息是将消息投递到 topic 为 RMQ_SYS_TRANS_HALF_TOPIC 中 queueId 等于 0 的队列中,当 RocketMQ 发送 commit 消息后再把消息投递到原始队列,这样消费者就可以拉取到这条消息了。
2 TCC 模式
从上面的讲解可以看到,消息队列的事务消息使用的是两阶段提交的方式。那跟 TCC 模式的两阶段提交有什么区别呢?这节我们来看一下 TCC 模式。
2.1 TCC 概述
TCC 模式将分布式事务分为 try 和 commit/rollback 两个阶段来执行,try 阶段对每个分支事务进行预留资源,如果 try 阶段所有节点都执行成功,则进入 commit 阶段提交全局事务,只要有一个节点执行失败则进入 rollback 阶段回滚全局事务。
TCC 模式有三个角色,TM、RM 和 TC:
- TC: 协调节点,保存分支事务元数据和状态,对分支事务发送提交或回滚命令;
- TM:管理全局事务,包括开启全局事务,提交/回滚全局事务;
- RM:管理分支事务,包括提交或回滚本地事务,向 TC 注册分支事务并上报分支事务状态。
以购物场景中保存订单、扣减库存、扣减金额的例子来讲,try 阶段如下图:
图片
commit/rollback 阶段如下图:
图片
可以看到,TCC 模式的两阶段提交,第一阶段是用来尝试预留资源,第二阶段来扣减资源或释放资源。而消息队列中生产消息、保存消息、消费消息是不需要预留资源的,这是完全不同的业务场景。
3 总结
本文主要讲述了消息队列中的事务消息跟分布式事务中两阶段提交的 TCC 模式在实现方式上的区别。TCC 模式中两阶段提交的目的主要是用来确定资源是否可用,而消息队列的事务消息是不需要考虑资源的,二者是不同的业务场景。