事务
事务时恢复和并发控制的基本单位,简单来说,几个操作要么一起成功,要么一起失败,事务的4个特性: 原子性(Atomic) ,一致性 (Consistency) 隔离性 (Isolation) 持久性(Durablity) 也叫做 AICID 特性。
什么是本地事务
本地事务是指在单数据源上的访问和更新操作
transaction begin
insert/update/delete
...
transaction commit/rollback
一个事务只连接一个支持事务的数据源,需要保证事务的 ACID 特性,例如在下单常见,库存和订单如果在不同节点,就涉及分布式事务。
原理介绍
一些业务场景需要实现本地操作和消息发送事务一致性,即消息发送成功,则本地操作成功,消息发送失败, 本地操作失败(成功也需要回滚),以避免出现操作成功但消息发送失败,或者操作失败但消息发送成功的情况。
消息发送成功,事务操作成功时操作步骤如下所示:
- 生产者发送一条事务准备消息到事务消息队列。
- 生产者发送操作日志消息到操作日志队列,日志中包含步骤1消息的消息句柄。
- 生产者执行本地事务操作成功。
- 生产者请求修改消息延迟时间,使消息对消费者可见。
- 生产者向操作日志队列确认操作日志,删除日志消息。
- 消费者从事务消息队列中接收事务消息。
- 消费者处理事务消息。
- 消费者请求删除事务消息
消息发送成功,事务操作失败时操作步骤如下所示:
- 生产者发送一条事务准备消息到事务消息队列。
- 生产者发送操作日志信息到操作日志队列,日志中包含步骤1消息的消息句柄。
- 生产者执行本地事务操作失败。
- (对应上图中步骤A)操作日志队列向生产者发送消息,请求读取超时未确认操作日志
- (对应上图中步骤B)生产者检查事务结果,发现操作失败。
- (对应上图中步骤C)生产者提交回滚消息请求,不修改消息延迟时间,消息对消费者不可见。
- (对应上图中步骤D)生产者向操作日志队列确认操作日志,删除日志消息。
代码例子
public class TransactionMessageDemo {
public class MyTransactionChecker implements TransactionChecker {
@Override
public boolean checkTransactionStatus(Message message) {
boolean checkResult = false;
String messageHandler = message.getReceiptHandle();
try {
//检查messageHandler事务是否成功。
checkResult = true;
} catch (Exception e) {
checkResult = false;
}
return checkResult;
}
}
public class MyTransactionOperations implements TransactionOperations {
@Override
public boolean doTransaction(Message message) {
boolean transactionResult = false;
String messageHandler = message.getReceiptHandle();
String messageBody = message.getMessageBody();
try {
//根据messageHandler和messageBody执行本地事务。
transactionResult = true;
} catch (Exception e) {
transactionResult = false;
}
return transactionResult;
}
}
public static void main(String[] args) {
System.out.println("Start TransactionMessageDemo");
String transQueueName = "transQueueName";
String accessKeyId = ServiceSettings.getMNSAccessKeyId();
String accessKeySecret = ServiceSettings.getMNSAccessKeySecret();
String endpoint = ServiceSettings.getMNSAccountEndpoint();
CloudAccount account = new CloudAccount(accessKeyId, accessKeySecret, endpoint);
MNSClient client = account.getMNSClient(); //初始化客户端。
//为事务队列创建队列。
QueueMeta queueMeta = new QueueMeta();
queueMeta.setQueueName(transQueueName);
queueMeta.setPollingWaitSeconds(15);
TransactionMessageDemo demo = new TransactionMessageDemo();
TransactionChecker transChecker = demo.new MyTransactionChecker();
TransactionOperations transOperations = demo.new MyTransactionOperations();
TransactionQueue transQueue = client.createTransQueue(queueMeta, transChecker);
//执行事务。
Message msg = new Message();
String messageBody = "TransactionMessageDemo";
msg.setMessageBody(messageBody);
transQueue.sendTransMessage(msg, transOperations);
//如果不使用队列,请删除队列并关闭客户端。
transQueue.delete();
//关闭客户端。
client.close();
System.out.println("End TransactionMessageDemo");
}
}
RocketMQ 事务消息
事务模型
交互图
plantUML 代码
Producer -> SendMessageProcessor : sendTransactionMsg 发送事务消息
SenMessageProcessor -> TransactionMsgService: prepareMesage
TransactionMsgService -> TransactionBridge: putHalfMessage
TransactionBridge -> MessageStore: putMessage
MessageStore -> TransactionBridge : PutMsgResult
TransactionBride -> TransactionMsgService :putMsgResult
TransactionMsgService -> SendMessageProcessor :PutMsgResult
SendMessageProcessor -> Producer: PutMsgResult
Producer -> Producer : executeTransaction
Producer -> EndTransactionProcessor : commit/rollback
EndTransactionProcessor -> TransactionService : commitMessage/rollbackMessage
TransactionService -> TransactionBridge :putOpMessage
TransactionBridge -> MessageStore : putMessage
MessageStore -> TransactionBridge :putMessageResult
TransactionBridge -> TransactionMsgService : putMsgResult
TransactionMsgService -> EndTransactionProcessor : PutMsgResult
EndTransactionProcessor -> EndTransactionProcessor : SendFinalMesssage
EndTransactionProcessor -> Consummer : ConsumeMessage
时序图
事务型消息最终是为了保证最终一致性的,本地事务和消费者的操作保持一致。
- 首先,生产者会发送一个半成品消息发送给代理(不让消费者消费),此时代理返回给生产者一个 OK
- 本地执行任务,生产者访问
MySQL
执行一个事务 - 如果生产者本地事务执行成功了,生产者让代理告诉消费者可以消费消息了,如果生产者本地事务执行失败,生产者会告诉代理回滚,让消费者不要感知到消息。
- 生产者通知嗲了如果失败了,但是本地事务执行成功了,代理只收到一个半成品消息,但是久久收不到生产者的消息,这时需要一个回调机制去自动核查。
- 生产者收到核查通知后,检查数据是否提交成功
- 根据核查结果,将核查结果返回给代理(成功了 Commit, 失败了 rollback)
功能
- 将半消息发送给代理
- 生产者执行本地事务
- 消费者回查生产者数据库
如果回查了几次,还是没有一个成功的反馈怎么办? 将消息放到死信队列中处理。
应用场景:异步扣减库存
- 创建一个流水,生产者再发消息给代理
- 生产者执行的本地事务是先扣减库存,再创建订单,最后需要更新流水状态和销量。
- 第2步成功了,消费者扣减数据库的库存,失败不扣减,保持一致
- 回查,消费者检查数据库中的流水。