分布式事物-全面详解(学习总结---从入门到深化)

分布式事物处理_认识本地事物

分布式事物-全面详解(学习总结---从入门到深化)_第1张图片

 什么是事物

事务就是针对数据库的一组操作,它可以由一条或多条SQL语句组 成,同一个事务的操作具备同步的特点,事务中的语句要么都执 行,要么都不执行。

 举个栗子:

你去小卖铺买东西,一手交钱,一手交货就是一个事务的例子,交钱和交货必须全部成功,事务才算成功,任一个活动失败,事务将撤销所有已成功的活动。

 什么是本地事物

在计算机系统中,更多的是通过关系型数据库来控制事务,这是利 用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用 主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。

分布式事物-全面详解(学习总结---从入门到深化)_第2张图片 分布式事物-全面详解(学习总结---从入门到深化)_第3张图片

 数据库事务的四大特性ACID

分布式事物-全面详解(学习总结---从入门到深化)_第4张图片

 

 总结

数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个 不可分割的执行单元,该执行单元中的所有操作要么都成功,要么 都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。

关系型数据库事务基础_并发事务带来的问题

分布式事物-全面详解(学习总结---从入门到深化)_第5张图片

 并发事务带来的问题

数据库一般会并发执行多个事务,而多个事务可能会并发地对相同 的数据进行增加、删除、修改和查询操作,进而导致并发事务问 题。

分布式事物-全面详解(学习总结---从入门到深化)_第6张图片

 脏写

当两个或两个以上的事务选择数据库中的同一行数据,并基于最初 选定的值更新该行数据时,因为每个事务之间都无法感知彼此的存 在,所以会出现最后的更新操作覆盖之前由其他事务完成的更新操 作的情况。也就是说,对于同一行数据,一个事务对该行数据的更新操作覆盖了其他事务对该行数据的更新操作。

 分布式事物-全面详解(学习总结---从入门到深化)_第7张图片

 解决方案: 让每个事物按照顺序串行的方式执行,按照一定的顺序一次进行写操作。

 脏读

一个事务正在对数据库中的一条记录进行修改操作,在这个事务完 成并提交之前,当有另一个事务来读取正在修改的这条数据记录 时,如果没有对这两个事务进行控制,则第二个事务就会读取到没 有被提交的脏数据,并根据这些脏数据做进一步的处理,此时就会 产生未提交的数据依赖关系。我们通常把这种现象称为脏读,也就是一个事务读取了另一个事务未提交的数据。

 分布式事物-全面详解(学习总结---从入门到深化)_第8张图片

 解决方案: 先写后读,也就是写完之后再读。

 不可重复读

一个事务读取了某些数据,在一段时间后,这个事务再次读取之前 读过的数据,此时发现读取的数据发生了变化,或者其中的某些记录已经被删除,这种现象就叫作不可重复读。

分布式事物-全面详解(学习总结---从入门到深化)_第9张图片

 解决方案: 先读后写,也就是读完之后再写。

 幻读

一个事务按照相同的查询条件重新读取之前读过的数据,此时发现 其他事务插入了满足当前事务查询条件的新数据,这种现象叫作幻读。

 分布式事物-全面详解(学习总结---从入门到深化)_第10张图片

 解决方案: 先读后写,也就是读完之后再写。

 关系型数据库事务基础_MySQL事务隔离级别

分布式事物-全面详解(学习总结---从入门到深化)_第11张图片

 MySQL中的InnoDB储存引擎提供SQL标准所描述的4种事务隔离级 别,分别为

读未提交 (Read Uncommitted)

读已提交 (ReadCommitted)

可重复读(Repeatable Read)

串行化 (Serializable)。

分布式事物-全面详解(学习总结---从入门到深化)_第12张图片 

1、读未提交(Read Uncommitted):事务可以读取未提交的数据,也称作脏读(Dirty Read)。一 般很少使用。

2、读已提交(Read Committed):是大都是 DBMS (如:Oracle, SQLServer)默认事务隔离。执行两次同意的查询却有不同的结果,也叫不可重复读。

3、可重复读(Repeable Read):是 MySQL 默认事务隔离级别。能确保同一事务多次读取同一数据 的结果是一致的。可以解决脏读的问题,但理论上无法解决幻读(Phantom Read)的问题。

4、可串行化(Serializable):是最高的隔离级别。强制事务串行执行,会在读取的每一行数据上加锁,这样虽然能避免幻读的问题,但也可能导致大量的超时和锁争用的问题。很少会应用到这种级别,只有在非常需要确保数据的一致性且可以接受没有并发的应用场景下才会考虑。

 MySQL事务隔离级别_模拟异常发生之脏读

分布式事物-全面详解(学习总结---从入门到深化)_第13张图片

 前置知识

# 查看 MySQL 版本
select version();

# 开启事务
start transaction;

# 提交事务
commit;

# 回滚事务
rollback;

查看连接的客户端详情

每个 MySQL 命令行窗口就是一个 MySQL 客户端,每个客户端都可 以单独设置(不同的)事务隔离级别,这也是演示 MySQL 并发事务的基础。

show processlist;

分布式事物-全面详解(学习总结---从入门到深化)_第14张图片

 新建数据库和测试数据

