1. 安装搭建 RocketMQ 服务器
搭建单机 Rocketmq 服务器笔记:
《关于Linux中安装Rocketmq说明》
搭建双主双从同步复制 Rocketmq 服务器笔记:
《关于LInux中RocketMQ双主双从同步复制集群说明》
2. 基于 RocketMQ 可靠消息的分布式事务方案原理
3. 基于 RocketMQ 可靠消息的分布式事务方案实现
3.1 准备工作
3.1.1 创建tx_table表
Rocketmq收到事务消息后,会等待生产者提交或回滚该消息。如果无法得到生产者的提交或回滚指令,则会主动向生产者询问消息状态,称为回查。
在 order 项目中,为了让Rocketmq可以回查到事务的状态,需要记录事务的状态,所以我们添加一个事务的状态表来记录事务状态。
CREATE TABLE tx_table(
`xid` char(32) PRIMARY KEY COMMENT '事务id',
`status` int COMMENT '0-提交,1-回滚,2-未知',
`created_at` BIGINT UNSIGNED NOT NULL COMMENT '创建时间'
);
3.1.2 创建order-topic
使用 order-topic
来收发消息,在 Rocketmq 服务器上创建这个 Topic:
3.1.3 添加RocketMQ 依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
3.1.4 修改application.yml配置
rocketmq:
name-server: 192.168.126.126:9876
producer:
group: order-group
3.2 添加生产者代码
3.2.1 添加TxMapper
访问事务状态表
事务状态保存到tx_table
表,在 TxMapper 接口和 TxMapper.xml 中添加事务状态数据的读写方法。
本地事务执行后要保存事务信息(事务id、事务状态)到数据库,以便之后进行事务回查,首先创建封装事务信息的类 TxInfo :
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TxInfo {
private String xid;
private Long created;
private Integer status;
}
TxMapper 接口:
public interface TxMapper extends BaseMapper<TxInfo> {
@select("SELECT COUNT(1) FROM tx_table WHERE xid=#{xid}")
Boolean exists(String xid);
}
3.2.2 TxOrderService 发送事务消息
TxAccountMessage 封装发送给账户服务的数据:用户id和扣减金额。另外还封装了事务id。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TxAccountMessage {
Long userId;
BigDecimal money;
String xid;
}
在业务方法 create()
中不直接保存订单,而是发送事务消息。
消息发出后,会触发 TxListener
执行本地事务,它执行时会回调这里的 doCreate()
方法完成订单的保存。
@Slf4j
@Primary
@Service
public class TxOrderService implements OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private OrderMapper orderMapper;
@Autowired
private TxMapper txMapper;
@Autowired
EasyIdGeneratorClient easyIdGeneratorClient;
/*
创建订单的业务方法
这里修改为:只向 Rocketmq 发送事务消息。
*/
@Override
public void create(Order order) {
// 产生事务ID
String xid = UUID.randomUUID().toString().replace("-", "");
//对事务相关数据进行封装,并转成 json 字符串
TxAccountMessage sMsg = new TxAccountMessage(order.getUserId(), order.getMoney(), xid);
String json = JsonUtil.to(sMsg);
//json字符串封装到 Spring Message 对象
Message<String> msg = MessageBuilder.withPayload(json).build();
//发送事务消息
rocketMQTemplate.sendMessageInTransaction("order-topic:account", msg, order);
log.info("事务消息已发送");
}
//本地事务,执行订单保存
//这个方法在事务监听器中调用
@Transactional
public void doCreate(Order order, String xid) {
log.info("执行本地事务,保存订单");
// 从全局唯一id发号器获得id
Long orderId = easyIdGeneratorClient.nextId("order_business");
order.setId(orderId);
orderMapper.create(order);
log.info("订单已保存! 事务日志已保存");
}
}
3.2.3 TxListener 事务监听器
发送事务消息后会触发事务监听器执行。
事务监听器有两个方法:
executeLocalTransaction()
: 执行本地事务checkLocalTransaction()
: 负责响应Rocketmq服务器的事务回查操作
@Slf4j
@Component
@RocketMQTransactionListener
public class TxListener implements RocketMQLocalTransactionListener {
@Autowired
private TxOrderService orderService;
@Autowired
private TxMapper txMapper;
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
log.info("事务监听 - 开始执行本地事务");
// 监听器中得到的 message payload 是 byte[]
String json = new String((byte[]) message.getPayload());
String xid = JsonUtil.getString(json, "xid");
log.info("事务监听 - "+json);
log.info("事务监听 - xid: "+xid);
RocketMQLocalTransactionState state;
int status = 0;
Order order = (Order) o;
try {
orderService.doCreate(order, xid);
log.info("本地事务执行成功,提交消息");
state = RocketMQLocalTransactionState.COMMIT;
status = 0;
} catch (Exception e) {
e.printStackTrace();
log.info("本地事务执行失败,回滚消息");
state = RocketMQLocalTransactionState.ROLLBACK;
status = 1;
}
TxInfo txInfo = new TxInfo(xid, System.currentTimeMillis(), status);
txMapper.insert(txInfo);
return state;
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
log.info("事务监听 - 回查事务状态");
// 监听器中得到的 message payload 是 byte[]
String json = new String((byte[]) message.getPayload());
String xid = JsonUtil.getString(json, "xid");
TxInfo txInfo = txMapper.selectById(xid);
if (txInfo == null) {
log.info("事务监听 - 回查事务状态 - 事务不存在:"+xid);
return RocketMQLocalTransactionState.UNKNOWN;
}
log.info("事务监听 - 回查事务状态 - "+ txInfo.getStatus());
switch (txInfo.getStatus()) {
case 0: return RocketMQLocalTransactionState.COMMIT;
case 1: return RocketMQLocalTransactionState.ROLLBACK;
default: return RocketMQLocalTransactionState.UNKNOWN;
}
}
}
3.3 添加消费者代码
3.3.1 修改application.yml 配置
rocketmq:
name-server: 192.168.126.129:9876
3.3.2 TxConsumer 接收事务消息
接收的消息转换成 TxAccountMessage
对象,这里先创建这个类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TxAccountMessage {
Long userId;
BigDecimal money;
String xid;
}
TxConsumer
实现消息监听,收到消息后完成扣减金额业务:
@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "account-consumer-group", topic = "order-topic", selectorExpression = "account")
public class TxConsumer implements RocketMQListener<String> {
@Autowired
private AccountService accountService;
@Override
public void onMessage(String msg) {
TxAccountMessage txAccountMessage = JsonUtil.from(msg, new TypeReference<TxAccountMessage>() {});
log.info("收到消息: "+txAccountMessage);
accountService.decrease(txAccountMessage.getUserId(), txAccountMessage.getMoney());
}
}
3.3.3 AccountServiceImpl 添加事务注解
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Transactional
@Override
public void decrease(Long userId, BigDecimal money) {
accountMapper.decrease(userId,money);
}
}
3.4 order 本地事务失败测试
@Slf4j
@Primary
@Service
public class TxOrderService implements OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private OrderMapper orderMapper;
@Autowired
private TxMapper txMapper;
@Autowired
EasyIdGeneratorClient easyIdGeneratorClient;
/*
创建订单的业务方法
这里修改为:只向 Rocketmq 发送事务消息。
*/
@Override
public void create(Order order) {
// 产生事务ID
String xid = UUID.randomUUID().toString().replace("-", "");
//对事务相关数据进行封装,并转成 json 字符串
TxAccountMessage sMsg = new TxAccountMessage(order.getUserId(), order.getMoney(), xid);
String json = JsonUtil.to(sMsg);
//json字符串封装到 Spring Message 对象
Message<String> msg = MessageBuilder.withPayload(json).build();
//发送事务消息
log.info("开始发送事务消息");
rocketMQTemplate.sendMessageInTransaction("order-topic:account", msg, order);
log.info("事务消息已发送");
}
//本地事务,执行订单保存
//这个方法在事务监听器中调用
@Transactional
public void doCreate(Order order, String xid) {
log.info("执行本地事务,保存订单");
// 从全局唯一id发号器获得id
Long orderId = easyIdGeneratorClient.nextId("order_business");
order.setId(orderId);
orderMapper.create(order);
if (Math.random() < 0.5) {
throw new RuntimeException("模拟异常");
}
log.info("订单已保存! 事务日志已保存");
}
}
3.5 account 本地事务失败测试
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Transactional
@Override
public void decrease(Long userId, BigDecimal money) {
accountMapper.decrease(userId,money);
if (Math.random() < 0.5) {
throw new RuntimeException("模拟异常");
}
}
}