现在有一个业务场景为了保证数据一致性,可以使用事务消息。
教师节学生给老师送花活动,先校验学生和老师是否是师生关系,再判断学生金币是否够,够则扣减学生金币,同时给老师增加相同的花数,对老师收到的鲜花进行全国排名,排名前十的可以获得平台赠送的实体书籍作为奖励。为了保证数据的一致性和高并发,扣减金币之后发送事务消息。
核心代码如下:
import com.aliyun.openservices.shade.com.alibaba.rocketmq.common.message.Message;
import com.aliyun.openservices.shade.com.alibaba.rocketmq.client.exception.MQClientException;
import com.aliyun.openservices.shade.com.alibaba.rocketmq.client.producer.SendResult;
import com.aliyun.openservices.shade.com.alibaba.rocketmq.client.producer.TransactionCheckListener;
import com.aliyun.openservices.shade.com.alibaba.rocketmq.client.producer.TransactionMQProducer;
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
TransactionCheckListener transactionCheckListener = new TransactionCheckListenerImpl();
TransactionMQProducer producer = new TransactionMQProducer("transaction_Producer");
producer.setNamesrvAddr("192.168.100.145:9876;192.168.100.146:9876;192.168.100.149:9876;192.168.100.239:9876");
// 事务回查最小并发数
producer.setCheckThreadPoolMinSize(2);
// 事务回查最大并发数
producer.setCheckThreadPoolMaxSize(2);
// 队列数
producer.setCheckRequestHoldMax(2000);
producer.setTransactionCheckListener(transactionCheckListener);
producer.start();
TransactionExecuterImpl tranExecuter = new TransactionExecuterImpl();
for (int i = 1; i <= 2; i++) {
try {
Message msg = new Message("TopicTransactionTest", "transaction" + i, "KEY" + i,
("Hello RocketMQ " + i).getBytes());
SendResult sendResult = producer.sendMessageInTransaction(msg, tranExecuter, null);
System.out.println(sendResult);
Thread.sleep(10);
} catch (MQClientException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 100000; i++) {
Thread.sleep(1000);
}
producer.shutdown();
}
}
TransactionExecuterImpl类用于执行本地事务,与消息的发送构成一个原子操作,要么发送消息,要么回滚。
import com.aliyun.openservices.shade.com.alibaba.rocketmq.client.producer.LocalTransactionExecuter;
import com.aliyun.openservices.shade.com.alibaba.rocketmq.client.producer.LocalTransactionState;
import com.aliyun.openservices.shade.com.alibaba.rocketmq.common.message.Message;
public class TransactionExecuterImpl implements LocalTransactionExecuter {
@Override
public LocalTransactionState executeLocalTransactionBranch(Message msg, Object o) {
System.out.println("执行本地事务msg = " + new String(msg.getBody()));
//todo 执行本地事务,扣减学生金币,执行成功则提交,失败则回滚
String tags = msg.getTags();
if (tags.equals("transaction2")) {
System.out.println("======我的操作============,失败了 -进行ROLLBACK");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
//todo 本地写一个表t_message_transaction记录本次操作是提交还是回滚,方便回查操作
return LocalTransactionState.COMMIT_MESSAGE;
}
}
本地事务操作的结果最好是记录在一个t_message_transaction表中,不然可能需要查很多表才确定本地事务执行成功与否。 TransactionCheckListenerImpl用于回查本地事务,代码如下:
import com.aliyun.openservices.shade.com.alibaba.rocketmq.client.producer.LocalTransactionState;
import com.aliyun.openservices.shade.com.alibaba.rocketmq.client.producer.TransactionCheckListener;
import com.aliyun.openservices.shade.com.alibaba.rocketmq.common.message.MessageExt;
public class TransactionCheckListenerImpl implements TransactionCheckListener {
//在这里,我们可以根据由MQ回传的key去数据库查询,这条数据到底是成功了还是失败了。
public LocalTransactionState checkLocalTransactionState(MessageExt msg) {
System.out.println("未决事务,服务器回查客户端msg =" + new String(msg.getBody().toString()));
//todo 回查t_message_transaction表,确定事务最终状态
// return LocalTransactionState.ROLLBACK_MESSAGE;
return LocalTransactionState.COMMIT_MESSAGE;
// return LocalTransactionState.UNKNOW;
}
}
消费方在收到消息时,可以进行消费失败,为了保险起见,同一条消息没消费失败一次,可以在数据库将计数器加1,消费失败会累计16次重发,达到这个阈值可以触发报警向钉钉群里发消息,这里不再写消费端的代码了。
补充:
1. 分布式事务等于事务消息吗?
两者并没有关系,事务消息仅仅保证本地事务和MQ消息发送形成整体的原子性,而投递到MQ服务器后,消费者是否能一定消费成功是无法保证的。
延伸阅读,事务消息的源码解读:https://segmentfault.com/a/1190000019755235?utm_source=tag-newest