事务消息
RocketMQ 提供了可靠性消息,也叫事务消息。
事务消息的原理
- 先发送半消息(半消息不会发送给消费者),保证通道连接
- 执行本地事务
执行成功 :将提交消息
1)半消息先放入第一个队列,本地事务成功以后提交消 息,从第一个队列拿出来放到自己的列队中(Topic2)。RocketMQ 发送给消费者B时的自动重发机制在绝大多数情况下,都可以保证消息被正确消费。
2)假如消息最终消费失败了,还可以由人工处理进行托底。
recketmq与B之间是执行18个事务级别,比如说第一次10s,没成功后间隔30s再询问, 没成功再间隔1min执行。
该模式没有全局回滚,理论上A成功,B必须成功。
执行失败:
1)回滚:事务执行失败时回滚消息
2)回查:(a与rocketmq之间)服务器无法得知消息状态时,如网络断开, 一分钟一次,一直循环
需要主动回查消息状态,每隔1min询问本地事务状态,一直询问不停歇。
会查的到的结果有三种:
LocalTransactionState.COMMIT_MESSAGE 提交
LocalTransactionState.ROLLBACK_MESSAGE 回滚
LocalTransactionState.UNKNOW 未知,之前的事务未执行结束或者之前事务状态没存储而取不到,导致不知道是成功还是失败,一般用不上,
测试一:
public class Producer {
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
// 创建生产者对象
DefaultMQProducer p = new DefaultMQProducer("prod-group1"); //prod-group1事物组组名
// 设置 name server
p.setNamesrvAddr("192.168.64.141:9876");
// 启动,连接
p.start();
// 创建消息封装对象 Message(Topic, Tag, 消息)
// 发送
while (true) {
System.out.print("输入消息:");
String s = new Scanner(System.in).nextLine();
// Topic 相当于是一级分类,Tag 相当于是二级分类
Message msg = new Message("Topic1", "TagA", s.getBytes());
msg.setDelayTimeLevel(3);
SendResult r = p.send(msg);
System.out.println(r);
}
}
}
```java
public class Consumer {
public static void main(String[] args) throws MQClientException {
// 创建消费者对象
DefaultMQPushConsumer c =
new DefaultMQPushConsumer("cons-group1");
// 设置 name server
c.setNamesrvAddr("192.168.64.141:9876");
// 设置从哪里订阅消息
// 标签: *--所有标签, TagA--只收TagA, TagA || TagB || TagC--受多种标签
c.subscribe("Topic1", "TagA || TagB || TagC");
// 设置消息监听器
// Concurrently -- 会启动多个线程处理消息
c.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt ext : msgs) {
String s = new String(ext.getBody());
System.out.println("收到:"+s);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
//return ConsumeConcurrentlyStatus.RECONSUME_LATER;//稍后重新消费
}
});
// 启动
c.start();
}
}
测试二:
public class Producer {
static String[] msgs = {
"15103111039,创建",
"15103111065,创建",
"15103111039,付款",
"15103117235,创建",
"15103111065,付款",
"15103117235,付款",
"15103111065,完成",
"15103111039,推送",
"15103117235,完成",
"15103111039,完成"
};
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
// 创建生产者对象
DefaultMQProducer p = new DefaultMQProducer("prod-group2");
// 设置 name server
p.setNamesrvAddr("192.168.64.141:9876");
// 启动,连接
p.start();
//遍历数组发送消息
for (String s : msgs) {
// s -- "15103111039,完成"
Long orderId = Long.valueOf(s.split(",")[0]);
Message msg = new Message("Topic2", s.getBytes());
// p.send(消息, 队列选择器, 选择依据)
SendResult r =
p.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Long orderId = (Long) arg;
int index = (int) (orderId % mqs.size());
return mqs.get(index);
}
}, orderId);
System.out.println(r);
}
}
}
public class Consumer {
public static void main(String[] args) throws MQClientException {
// 创建消费者对象
DefaultMQPushConsumer c =
new DefaultMQPushConsumer("cons-group2");
// 设置 name server
c.setNamesrvAddr("192.168.64.141:9876");
// 设置从哪里订阅消息
// 标签: *--所有标签, TagA--只收TagA, TagA || TagB || TagC--受多种标签
c.subscribe("Topic2", "*");
// 设置消息监听器
// Concurrently -- 会启动多个线程处理消息
// Orderly -- 单个线程
c.setMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt ext : msgs) {
String s = new String(ext.getBody());
System.out.println("收到: "+s);
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
// 启动
c.start();
}
}
测试三:
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.Scanner;
public class Producer {
public static void main(String[] args) throws MQClientException {
//创建事务消息生产者
TransactionMQProducer p = new TransactionMQProducer("prod-group1");
//设置 name server
p.setNamesrvAddr("192.168.124.160:9876");
//设置事务消息监听器
//1.执行本地事务(service.业务方法())
//2.处理rocketmq的事务回查
p.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 模拟网络不稳定,不能向服务器提交或回滚消息
// if (Math.random() < 1) {
// System.out.println("网络中断,无法向服务器提交或回滚");
// return LocalTransactionState.UNKNOW;
// }
System.out.println("执行本地业务,参数: "+arg);
if (Math.random()<0.5) {
System.out.println("本地事务执行成功");
return LocalTransactionState.COMMIT_MESSAGE; // 提交消息,通知服务器消息可以投递
} else {
System.out.println("本地事务执行失败");
return LocalTransactionState.ROLLBACK_MESSAGE; // 回滚消息,撤回消息
}
// return LocalTransactionState.UNKNOW; // 未知,当前方法中,一般不会出现这种状态
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
// System.out.println("rocketmq 服务器正在回查事务状态"); //如果第一次提交UNKNOW,则执行,
// return LocalTransactionState.UNKNOW; //自己设置,继续每次会查都会返回未知状态
return null;
}
});
//启动
p.start();
//发送事务消息,触发监听器
while (true) {
System.out.print("输入消息: ");
String s = new Scanner(System.in).nextLine();
Message msg = new Message("Topic3", s.getBytes());
TransactionSendResult r =
p.sendMessageInTransaction(msg, "业务数据参数");
System.out.println("----------------事务消息结果:" + r);
}
}
}
public class Consumer {
public static void main(String[] args) throws MQClientException {
// 创建消费者对象
DefaultMQPushConsumer c =
new DefaultMQPushConsumer("cons-group3");
// 设置 name server
c.setNamesrvAddr("192.168.64.141:9876");
// 设置从哪里订阅消息
// 标签: *--所有标签, TagA--只收TagA, TagA || TagB || TagC--受多种标签
c.subscribe("Topic3", "*");
// 设置消息监听器
// Concurrently -- 会启动多个线程处理消息
c.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt ext : msgs) {
String s = new String(ext.getBody());
System.out.println("收到:"+s);
}
//return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
return ConsumeConcurrentlyStatus.RECONSUME_LATER;//稍后重新消费,通常Try,catch使用
}
});
// 启动
c.start();
}
}
模拟提交失败,每分钟询问一次,一个事务一条
只有一条消息,间隔60s,如图
有多条消息,如图
业务中。。。
本地事务的成败将保存到数据库中,因对实物回查
事物id,成/败
Autowired
private RocketMQTemplate t;
发送事物消息
业务和监听器一起写
import cn.tedu.order.entity.AccountMessage;
import cn.tedu.order.entity.Order;
import cn.tedu.order.entity.TxInfo;
import cn.tedu.order.feign.AccountClient;
import cn.tedu.order.feign.EasyIdClient;
import cn.tedu.order.feign.StorageClient;
import cn.tedu.order.mapper.OrderMapper;
import cn.tedu.order.mapper.TxMapper;
import cn.tedu.order.util.JsonUtil;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Random;
import java.util.UUID;
@RocketMQTransactionListener
@Service
public class OrderServiceImpl implements OrderService, RocketMQLocalTransactionListener {
@Autowired
private OrderMapper orderMapper;
@Autowired
private EasyIdClient easyIdClient;
@Autowired
private RocketMQTemplate t;
@Autowired
private TxMapper txMapper;
@Override
public void create(Order order) {
// "xxx-xxx-xxx-xxx-xxx" 有4个减号,共36位
// 这里把减号去掉,保留32位
String xid = UUID.randomUUID().toString().replace("-", "");
AccountMessage am = new AccountMessage(
order.getUserId(), order.getMoney(), xid);
// am ---> json
String json = JsonUtil.to(am);
// spring封装的通用message对象
// payLoad会自动被转换成 byte[]
Message<String> message = MessageBuilder.withPayload(json).build();
// 发送事务消息
// 如果添加Tag,格式: "orderTopic:TagA"
// t.sendMessageInTransaction("orderTopic",message,业务数据参数);
t.sendMessageInTransaction("orderTopic",message,order);
}
//本地事务,执行订单保存
//这个方法在事务监听器中调用
@Transactional
// 在监听器中,会调用这个业务方法
public void doCreate(Order order) {
// 远程调用发号器,获取订单id
String s = easyIdClient.nextId("order_business");
Long orderId = Long.valueOf(s);
order.setId(orderId);
orderMapper.create(order);
}
@Transactional
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
RocketMQLocalTransactionState state; //用来返回
int status; //用来存储到数据库
try {
Order order = (Order) o;
doCreate(order);
state=RocketMQLocalTransactionState.COMMIT;
status=0;
} catch (Exception e) {
e.printStackTrace();
state=RocketMQLocalTransactionState.ROLLBACK;
status=1;
}
// TxInfo 封装事务状态
String json = new String((byte[]) message.getPayload());
// {"xid":"u5y4g5t34", "userId":7, ...}
String xid = JsonUtil.getString(json, "xid");
TxInfo txInfo = new TxInfo(xid, status, System.currentTimeMillis());
txMapper.insert(txInfo);
return state;
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
String json = new String((byte[]) message.getPayload());
String xid = JsonUtil.getString(json, "xid");
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;
}
}
}
或者分开写业务和监听器
在业务方法 create() 中不直接保存订单,而是发送事务消息。
消息发出后,会触发 TxListener 执行本地事务,它执行时会回调这里的 doCreate() 方法完成订单的保存。
package cn.tedu.order.tx;
import cn.tedu.order.entity.Order;
import cn.tedu.order.feign.EasyIdGeneratorClient;
import cn.tedu.order.mapper.OrderMapper;
import cn.tedu.order.mapper.TxMapper;
import cn.tedu.order.service.OrderService;
import cn.tedu.order.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;
@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("订单已保存! 事务日志已保存");
}
}
TxListener 事务监听器
发送事务消息后会触发事务监听器执行。
事务监听器有两个方法:
executeLocalTransaction(): 执行本地事务
checkLocalTransaction(): 负责响应Rocketmq服务器的事务回查操作
package cn.tedu.order.tx;
import cn.tedu.order.entity.Order;
import cn.tedu.order.mapper.TxMapper;
import cn.tedu.order.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
@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;
}
}
}
接收事务消息
rocketmq:
name-server: 192.168.64.151:9876;192.168.64.152:9876
接收的消息转换成 pojo 对象
消息监听
package cn.tedu.account.tx;
import cn.tedu.account.service.AccountService;
import cn.tedu.account.util.JsonUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@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());
}
}