1):为什么需要分布式事务?
假设用户购买商品时,会生成一个订单,修改库存,修改用户积分。
倘若单库的话,可通过开启事务方式,一起失败,或者一起成功。
但是当体量达到一个级别,不得不采用分库分表的方式,将其垂直拆分成多个库。比如用户中心库,库存中心库,订单模块库。
而服务也对应的进行了拆分,用户中心,库存中心,订单中心三个服务。修改数据,不得不跨服务访问库,这个时候,假设订单模块库成功了,库存中心库执行失败了,那么就要通知订单中心进行回滚操作。这就添加了复杂性。
随之而来出现了很多针对此场景的解决方案。
2):常见的解决方案如下?
分布式事务实现方案从类型上去分刚性事务、柔型事务。
刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务。一般出现在金融领域。
柔性事务:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。只追求数据最终结果的可采用。
刚性事务:XA 协议(2PC、JTA、JTS)、3PC
柔型事务:TCC/FMT、Saga(状态机模式、Aop模式)、本地事务消息、消息事务(半消息)
2)1):二阶段提交(2PC)
引入一个协调者,统一掌控所有参与者,并指示他们是否操作结果提交还是回滚
分为2个阶段
投票阶段:参与则通知协调者,协调者反馈结果
提交阶段:收到参与者反馈后,协调者再向参与者发出通知,根据反馈情况决定每个参与者到底还是提交还是回滚
例子:老大BOSS说要吃饭,统计下面员工意见,有员工ABC同意去,但D没回复,则BOSS依旧处于接受信息状态,ABC则阻塞.当统计了ABCD员工意见后,如果有一个员工不同意,则通知ABCD不去(回滚),然后ABCD收到后ACK BOSS,这个阶段如果BOSS没收到会一直阻塞
缺点
执行过程中,所有节点处于阻塞,所有节点持有资源锁定状态,当有一个节点出现问题无法回复,则会一直阻塞,就需要更复杂的超时机制处理.
2)2):TXC逆向SQL
1. 拦截,并解析SQL
2. 查询受影响的行数,并记录值
TXM通知事务提交,则删除记录
若回滚则,将记录的值创建逆向SQL,然后执行
2)3):TCC(Try、Confirm、Cancel)
//尝试方法
function try(){
//记录日志
todo save A 转出了 100 元
todo save B 转入了 100 元
//执行转账
update amount set balacne = balacne-100 where id = 1
update amount set balacne = balacne+100 where id = 2
}
//确认方法
function confirm(){
//清理日志
clean save A 转出了 100 元
clean save B 转出了 100 元
}
//取消方法
function cancle(){
//加载日志
load log A
load log B
//退钱
update amount set balacne = balacne+100 where id = 1
update amount set balacne = balacne-100 where id = 2
}
每个服务需要实现三个方法,开发较重。但是针对db和redis的混合开发,有不错的支持。
2)4):增量日志
确保最终一致性,延迟取决于日志对比时间
模仿mysql主从复制,binLog,涉及修改添加到日志,然后间隔多长时间进行正向反向表数据比对.
2)5):补偿事务
在业务端执行业务逆向操作事务
flag1=执行事务一返回结果
if(flag1){
flag2=执行事务二返回结果
if(flag2){
...执行事务...
}else{
回滚事务二
}
}else{
回滚事务一
}
缺点
嵌套过多
不同业务需要写不同的补偿事务
不具备通用性
没有考虑补偿事务失败
2)6):后置提交优化
一个事务,分别为执行和提交2阶段
执行比提交耗时长
改变事务执行与提交时序,变成事务先执行然后一起提交
执行事务一,提交.执行事务耗时200ms,提交2ms
执行事务二,提交.执行事务耗时200ms,提交2ms
执行事务三,提交.执行事务耗时200ms,提交2ms
当执行事务二~三之间出现异常,数据就不一致,时间范围为202+202ms
通过改变时序后:
执行事务一,执行事务耗时200ms
执行事务二,执行事务耗时200ms
执行事务三,执行事务耗时200ms
提交事务一,提交2ms
提交事务二,提交2ms
提交事务三,提交2ms
后置优化后,在提交事务二~三阶段才会出现不一致情况,时间耗时4ms
虽然不能根本解决数据一致问题,但通过缩小时间概率,降低不一致概率
缺点:
串行执行事务提交后,连接就释放了,但后置提交优化后,所有库的连接,需要等待全部事务执行完才能释放,数据库连接占用时间加长,吞吐降低了
3):txlcn源码解析
执行时序图
3)1):事务发起方执行分布式事务流程
仔细思考,作为发起方在整个分布式事务中出现情况如下:
- 发起方执行成功(在这种情况下,所有参与方均执行成功,发起方才能成功。)
- 参与方第二阶段成功,提交。
- 参与方第二阶段失败,回滚。通知txm加入补偿日志。
- 自身提交or回滚失败,通知txm加入补偿日志。
- 发起方失败,自身回滚。通知tx-m执行失败,通知其他服务回滚。
当我们在方法上添加TxTransaction时,表示开启分布式事务。注意作为服务发起方需要标记isStart=true。
这个时候会被aop环绕注解拦截。com.codingapi.tx.springcloud.interceptor.TransactionAspect#transactionRunning
@Around("@annotation(com.codingapi.tx.annotation.TxTransaction)")
public Object transactionRunning(ProceedingJoinPoint point) throws Throwable
{
logger.debug("annotation-TransactionRunning-start---->");
Object obj = txManagerInterceptor.around(point);
logger.debug("annotation-TransactionRunning-end---->");
return obj;
}
从请求头中获取事务组id和事务模式。
com.codingapi.tx.springcloud.interceptor.TxManagerInterceptor#around
public Object around(ProceedingJoinPoint point) throws Throwable {
String groupId = null;
String mode = null;
try {
//如果是发起方,这里都是null。
//如果是调用方,则这里会有发起方通过feign发送过来的事务组id和事务模式。
//事务组id:事务唯一标识。
//事务模式:包括lcn,或者tcc等。
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = requestAttributes == null ? null : ((ServletRequestAttributes) requestAttributes).getRequest();
groupId = request == null ? null : request.getHeader("tx-group");
mode = request == null ? null : request.getHeader("tx-mode");
}catch (Exception e){}
//继续调用切面方法
return aspectBeforeService.around(groupId, point, mode);
}
封装事务信息,调用核心类执行。
com.codingapi.tx.aop.service.impl.AspectBeforeServiceImpl#around
@Override
public Object around(String groupId, ProceedingJoinPoint point, String mode) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
//获取接口执行方法
Method method = signature.getMethod();
Class<?> clazz = point.getTarget().getClass();
//获取方法参数
Object[] args = point.getArgs();
//获取具体执行的子类方法
Method thisMethod = clazz.getMethod(method.getName(), method.getParameterTypes());
//获取分布式事务注解
TxTransaction transaction = thisMethod.getAnnotation(TxTransaction.class);
TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
logger.debug("around--> groupId-> " +groupId+",txTransactionLocal->"+txTransactionLocal);
//封装事务方法
TransactionInvocation invocation = new TransactionInvocation(clazz, thisMethod.getName(), thisMethod.toString(), args, method.getParameterTypes());
//封装事务信息,包含注解信息,方法信息,事务组id。
TxTransactionInfo info = new TxTransactionInfo(transaction,txTransactionLocal,invocation,groupId);
try {
//设置事务类型
info.setMode(TxTransactionMode.valueOf(mode));
} catch (Exception e) {
info.setMode(TxTransactionMode.TX_MODE_LCN);
}
//创建事务服务
TransactionServer server = transactionServerFactoryService.createTransactionServer(info);
//执行核心事务方法
return server.execute(point, info);
}
3)1)1):创建事务服务
com.codingapi.tx.aop.service.impl.TransactionServerFactoryServiceImpl
public TransactionServer createTransactionServer(TxTransactionInfo info) throws Throwable {
if (!SocketManager.getInstance().isNetState()) {
//检查socket通讯是否正常 (第一次执行时启动txRunningTransactionServer的业务处理控制,然后嵌套调用其他事务的业务方法时都并到txInServiceTransactionServer业务处理下)
logger.warn("tx-manager not connected.");
return txDefaultTransactionServer;
}
/*********分布式事务处理逻辑***********/
logger.info("分布式事务处理逻辑...开始");
/** 事务发起方:仅当TxTransaction注解不为空,其他都为空时。表示分布式事务开始启动 **/
//注解不为空,且注解声明了为发起方,且事务组id为空,这就说明为事务发起方。
if (info.getTxTransaction() != null && info.getTxTransaction().isStart() && info.getTxTransactionLocal() == null && StringUtils.isEmpty(info.getTxGroupId())) {
//检查socket通讯是否正常 (当启动事务的主业务方法执行完以后,再执行其他业务方法时将进入txInServiceTransactionServer业务处理)
if (SocketManager.getInstance().isNetState()) {
return txStartTransactionServer;
} else {
logger.warn("tx-manager not connected.");
return txDefaultTransactionServer;
}
}
/** 事务参与方:分布式事务已经开启,业务进行中 **/
logger.debug("事务参与方:分布式事务已经开启,业务进行中");
if (info.getTxTransactionLocal() != null || StringUtils.isNotEmpty(info.getTxGroupId())) {
//检查socket通讯是否正常 (第一次执行时启动txRunningTransactionServer的业务处理控制,然后嵌套调用其他事务的业务方法时都并到txInServiceTransactionServer业务处理下)
if (SocketManager.getInstance().isNetState()) {
if (info.getTxTransactionLocal() != null) {
return txDefaultTransactionServer;
} else {
/** 表示整个应用没有获取过DB连接 || 无事务业务的操作 **/
if (transactionControl.isNoTransactionOperation() || info.getTxTransaction().readOnly()) {
return txRunningNoTransactionServer;
} else {
return txRunningTransactionServer;
}
}
} else {
logger.warn("tx-manager not connected.");
return txDefaultTransactionServer;
}
}
/*********分布式事务处理逻辑*结束***********/
logger.debug("分布式事务处理逻辑*结束");
return txDefaultTransactionServer;
}
当出现网络不通时,会选择com.codingapi.tx.aop.service.impl.TxDefaultTransactionServerImpl,就是一个简单的调用。
@Override
public Object execute(ProceedingJoinPoint point, TxTransactionInfo info) throws Throwable {
logger.info("默认事务管理器...");
return point.proceed();
}
com.codingapi.tx.aop.service.impl.TxDefaultTransactionServerImpl类继承TransactionServer,该类下有4个实现,分别针对发起方和调用方,默认以及无事务4种实现。
3)1)2):执行分布式事务
作为调用方,这里只看com.codingapi.tx.aop.service.impl.TxStartTransactionServerImpl#execute
-
构建事务组id
-
向tx-mange发起事务创建,对应图中这步。
-
封装事务上下文
-
执行业务方法,失败或成功通过state记录状态。
-
通知tx-m关闭事务组,进入事务提交第一阶段,tx-m会通知所有参与者提交。获取所有参与者执行后的最终结果。
- 对应如图
-
若task不为空,则唤醒阻塞任务,这里会执行本地db连接池commit,或者回滚。然后归还db连接。
-
发起方执行失败但是参与方成功,或者发起方成功,参与方失败。则通知tx-M记录补偿
public Object execute(ProceedingJoinPoint point,final TxTransactionInfo info) throws Throwable {
//分布式事务开始执行
logger.info("事务发起方...");
logger.debug("--->分布式事务开始执行 begin start transaction");
final long start = System.currentTimeMillis();
//标记执行状态,0失败,1成功。
int state = 0;
//1:构建事务组id
final String groupId = TxCompensateLocal.current()==null?KidUtils.generateShortUuid():TxCompensateLocal.current().getGroupId();
//2:向tx-mange发起事务创建
logger.debug("创建事务组并发送消息");
txManagerService.createTransactionGroup(groupId);
//3:封装事务上下文(封装组id,事务超时时间,事务模式,是否发起方),并设置当前线程中。这里贯穿整个生命周期。
TxTransactionLocal txTransactionLocal = new TxTransactionLocal();
txTransactionLocal.setGroupId(groupId);
txTransactionLocal.setHasStart(true);
txTransactionLocal.setMaxTimeOut(Constants.txServer.getCompensateMaxWaitTime());
txTransactionLocal.setMode(info.getTxTransaction().mode());
txTransactionLocal.setReadOnly(info.getTxTransaction().readOnly());
TxTransactionLocal.setCurrent(txTransactionLocal);
try {
//4:执行业务方法
Object obj = point.proceed();
//修改执行结果状态
state = 1;
return obj;
} catch (Throwable e) {
//5:回滚事务
state = rollbackException(info,e);
throw e;
} finally {
final String type = txTransactionLocal.getType();
//6:通知tx-m关闭事务组,进入事务提交第一阶段,tx-m会通知所有参与者提交。获取所有参与者执行后的最终结果。
int rs = txManagerService.closeTransactionGroup(groupId, state);
int lastState = rs==-1?0:state;
int executeConnectionError = 0;
//获取task,走到这里的时候,发起方若执行了回滚,这里为null。
final TxTask waitTask = TaskGroupManager.getInstance().getTask(groupId, type);
if(waitTask!=null){
//设置最终执行结果
waitTask.setState(lastState);
//唤醒阻塞任务,这里会执行本地db连接池commit,或者回滚。然后归还db连接。
waitTask.signalTask();
//自旋等待线程池删除任务。
while (!waitTask.isRemove()){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(waitTask.getState()== TaskState.connectionError.getCode()){
//本地执行失败.
executeConnectionError = 1;
lastState = 0;
}
}
final TxCompensateLocal compensateLocal = TxCompensateLocal.current();
if (compensateLocal == null) {
long end = System.currentTimeMillis();
long time = end - start;
if ((executeConnectionError == 1&&rs == 1)||(lastState == 1 && rs == 0)) {
//发起方执行失败但是参与方成功,或者发起方成功,参与方失败。则通知tx-M记录补偿
txManagerService.sendCompensateMsg(groupId, time, info,executeConnectionError);
}
}else{
if(rs==1){
lastState = 1;
}else{
lastState = 0;
}
}
//清除事务上下文信息
TxTransactionLocal.setCurrent(null);
logger.debug("<---分布式事务 end start transaction");
logger.debug("start transaction over, res -> groupId:" + groupId + ", now state:" + (lastState == 1 ? "commit" : "rollback"));
}
}
3)1)3):获取线程连接
当执行业务方法的时,会通过封装db连接,达到控制commit或者rollback操作。
拦截获取数据库连接方法com.codingapi.tx.datasource.aspect.DataSourceAspect#around
@Around("execution(* javax.sql.DataSource.getConnection(..))")
public Connection around(ProceedingJoinPoint point)throws Throwable{
logger.debug("getConnection-start---->");
//获取封装之后的线程池对象。
Connection connection = lcnConnection.getConnection(point);
logger.debug("connection-->"+connection);
logger.debug("getConnection-end---->");
return connection;
}
com.codingapi.tx.datasource.relational.LCNTransactionDataSource#getConnection
@Override
public Connection getConnection(ProceedingJoinPoint point) throws Throwable {
//说明有db操作.
hasTransaction = true;
//设置db类型
initDbType();
//从缓存中获取db资源
Connection connection = (Connection)loadConnection();
//获取不到则新建
if(connection==null) {
//没获取到利用spring生成db连接,并进行封装,加入缓存中。
connection = initLCNConnection((Connection) point.proceed());
if(connection==null){
throw new SQLException("connection was overload");
}
return connection;
}else {
return connection;
}
}
从缓存中获取db资源
protected ILCNResource loadConnection(){
//从当前线程获取事务远程调用控制对象
TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
if(txTransactionLocal==null){
logger.debug("loadConnection -> null !");
return null;
}
if (txTransactionLocal.isReadOnly()) {
logger.debug("readonly tx don't reuse connection.");
return null;
}
//这里每次获取新的连接的时候会将,事务组id作为key,db连接作为value存储pool中。这样同一个事务组获取的始终是同一个线程,便于后面回滚操作。
//是否获取旧连接的条件:同一个模块下被多次调用时第一次的事务操作
ILCNResource old = pools.get(txTransactionLocal.getGroupId());
if (old != null) {
//已经有连接了则返回null,后面新建一个连接
if(txTransactionLocal.isHasConnection()){
logger.debug("connection is had , transaction get a new connection .");
return null;
}
logger.debug("loadConnection -> old !");
//标记有连接
txTransactionLocal.setHasConnection(true);
return old;
}
return null;
}
若没获取到db连接,则利用spring生成db连接,并进行封装,加入缓存中。
protected C initLCNConnection(C connection) {
logger.debug("initLCNConnection");
C lcnConnection = connection;
TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
//没有从缓存中获取到db连接,且事务注解没有设置本服务为只读,则封装db连接。
if (txTransactionLocal != null&&!txTransactionLocal.isHasConnection()
&& !txTransactionLocal.isReadOnly()) {
logger.debug("lcn datasource transaction control ");
//补偿的情况的
// if (TxCompensateLocal.current() != null) {
// logger.info("rollback transaction ");
// return getCompensateConnection(connection,TxCompensateLocal.current());
// }
if(StringUtils.isNotEmpty(txTransactionLocal.getGroupId())){
logger.debug("lcn transaction ");
//封装连接
return createConnection(txTransactionLocal, connection);
}
}
logger.debug("load default connection !");
return lcnConnection;
}
- 当事务模式是TCC,则直接创建。
- 不是TCC,则说明事务模式是Lcn,当db连接个数若等于最大创建个数,则等待一秒再次获取连接,超过30s则返回源db。
- 低于创建个数,直接创建
- 大于最大创建个数,说明资源紧张,返回null。
private C createConnection(TxTransactionLocal txTransactionLocal, C connection){
if (txTransactionLocal.getMode() != null
&& txTransactionLocal.getMode() == TxTransactionMode.TX_MODE_TXC) {
// 1:txc 模式下没有maxCount的限制 直接创建
return createTxcConnection(connection, txTransactionLocal);
}
//2:若等于最大创建个数,则等待一秒再次获取连接,超过30s则返回源db。
if (nowCount == maxCount) {
for (int i = 0; i < maxWaitTime; i++) {
for(int j=0;j<100;j++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (nowCount < maxCount) {
//封装连接
return createLcnConnection(connection, txTransactionLocal);
}
}
}
} else if (nowCount < maxCount) {
//3:低于创建个数,直接创建
return createLcnConnection(connection, txTransactionLocal);
} else {
//4:超过创建个数,说明资源紧张,则返回null。
logger.info("connection was overload");
return null;
}
return connection;
}
针对发起方和调用方分别不同封装db连接
@Override
protected Connection createLcnConnection(Connection connection, TxTransactionLocal txTransactionLocal) {
//累加当前获取db连接数。
nowCount++;
//如果是发起方
if(txTransactionLocal.isHasStart()){
//注册回调函数,用于清除缓存中连接资源。
LCNStartConnection lcnStartConnection = new LCNStartConnection(connection,subNowCount);
logger.debug("get new start connection - > "+txTransactionLocal.getGroupId());
//添加到缓存
pools.put(txTransactionLocal.getGroupId(), lcnStartConnection);
//标记已经获取到db连接
txTransactionLocal.setHasConnection(true);
return lcnStartConnection;
}else {
//调用方
LCNDBConnection lcn = new LCNDBConnection(connection, dataSourceService, subNowCount);
logger.debug("get new connection ->" + txTransactionLocal.getGroupId());
pools.put(txTransactionLocal.getGroupId(), lcn);
txTransactionLocal.setHasConnection(true);
return lcn;
}
}
创建连接时会新建一个task,该task主要用于,在等待tx-m告知事务所有参与方最终执行结果之前,发起方一直阻塞在commit或rollback处。获取tx-m告知的结果后,在执行commit或rollback。
com.codingapi.tx.datasource.relational.LCNStartConnection#LCNStartConnection
public LCNStartConnection(Connection connection, ICallClose<ILCNResource> subNowCount) {
this.connection = connection;
this.subNowCount = subNowCount;
if(TxCompensateLocal.current()!=null){
isCompensate = true;
logger.info("transaction is compensate-connection.");
TxCompensateLocal txCompensateLocal = TxCompensateLocal.current();
groupId = txCompensateLocal.getGroupId();
TaskGroup taskGroup = TaskGroupManager.getInstance().createTask(groupId,txCompensateLocal.getType());
waitTask = taskGroup.getCurrent();
startState = txCompensateLocal.getStartState();
}else{
isCompensate = false;
logger.info("transaction is start-connection.");
TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
groupId = txTransactionLocal.getGroupId();
//新建任务
TaskGroup taskGroup = TaskGroupManager.getInstance().createTask(groupId, txTransactionLocal.getType());
//将新建的任务捞出赋给waitTask
waitTask = taskGroup.getCurrent();
}
logger.info("lcn start connection init ok .");
}
3)1)4):执行业务方法
@Override
@TxTransaction(isStart = true)
@Transactional
public int save(String name) {
testMapper.save("demo1");
demo2Client.save( "demo2");
demo3Client.save();
return 2;
}
3)1)4)1):调用远程接口传递groupId
注意:当执行demo2Client.save( “demo2”)时,会在请求头传递事务组id给demo2服务。
com.codingapi.tx.springcloud.feign.TransactionRestTemplateInterceptor
@Override
public void apply(RequestTemplate requestTemplate) {
TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
String groupId = txTransactionLocal == null ? null : txTransactionLocal.getGroupId();
logger.info("LCN-SpringCloud TxGroup info -> groupId:"+groupId);
if (txTransactionLocal != null) {
requestTemplate.header("tx-group", groupId);
}
}
3)1)5):提交
业务方法执行成功结束后,会走到这里。
com.codingapi.tx.datasource.relational.LCNStartConnection#commit
public void commit() throws SQLException {
logger.info("commit label");
//标记正确执行
state=1;
//异步线程执行commit或者rollback
close();
//设置已经执行过close
isClose.set(true);
}
异步线程执行commit或者rollback,方法有如下作用:
- 对rollback处理,业务方法执行过程中有任何报错,包含参与方的报错,走到这里回滚业务方访问db的操作。
- 对commit的处理,都到这说明业务方法顺利执行。
- 3:异步线程执行,等待txm返回的最终结果,决定是否commit或者rollback。
public void close() throws SQLException {
if(isClose.get()!=null&& isClose.get()){
return;
}
if(connection==null||connection.isClosed()){
return;
}
//1:回滚的处理
if(state==0){
//数据库回滚
rollbackConnection();
//关闭回调函数,关闭task,从缓存中清除db连接,归还db连接。
closeConnection();
return;
}
//2:正确执行的处理
if(state == 1) {
TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
boolean isGroup = (txTransactionLocal != null) ? txTransactionLocal.isHasIsGroup() : false;
//不是同一个db连接调用多次
if (isGroup) {
//加入队列的连接,仅操作连接对象,不处理事务
logger.info("start connection hasGroup -> " + isGroup);
return;
}
//3:异步线程执行,等待txm返回的最终结果,决定是否commit或者rollback。
startRunnable();
}
}
protected void startRunnable(){
if(hasStartTransaction){
logger.debug("start connection is wait ! ");
return;
}
//标记已执行过该方法
hasStartTransaction = true;
Runnable runnable = new HookRunnable() {
@Override
public void run0() {
//清空上下文对象
TxTransactionLocal.setCurrent(null);
try {
//执行提交或回滚。
transaction();
} catch (Exception e) {
logger.error(e.getMessage());
try {
rollbackConnection();
} catch (SQLException e1) {
logger.error(e1.getMessage());
}
} finally {
try {
//关闭回调函数,关闭task,从缓存中清除db连接,归还db连接。
closeConnection();
} catch (SQLException e) {
logger.error(e.getMessage());
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
执行提交或回滚操作。
com.codingapi.tx.datasource.relational.LCNStartConnection#transaction
public void transaction()throws SQLException{
if (waitTask == null) {
rollbackConnection();
System.out.println(" start waitTask is null");
return;
}
logger.info(" start transaction is wait for TxManager notify, groupId : " + getGroupId());
//1:这里会阻塞等待,直到tx-m通知所有参与者提交,获取到所有参与者执行后的最终结果后,才会被唤醒。
waitTask.awaitTask();
//获取最终执行结果。
int rs = waitTask.getState();
try {
//说明tx-m返回成功
if (rs == 1) {
if(isCompensate) {
//2:补偿时需要根据补偿数据决定提交还是回滚.
rs = startState;
if(rs==1) {
connection.commit();
}else{
connection.rollback();
}
}else{
//3:提交
connection.commit();
}
} else {
//tx-m
//4:回滚事务
rollbackConnection();
}
logger.info(" lcn start transaction over, res -> groupId:"+getGroupId()+" and state is "+(rs==1?"commit":"rollback"));
}catch (SQLException e){
//回滚事务
rollbackConnection();
//记录发起方提交失败
waitTask.setState(TaskState.connectionError.getCode());
System.out.println(" lcn start transaction over,but connection is closed, res -> groupId:"+getGroupId());
}
//删除任务
waitTask.remove();
}
这里由一个阻塞等待操作,该操作主要在com.codingapi.tx.aop.service.impl.TxStartTransactionServerImpl#execute被唤醒,后决定提交还是回滚。
这里执行失败后,会设置结果为:TaskState.connectionError.getCode()。在在com.codingapi.tx.aop.service.impl.TxStartTransactionServerImpl#execute类中会针对此通知tx-M记录补偿。
3)1)6):回滚
业务方法执行失败结束后,会走到这里。
com.codingapi.tx.datasource.relational.LCNStartConnection#rollback()
@Override
public void rollback() throws SQLException {
logger.info("rollback label");
//标记执行失败。
state=0;
//执行rollback,归还线程资源。
close();
//设置已经执行过close
isClose.set(true);
}
@Override
protected void rollbackConnection() throws SQLException {
connection.rollback();
}
@Override
protected void closeConnection() throws SQLException{
//关闭task,从缓存中清除db连接
subNowCount.close(this);
//spring封装的连接池归还
connection.close();
}
调用回调函数,关闭task,从缓存中清除db连接。这里回调函数是第一次构造connection时传递过来的。
protected ICallClose<ILCNResource> subNowCount = new ICallClose<ILCNResource>() {
@Override
public void close(ILCNResource connection) {
//删除回调任务
Task waitTask = connection.getWaitTask();
if (waitTask != null) {
if (!waitTask.isRemove()) {
waitTask.remove();
}
}
//从连接池中剔除事务组对应的db连接池
pools.remove(connection.getGroupId());
nowCount--;//当前连接池数量-1
}
};
至此发起方流程结束。
3)2):事务参与方执行分布式事务流程
仔细思考,作为参与者在整个分布式事务中出现情况如下:
- 参与方成功
- 发起方成功,提交。
- 发起方失败,回滚。
- 自身提交or回滚失败。
- 参与方失败,自身会回滚,首先不会影响自身,然后对上层服务会抛异常,上层服务会根据异常回滚操作。
针对上述四种情况均需要汇报给tx-m,让tx-m通知其他节点做出决策。
接下来查看执行流程,当发起方调用feign后,会在请求头注入事务组id,然后访问参与者接口。
参与者service层方法实现如下,可以看到该方法也需要添加事务注解,表明加入分布式事务交由tx-m进行管理。
调用save前,会进过aop环绕切面,走到这个方法,获取groupId。
3)2)1):创建事务服务
和事务发起方不同的就是获取的事务服务执行器不一样,其余流程都一样。当事务组id不为空时,会进入该方法。groupId是由发起方通过feign访问时设置到请求头里面的。
com.codingapi.tx.aop.service.impl.TransactionServerFactoryServiceImpl#createTransactionServer
3)2)2):执行分布式事务
- 构建任务id
- 构建上下文对象。
- 执行业务方法
- 针对有db操作的处理。
com.codingapi.tx.aop.service.impl.TxRunningTransactionServerImpl#execute
@Override
public Object execute(final ProceedingJoinPoint point, final TxTransactionInfo info) throws Throwable {
logger.info("事务参与方...");
//1:构建任务id
String kid = KidUtils.generateShortUuid();
String txGroupId = info.getTxGroupId();
logger.debug("--->begin running transaction,groupId:" + txGroupId);
long t1 = System.currentTimeMillis();
//是否是同一个事务下,第一次进入默认不在。
boolean isHasIsGroup = transactionControl.hasGroup(txGroupId);
//2:构建上下文对象。
TxTransactionLocal txTransactionLocal = new TxTransactionLocal();
txTransactionLocal.setGroupId(txGroupId);
txTransactionLocal.setHasStart(false);
txTransactionLocal.setKid(kid);
txTransactionLocal.setHasIsGroup(isHasIsGroup);
txTransactionLocal.setMaxTimeOut(Constants.txServer.getCompensateMaxWaitTime());
txTransactionLocal.setMode(info.getMode());
TxTransactionLocal.setCurrent(txTransactionLocal);
try {
//3:执行业务方法
Object res = point.proceed();
//4:针对有db操作的处理。
if(!txTransactionLocal.isReadOnly()) {
//获取该注解的业务方法名
String methodStr = info.getInvocation().getMethodStr();
//添加事务组子对象
TxGroup resTxGroup = txManagerService.addTransactionGroup(txGroupId, kid, isHasIsGroup, methodStr);
//已经进入过该模块的,不再执行此方法
if(!isHasIsGroup) {
String type = txTransactionLocal.getType();
//获取任务,该任务在创建db连接时创建。
TxTask waitTask = TaskGroupManager.getInstance().getTask(kid, type);
//lcn 连接已经开始等待时.自旋等待任务被删除
while (waitTask != null && !waitTask.isAwait()) {
TimeUnit.MILLISECONDS.sleep(1);
}
//tx-m没有发现该事务组,则回滚。
if (resTxGroup == null) {
//通知业务回滚事务
if (waitTask != null) {
//修改事务组状态异常
waitTask.setState(-1);
//唤醒db连接进行回滚。
waitTask.signalTask();
throw new ServiceException("update TxGroup error, groupId:" + txGroupId);
}
}
}
}
return res;
} catch (Throwable e) {
// 这里处理以下情况:当 point.proceed() 业务代码中 db事务正常提交,开始等待,后续处理发生异常。
// 由于没有加入事务组,不会收到通知。这里唤醒并回滚
if(!isHasIsGroup) {
String type = txTransactionLocal.getType();
TxTask waitTask = TaskGroupManager.getInstance().getTask(kid, type);
// 有一定几率不能唤醒: wait的代码是在另一个线程,有可能线程还没执行到wait,先执行到了这里
// TODO 要不要 sleep 1毫秒
logger.warn("wake the waitTask: {}", (waitTask != null && waitTask.isAwait()));
if (waitTask != null && waitTask.isAwait()) {
waitTask.setState(-1);
waitTask.signalTask();
}
}
throw e;
} finally {
//清空上下文信息
TxTransactionLocal.setCurrent(null);
long t2 = System.currentTimeMillis();
logger.debug("<---end running transaction,groupId:" + txGroupId+",execute time:"+(t2-t1));
}
}
3)2)3):获取线程连接
com.codingapi.tx.datasource.relational.LCNTransactionDataSource#createLcnConnection
这里需要注意的是,实现的连接是LCNDBConnection,比发起方多了一个传参dataSourceService。
@Override
protected Connection createLcnConnection(Connection connection, TxTransactionLocal txTransactionLocal) {
//累加当前获取db连接数。
nowCount++;
//如果是发起方
if(txTransactionLocal.isHasStart()){
//注册回调函数,用于清除缓存中连接资源。
LCNStartConnection lcnStartConnection = new LCNStartConnection(connection,subNowCount);
logger.debug("get new start connection - > "+txTransactionLocal.getGroupId());
//添加到缓存
pools.put(txTransactionLocal.getGroupId(), lcnStartConnection);
//标记已经获取到db连接
txTransactionLocal.setHasConnection(true);
return lcnStartConnection;
}else {
//调用方
LCNDBConnection lcn = new LCNDBConnection(connection, dataSourceService, subNowCount);
logger.debug("get new connection ->" + txTransactionLocal.getGroupId());
pools.put(txTransactionLocal.getGroupId(), lcn);
txTransactionLocal.setHasConnection(true);
return lcn;
}
}
3)2)4):执行业务方法
@Override
@TxTransaction
@Transactional
public int save(String name) {
return testMapper.save(name);
}
3)2)5):提交
com.codingapi.tx.datasource.relational.LCNDBConnection#commit
下述方法同事务发起方的代码一致,可直接看com.codingapi.tx.datasource.relational.LCNDBConnection#transaction
@Override
public void commit() throws SQLException {
logger.debug("commit label");
//标记成功执行。
state = 1;
close();
//设置该db已关闭
isClose.set(true);
}
@Override
public void close() throws SQLException {
//这里会调用二次close,一次是commit时调用。另外一次是spring关闭时调用。
//若已经close则返回。
if(isClose.get()!=null&& isClose.get()){
return;
}
//db连接关闭则返回
if(connection==null||connection.isClosed()){
return;
}
//针对连接只读的处理。
if(readOnly){
closeConnection();
logger.debug("now transaction is readOnly , groupId:" + groupId);
return;
}
logger.debug("now transaction state is " + state + ", (1:commit,0:rollback) groupId:" + groupId);
//执行失败,回滚的处理。
if (state==0) {
rollbackConnection();
closeConnection();
logger.debug("rollback transaction ,groupId:" + groupId);
}
//执行成功
if (state==1) {
TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
//同一个模块被多次请求则直接返回。
boolean hasGroup = (txTransactionLocal!=null)?txTransactionLocal.isHasIsGroup():false;
if (hasGroup) {
//加入队列的连接,仅操作连接对象,不处理事务
logger.debug("connection hasGroup -> "+hasGroup);
return;
}
startRunnable();
}
}
protected void startRunnable(){
if(hasStartTransaction){
logger.debug("start connection is wait ! ");
return;
}
//标记已执行过该方法
hasStartTransaction = true;
Runnable runnable = new HookRunnable() {
@Override
public void run0() {
//清空上下文对象
TxTransactionLocal.setCurrent(null);
try {
//执行提交或回滚。
transaction();
} catch (Exception e) {
logger.error(e.getMessage());
try {
rollbackConnection();
} catch (SQLException e1) {
logger.error(e1.getMessage());
}
} finally {
try {
//关闭回调函数,关闭task,从缓存中清除db连接,归还db连接。
closeConnection();
} catch (SQLException e) {
logger.error(e.getMessage());
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
同事务发起方不同实现在此com.codingapi.tx.datasource.relational.LCNDBConnection#transaction
3)2)5)1):最终提交
该方法主要等待tx-m返回最终结果,决定是否提交还是回滚。倘若tx-m超过5秒没返回任务,则执行超时任务。超时任务会主动向txm发起询问,针对结果处理。
-
等待5秒,倘若没收到tx-m返回的事务执行结果,主动询问
-
阻塞等待,等待tx-m返回的结果。对应步骤如图。
-
获取tx-m返回的结果
-
说明发起方调用方执行成功,则提交事务。否则回滚。
-
删除任务
@Override
public void transaction() throws SQLException {
if (waitTask == null) {
rollbackConnection();
System.out.println(" waitTask is null");
return;
}
//start 结束就是全部事务的结束表示,考虑start挂掉的情况
Timer timer = new Timer();
System.out.println(" maxOutTime : "+maxOutTime);
//1:等待5秒,倘若没收到tx-m返回的事务执行结果,主动询问
timer.schedule(new TimerTask() {
@Override
public void run() {
logger.info("auto execute ,groupId:" + getGroupId());
dataSourceService.schedule(getGroupId(), waitTask);
}
}, maxOutTime);
logger.info("transaction is wait for TxManager notify, groupId {}", getGroupId());
//2:阻塞等待,等待tx-m返回的结果
waitTask.awaitTask();
//说明获取到结果,定时任务取消。
timer.cancel();
//3:获取返回的结果
int rs = waitTask.getState();
try {
//4:说明发起方调用方执行成功,则提交事务。
if (rs == 1) {
connection.commit();
} else {
//5:说明其他方执行失败,则回滚事务
rollbackConnection();
}
logger.info("lcn transaction over, res -> groupId:"+getGroupId()+" and state is "+(rs==1?"commit":"rollback"));
}catch (SQLException e){
logger.info("lcn transaction over,but connection is closed, res -> groupId:"+getGroupId());
//记录连接错误。
waitTask.setState(TaskState.connectionError.getCode());
}
//6:删除任务
waitTask.remove();
}
倘若没收到tx-m返回的事务执行结果,主动发起询问
com.codingapi.tx.datasource.service.impl.DataSourceServiceImpl#schedule
@Override
public void schedule(String groupId, Task waitTask) {
String waitTaskId = waitTask.getKey();
//1:主动询问tx-m结果状态。
int rs = txManagerService.cleanNotifyTransaction(groupId, waitTaskId);
//执行成功或失败则存储结果唤醒。
if (rs == 1 || rs == 0) {
//将tx-m返回的结果存储。
waitTask.setState(rs);
//唤醒任务
waitTask.signalTask();
return;
}
//2:走到这里说明网络或连接错误,通过http请求询问tx-m结果状态,同通知txm清理事务数据。
rs = txManagerService.cleanNotifyTransactionHttp(groupId, waitTaskId);
if (rs == 1 || rs == 0) {
waitTask.setState(rs);
waitTask.signalTask();
return;
}
//3:走到这里说明tx-m异常,则添加到补偿队列
waitTask.setState(-100);
waitTask.signalTask();
}
3)2)5)2):通知tx-m最终执行结果。
com.codingapi.tx.control.service.impl.ActionTServiceImpl#notifyWaitTask对应这步。该方法通知参与方提交还是回滚,并通过自旋方式等待参与方最终结果,上报给tx-m。
private String notifyWaitTask(TaskGroup task, int state) {
String res;
//传递tx-m返回结果
task.setState(state);
//唤醒db连接,根据state提交或回滚
task.signalTask();
int count = 0;
//自旋等待db连接最终执行结果
while (true) {
if (task.isRemove()) {
if (task.getState() == TaskState.rollback.getCode()
|| task.getState() == TaskState.commit.getCode()) {
res = "1";
} else {
res = "0";
}
break;
}
if (count > 1000) {
//已经通知了,有可能失败.
res = "2";
break;
}
count++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//返回结果给tx-m
return res;
}
3)2)5)3):超时处理 todo
dataSourceService.schedule(getGroupId(), waitTask);
3)2)6):回滚
同事务发起方一样。
3)3):tx-m
3)3)1):启动流程
向spring注入ServerListener对象,该对象继承com.codingapi.tm.listener.ServerListener,在servlet容器启动时触发。
就是简单的启动netty服务。
@Component
public class ServerListener implements ServletContextListener {
private WebApplicationContext springContext;
private InitService initService;
@Override
public void contextInitialized(ServletContextEvent event) {
springContext = WebApplicationContextUtils
.getWebApplicationContext(event.getServletContext());
initService = springContext.getBean(InitService.class);
//启动netty内嵌服务
initService.start();
}
@Override
public void contextDestroyed(ServletContextEvent event) {
initService.close();
}
}
com.codingapi.tm.listener.service.impl.InitServiceImpl
@Override
public void start() {
/**加载本地服务信息**/
Constants.socketPort = configReader.getSocketPort();
Constants.maxConnection = configReader.getSocketMaxConnection();
nettyServerService.start();
}
com.codingapi.tm.netty.service.impl.NettyServerServiceImpl#start
public void start() {
final int heartTime = configReader.getTransactionNettyHeartTime()+10;
txCoreServerHandler = new TxCoreServerHandler(threadPool,nettyService);
bossGroup = new NioEventLoopGroup(50); // (1)
workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("timeout", new IdleStateHandler(heartTime, heartTime, heartTime, TimeUnit.SECONDS));
ch.pipeline().addLast(new LengthFieldPrepender(4, false));
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
ch.pipeline().addLast(txCoreServerHandler);
}
});
// Start the server.
b.bind(Constants.socketPort);
logger.info("Socket started on port(s): " + Constants.socketPort + " (socket)");
} catch (Exception e) {
// Shut down all event loops to terminate all threads.
e.printStackTrace();
}
}
com.codingapi.tm.netty.handler.TxCoreServerHandler
该方法处理请求。
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
final String json = SocketUtils.getJson(msg);
logger.debug("request->"+json);
//接收请求后,线程池异步处理,提高吞吐。
threadPool.execute(new Runnable() {
@Override
public void run() {
service(json,ctx);
}
});
}
通过命令类型寻找对应类处理。
private void service(String json,ChannelHandlerContext ctx){
if (StringUtils.isNotEmpty(json)) {
JSONObject jsonObject = JSONObject.parseObject(json);
//获取命令类型
String action = jsonObject.getString("a");
String key = jsonObject.getString("k");
JSONObject params = JSONObject.parseObject(jsonObject.getString("p"));//获取请求参数
//获取客户端地址
String channelAddress = ctx.channel().remoteAddress().toString();
//通过命令类型,从spring容器中寻找对应的类处理
IActionService actionService = nettyService.getActionService(action);
String res = actionService.execute(channelAddress,key,params);
//封装响应结果,并刷出站
JSONObject resObj = new JSONObject();
resObj.put("k", key);
resObj.put("d", res);
SocketUtils.sendMsg(ctx,resObj.toString());
}
}
com.codingapi.tm.netty.service.impl.NettyServiceImpl#getActionService
@Override
public IActionService getActionService(String action) {
return spring.getBean(action,IActionService.class);
}
3)3)2):创建事务组
发起者向tx-m发起请求,命令类型为cg
com.codingapi.tx.netty.service.impl.MQTxManagerServiceImpl#createTransactionGroup
@Override
public void createTransactionGroup(String groupId) {
JSONObject jsonObject = new JSONObject();
//事务组
jsonObject.put("g", groupId);
Request request = new Request("cg", jsonObject.toString());
SocketManager.getInstance().sendMsg(request);
}
直接看tx-m cg对应的处理类实现。
com.codingapi.tm.netty.service.impl.ActionCGServiceImpl#execute
@Override
public String execute(String channelAddress, String key, JSONObject params ) {
String res = "";
//获取事务组id
String groupId = params.getString("g");
//创建事务组
TxGroup txGroup = txManagerService.createTransactionGroup(groupId);
if(txGroup!=null) {
//设置当前时间到事务对象中。
txGroup.setNowTime(System.currentTimeMillis());
res = txGroup.toJsonString(false);
}else {
res = "";
}
return res;
}
简单的将事务对象存储redis中。
com.codingapi.tm.manager.service.impl.TxManagerServiceImpl#createTransactionGroup
@Override
public TxGroup createTransactionGroup(String groupId) {
logger.info("创建事物组");
//新建事务对象
TxGroup txGroup = new TxGroup();
//从redis缓存中获取是否有补偿信息
if (compensateService.getCompensateByGroupId(groupId)!=null) {
txGroup.setIsCompensate(1);
}
txGroup.setStartTime(System.currentTimeMillis());
txGroup.setGroupId(groupId);
//将事务信息存储redis中
String key = configReader.getKeyPrefix() + groupId;
redisServerService.saveTransaction(key, txGroup.toJsonString());
return txGroup;
}
3)3)3):加入事务组
参与者向tx-m发送加入事务组请求,数据如下:
@Override
public TxGroup addTransactionGroup(String groupId, String taskId, boolean isGroup, String methodStr) {
JSONObject jsonObject = new JSONObject();
//groupId
jsonObject.put("g", groupId);
//参与者方taskId
jsonObject.put("t", taskId);
//参与者业务方法名
jsonObject.put("ms", methodStr);
//是否同一个事务组下
jsonObject.put("s", isGroup ? 1 : 0);
Request request = new Request("atg", jsonObject.toString());
String json = SocketManager.getInstance().sendMsg(request);
return TxGroup.parser(json);
}
直接看tx-m atg对应的处理类实现。
com.codingapi.tm.netty.service.impl.ActionATGServiceImpl#execute
@Override
public String execute(String channelAddress,String key,JSONObject params ) {
String res = "";
//groupId
String groupId = params.getString("g");
//参与者方taskId
String taskId = params.getString("t");
//参与者业务方法名
String methodStr = params.getString("ms");
//获取参与者执行结果
int isGroup = params.getInteger("s");
//添加事务组
TxGroup txGroup = txManagerService.addTransactionGroup(groupId, taskId, isGroup, channelAddress, methodStr);
if(txGroup!=null) {
txGroup.setNowTime(System.currentTimeMillis());
res = txGroup.toJsonString(false);
}else {
res = "";
}
return res;
}
com.codingapi.tx.netty.service.impl.MQTxManagerServiceImpl#addTransactionGroup
@Override
public TxGroup addTransactionGroup(String groupId, String taskId, int isGroup, String channelAddress, String methodStr) {
logger.info("添加事务组子对象...");
//构建key,并从redis获取事务信息
String key = getTxGroupKey(groupId);
TxGroup txGroup = getTxGroup(groupId);
//说明事务在redis中不存在,则直接返回。
if (txGroup==null) {
return null;
}
//封装参与方事务信息
TxInfo txInfo = new TxInfo();
txInfo.setChannelAddress(channelAddress);
txInfo.setKid(taskId);//参与方内部的任务,主要用来接受当所有参与方执行后,发起者告知txm是否通知大伙儿一起提交还是回滚。
txInfo.setAddress(Constants.address);
txInfo.setIsGroup(isGroup);
txInfo.setMethodStr(methodStr);
//通过发起方地址,从缓存中寻找到注册信息。
ModelInfo modelInfo = ModelInfoManager.getInstance().getModelByChannelName(channelAddress);
if(modelInfo!=null) {//将发起方地址信息,存储事务信息中
txInfo.setUniqueKey(modelInfo.getUniqueKey());
txInfo.setModelIpAddress(modelInfo.getIpAddress());
txInfo.setModel(modelInfo.getModel());
}
//
txGroup.addTransactionInfo(txInfo);
//添加redis缓存中
redisServerService.saveTransaction(key, txGroup.toJsonString());
return txGroup;
}
3)3)4):关闭事务组
com.codingapi.tx.netty.service.impl.MQTxManagerServiceImpl#closeTransactionGroup
@Override
public int closeTransactionGroup(final String groupId, final int state) {
JSONObject jsonObject = new JSONObject();
//groupId
jsonObject.put("g", groupId);
//发起者业务方法执行结果,通过该结果,tx-m决定大伙儿一起提交还是回滚。
jsonObject.put("s", state);
Request request = new Request("ctg", jsonObject.toString());
String json = SocketManager.getInstance().sendMsg(request);
try {
return Integer.parseInt(json);
}catch (Exception e){
return 0;
}
}
直接看tx-m ctg对应的处理类实现,该方法主要流程如下:
- 当接收到发起方的通知后,根据发起方的执行结果,异步通知每个参与者提交或回滚,然后阻塞等待结果。
- 参与者收到tx-m通知后,执行提交或回滚。执行结束,将执行结果通知tx-m。
- tx-m此时都在阻塞等待参与者最终结果,然后响应给发起方。当有一个参与者没执行成功,则发起方会回滚,并且记录补偿日志。
com.codingapi.tm.netty.service.impl.ActionCTGServiceImpl#execute#execute
@Override
public String execute(String channelAddress, String key, JSONObject params ) {
//获取事务组
String groupId = params.getString("g");
//获取参与者执行结果
int state = params.getInteger("s");
String res = String.valueOf(txManagerService.closeTransactionGroup(groupId,state));
return res;
}
com.codingapi.tm.manager.service.impl.TxManagerServiceImpl#closeTransactionGroup
@Override
public int closeTransactionGroup(String groupId,int state) {
logger.info("关闭事务组");
//构建key,从redis获取事务信息
String key = getTxGroupKey(groupId);
TxGroup txGroup = getTxGroup(groupId);
//为空则说明事务不存在,返回失败。
if(txGroup==null){
return 0;
}
//设置发起方事务执行结果
txGroup.setState(state);
//标记事务结束
txGroup.setHasOver(1);
//存储redis中
redisServerService.saveTransaction(key,txGroup.toJsonString());
return transactionConfirmService.confirm(txGroup);
}
该方法执行流程如下:
- 从事务中获取所有参与者网络信息。然后检查网络,绑定管道对象。
- 事务不满足直接回滚事务
- 查看所有参与者是否执行成功。
- 如果所有参与者执行成功,则从redis中删除事务组
- 返回结果给发起者。
com.codingapi.tm.manager.service.impl.TxManagerSenderServiceImpl#confirm
@Override
public int confirm(TxGroup txGroup) {
//1:从事务中获取所有参与者网络信息。然后检查网络,绑定管道对象。
setChannel(txGroup.getList());
//2:事务不满足直接回滚事务
if (txGroup.getState()==0) {
transaction(txGroup, 0);
return 0;
}
if(txGroup.getRollback()==1){
transaction(txGroup, 0);
return -1;
}
//3:查看所有参与者是否执行成功。
boolean hasOk = transaction(txGroup, 1);
//4:如果所有参与者执行成功,则从redis中删除事务组
txManagerService.dealTxGroup(txGroup,hasOk);
//5:返回结果给发起者。
return hasOk?1:0;
}
从事务中获取所有参与者网络信息。然后检查网络,绑定管道对象。
private void setChannel(List<TxInfo> list) {
for (TxInfo info : list) {
if(Constants.address.equals(info.getAddress())){
//参与者记录的tx-m地址和当前tx-m地址一样,则查看管道是否存活
Channel channel = SocketManager.getInstance().getChannelByModelName(info.getChannelAddress());
if (channel != null &&channel.isActive()) {
//存活则绑定管道到参与者信息中
ChannelSender sender = new ChannelSender();
sender.setChannel(channel);
info.setChannel(sender);
}
}else{
//不存在说明tx-m触发了变更
ChannelSender sender = new ChannelSender();
sender.setAddress(info.getAddress());
sender.setModelName(info.getChannelAddress());
info.setChannel(sender);
}
}
}
查看所有参与者是否执行成功,就是简单的异步线程访问所有的参与者,然后汇总结果。
private boolean transaction(final TxGroup txGroup, final int checkSate) {
if (checkSate == 1) {
logger.info("事务提交");
//补偿请求,加载历史数据
if (txGroup.getIsCompensate() == 1) {
compensateService.reloadCompensate(txGroup);
}
CountDownLatchHelper<Boolean> countDownLatchHelper = new CountDownLatchHelper<Boolean>();
for (final TxInfo txInfo : txGroup.getList()) {
if (txInfo.getIsGroup() == 0) {
countDownLatchHelper.addExecute(new IExecute<Boolean>() {
@Override
public Boolean execute() {//channel空判断
if(txInfo.getChannel()==null){
return false;
}
final JSONObject jsonObject = new JSONObject();
jsonObject.put("a", "t");
/** 补偿请求 **/
if (txGroup.getIsCompensate() == 1) {
jsonObject.put("c", txInfo.getIsCommit());
} else { //正常业务
jsonObject.put("c", checkSate);
}
//获取参与方的任务id
jsonObject.put("t", txInfo.getKid());
final String key = KidUtils.generateShortUuid();
jsonObject.put("k", key);
//新建一个任务
Task task = ConditionUtils.getInstance().createTask(key);
//超时处理,客户端超过5秒没有返回db执行状态,则设置结果为失败
ScheduledFuture future = schedule(key, configReader.getTransactionNettyDelayTime());
//向客户端发送消息
threadAwaitSend(task, txInfo, jsonObject.toJSONString());
//阻塞等待客户端回复消息
task.awaitTask();
//超时任务还在,则取消。
if (!future.isDone()) {
future.cancel(false);
}
try {//获取参与者执行结果
String data = (String) task.getBack().doing();
// 1 成功 0 失败 -1 task为空 -2 超过
boolean res = "1".equals(data);
if (res) {
txInfo.setNotify(1);
}
return res;
} catch (Throwable throwable) {
throwable.printStackTrace();
return false;
} finally {//删除任务
task.remove();
}
}
});
}
}
//异步获取所有参与者db执行结果
List<Boolean> hasOks = countDownLatchHelper.execute().getData();
//更新事务组信息
String key = configReader.getKeyPrefix() + txGroup.getGroupId();
redisServerService.saveTransaction(key, txGroup.toJsonString());
//标记是否所有参与者执行成功,只要有一个失败则返回失败。
boolean hasOk = true;
for (boolean bl : hasOks) {
if (!bl) {
hasOk = false;
break;
}
}
logger.info("--->" + hasOk + ",group:" + txGroup.getGroupId() + ",state:" + checkSate + ",list:" + txGroup.toJsonString());
return hasOk;
}
logger.info("事务回滚");
//回滚操作只发送通过不需要等待确认
for (TxInfo txInfo : txGroup.getList()) {
if(txInfo.getChannel()!=null) {
if (txInfo.getIsGroup() == 0) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("a", "t");
jsonObject.put("c", checkSate);
jsonObject.put("t", txInfo.getKid());
String key = KidUtils.generateShortUuid();
jsonObject.put("k", key);
txInfo.getChannel().send(jsonObject.toJSONString());
}
}
}
//删除事务组
txManagerService.deleteTxGroup(txGroup);
return true;
}
参与者超过5秒没返回db执行状态,则设置结果为失败
private ScheduledFuture schedule(final String key, int delayTime) {
ScheduledFuture future = executorService.schedule(new Runnable() {
@Override
public void run() {
Task task = ConditionUtils.getInstance().getTask(key);
if(task!=null&&!task.isNotify()) {
task.setBack(new IBack() {
@Override
public Object doing(Object... objs) throws Throwable {
return "-2";
}
});
task.signalTask();
}
}
}, delayTime, TimeUnit.SECONDS);
return future;
}
向客户端发送消息
private void threadAwaitSend(final Task task, final TxInfo txInfo, final String msg){
threadPool.execute(new Runnable() {
@Override
public void run() {
while (!task.isAwait() && !Thread.currentThread().interrupted()) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断管道是否关闭
if(txInfo!=null&&txInfo.getChannel()!=null) {
//发送消息给参与者,执行db操作,参与者返回状态时会唤醒tx-m
txInfo.getChannel().send(msg,task);
}else{
//管道不存在,则设置失败,并唤醒。
task.setBack(new IBack() {
@Override
public Object doing(Object... objs) throws Throwable {
return "-2";
}
});
task.signalTask();
}
}
});
}