分布式事务常规解决方式有一下几种:
1,采用业务回滚方式:
例如:
public void updateByPrimaryKey(Pay record) throws Exception {
try {
//加入在数据库进行了+100
payMapper.updateByPrimaryKey(record);
//执行完上步操作后,继续走下面,又去另外一个数据库做修改,结果失败了,那么此时上步如何回滚
} catch (Exception e) {
e.printStackTrace();
//记录一个标示位,如果第一步已经执行成功,那么在此修改回去
payMapper.updateByPrimaryKey(record);
}
}
但是这是回花费大量的代码,而且容易出错.
2,采用JTA进行分布式事物的管理 就不做例子了,
3,创建中间系统,同事控制数据库,避免两个应用控制两个数据库,进而增加困难度
今天给大家推荐的是阿里为淘宝量身定做的RocketMQ消息队列,支持事物消息,顺序消费,批量消费,支持大并发,海量消息.
事务消息原理实现过程:
一阶段:
Producer向Broker发送1条类型为TransactionPreparedType的消息,Broker接收消息保存在CommitLog中,然后返回 消息的queueOffset和MessageId到Producer,MessageId包含有commitLogOffset(即消息在CommitLog中的偏移量, 通过该变量可以直接定位到消息本身),由于该类型的消息在保存的时候,commitLogOffset没有被保存到 consumerQueue中,此时客户端通过consumerQueue取不到commitLogOffset,所以该类型的消息无法被取到,导致 不会被消费。
一阶段的过程中,Broker保存了1条消息。
二阶段:
Producer端的TransactionExecuterImpl执行本地操作,返回本地事务的状态,然后发送一条类型为 TransactionCommitType或者TransactionRollbackType的消息到Broker确认提交或者回滚,Broker通过Request中的 commitLogOffset,获取到上面状态为TransactionPreparedType的消息(简称消息A),然后重新构造一条与消息A内 容相同的消息B,设置状态为TransactionCommitType或者TransactionRollbackType,然后保存。其中 TransactionCommitType类型的,会放commitLogOffset到consumerQueue中,TransactionRollbackType类型的,消 息体设置为空,不会放commitLogOffset到consumerQueue中。
发送事物消息:
private final String GROUP_NAME = "transaction-pay";
private final String NAMESRV_ADDR = "192.168.68.129:9876";
private TransactionMQProducer producer;
public MQProducer() {
this.producer = new TransactionMQProducer(GROUP_NAME);
this.producer.setNamesrvAddr(NAMESRV_ADDR); //nameserver服务
this.producer.setCheckThreadPoolMinSize(5); // 事务回查最小并发数
this.producer.setCheckThreadPoolMaxSize(20); // 事务回查最大并发数
this.producer.setCheckRequestHoldMax(2000); // 队列数
//服务器回调Producer,检查本地事务分支成功还是失败
this.producer.setTransactionCheckListener(new TransactionCheckListener() {
public LocalTransactionState checkLocalTransactionState(MessageExt msg) {
System.out.println("state -- "+ new String(msg.getBody()));
return LocalTransactionState.COMMIT_MESSAGE;
}
});
try {
this.producer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) throws Exception {
return this.producer.queryMessage(topic, key, maxNum, begin, end);
}
public LocalTransactionState check(MessageExt me){
LocalTransactionState ls = this.producer.getTransactionCheckListener().checkLocalTransactionState(me);
return ls;
}
public void sendTransactionMessage(Message message, LocalTransactionExecuter localTransactionExecuter, Map<String, Object> transactionMapArgs) throws Exception {
TransactionSendResult tsr = this.producer.sendMessageInTransaction(message, localTransactionExecuter, transactionMapArgs);
System.out.println("send返回内容:" + tsr.toString());
}
public void shutdown(){
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
producer.shutdown();
}
}));
System.exit(0);
}
消费事物消息:
public void queryMessage(String topic) throws MQClientException{
consumer = new DefaultMQPushConsumer(GROUP_NAME);
consumer.setNamesrvAddr(NAMESRV_ADDR);
consumer.setConsumeMessageBatchMaxSize(10);
/**
* 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费<br>
* 如果非第一次启动,那么按照上次消费的位置继续消费
*/
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.subscribe(topic, "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
try {
for (MessageExt msg : msgs) {
System.out.println(msg + ",内容:" + new String(msg.getBody()));
}
} catch (Exception e) {
e.printStackTrace();
return ConsumeConcurrentlyStatus.RECONSUME_LATER;// 重试
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;// 成功
}
});
consumer.start();
System.out.println("transaction_Consumer Started.");
}
本地事物执行:
package bhz.mq;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import bhz.entity.Pay;
import bhz.service.PayService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.rocketmq.client.producer.LocalTransactionExecuter;
import com.alibaba.rocketmq.client.producer.LocalTransactionState;
import com.alibaba.rocketmq.common.message.Message;
/**
* 执行本地事务,由客户端回调
*/
@Component
public class TransactionExecuterImpl implements LocalTransactionExecuter {
@Autowired
private PayService payService;
public LocalTransactionState executeLocalTransactionBranch(Message msg, Object arg) {
try {
//Message Body
String a=new String(msg.getBody(), "utf-8");
JSONObject messageBody = JSON.parseObject(a);
//Transaction MapArgs
// --------------------IN PUT---------------------- //
System.out.println("message body = " + messageBody);
System.out.println("message tag = " + msg.getTags());
// --------------------IN PUT---------------------- //
//userid
Integer userid = messageBody.getInteger("userid");
//money
double money =Double.parseDouble(messageBody.getString("money"));
//mode
String pay_mode = messageBody.getString("pay_mode");
//pay
//Pay pay = payService.selectByPrimaryKey(userid);
//持久化数据
Pay pay=new Pay();
pay.setAmount(11.2);
pay.setUserid(1);
payService.updateAmount(pay, pay_mode, money);
//成功通知MQ消息变更 该消息变为:<确认发送>
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
e.printStackTrace();
//失败则不通知MQ 该消息一直处于:<暂缓发送>
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
}
客户端回调:
package bhz.mq;
import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.rocketmq.client.producer.LocalTransactionState;
import com.alibaba.rocketmq.client.producer.TransactionCheckListener;
import com.alibaba.rocketmq.common.message.MessageExt;
public class TransactionCheckListenerImpl implements TransactionCheckListener{
private AtomicInteger transactionIndex = new AtomicInteger(0);
@Override
public LocalTransactionState checkLocalTransactionState(MessageExt msg) {
System.out.println("server checking TrMsg " + msg.toString());
int value = transactionIndex.getAndIncrement();
if ((value % 6) == 0) {
throw new RuntimeException("Could not find db");
}
else if ((value % 5) == 0) {
return LocalTransactionState.ROLLBACK_MESSAGE;
}
else if ((value % 4) == 0) {
return LocalTransactionState.COMMIT_MESSAGE;
}
return LocalTransactionState.UNKNOW;
}
}
有关rocketmq的顺序消费和filer将在下篇给大家分析