目录
1. 如何实现消息最终一致性事物
生产者发送消息到RockerMQ, 消费者接收消息处理, 如果处理失败, 会一直重试直到成功, 一共重试16次, 如果还是失败, 则需要人工兜底
2. 编辑pom.xml配置文件
pom.xml文件添加依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
3. 编辑application.yml配置文件
rocketmq:
name-server: 192.168.126.130:9876
producer:
group: orderGroup
一般在service层使用, 不直接请求Mapper层进行数据库存储, 而是通过消息队列将大量的请求进行削峰平谷
4. 添加事务状态表
Rocketmq收到事务消息后,会等待生产者提交或回滚该消息。如果无法得到生产者的提交或回滚指令,则会主动向生产者询问消息状态,称为回查。
在 order 项目中,为了让Rocketmq可以回查到事务的状态,需要记录事务的状态,所以我们添加一个事务的状态表来记录事务状态。
一般需要添加你要发送消息的事物id, 状态码, 时间
5. Rocketmq 中添加 Topic
6. 发送消息
6.1 使用RocketMQTemplate对象发送消息
@Autowired
private RocketMQTemplate rocketMQTemplate;
- 使用Map或者pojo对象封装消息
- 将map或者对象转换成json串, 可以使用JsonUtil工具(网上开源)
注意: HashMap和对象转换成json的区别, HashMap转换成对象, 如果key对应value值为null, 会丢失该key, 而对象不会 - 将String类型的Json串封装到Spring通用的消息对象Message
- 发送消息
实现:
order.getUserId() 需要修改数据的id值
order.getMoney() 需要修改的数据
xid 事物id
// AccountMessage pojo对象封装数据(全参构造)
AccountMessage am =
new AccountMessage(order.getUserId(), order.getMoney(), xid);
// 转成json
// "{userId:8, money:534.534, xid:y4y54}"
String json = JsonUtil.to(am);
// json封装到 spring 的通用 message
/*调用MessageBuilder.withPayload(数据).bulid();
构建消息体*/
Message<String> msg = MessageBuilder.withPayload(json).build();
// 发送消息
/* 如果有tag(标签),格式: orderTopic:tag1
Topic 相当于消息的一级分类
Tag 相当于消息的二级分类
*/
// rocketMQTemplate.sendMessageInTransaction( "orderTopic", msg, 执行监听器需要的业务数据参数);
rocketMQTemplate.sendMessageInTransaction("orderTopic", msg, order);
sendMessageInTransaction()方法发送数据
该方法有三个参数:
- destination:目的地(主题),这里发送给orderTopic这个主题, 如果有tag,则需要发送的主题为orderTopic:tag1
- msg:发送给消费者的消息体,需要使用MessageBuilder.withPayload().build() 来构建消息
- order:消息参数
7. 执行本地事物
实现RocketMQLocalTransactionListener接口
重写方法
- executeLocalTransaction(Message var1, Object var2);
- checkLocalTransaction(Message var1);
7.1 事物执行方法
executeLocalTransaction(Message msg, Object o);
该方法会在消息预写入(半消息)Rocketmq就会开始执行
参数:
- Message msg 消息体
- Object o 消息参数
返回值:
事物状态:
状态 | 效果 |
---|---|
RocketMQLocalTransactionState.COMMIT | 提交 |
RocketMQLocalTransactionState.ROLLBACK | 回滚 |
RocketMQLocalTransactionState.UNKNOWN | 未知 |
实现
@Transactional
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
// 存储到数据表的状态值用 0-成功,1-失败 表示
int status;
// 返回的事务状态是 State 对象
RocketMQLocalTransactionState state;
try {
Order order = (Order) o;
// 执行原来的业务代码,即执行数据库操作
doCreate(order);
//设置确定存储到数据库
status = 0;
//设置事物状态成功
state = RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
log.error("本地事务存储订单失败", e);
status = 1;
state = RocketMQLocalTransactionState.ROLLBACK;
}
// 把事务状态存到数据表的 tx_table
// "{userId:8, money:534.534, xid:y4y54}"
String json = new String((byte[]) message.getPayload());
String xid = JsonUtil.getString(json, "xid");
txMapper.insert(new TxInfo(xid, status, System.currentTimeMillis()));
return state;
}
7.2 事物状态回查
1. 回查时间:
发送的半消息会存储在RMQ_SYS_TRANS_HALF_TOPIC主题中
RocketMQ使用TransactionalMessageCheckService线程定时去检测
RMQ_SYS_TRANS_HALF_TOPIC主题中的消息,回查消息的事务状态。
TransactionalMessageCheckService的检测频率默认1分钟,可通过在broker.conf文件中设置transactionCheckInterval的值来改变默认值,单位为毫秒。
2. 回查次数:
次数: 3次
在broker.conf文件中设置transactionCheckMax的值改变回查次数
3. 方法参数:
Message msg 消息体
4. 返回值:
事物状态
添加TxMapper实现查询, 事物状态表
public interface TxMapper extends BaseMapper<TxInfo> {
// 存储事务状态 insert() ,通过MyBatisPlus自带的方法
// 查询事务状态 selectById() ,通过MyBatisPlus自带的方法
}
实现事物状态回查:
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
//60秒回查一次
log.info("正在执行事物回查");
String json = new String((byte[]) message.getPayload());
String xid = JsonUtil.getString(json, "xid");
//通过txMapper查询事物状态表
TxInfo txInfo = txMapper.selectById(xid);
if (txInfo == null) {
return RocketMQLocalTransactionState.UNKNOWN;
}
switch (txInfo.getStatus()) {
case 0: return RocketMQLocalTransactionState.COMMIT;
case 1: return RocketMQLocalTransactionState.ROLLBACK;
default: return RocketMQLocalTransactionState.UNKNOWN;
}
}
8. 接收消息
消费者接收消息,并处理
8.1 pom.xml文件
与生产者一样,添加rocketmq-spring-boot-starter依赖, 如果父级项目存在, 不用单独添加
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
8.2 application.yml文件
添加RocketMQ连接配置, 如果是集群, 多个地址通过分号 ; 隔开
rocketmq:
name-server: 192.168.126.130:9876
8.3 创建对象保存消息
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AccountMessage {
Long userId;
BigDecimal money;
String xid;
}
8.4 接收消息, 处理消息
实现消息监听,收到消息后完成业务处理
/*注解接收rocketmq消息
*参数: consumerGroup 消费者组,同一个消费者组,可以消费同一组的数据
* topic: 分类,标题
* selectorExpression: 消息选择器类型
*/
@RocketMQMessageListener(topic = "orderTopic",consumerGroup = "accountGroup")
@Component
//将消息传递到方法参数,需要实现RocketMQListener<String>接口
public class AccountConsumer implements RocketMQListener<String> {
//controller不调用,靠consumer消费者调用
@Autowired
private AccountService accountService;
@Override
public void onMessage(String json) {
//json-->AccountMessage对象 通过JsonUtil工具类(网上开源)
AccountMessage am = JsonUtil.from(json, AccountMessage.class);
//调用业务方法,扣减账户金额
accountService.decrease(am.getUserId(),am.getMoney());
}
}