-- 创建数据库
drop database if exists testdb;
create database testdb;
use testdb;
-- 创建表
create table userinfo(
 id int primary key auto_increment,
 name varchar(250) not null,
 balance decimal(10,2) not null default 0
);
-- 插入测试数据
insert into userinfo(id,name,balance)
values(1,'Java',100),(2,'MySQL',200);

查询事务的隔离级别

select
@@global.transaction_isolation,@@transaction_is
olation;

设置客户端的事务隔离级别

通过以下 SQL 可以设置当前客户端的事务隔离级别:

set session transaction isolation level 事务隔离 级别;

分布式事物-全面详解(学习总结---从入门到深化)_第15张图片

 脏读

一个事务读到另外一个事务还没有提交的数据,称之为脏读。脏读 演示的执行流程如下:

分布式事物-全面详解(学习总结---从入门到深化)_第16张图片

 

脏读演示步骤1 

 设置窗口 2 的事务隔离级别为读未提交,设置命令如下:

set session transaction isolation level read
uncommitted;

分布式事物-全面详解(学习总结---从入门到深化)_第17张图片

 注意: 事务隔离级别读未提交存在脏读的问题。

 脏读演示步骤2

窗口2开启事务,查询用户表如下图所示:

start transaction;
select * from userinfo;

分布式事物-全面详解(学习总结---从入门到深化)_第18张图片

 注意: 从上述结果可以看出,在窗口 2 中读取到了窗口 1 中事务未提 交的数据,这就是脏读。

脏读演示步骤3 

在窗口 1 中开启一个事务,并给 Java 账户加 50 元,但不提交事 务,执行的 SQL 如下:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update userinfo set balance=balance+50
where name="java";
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

分布式事物-全面详解(学习总结---从入门到深化)_第19张图片

 脏读演示步骤4

在窗口 2 中再次查询用户列表,执行结果如下:

分布式事物-全面详解(学习总结---从入门到深化)_第20张图片

 注意: 从上述结果可以看出,在窗口 2 中读取到了窗口 1 中事务未提交的数据,这就是脏读。

 不可重复读

不可重复读是指一个事务先后执行同一条 SQL,但两次读取到的数据不同,就是不可重复读。不可重复读演示的执行流程如下:

分布式事物-全面详解(学习总结---从入门到深化)_第21张图片

 不可重复读演示步骤1

设置窗口 2 的事务隔离级别为读已提交

set session transaction isolation level read
committed;

分布式事物-全面详解(学习总结---从入门到深化)_第22张图片

 注意: 读已提交可以解决脏读的问题,但存在不可重复读的问题。

不可重复读演示步骤2 

在窗口 2 中开启事务,并查询用户表,执行结果如下

分布式事物-全面详解(学习总结---从入门到深化)_第23张图片

 不可重复读演示步骤3

在窗口 1 中开启事务,并给 Java 用户添加 20 元,但不提交事务, 再观察窗口 2 中有没有脏读的问题,具体执行结果如下图所示:

分布式事物-全面详解(学习总结---从入门到深化)_第24张图片

 从上述结果可以看出,当把窗口的事务隔离级别设置为读已提交, 已经不存在脏读问题了。接下来在窗口 1 中提交事务,执行结果如下图所示:

分布式事物-全面详解(学习总结---从入门到深化)_第25张图片

 不可重复读演示步骤4

切换到窗口 2 中再次查询用户列表,执行结果如下:

分布式事物-全面详解(学习总结---从入门到深化)_第26张图片

 不可重复读和脏读的区别:

脏读可以读到其他事务中未提交的数据,而不可重复读是读取到了其他事务已经提交的数据,但前后两次读取的结果不同。

 幻读

幻读名如其文,它就像发生了某种幻觉一样,在一个事务中明明没 有查到主键为 X 的数据,但主键为 X 的数据就是插入不进去,就像某种幻觉一样。幻读演示的执行流程如下:

分布式事物-全面详解(学习总结---从入门到深化)_第27张图片

 幻读演示步骤1

在窗口1和窗口2修改事务隔离级别为可重复读。

set session transaction isolation level
repeatable read;

幻读演示步骤2

设置窗口 2 为可重复读,可重复有幻读的问题,查询编号为 3 的用户,具体执行 SQL 如下:

start transaction;
select * from userinfo where id=3;

分布式事物-全面详解(学习总结---从入门到深化)_第28张图片

 注意:从上述结果可以看出,查询的结果中 id=3 的数据为空。

 幻读演示步骤3

开启窗口 1 的事务,插入用户编号为 3 的数据,然后成功提交事务,执行 SQL 如下:

start transaction;
insert into userinfo(id,name,balance)
values(3,'Spring',100);
commit;

分布式事物-全面详解(学习总结---从入门到深化)_第29张图片

 幻读演示步骤4

在窗口 2 中插入用户编号为 3 的数据,执行 SQL 如下:

insert into userinfo(id,name,balance)
values(3,'Spring',100);

 注意: 添加用户数据失败,提示表中已经存在了编号为 3 的数据,且 此字段为主键,不能添加多个。

幻读演示步骤5 

在窗口 2 中,重新执行查询:

select * from userinfo where id=3;

 注意: 在此事务中查询明明没有编号为 3 的用户,但插入的时候却却 提示已经存在了,这就是幻读。

 不可重复读和幻读的区别

