一、事务的5个特性
- 事务(Transaction): 是访问并可能更新数据库中各种数据项的一个程序执行单元。在关系型数据库中,一个事务由一组sql语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常被称为ACID特性。
- 原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
- 一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态,事务的中间状态不能被观察到的。
- 隔离性(isolation): 一个事务的执行不能被其它事务所干扰。即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离性又分为四个级别:读未提交(read uncommitted)、读已提交(read committed,解决脏读)、可重复读(repeatable read,解决虚读)、串行化(serializable,解决幻读)。
- 持久性(durability): 持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其有任何影响。
任何事务机制在实现时,都应该考虑事务的ACID特性,包括:本地事务,分布式事务,即使不能都很好的满足,也要考虑支持到什么程度。
二、本地事务 @Transaction
大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务被称之为本地事务(Local Transaction)。本地事务的ACID特性是数据库直接提供支持。本地事务应用架构如下所示:
三、分布式事务问题
分布式服务中,单体应用被拆分成微服务应用,原来的多个模块被拆分成多个应用,分别使用多个独立的数据源,
业务操作需要调用多个服务来完成。此时每个服务内部的数据一致性有本地事务来保证,但是全局的数据一致性问题无法保证。
举例:
用户购买商品的业务逻辑,整个业务逻辑由3个微服务提供支持:
- 仓储服务:对给定的商品扣除仓储数量
- 订单服务:根据采购需求创建订单
- 账号服务:从用户账号中扣除余额
架构图:
一次业务操作需要垮多个数据源或需要垮多个系统进行远程调用,就会产生分布式事务问题
四、Seata 是什么?
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,其内部版本在阿里经济体内部一直扮演着应用架构层数据一致性的中间件角色,帮助经济体平稳的度过历年的双11,对上层业务进行了有力的技术支撑。经过多年沉淀与积累,其商业化产品先后在阿里云、金融云上售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助用户快速落地分布式事务解决方案。
源码:http://github.com/seata/seata
官方Demo: https://gitee.com/seata-io/seata-samples
Seata的三大角色
Transaction ID(XID):全局唯一的事务id
在Seata的架构中,一共有三大角色:
TC(Transaction Coordinator)- 事务协调者 维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM(Transaction Manager)- 事务管理器 定义全局事务的范围:开始全局事务、提交或回滚全局事务
RM(Resource Manager)- 资源管理器 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中,TC为单独部署的Server服务端,TM和RM为嵌入到应用中的Client客户端。
在Seata中,一个分布式事务的生命周期如下:
处理过程:
- TM请求TC开启一个全局事务。TC会生成一个XID作为该全局事务的编号。
- XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。
- RM请求TC将本地事务注册为全局事务的分支事务,通过全局事务的XID进行关联。
- TM请求TC告诉XID对应的全局事务是进行提交还是回滚。
- TC驱动RM们将XID对应的自己的本地事务进行提交还是回滚。
详情参见官网 http://seata.io/zh-cn/docs/overview/what-is-seata.html
五、Seata 快速开始demo–微服务示例
以下演示都需要先启动Nacos后启动Seata,保证两个都OK
1,创建业务数据库
全局事务会话信息由3块内容构成,全局事务–>分支事务–>全局锁,对应表global_table、branch_table、lock_table。
CREATE TABLE IF NOT EXISTS `global_table` ( `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `status` TINYINT NOT NULL, `application_id` VARCHAR(32), `transaction_service_group` VARCHAR(32), `transaction_name` VARCHAR(128), `timeout` INT, `begin_time` BIGINT, `application_data` VARCHAR(2000), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`xid`), KEY `idx_gmt_modified_status` (`gmt_modified`, `status`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; -- the table to store BranchSession data CREATE TABLE IF NOT EXISTS `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `resource_group_id` VARCHAR(32), `resource_id` VARCHAR(256), `branch_type` VARCHAR(8), `status` TINYINT, `client_id` VARCHAR(64), `application_data` VARCHAR(2000), `gmt_create` DATETIME(6), `gmt_modified` DATETIME(6), PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; -- the table to store lock data CREATE TABLE IF NOT EXISTS `lock_table` ( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(96), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_branch_id` (`branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
create database seata_order; create database seata_storage; create database seata_account;
seata_order库下新建t_order表
DROP TABLE IF EXISTS `t_order`; CREATE TABLE `t_order` ( `int` bigint(11) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) DEFAULT NULL COMMENT '用户id', `product_id` bigint(11) DEFAULT NULL COMMENT '产品id', `count` int(11) DEFAULT NULL COMMENT '数量', `money` decimal(11, 0) DEFAULT NULL COMMENT '金额', `status` int(1) DEFAULT NULL COMMENT '订单状态: 0:创建中 1:已完结', PRIMARY KEY (`int`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '订单表' ROW_FORMAT = Dynamic;
seata_storage库下新建t_storage表
DROP TABLE IF EXISTS `t_storage`; CREATE TABLE `t_storage` ( `int` bigint(11) NOT NULL AUTO_INCREMENT, `product_id` bigint(11) DEFAULT NULL COMMENT '产品id', `total` int(11) DEFAULT NULL COMMENT '总库存', `used` int(11) DEFAULT NULL COMMENT '已用库存', `residue` int(11) DEFAULT NULL COMMENT '剩余库存', PRIMARY KEY (`int`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '库存' ROW_FORMAT = Dynamic; INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100);
seata_account库下新建t_account表
CREATE TABLE `t_account` ( `id` bigint(11) NOT NULL COMMENT 'id', `user_id` bigint(11) DEFAULT NULL COMMENT '用户id', `total` decimal(10, 0) DEFAULT NULL COMMENT '总额度', `used` decimal(10, 0) DEFAULT NULL COMMENT '已用余额', `residue` decimal(10, 0) DEFAULT NULL COMMENT '剩余可用额度', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '账户表' ROW_FORMAT = Dynamic; INSERT INTO `t_account` VALUES (1, 1, 1000, 0, 1000);
按照上述3库分别建立对应的回滚日志表:
数据库建表样图: