理解Java分布式事务的实现原理
什么时候使用分布式事务
我们需要事物的时候一般是对数据库进行了写或者删除的操作。分布式事务常用在一个项目调用了另外一个项目的api , 如果这个api执行成功了并且调用的这个service方法也没有抛出异常调用方和被调用方都同时提交,只要有一方抛出了异常就全部回滚。
实现原理
简单分析下我们希望达到的效果是如果A调用了B
1. B如果抛出了异常就需要将A和B同时回滚,
2. A如果在调用B之前抛出了异常就只回滚A,
3. A在调用B之后抛出了异常就同时回滚A和B
4. A和B都没有抛出异常就都提交
最简单的实现方法
所能想到的最简单的方法就是A和B使用不同的事物管理器。B如果抛出了异常就先回滚自己的,A调用B失败在手动调用B提供的一个用于回滚的方法,然后在回滚自己的。但是这不够优雅,而且B必须提供一个用于回滚的api. 造成这么复杂的原因是因为他们使用了不同的事物管理器。
实现方法升级
A和B都使用同一个事物管理器。这个事物管理器可以是使用Netty创建的服务。
- A开始执行的时候向事物管理器发送一条消息创建一个事物组。
- A调用B的时候,B执行完真正的数据库操作后,但是不提交hold住,B也向事物管理器发送一条消息,把自己加入这个事物组中, 并且告诉管理器自己的状态是回滚还是提交
- A执行完真正的数据库操作后,但是不提交hold住,A向管理器发送一条自己的状态
- 管理器检查到所有的事物都执行完了后,向A和B发送一个提交或者回滚的命令,A和B才进行真正的回滚或者提交
大概过程就向下面这个图这样
实现的难点
- 什么实现A执行完真正的数据库操作后,不提交hold住?
比较简单的实现方法就是在spring的事物基础上封装,
重写Connection类里面的commit和rollback方法。
public class LbConnection implements Connection {
private Connection connection;
private LbTransaction lbTransaction;
public LbConnection(Connection connection, LbTransaction lbTransaction) {
this.connection = connection;
this.lbTransaction = lbTransaction;
}
public void commit() {
new Thread(new Runnable() {
public void run() {
//先阻塞着
lbTransaction.getTask().waitTask();
try {
if (lbTransaction.getTransactionType().equals(TransactionType.commit)) {
connection.commit();
} else {
connection.rollback();
}
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}).start();
}
public void rollback() throws SQLException {
new Thread(new Runnable() {
@Override
public void run() {
try {
lbTransaction.getTask().waitTask();
connection.rollback();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}).start();
}
}
写一个切面当事物获取connection的时候获取我们自定义的connection
@Aspect
@Component
public class LbDataSourceAspect {
@Around("execution(* javax.sql.DataSource.getConnection(..))")
public Connection around(ProceedingJoinPoint point) throws Throwable {
if (LbTransactionManager.getCurrent() != null) {
Connection connection = (Connection) point.proceed();
connection.setAutoCommit(false);
return new LbConnection(connection, LbTransactionManager.getCurrent().get());
} else {
return (Connection) point.proceed();
}
}
}
- 管理器向A和B发送提交或者回滚的命令,A和B怎么提交或者回滚
有了上面的基础这个就简单了,A和B接收到命令后直接将阻塞的唤醒就可以了。
@Override
public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = ((ByteBuf) msg).toString(CharsetUtil.UTF_8);
System.out.println("接收数据" + s);
JSONObject jsonObject = JSON.parseObject(s);
String groupId = jsonObject.getString("groupId");
String command = jsonObject.getString("command");
LbTransaction lbTransaction = LbTransactionManager.getLbTransaction(groupId);
if("rollback".equals(command)){
lbTransaction.setTransactionType(TransactionType.rollback);
}else{
lbTransaction.setTransactionType(TransactionType.commit);
}
//执行rollback 或者commit
lbTransaction.getTask().singleTask();
}
- 怎么让使用更加方便,比如像spring事物那样,一个注解就使用了分布式事物。
可以定义一个注解,并且给加了这个注解的方法定义一个代理。只要检测到方法中有这个注解,就向事物管理器发送自己执行的结果。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LbTransactional {
boolean isEnd() default false;
boolean isStart() default false;
}
@Aspect
@Component
public class LbTransactionAspect implements Ordered {
@Around("@annotation(com.sl.server.global.transation.annotation.LbTransactional)")
public void invoke(ProceedingJoinPoint point) throws Exception {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
LbTransactional lbTransactional = method.getAnnotation(LbTransactional.class);
String groupId = "";
if (lbTransactional.isStart()) {
//向事物管理器发送创建事物组的命令
groupId = LbTransactionManager.createLbTransactionGroup();
} else {
groupId = LbTransactionManager.getCurrentGroupId();
}
LbTransaction lbTransaction = LbTransactionManager.createLbTransaction(groupId);
try {
//spring 开启mysql事物
point.proceed();
//向事物管理器发送消息
LbTransactionManager.addLbtransaction(lbTransaction, lbTransactional.isEnd(), TransactionType.commit);
} catch (Throwable throwable) {
LbTransactionManager.addLbtransaction(lbTransaction, lbTransactional.isEnd(), TransactionType.rollback);
throwable.printStackTrace();
}
}
public int getOrder() {
return 1000;
}
}