二者描述的则重点不同,不可重复读描述的侧重点是修改操作,而 幻读描述的侧重点是添加和删除操作。

MySQL中锁的分类

分布式事物-全面详解(学习总结---从入门到深化)_第30张图片

 从本质上讲,锁是一种协调多个进程或多个线程对某一资源的访问 的机制,MySQL使用锁和MVCC机制实现了事务隔离级别。

锁的分类

分布式事物-全面详解(学习总结---从入门到深化)_第31张图片

 悲观锁和乐观锁

悲观锁

顾名思义,悲观锁对于数据库中数据的读写持悲观态度,即在整个数据处理的过程中,它会将相应的数据锁定。在数据库中,悲观锁的实现需要依赖数据库提供的锁机制,以保证对数据库加锁后,其他应用系统无法修改数据库中的数据。

分布式事物-全面详解(学习总结---从入门到深化)_第32张图片

 注意:

在悲观锁机制下,读取数据库中的数据时需要加锁,此时不能对这些数据进行修改操作。修改数据库中的数据时也需要加锁,此时不能对这些数据进行读取操作。

 乐观锁

悲观锁会极大地降低数据库的性能,特别是对长事务而言,性能的损耗往往是无法承受的。乐观锁则在一定程度上解决了这个问题。

 分布式事物-全面详解(学习总结---从入门到深化)_第33张图片

 注意:

实现乐观锁的一种常用做法是为数据增加一个版本标识,如果是通过数据库实现,往往会在数据表中增加一个类似version的版本号字段。

读锁和写锁 

读锁

读锁又称为共享锁,共享锁就是多个事务对于同一数据可以共享一 把锁,都能访问到数据,但是只能读不能修改。

 写锁

写锁又称为排他锁,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁, 包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

 注意:

需要注意的是,对同一份数据,如果加了读锁,则可以继续为 其加读锁,且多个读锁之间互不影响,但此时不能为数据增加 写锁。一旦加了写锁,则不能再增加写锁和读锁。因为读锁具有共享性,而写锁具有排他性。

 表锁、行锁和页面锁

 表锁

表锁也称为表级锁,就是在整个数据表上对数据进行加锁和释放锁。典型特点是开销比较小,加锁速度快,一般不会出现死锁,锁定的粒度比较大,发生锁冲突的概率最高,并发度最低。

 手动增加表锁

mysql> lock table userinfo read;
Query OK, 0 rows affected (0.00 sec)
mysql> lock table userinfo write;
Query OK, 0 rows affected (0.00 sec)

查看数据表上增加的锁

show open tables;

分布式事物-全面详解(学习总结---从入门到深化)_第34张图片

 删除表锁

mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

行锁

行锁也称为行级锁,就是在数据行上对数据进行加锁和释放锁。典 型特点是开销比较大,加锁速度慢,可能会出现死锁,锁定的粒度最小,发生锁冲突的概率最小,并发度最高。

分布式事物-全面详解(学习总结---从入门到深化)_第35张图片

 页面锁

页面锁也称为页级锁,就是在页面级别对数据进行加锁和释放锁。 对数据的加锁开销介于表锁和行锁之间,可能会出现死锁,锁定的粒度大小介于表锁和行锁之间,并发度一般。

 间隙锁和临键锁

间隙锁

在MySQL中使用范围查询时,如果请求共享锁或排他锁,InnoDB 会给符合条件的已有数据的索引项加锁。如果键值在条件范围内, 而这个范围内并不存在记录,则认为此时出现了“间隙(也就是 GAP)”。InnoDB存储引擎会对这个“间隙”加锁,而这种加锁机制就是间隙锁(GAP Lock)。

 例如,userinfo数据表中存在如下数据。

分布式事物-全面详解(学习总结---从入门到深化)_第36张图片

解释:

此时,userinfo数据表中的间隙包括id为(3,15]、(15,20]、 (20,正无穷]的三个区间。如果执行如下命令,将符合条件的用 户的账户余额增加100元。 update userinfo set balance = balance + 100 where id > 5 and id <16; 则其他事务无法在(3,20]这个区间内插入或者修改任何数据。 这里需要注意的是,间隙锁只有在可重复读事务隔离级别下才 会生效。

 临键锁

临键锁(Next-Key Lock)是行锁和间隙锁的组合,例如上面例子中 的区间(3,20]就可以称为临键锁。

MySQL中的死锁问题

分布式事物-全面详解(学习总结---从入门到深化)_第37张图片

 什么是死锁

死锁是并发系统中常见的问题,同样也会出现在数据库MySQL的并 发读写请求场景中。当两个及以上的事务,双方都在等待对方释放 已经持有的锁或因为加锁顺序不一致造成循环等待锁资源,就会出 现“死锁”。

 Deadlock found when trying to get lock...

举例来说 A 事务持有 X1 锁 ,申请 X2 锁,B事务持有 X2 锁,申请 X1 锁。A 和 B 事务持有锁并且申请对方持有的锁进入循环等待,就造成了死锁。 

分布式事物-全面详解(学习总结---从入门到深化)_第38张图片

 分布式事物-全面详解(学习总结---从入门到深化)_第39张图片

 第一步

打开终端A,登录MySQL,将事务隔离级别设置为可重复读,开启 事务后为userinfo数据表中id为1的数据添加排他锁,如下所示。

