事务的介绍
一、什么是本地事务?
当你需要一次执行多条事务的时候。通俗一点说就是如果这几条sql全部执行成功 或者全部执行失败,全部回滚保持原子性。想要达到这种效果就需要事务来进行控制。
回顾一下数据库事务的四大特性(ACID):
- 原子性(Atomicity):要么都执行要么都不执行
- 一致性(Consistency):事务前后的的数据都是正确的
- 隔离性(Isolation):事务之前互相隔离,互不干扰(并发执行事务的时候看不见彼此的状态)
- 持久性(Durability):事务一旦提交不能回滚
二、本地事务控制
举个例子:Mysql数据库的InnoDB存储引擎支持事务。mysql数据库又和应用存储在同一台服务器上,此时就称为本地事务。
1.数据库本身进行事务控制
begin transaction;
//1.本地数据库操作:保存订单
//2.本地数据库操作:扣减库存
rollback;
或
commit transaction;
2.JDBC进行控制事务
- 获取对数据库的连接
- 设置数据库不自动提交
conn.setAutoCommit(false); //其中conn是第一步获取的随数据库的连接对象。
- 把想要控制的sql使用事务进行提交
try{
Statement stmt = null;
stmt =conn.createStatement();
stmt.executeUpdate(sql1);
int a=6/0;
stmt.executeUpdate(Sql2);
.
conn.commit(); //使用commit提交事务
}
- 捕获异常,进行数据回滚
catch(Exception e) {
...
conn.rollback();
}
AOP进行控制事务
@Transactional //mybatis的注解
begin tran
public void insertOrder(TbOrder tbOrder) {
//1、保存订单
tbOrderMapper.insertSelective(tbOrder);
//2、扣减库存
itemServiceFeign.updateItem(tbOrder.getItemId(), tbOrder.getNum());
}
commit/rollback
三、分布式事务 (重点)
分布式事务总而言之就是一句,在服务中调用另外一个服务(不在同一个模块甚至不是同一个应用),还可以做到多个sql保持ACID属性。
举例:
@Transactional
@Override
public void insertOrder(TbOrder tbOrder) {
//1、保存订单
tbOrderMapper.insertSelective(tbOrder);
//2、扣减库存
itemServiceFeign.updateItem(tbOrder.getItemId(), tbOrder.getNum());
//模拟扣款失败,此时扣减库存会回滚吗?
int a = 6/0;
}
此时如果不进行分布式事务控制,扣减的库存是不会回滚的。
但是分布式事务的应用场景并不是只有这一种,跨数据库,跨服务,两个服务使用同一个数据库也是需要分布式事务进行控制的
所以就需要分布式事务开加以控制。
最为简单的分布式事务解决方案Seata
一、Seata的介绍
这个玩意儿呢是阿里中间件团队发起的开源项目Fescar(Fast & EaSy Commit And Rollback)后来改名成 Seata (Simple Extensible Autonomous Transaction Architecture),就是一套分布式解决方案。
官网放在这里:http://seata.io/zh-cn/
二、Seata的分布式事务解决方案
Seata提供了四种不同的分布式事务解决方案:
- **XA模式:**强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入.
- **TCC模式:**最终一致的分阶段事务模式,有业务侵入
- **AT(auto transaction)模式:**最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式.
- **SAGA模式:**长事务模式,有业务侵入
这四种模式我们最常用的就是AT模式,也是默认模式。为什么用它,虽然可靠消息最终一致性最后也能达到一致性,但是为什么不用呢?那你思考一下,当你给你的好妹妹转账的时候,你上午都转了,晚上了都好妹妹都跑路了,钱还没转到,这合适吗。。。
所以说此时我们需要强制一致性,要很快的能够得到结果。扯远了啊,AT模式主要是因为无业务侵入,就很干净,还有可用性的存在。所以那就用它
三、Seata的核心组件
Seata事务管理有三个重要的核心组件(TM和他儿子们RMs与管家TC的故事)
其中5不一定会进行。如果都成功了还回滚啥回滚、
**注意:**第2步中,RM是已经将事务提交了的。所以第4步的提交只是确认一下。
都提交了为啥还能回滚呢?这就要说一下他的机制了。
四、AT模式的工作流程
一阶段
看图上的介绍,其中业务DB的中业务的旁边存在一个Log表,在事务开始时会将原快照(前镜像),执行sql包括sql之后的新快照(后镜像)都存放到Log表中。
表中的字段看上图
-
注册分支事务
RM向TC注册分支事务将其其纳入XID对应全局事务的管辖,并申请 product 表中主键值等于 1 的记录的全局锁 。 -
本地事务提交
业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。 -
上报事务状态
将本地事务提交的结果上报给 TC。
到此为止一阶段就算完成了
二阶段 - 回滚
(1)收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
(2)通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
(3)数据校验
拿 UNDO LOG 中的后镜与当前数据进行比较,根据校验结果决定是否做回滚。
(4)根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
update product set name = 'TXC' where id = 1;
(5)提交本地事务
并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
二阶段 - 提交
第二阶段全局事务提交,TC会通知各各分支参与者提交分支事务,在第一阶段就已经提交了分支事务,这里各各参与者只需要删除undo_log即可,并且可以异步执行,第二阶段很快可以完成。所以一阶段的RM是提交了事务的。
Seata安装和启动
一、下载地址
下载地址:https://github.com/seata/seata/releases
二、安装
- 上传并解压安装包:
[root@localhost ~]# cd /usr/upload
[root@localhost upload]# tar -zxvf seata-server-1.4.2.tar.gz -C /usr/local
- 修改
seata/seata-server-1.4.2/conf/
目录下的registry.conf
文件:
registry {
#tc服务的注册中心类型,这里选择nacos,也可以是eureka、zookeeper等
type = "nacos"
nacos {
# seata tc服务注册到nacos的服务名称,可以自定义
application = "seata-server"
# nacos的地址
serverAddr = "192.168.112.133:8848"
# seata服务所在分组
group = "DEFAULT_GROUP"
# seata服务所在的名称空间,这里不填就是使用默认的"public"
namespace = ""
# TC集群名,默认是"default"
cluster = "default"
# 这个是nacos的用户名
username = ""
# 这个是nacos的密码
password = ""
}
}
config {
# tc服务的配置中心类型:file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "192.168.112.133:8848"
namespace = ""
group = "DEFAULT_GROUP"
username = ""
password = ""
dataId = "seataServer.properties"
}
}
- 在Nacos中添加配置信息
配置信息地址:https://gitee.com/seata-io/seata/blob/develop/script/config-center/config.txt
**划重点:**只需要看上面和数据库连接的内容,其他的玩意儿如果你需要改也不用来看这个文档了
# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.112.137:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
# 事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
启动成功了效果如下:
三、创建TC服务数据表
- 建库
- 建表
建表语句地址:https://gitee.com/seata-io/seata/blob/develop/script/server/db/mysql.sql
-- -------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
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;
CV这一个也是可以。
四、启动Seata
- 启动
[root@localhost local]# cd seata/bin
[root@localhost bin]# ./seata-server.sh
#或
[root@localhost bin]# ./seata-server.sh -h 192.168.112.133 -p 8091
... ...
io.seata.config.FileConfiguration : The configuration file used is /usr/local/seata/seata-server-1.4.2/conf/registry.conf
com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
i.s.core.rpc.netty.NettyServerBootstrap : Server started, listen port: 8091
- 测试seata是否注册成功