BASE是基于可用、柔性状态和最终一致性这三个要素
- 基本可用(Basically Available)保证分布式事务参与方不一定要同时在线;
- 柔性状态(Soft state)则允许系统状态更新有一定的延迟,这个延时对客户来说不一定能够察觉到;
- 最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性;
- 分布式XA事务,在ACID事务中对隔离性要求很高,在事务执行过程中,必须将所有资源锁定。(因为XA事务和本地事务是互斥的)
- 分布式柔性事务,理念则是通过业务逻辑层面将互斥锁操作从资源层面上移到业务层面。(通过放宽强一致性要求,来换取系统吞吐量的提升)
- 原子性(Atomicity)正常情况下保证
- 一致性(Consistency)在某个时间点,会出现A库和B库的数据违反一致性要求的情况,但是最终是一致性的
- 隔离性(Isolation)在某个时间点,A事务能够读到B事务部分提交的结果
- 持久性(Durability)和本地事务一样,只要commit则数据被持久
Seata是阿里开源的分布式框架,目前在分布式事务领域中应用最为广泛。 Seata AT 事务模型包含了:TM(事务管理器),RM(资源管理器)和TC(事务协调器)。
- TC是一个独立部署的服务,TM和RM以jar包的方式同业务应用一同部署,它们同TC建立长连接,在整个事务生命周期内,保持远程通信。
- TM是全局事务的发起放,负责全局事务的开启,提交和回滚。
- RM是全局事务的参与者,负责分支事务的执行结果上报,并且通过TC协调器进行分支事务的提交和回滚。
- TM要求TC开始一个全局事务
- TC生成一个代表该全局事务的XID
- XID贯穿于微服务的整个调用链
- TM要求TC提交或回滚XID对应的全局事务
- TC驱动XID对应的全局事务下的所有分支事务完成提交和回滚
Seata-AT两阶段提交协议的演变:
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
- 二阶段:提交异步化,非常快速地完成。回滚通过一阶段日志进行方向SQL补偿。
Seata-AT通过全局锁的方式,实现读写隔离
- 本地锁控制本地操作
- 全局锁控制全局提交
- seata-at-account-service
- seata-at-bussiness-service
- seata-at-stroage-service
- seata-at-order-service
# db_seata
DROP SCHEMA IF EXISTS db_seata;
CREATE SCHEMA db_seata;
USE db_seata;
# Account
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;
INSERT INTO account_tbl (id, user_id, money)
VALUES (1, '1001', 10000);
INSERT INTO account_tbl (id, user_id, money)
VALUES (2, '1002', 10000);
# Order
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;
# Storage
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` (`commodity_code`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO storage_tbl (id, commodity_code, count)
VALUES (1, '2001', 1000);
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
- 下载Seata
wget https://github.com/seata/seata/releases/download/v1.0.0/seata-server-1.0.0.zip
- 解压Seata
yum install unzip -y
unzip -o seata-server-1.0.0.zip
cd seata-server-1.0.0
- 修改配置
vi ./conf/file.conf
使用file模式
service {
default.grouplist = "10.211.55.6:8091"
disableGlobalTransaction = false
}
store {
mode = "file"
file {
dir = "sessionStore"
}
db {
datasource = "dbcp"
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "root"
password = "root"
}
}
- 启动Seata Server
sh ./bin/seata-server.sh
测试事务提交成功场景:curl -X POST http://127.0.0.1:8084/api/business/purchase/commit
测试事务回滚成功场景:http://127.0.0.1:8084/api/business/purchase/rollback
5.1 在所有分支事务设置全局事务XID:Seata的事务上下文由RootContext来管理。应用开启全局事务后,所有的分支事务都必须绑定这个全局的XID。当事务结束(提交或回滚完成),RootContext会自动解绑XID。另外RootContext是基于ThreadLocal线程安全的。(本次测试是在SpringBoot使用的是HTTP方式,所以才需要通过HTTP过滤器手动进行绑定XID。如果是Dubbo或SpringCloud环境,则框架层自动绑定)
@Component
public class SeataFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
String xid = req.getHeader(RootContext.KEY_XID.toLowerCase());
boolean isBind = false;
if (StringUtils.isNotBlank(xid)) {
//绑定全局事务XID(Global Transaction)
RootContext.bind(xid);
isBind = true;
}
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
if (isBind) {
RootContext.unbind();
}
}
}
@Override
public void destroy() {
}
}
5.2 开启AT全局事务:在Seata中AT模式和TCC模式一样,业务方需要使用@GlobalTransactional注解来开启全局事务(Global Transaction),会初始化GlobalTransactionInterceptor拦截器,开启一个全局事务,获取全局的事务ID(即RootContext.getXID())
@Service
@RequiredArgsConstructor(onConstructor_ = {@Autowired(required = false)})
public class BusinessService {
private static final Logger LOGGER = LoggerFactory.getLogger(BusinessService.class);
private final StorageClient storageClient;
private final OrderClient orderClient;
/**
* 开启分布式全局事务,完成(减库存,下订单)操作
*
* @param userId
* @param commodityCode
* @param orderCount
*/
@GlobalTransactional
@SneakyThrows
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
storageClient.deduct(commodityCode, orderCount);
orderClient.create(userId, commodityCode, orderCount);
}
}