mysql> set session transaction isolation level
repeatable read;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from userinfo;
+----+-------+---------+
| id | name  | balance |
+----+-------+---------+
|  1 | Java  |  100.00 |
|  2 | MySQL |  200.00 |
+----+-------+---------+
2 rows in set (0.00 sec)

第二步

打开终端B,登录MySQL,将事务隔离级别设置为可重复读,开启事务后为userfinfo数据表中id为2的数据添加排他锁,如下所示。

mysql> set session transaction isolation level
repeatable read;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from userinfo where id = 2;
+----+-------+---------+
| id | name  | balance |
+----+-------+---------+
|  2 | MySQL |  200.00 |
+----+-------+---------+
1 row in set (0.00 sec)

第三步

在终端A为userinfo数据表中id为2的数据添加排他锁,如下所示。

mysql> select * from userinfo where id =2 for
update;

注意: 此时,线程会一直卡住,因为在等待终端B中id为2的数据释放排他锁。

 第四步

在终端B中为userinfo数据表中id为1的数据添加排他锁,如下所示。

mysql> select * from userinfo where id =1 for
update;
ERROR 1213 (40001): Deadlock found when trying
to get lock; try restarting transaction

通过如下命令可以查看死锁的日志信息。

show engine innodb status\G

注意:

通过命令行查看LATEST DETECTED DEADLOCK选项相关的信 息,可以发现死锁的相关信息,或者通过配置 innodb_print_all_deadlocks(MySQL 5.6.2版本开始提供)参数为ON,将死锁相关信息打印到MySQL错误日志中。

 如何避免死锁

分布式事物-全面详解(学习总结---从入门到深化)_第40张图片

 MySQL事务的实现原理_什么是redo log

分布式事物-全面详解(学习总结---从入门到深化)_第41张图片

 MySQL的事务实现离不开Redo Log和Undo Log。从某种程度上 说,事务的隔离性是由锁和MVCC机制实现的,原子性和持久性是 由Redo Log实现的,一致性是由Undo Log实现的。

分布式事物-全面详解(学习总结---从入门到深化)_第42张图片

 什么是redo log

redo log叫做重做日志,是用来实现事务的持久性。该日志文件由 两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件 (redo log),前者是在内存中,后者在磁盘中。当事务提交之后会 把所有修改信息都会存到该日志中。

 

 注意:

 先写日志,再写磁盘的技术就是 MySQL 里经常说到的 WAL(Write-Ahead Logging) 技术。

 Redo Log刷盘规则

在计算机操作系统中,用户空间( user space )下的缓冲区数据一般情况 下是无法直接写入磁盘的,中间必须经过操作系统内核空间( kernel space )缓冲区( OS Buffer )。因此, redo log buffer 写入 redo log file 实际上是先 写入 OS Buffer ,然后再通过系统调用 fsync() 将其刷到 redo log file 中。

分布式事物-全面详解(学习总结---从入门到深化)_第43张图片 

 mysql 支持三种将 redo log buffer 写入 redo log file 的时机。 innodb_flush_log_at_trx_commit。

分布式事物-全面详解(学习总结---从入门到深化)_第44张图片

 Redo Log刷盘最佳实践

不同的Redo Log刷盘规则,对MySQL数据库性能的影响也不同。

创建测试数据库

create database if not exists test;
create table flush_disk_test(
 id int not null auto_increment,
 name varchar(20),
primary key(id)
)engine=InnoDB;

编写存储过程

为了测试方便,这里创建一个名为insert_data的存储过程,接收一 个int类型的参数。这个参数表示向flush_disk_test数据表中插入的记录行数。

drop procedure if exists insert_data;
-- 该段命令是否已经结束了,mysql是否可以执行了。
delimiter $$
create procedure insert_data(i int)
begin

-- 声明变量 s
declare s int default 1;

-- 声明变量 c
declare c varchar(50) default 'binghe';

-- while循环
while s<=i do

-- 开启事务
start transaction;

-- 添加数据
insert into flush_disk_test (name) values(c);

-- 提交事务
commit;

-- s变量累加
set s=s+1;

-- 循环结束
end while;
end$$

-- 该段命令是否已经结束了,mysql是否可以执行了。
delimiter ;

查看刷盘规则

show variables like
'innodb_flush_log_at_trx_commit';

第一步

将innodb_flush_log_at_trx_commit变量的值设置为0。

set global innodb_flush_log_at_trx_commit=0;

调用insert_data向flush_disk_test数据表中插入10万条数据,如下 所示。

mysql> call insert_data (100000);
Query OK, 0 rows affected (2.18 sec)

注意: 可以看到,当innodb_flush_log_at_trx_commit变量的值设置为0时,向表中插入10万条数据耗时2.18s。

 第二步

将innodb_flush_log_at_trx_commit变量的值设置为1。

set global innodb_flush_log_at_trx_commit=1;

调用insert_data向flush_disk_test数据表中插入10万条数据,如下所示。

mysql> call insert_data (100000);
Query OK, 0 rows affected (16.18 sec)

注意:

可以看到,当innodb_flush_log_at_trx_commit变量的值设置为1时,向表中插入10万条数据耗时16.18s。

 第三步

将innodb_flush_log_at_trx_commit变量的值设置为2。

