文章目录
简介
运行官方demo
事务回滚原理简介
简介
阿里巴巴近日开源了分布式事务中间件 fescar。GitHub地址是 https://github.com/alibaba/fescar。
官方中文文档:https://github.com/alibaba/fescar/wiki/Home_Chinese
但是现在中文文档连接都不对,打不开,不知为何。
阿里巴巴现在内部使用的版本是GTS,fescar是其开源社区版本,fescar是Fast & EaSy Commit And Rollback, FESCAR的简称。更多简介请参考
fescar官方wiki。
fescar架构简介参考: 阿里巴巴开源分布式事务解决方案 Fescar
运行官方demo
将https://github.com/alibaba/fescar克隆到本地IDEA,demo运行使用到的是examples和server。
我这里使用的是0.1.1版本:
运行程序首先需要配置数据库并且初始化SQL。
1、修改以下三个配置文件的MySQL地址
test module中的MySQL配置也改一下:
另外由于我本地的MySQL是8.X版本,所以驱动要升级。
我使用的JDK11,com.alibaba.fescar.tm.dubbo.impl.OrderServiceImpl有个BUG,运行时会报错,需要修改一下:
最后执行以下SQL语句初始化表:
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
好了,准备工作做完。
实例简述:
该实例模拟一个下单功能,由businessService发起,首先扣减库存,然后创建订单。
启动server项目的com.alibaba.fescar.server.Server的main方法;
启动example项目的AccountServiceImpl、OrderServiceImpl、StorageServiceImpl三个类的main方法,也就是dubbo服务提供者;
以上4个服务启动完成后,查看DB中的记录,会初始化account_tbl和storage_tbl两张表,插入一条记录(左侧的表)
执行com.alibaba.fescar.tm.dubbo.impl.BusinessServiceImplmain方法。会发现执行报错,DB表数据没有变更。
是因为在com.alibaba.fescar.tm.dubbo.impl.BusinessServiceImpl#purchase方法中存在模拟的异常,我们将其注释掉,再次执行:
注释掉以后执行,可以发现没有报错,DB中的数据已经正确修改(参见上图的右侧三张表的数据)。
至此demo运行成功。
事务回滚原理简介
根据 阿里巴巴开源分布式事务解决方案 Fescar 介绍的原理,简单看看其rollback的原理。后续专门分析一下fescar的源码。
阿里巴巴开源分布式事务解决方案 Fescar 讲到它是优化了两阶段提交,减少锁的时间,利用本地事务真正提交事务,并且记录可用于回滚的日志,然后出错时根据日志回滚。
TransactionalTemplate是核心入口类;
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.fescar.tm.api;
import com.alibaba.fescar.core.exception.TransactionException;
/**
* Template of executing business logic with a global transaction.
*/
public class TransactionalTemplate {
/**
* Execute object.
*
* @param business the business
* @return the object
* @throws TransactionalExecutor.ExecutionException the execution exception
*/
public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException {
// 1. get or create a transaction
//获取全局事务管理器
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
// 2. begin transaction 事务begin
try {
tx.begin(business.timeout(), business.name());
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.BeginFailure);
}
Object rs = null;
try {
// Do Your Business 执行我们具体的业务逻辑
rs = business.execute();
} catch (Throwable ex) {
// 3. any business exception, rollback. 出错时回滚
try {
tx.rollback();
// 3.1 Successfully rolled back
throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);
} catch (TransactionException txe) {
// 3.2 Failed to rollback
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.RollbackFailure, ex);
}
}
// 4. everything is fine, commit. 事务提交
try {
tx.commit();
} catch (TransactionException txe) {
// 4.1 Failed to commit
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.CommitFailure);
}
return rs;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
事务提交有两种:
public enum GlobalTransactionRole {
/**
* The Launcher.
* 开启全局事务的发起者
*/
// The one begins the current global transaction.
Launcher,
/**
* The Participant.
* 分支事务,也就是分布在各个系统中的本地事务
*/
// The one just joins into a existing global transaction.
Participant
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
通过代码可以看到,分支事务什么都不做,也就是直接提交本地事务。Launcher事务会进行全局事务的提交。
记录回滚日志的关键代码com.alibaba.fescar.rm.datasource.undo.UndoLogManager#flushUndoLogs中的undoLogContent:
public static void flushUndoLogs(ConnectionProxy cp) throws SQLException {
assertDbSupport(cp.getDbType());
ConnectionContext connectionContext = cp.getContext();
String xid = connectionContext.getXid();
long branchID = connectionContext.getBranchId();
BranchUndoLog branchUndoLog = new BranchUndoLog();
branchUndoLog.setXid(xid);
branchUndoLog.setBranchId(branchID);
branchUndoLog.setSqlUndoLogs(connectionContext.getUndoItems());
String undoLogContent = UndoLogParserFactory.getInstance().encode(branchUndoLog);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Flushing UNDO LOG: " + undoLogContent);
}
PreparedStatement pst = null;
try {
pst = cp.getTargetConnection().prepareStatement(INSERT_UNDO_LOG_SQL);
pst.setLong(1, branchID);
pst.setString(2, xid);
pst.setBlob(3, BlobUtils.string2blob(undoLogContent));
pst.executeUpdate();
} catch (Exception e) {
if (e instanceof SQLException) {
throw (SQLException) e;
} else {
throw new SQLException(e);
}
} finally {
if (pst != null) {
pst.close();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
这里根据上面的实例,查看其中一条日志:
{
"branchId": 1890459,
"sqlUndoLogs": [{
"afterImage": {
"rows": [{
"fields": [{
"keyType": "PrimaryKey",
"name": "ID",
"type": 4,
"value": 8
}, {
"keyType": "NULL",
"name": "MONEY",
"type": 4,
"value": 199
}]
}],
"tableName": "account_tbl"
},
"beforeImage": {
"rows": [{
"fields": [{
"keyType": "PrimaryKey",
"name": "ID",
"type": 4,
"value": 8
}, {
"keyType": "NULL",
"name": "MONEY",
"type": 4,
"value": 599
}]
}],
"tableName": "account_tbl"
},
"sqlType": "UPDATE",
"tableName": "account_tbl"
}],
"xid": "10.240.130.105:8091:1890457"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
可以看到,日志中记录了修改之前和之后的数据变化情况,也就是数据镜像,回滚时就是根据这个进行回滚的。
由于现在fescar才刚刚开源,远没有达到商用,需要到1.0版本才可以线上使用。本文只是简单了解入门一下,后续在升级几个版本之后再详细的分析其源码。