set global innodb_flush_log_at_trx_commit=2;

调用insert_data向flush_disk_test数据表中插入10万条数据,如下所示。

mysql> call insert_data (100000);
Query OK, 0 rows affected (3.05 sec)

注意:

可以看到,当innodb_flush_log_at_trx_commit变量的值设置为2时,向表中插入10万条数据耗时3.05s。

 结论

分布式事物-全面详解(学习总结---从入门到深化)_第45张图片

分布式事物-全面详解(学习总结---从入门到深化)_第46张图片

 MySQL事务的实现原理_什么是undo log

分布式事物-全面详解(学习总结---从入门到深化)_第47张图片

 undo log的概念

undo log是mysql中比较重要的事务日志之一,顾名思义,undo log是一种用于撤销回退的日志,在事务没提交之前,MySQL会先 记录更新前的数据到 undo log日志文件里面,当事务回滚时或者数据库崩溃时,可以利用 undo log来进行回退。

undo log的作用

在MySQL中,undo log日志的作用主要有两个:

1、提供回滚操作

---修改之前name = 张三
update user set name = "李四" where id = 1;  
----此时undo log会记录一条相反的update语句,如下:
update user set name = "张三" where id = 1;

注意: 如果这个修改出现异常,可以使用undo log日志来实现回滚操作,以保证事务的一致性。


2、提供多版本控制(MVCC)

MVCC,即多版本控制。在MySQL数据库InnoDB存储引擎中,用 undo Log来实现多版本并发控制(MVCC)。当读取的某一行被其他事务锁定时,它可以从undo log中分析出该行记录以前的数据版本是怎样的,从而让用户能够读取到当前事务操作之前的数据【快照读】。

快照读:

SQL读取的数据是快照版本【可见版本】,也就是历史版本,不用加锁,普通的SELECT就是快照读。

当前读:

SQL读取的数据是最新版本。通过锁机制来保证读取的数据无法 通过其他事务进行修改UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE都是当前 读。

 undo log的存储机制

undo log的存储由InnoDB存储引擎实现,数据保存在InnoDB的数据文件中。在InnoDB存储引擎中,undo log是采用分段(segment) 的方式进行存储的。

分布式事物-全面详解(学习总结---从入门到深化)_第48张图片

 undo log的工作原理

在更新数据之前,MySQL会提前生成undo log日志,当事务提交的时候,并不会立即删除undo log,因为后面可能需要进行回滚操 作,要执行回滚(rollback)操作时,从缓存中读取数据。undo log日志的删除是通过通过后台purge线程进行回收处理的。

分布式事物-全面详解(学习总结---从入门到深化)_第49张图片

 总结

undo log是用来回滚数据的用于保障未提交事务的原子性。

 分布式事物处理_认识分布式事物

分布式事物-全面详解(学习总结---从入门到深化)_第50张图片

 前言

随着互联网的快速发展,软件系统由原来的单体应用转变为分布式 应用,下图描述了单体应用向微服务的演变。

分布式事物-全面详解(学习总结---从入门到深化)_第51张图片

 注意:

分布式系统会把一个应用系统拆分为可独立部署的多个服务, 因此需要服务与服务之间远程协作才能完成事务操作,这种分 布式系统环境下由不同的服务之间通过网络远程协作完成事务 称之为分布式事务,例如用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务。

假如没有分布式事务 

分布式事物-全面详解(学习总结---从入门到深化)_第52张图片

 解释:

上图中包含了库存和订单两个独立的微服务,每个微服务维护了自己的数据库。在交易系统的业务逻辑中,一个商品在下单 之前需要先调用库存服务,进行扣除库存,再调用订单服务, 创建订单记录。

正常情况下,两个数据库各自更新成功,两边数据维持着一致性。

 分布式事物-全面详解(学习总结---从入门到深化)_第53张图片

 但是,在非正常情况下,有可能库存的扣减完成了,随后的订单记 录却因为某些原因插入失败。这个时候,两边数据就失去了应有的一致性。

分布式事物-全面详解(学习总结---从入门到深化)_第54张图片

 问题: 这种时候需要要保证数据的一致性,单数据源的一致性靠单机 事物来保证,多数据源的一致性就要靠分布式事物保证。

什么是分布式事务

指一次大的操作由不同的小操作组成的,这些小的操作分布在不同 的服务器上,分布式事务需要保证这些小操作要么全部成功,要么全部失败。从本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

 分布式架构的理论知识_CAP理论

分布式事物-全面详解(学习总结---从入门到深化)_第55张图片

 前言

分布式系统正变得越来越重要,大型网站几乎都是分布式的。分布式系统的最大难点,就是各个节点的状态如何同步。CAP 定理是这方面的基本定理,也是理解分布式系统的起点。

分布式事物-全面详解(学习总结---从入门到深化)_第56张图片

 1998年,加州大学的计算机科学家Eric Brewer 提出,分布式系统有三个指标。

分布式事物-全面详解(学习总结---从入门到深化)_第57张图片

 它们的第一个字母分别是 C、A、P。这三个指标不可能同时做到。 这个结论就叫做CAP 定理。

 分布式事物-全面详解(学习总结---从入门到深化)_第58张图片

 分区容错性

大多数分布式系统都分布在多个子网络。每个子网络就叫做一个 区。分区容错的意思是,区间通信可能失败。比如,一台服务器放 在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。

分布式事物-全面详解(学习总结---从入门到深化)_第59张图片

 结论:

分区容错无法避免,因此可以认为 CAP 的 P 总是成立。CAP 定理告诉我们,剩下的 C 和 A 无法同时做到。

 一致性

Consistency 中文叫做"一致性"。意思是,写操作之后的读操作,必须返回该值。举例来说,某条记录是 v0,用户向 G1 发起一个写操 作,将其改为 v1。

 分布式事物-全面详解(学习总结---从入门到深化)_第60张图片

 接下来,用户的读操作就会得到 v1。这就叫一致性。

分布式事物-全面详解(学习总结---从入门到深化)_第61张图片

 问题是,用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是 v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。

分布式事物-全面详解(学习总结---从入门到深化)_第62张图片

 为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发 送一条消息,要求 G2 也改成 v1。

 分布式事物-全面详解(学习总结---从入门到深化)_第63张图片

 这样的话,用户向 G2 发起读操作,也能得到 v1。

分布式事物-全面详解(学习总结---从入门到深化)_第64张图片

 可用性

只要收到用户的请求,服务器就必须给出回应。

 分布式事物-全面详解(学习总结---从入门到深化)_第65张图片

 一致性和可用性的矛盾

分布式事物-全面详解(学习总结---从入门到深化)_第66张图片

 解释:

如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的 读操作和写操作。只有数据同步后,才能重新开放读写。锁定 期间,G2 不能读写,没有可用性。如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。

 一致性和可用性如何选择

分布式事物-全面详解(学习总结---从入门到深化)_第67张图片

 分布式事物处理_分布式事务产生的场景

分布式事物-全面详解(学习总结---从入门到深化)_第68张图片

 跨JVM进程

当我们将单体项目拆分为分布式、微服务项目之后,各个服务之间通过远程REST或者RPC调用来协同完成业务操作。

分布式事物-全面详解(学习总结---从入门到深化)_第69张图片

 典型的场景:

商城系统中的订单微服务和库存微服务,用户在下单时会访问 订单微服务,订单微服务在生成订单记录时,会调用库存微服务来扣减库存。各个微服务是部署在不同的JVM进程中的,此时,就会产生因跨JVM进程而导致的分布式事务问题。

跨数据库实例 

单体系统访问多个数据库实例,也就是跨数据源访问时会产生分布式事务。

分布式事物-全面详解(学习总结---从入门到深化)_第70张图片

 典型的场景:

例如,我们的系统中的订单数据库和交易数据库是放在不同的 数据库实例中,当用户发起退款时,会同时操作用户的订单数 据库和交易数据库,在交易数据库中执行退款操作,在订单数 据库中将订单的状态变更为已退款。由于数据分布在不同的数 据库实例,需要通过不同的数据库连接会话来操作数据库中的 数据,此时,就产生了分布式事务。

 多个服务数据库

多个微服务访问同一个数据库。

分布式事物-全面详解(学习总结---从入门到深化)_第71张图片

分布式事物-全面详解(学习总结---从入门到深化)_第72张图片

 分布式事物解决方案_强一致性分布式事务之2PC模型

分布式事物-全面详解(学习总结---从入门到深化)_第73张图片

 两阶段提交又称2PC,2PC是一个非常经典的强一致、中心化的原子提交协议。这里所说的中心化是指协议中有两类节点:一个是中心化 协调者节点N个参与者节点

 分布式事物-全面详解(学习总结---从入门到深化)_第74张图片

 生活中的2PC

A组织B、C和D三个人去爬山:如果所有人都同意去爬山,那么活动将举行;如果有一人不同意去爬山,那么活动将取消。

 首先A将成为该活动的协调者,B、C和D将成为该活动的参与者。

 具体流程:

      阶段1:   

     ①A发邮件给B、C和D,提出下周三去爬山,问是否同意。 那么此时A需要等待B、C和D的邮件。

 ②B、C和D分别查看自己的日程安排表。B、C发现自己在 当日没有活动安排,则发邮件告诉A它们同意下周三去爬山。由 于某种原因, D白天没有查看邮 件。那么此时A、B和C均需要 等待。到晚上的时候,D发现了A的邮件,然后查看日程安排, 发现周三当天已经有别的安排,那么D回复A说活动取消吧。

     

       阶段2:   

     ①此时A收到了所有活动参与者的邮件,并且A发现D下周 三不能去爬山。那么A将发邮件通知B、C和D,下周三爬山活动 取消。   

    ②此时B、C回复A“太可惜了”,D回复A“不好意思”。至此该 事务终止。

 2PC阶段处理流程

举例订单服务A,需要调用支付服务B去支付,支付成功则处理购物 订单为待发货状态,否则就需要将购物订单处理为失败状态。

第一阶段:投票阶段

分布式事物-全面详解(学习总结---从入门到深化)_第75张图片

 分布式事物-全面详解(学习总结---从入门到深化)_第76张图片

分布式事物-全面详解(学习总结---从入门到深化)_第77张图片

分布式事物-全面详解(学习总结---从入门到深化)_第78张图片

 分布式事物解决方案_XA方案

分布式事物-全面详解(学习总结---从入门到深化)_第79张图片

 什么是DTP

2PC的传统方案是在数据库层面实现的,如Oracle、MySQL都支持 2PC协议,为了统一标准减少行业内不必要的对接成本,需要制定 标准化的处理模型及接口标准,国际开放标准组织Open Group定 义分布式事务处理模型DTP(Distributed Transaction Processing Reference Model)。

分布式事物-全面详解(学习总结---从入门到深化)_第80张图片

分布式事物-全面详解(学习总结---从入门到深化)_第81张图片

 分布式事物解决方案_Seata实现

分布式事物-全面详解(学习总结---从入门到深化)_第82张图片

 Seata是什么

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简 单易用的分布式事务服务。Seata 为用户提供了 AT、TCC、SAGAXA 事务模式,为用户打造一站式的分布式解决方案。

 分布式事物-全面详解(学习总结---从入门到深化)_第83张图片

 Seata整体框架

全局事务与分支事务的关系图

分布式事物-全面详解(学习总结---从入门到深化)_第84张图片

 与传统2PC的模型类似,Seata定义了三个组件来协议分布式事务的处理过程

分布式事物-全面详解(学习总结---从入门到深化)_第85张图片 分布式事物-全面详解(学习总结---从入门到深化)_第86张图片

 还拿新用户注册送积分举例Seata的分布式事务过程

分布式事物-全面详解(学习总结---从入门到深化)_第87张图片

 

 执行流程 :

分布式事物-全面详解(学习总结---从入门到深化)_第88张图片

 Seata实现2PC与传统2PC的差别

分布式事物-全面详解(学习总结---从入门到深化)_第89张图片

 Seata提供XA模式实现分布式事务_业务说明

分布式事物-全面详解(学习总结---从入门到深化)_第90张图片

 业务说明

本实例通过Seata中间件实现分布式事务,模拟两个账户的转账交易 过程。两个账户在两个不同的银行(张三在bank1、李四在 bank2),bank1和bank2是两个微服务。交易过程中,张三给李四 转账制定金额。上述交易步骤,要么一起成功,要么一起失败,必须是一个整体性的事务。

 分布式事物-全面详解(学习总结---从入门到深化)_第91张图片

 工程环境

分布式事物-全面详解(学习总结---从入门到深化)_第92张图片

 创建数据库

bank1库,包含张三账户

CREATE DATABASE /*!32312 IF NOT EXISTS*/`bank1`
/*!40100 DEFAULT CHARACTER SET utf8 */;
USE `bank1`;
/*Table structure for table `account_info` */
DROP TABLE IF EXISTS `account_info`;
CREATE TABLE `account_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `account_name` varchar(100) COLLATE utf8_bin
DEFAULT NULL COMMENT '户主姓名'
,
  `account_no` varchar(100) COLLATE utf8_bin
DEFAULT NULL COMMENT '银行卡号'
,
  `account_password` varchar(100) COLLATE
utf8_bin DEFAULT NULL COMMENT '帐户密码'
,`account_balance` double DEFAULT NULL COMMENT
'帐户余额'
,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT
CHARSET=utf8 COLLATE=utf8_bin
ROW_FORMAT=DYNAMIC;
/*Data for the table `account_info` */
insert  into
`account_info`(`id`,`account_name`,`account_no`
,`account_password`,`account_balance`) values(2,'张三','1',NULL,1000);

bank2库,包含李四账户

CREATE DATABASE /*!32312 IF NOT EXISTS*/`bank2`
/*!40100 DEFAULT CHARACTER SET utf8 */;
USE `bank2`;
/*Table structure for table `account_info` */
DROP TABLE IF EXISTS `account_info`;
CREATE TABLE `account_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `account_name` varchar(100) COLLATE utf8_bin
DEFAULT NULL COMMENT '户主姓名'
,
  `account_no` varchar(100) COLLATE utf8_bin
DEFAULT NULL COMMENT '银行卡号'
,
  `account_password` varchar(100) COLLATE
utf8_bin DEFAULT NULL COMMENT '帐户密码'
,`account_balance` double DEFAULT NULL COMMENT
'帐户余额'
,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT
CHARSET=utf8 COLLATE=utf8_bin
ROW_FORMAT=DYNAMIC;
/*Data for the table `account_info` */
insert  into
`account_info`(`id`,`account_name`,`account_no`
,`account_password`,`account_balance`) values(3,'李四','2',NULL,0);

Seata提供XA模式实现分布式事务_下载启动Seata服务

分布式事物-全面详解(学习总结---从入门到深化)_第93张图片

 下载seata服务器

下载地址 :https://github.com/seata/seata/releases

分布式事物-全面详解(学习总结---从入门到深化)_第94张图片

 解压并启动

tar -zxvf seata-server-1.4.2.tar.gz -C
/usr/local/
#后台运行
nohup sh seata-server.sh -p 9999 -h
192.168.66.100 -m file &> seata.log &

注意:

其中9999为服务端口号;file为启动模式,这里指seata服务将采用文件的方式存储信息。

 测试

查看启动日志

cat seata.log

分布式事物-全面详解(学习总结---从入门到深化)_第95张图片

 Seata提供XA模式实现分布式事务_搭建聚合父工程构建

创建工程distribute-transaction

分布式事物-全面详解(学习总结---从入门到深化)_第96张图片

 字符编码

分布式事物-全面详解(学习总结---从入门到深化)_第97张图片

 注解生效激活

分布式事物-全面详解(学习总结---从入门到深化)_第98张图片

 Java编译版本选择

 
    
        
            
                org.apache.maven.plugins
                maven-compilerplugin
                
                    1.8
                    1.8
                    UTF-8
                
            
        
    

File Type过滤

分布式事物-全面详解(学习总结---从入门到深化)_第99张图片

 pom配置版本


        2.6.3
        2021.0.1
        2021.0.1.0
      1.18.22

    
          
            
                org.springframework.boot
                spring-bootstarter-parent
                ${spring-boot.version}
                pom
                import
            
            
            
                org.springframework.cloud
                spring-clouddependencies
                ${spring.cloud.version}
                pom
                import
            
            
            
                com.alibaba.cloud
                spring-cloudalibaba-dependencies
                ${spring.cloud.alibaba.version}
                pom
                import
            
            
                org.projectlombok
                lombok
                ${lombok.version}
            
        
    

IDEA开启Dashboard

普通的Run面板

分布式事物-全面详解(学习总结---从入门到深化)_第100张图片

 Run Dashboard面板

分布式事物-全面详解(学习总结---从入门到深化)_第101张图片

 修改配置文件

在.idea/workspace.xml 文件中找到

 添加配置


  
  

Seata提供XA模式实现分布式事务_转账功能实现上

实现如下功能

李四账户增加金额。

 创建bank2

分布式事物-全面详解(学习总结---从入门到深化)_第102张图片

 pom引入依赖

        
          org.springframework.boot
          spring-boot-starterweb
        
        
            com.baomidou
            mybatis-plus-bootstarter
            3.5.1
        
        
            mysql
            mysql-connectorjava
            5.1.49
        
        
           org.projectlombok
           lombok
        
        
            com.alibaba.cloud
            spring-cloud-starteralibaba-nacos-discovery
        

编写主启动类

//添加对mapper包扫描 Mybatis-plus
@MapperScan("com.itbaizhan.mapper")
@SpringBootApplication
@Slf4j
//开启发现注册
@EnableDiscoveryClient
public class SeataBank2Main6002 {
    public static void main(String[] args) {
       SpringApplication.run(SeataBank1Main6002.class,args);
        log.info("************** SeataBank1Main6002 *************");
   }
}

编写YML配置文件

server:
 port: 6002
spring:
 application:
   name: seata-bank2
cloud:
   nacos:
     discovery:
        # Nacos server地址
       server-addr: 192.168.66.101:8848
 datasource:
   url: jdbc:mysql://localhost:3306/bank2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
   username: root
   password01: 123456
   driver-class-name: com.mysql.jdbc.Driver

代码生成

引入Mybatis Plus代码生成依赖

        
            com.baomidou
            mybatis-plusgenerator
            3.5.2
        
        
        
            org.apache.velocity
            velocity-enginecore
            2.0
        

生成代码

package com.itbaizhan.utils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.Arrays;
import java.util.List;
public class CodeGenerator {
    public static void main(String[] args) {
      FastAutoGenerator.create("jdbc:mysql://192.168.66.100:3306/bank2", "root", "123456")
               .globalConfig(builder -> {
                    builder.author("itxiaotong")// 设置作者
                           .commentDate("MM-dd") // 注释日期格式
                           .outputDir(System.getProperty("user.dir")+"/xa-seata/bank2"+ "/src/main/java/") // 指定输出目录
                           .fileOverride(); //覆盖文件
               })
                // 包配置
               .packageConfig(builder -> { builder.parent("com.itbaizhan") // 包名前缀
                           .entity("entity")//实体类包名
                           .mapper("mapper")//mapper接口包名
                           .service("service"); //service包名
               })
               .strategyConfig(builder -> {
                    // 设置需要生成的表名
                    List strings = Arrays.asList("account_info");
                    builder.addInclude(strings)
                            // 开始实体类配置
                           .entityBuilder()
                            // 开启lombok模型
                           .enableLombok()
                            //表名下划线转驼峰
                           .naming(NamingStrategy.underline_to_camel)
                            //列名下划线转驼峰
                           .columnNaming(NamingStrategy.underline_to_camel);
               })
               .execute();
   }
}

编写转账接口

public interface IAccountInfoService {
    //李四增加金额
    void updateAccountBalance(String accountNo, Double amount);
}

编写转账接口实现类

@Service
@Slf4j
public class AccountInfoServiceImpl implements IAccountInfoService {
    @Autowired
    AccountMapper accountMapper;
    @Override
    public void updateAccountBalance(String accountNo, Double amount) {
        // 1. 获取用户信息
        AccountInfo accountInfo = accountMapper.selectById(accountNo);
        accountInfo.setAccountBalance(accountInfo.getAccountBalance() + amount);
        accountMapper.updateById(accountInfo);
   }
}

编写控制层

@RestController
@RequestMapping("/bank2")
public class Bank2Controller {
    @Autowired
    IAccountInfoService accountInfoService;
    //接收张三的转账
    @GetMapping("/transfer")
    public String transfer(Double amount){
        //李四增加金额
        accountInfoService.updateAccountBalance("3",amount);
        return "bank2"+amount;
   }
}

你可能感兴趣的:(学习,分布式,锁)