From 933c1e4298d1946fb5dd3e64fac790d7ade195c7 Mon Sep 17 00:00:00 2001 From: wangliang <1991wangliang@gmail.com> Date: Wed, 25 Dec 2019 22:50:46 +0800 Subject: [PATCH 1/3] lesson02 --- _data/docs.yml | 4 +- _docs/txlcn/lessson01.md | 598 --------------------------------------- _docs/txlcn/lessson02.md | 146 ---------- 3 files changed, 2 insertions(+), 746 deletions(-) delete mode 100644 _docs/txlcn/lessson01.md delete mode 100644 _docs/txlcn/lessson02.md diff --git a/_data/docs.yml b/_data/docs.yml index f45ca3e54..49c741c60 100644 --- a/_data/docs.yml +++ b/_data/docs.yml @@ -13,5 +13,5 @@ - title: TX-LCN docs: - txlcn-index - - txlcn-lessson01 - - txlcn-lessson02 \ No newline at end of file + - txlcn-lesson01 + - txlcn-lesson02 \ No newline at end of file diff --git a/_docs/txlcn/lessson01.md b/_docs/txlcn/lessson01.md deleted file mode 100644 index 049b4ae32..000000000 --- a/_docs/txlcn/lessson01.md +++ /dev/null @@ -1,598 +0,0 @@ ---- -title: 分布式事务从0到1-认识分布式事务 -permalink: /docs/txlcn-lessson01/ ---- - - - -本节课讲解的主要内容是先介绍与分布式事务相关的一些理论 -ACID 隔离级别 spring事务传播行为 乐观锁悲观锁 BASE理论 ACP理论 拜占庭将军问题 共识算法 - -### 前言 -随着分布式技术的发展,分布式事务问题越来越显著。如何解决分布式事务问题变得非常紧迫。当目前业界较为成熟的分布式事务框架尚不存在,且又因为分布式事务相关的实现原理介绍不够完善,就让我们从今天开始结合原理与实践一起动手去解决分布式事务问题吧。 - -#### ACID理论 -在解决分布式事务这个问题之前,我们先来回顾一下什么事务,先理解事务的本质,先来看看本地事务的ACID理论。 - - -* 原子性(Atomicity) -原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 - -* 一致性(Consistency) -一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。 - -* 隔离性(Isolation) -事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离 - -* 持久性Durability) -这是最好理解的一个特性:持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。(完成的事务是系统永久的部分,对系统的影响是永久性的,该修改即使出现致命的系统故障也将一直保持) - -#### 数据库的隔离级别 - -- √为会发生,×为不会发生: - -| 隔离级别 | 脏读 | 不可重复读 | 幻读 | -| :---- | :---- | :---- | :---- | -| read uncommitted(未提交读) | √ | √ | √ | -| read committed(提交读) | x |√ | √ | -| repeatable read(可重复读 | x | x | √ | -| serialization(可串行化) | x | x| x | - -再总结mysql 的常用命令(下面会用到): -查看MySQL隔离级别: `SELECT @@tx_isolation` -会话层面设置隔离级别: `set session transaction isolation level 隔离级别` -开启事务: `start transaction` -提交事务:`commit` -回滚事务:`rollback` - - -##### 脏读演示 -表中的数据如下,设置隔离级别为未提交读 - -| id | name | balacne | -| ---- | ---- | ---- | -| 1 | 张三 | 1000 | -| 2 | 李四 | 0 | - -执行流程 - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
时间客户端A客户端B
T1 -set session transaction isolation level read uncommitted;start transaction(开启事务); -update account set balance = balance+1000 where id = 1; -select * from account where id = 1; -设置为未提交读,给张三账号+1000,输出为2000 - - -
T2 - - -set session transaction isolation level read uncommitted; -start transaction; -select * from account where id = 1; -查询余额输出为2000 -
T3 -rollback - - -
T4 - - -commit -
T5 - - -select * from account where id = 1; -查询余额输出为1000 -
- - -再举一个例子 - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
时间客户端A客户端B
T1 -set session transaction isolation level read uncommitted; -start transaction; -update account set balance = balance-1000 where id = 1; -update account set balance = balance+1000 where id = 2; - - -
T2 - - -set session transaction isolation level read uncommitted; -start transaction; -select balance from account where id = 2; -update account set balance = balance -1000 where id = 2; -更新语句被阻塞 -
T3 -rollback - - -
T4 - - -commit -
-执行完成,数据库中的数据如下 - - -| id | name | balacne | -| ---- | ---- | ---- | -| 1 | 张三 | 1000 | -| 2 | 李四 | -1000 | - -解释如下: -T1: 1给2转账1000 -T2: 2的余额够1000,购买1000元商品,更新语句被阻塞 -T3: 1回滚,1的余额变成1000,2的余额变成0 -T4: 2成功扣款,余额为0-1000=-1000 - - -##### 不可重复读演示 -表中的数据如下,设置隔离级别为提交读 - -数据库中的数据如下 - -| id | name | balacne | -| ---- | ---- | ---- | -| 1 | 张三 | 1000 | -| 2 | 李四 | 0 | - - - -执行流程 - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - -
时间客户端A客户端B
T1 -set session transaction isolation level read committed; -start transaction; -select * from account where id = 2; -查询出余额输出为0; - - -
T2 - - -set session transaction isolation level read committed; -start transaction; -update account set balance = balance + 1000 where id = 2; -select * from account where id = 2; -commit; -查询余额输出1000 -
T3 -select * from account where id = 2; -commit; -查询余额输出1000 - - -
- - -现在用上面的例子看一下可重复读是个什么过程? -表中的数据如下,设置隔离级别为可重复读 - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - -
时间客户端A客户端B
T1 -set session transaction isolation level repeatable read; -start transaction; -select * from account where id = 2; -查询出余额输出为0; - - -
T2 - - -set session transaction isolation level repeatable read; -start transaction; -update account set balance = balance + 1000 where id = 2; -select * from account where id = 2; -commit; -查询余额输出1000 -
T3 -select * from account where id = 2; -commit; -查询余额输出0 - - -
- -仔细看这个例子和上面的例子在T3时间段的输出,理解了什么叫可重复读了吧?当我们将当前会话的隔离级别设置为可重复读的时候,当前会话可以重复读,就是每次读取的结果集都相同,而不管其他事务有没有提交。 -----------但是在可重复读的隔离级别上,会产生幻读的问题。 - -##### 幻读演示 - -数据库中的数据如下 - -| id | name | balacne | -| ---- | ---- | ---- | -| 1 | 张三 | 1000 | -| 2 | 李四 | 0 | - -先上一段《高性能MySQL》对于幻读的解释 -- 所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行。InnoDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。 - -用大白话解释一下,就是事务1查询id<10的记录时,返回了2条记录,接着事务2插入了一条id为3的记录,并提交。接着事务1查询id<10的记录时,返回了3条记录,说好的可重复读呢?结果却多了一条数据。 - -MySQL通过MVCC解决了这种情况下的幻读,我们可以验证一下 - - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - -
时间客户端A客户端B
T1 -set session transaction isolation level repeatable read; -start transaction; -select count(*) from account where id <=10; -输出2; - - -
T2 - - -set session transaction isolation level repeatable read; -start transaction; -insert into account(id,name,balance) values(“3”,“王五”,“0”) ; -select count(*) from account where id <=10; -commit; -输出3 -
T3 -select count(*) from account where id <=10; -commit; -输出2 - - -
- -这种情况下的幻读被解决了,再举一例,表中的数据如下 - - -| id | name | balacne | -| ---- | ---- | ---- | -| 1 | 张三 | 1000 | -| 2 | 李四 | 0 | - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
时间客户端A客户端B
T1 -set session transaction isolation level repeatable read; -start transaction; -select count(*) from account where id =3; -输出0; - - -
T2 - - -set session transaction isolation level repeatable read; -start transaction; -insert into account(id,name,balance) values(“3”,“王五”,“0”) ; -commit; -
T3 -insert into account(id,name,balance) values(“3”,“王五”,“0”); -主键重复,插入失败 - - -
T4 -select count(*) from account where id =3; -输出0; - - -
T5 -rollback; - - -
- -select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,这个就有问题了。 -很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。 - -注意:不可重复读和幻读的区别是:前者是指读到了已经提交的事务的更改数据(修改或删除),后者是指读到了其他已经提交事务的新增数据。 -对于这两种问题解决采用不同的办法,防止读到更改数据,只需对操作的数据添加行级锁,防止操作中的数据发生变化;而防止读到新增数据,往往需要添加表级锁,将整张表锁定,防止新增数据(oracle采用多版本数据的方式实现)。 - -当隔离级别设置为可串行化,强制事务串行执行,避免了前面说的幻读的问题。 - - - - - -##### Spring事务的七种传播行为 - -| 事务传播行为类型 | 说明 | -| ---- | ---- | -| PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 | -| PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 | -| PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 | -| PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 | -| PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 | -| PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 | -| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 | - - -spring隔离级别与事务传播行为控制 -`@Transactional(propagation = Propagation.NESTED,isolation = Isolation.READ_UNCOMMITTED)` - - -##### 乐观锁与悲观锁 - -悲观锁(Pessimistic Lock) - -顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。 - -乐观锁(Optimistic Lock) - -顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。 - - -两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。 - - -MySQL select…for update的Row Lock与Table Lock -使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。 -``` --- (若主键1中存在数据,那么触发的就是RowLock) -select * from user where id = 1 for update --- (触发的就是TableLock) -select * from user where name like = '%小明%' for update -``` - -##### BASE理论 -BASE理论 - BASE是Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 - -基本可用 - 基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性----注意,这绝不等价于系统不可用。比如: - -(1)响应时间上的损失。正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒 - -(2)系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面 - -软状态 - 软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时 - -最终一致性 - 最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。 - - -##### CAP定律 - -这个定理的内容是指的是在一个分布式系统中、Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。 - -一致性(C) - 在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本) - -可用性(A) - 在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性) - -分区容错性(P) - 以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。 - -##### 拜占庭将军问题 - -![](/img/txlcn/950B4878-0B07-4233-A5AA-F6E28A036A6E.png) - -##### 共识机制算法介绍 -https://www.cnblogs.com/davidwang456/articles/9001331.html - - -
- -本文参考 -快速理解脏读,不可重复读,幻读 : https://blog.csdn.net/Vincent2014Linux/article/details/89669762 -Spring事务传播行为:https://blog.csdn.net/xzz1173724284/article/details/88954730 -乐观锁和悲观锁的区别: https://blog.csdn.net/coderDogg/article/details/85093741 -mysql(for update)悲观锁总结与实践: https://blog.csdn.net/zmx729618/article/details/52701972/ \ No newline at end of file diff --git a/_docs/txlcn/lessson02.md b/_docs/txlcn/lessson02.md deleted file mode 100644 index 049542fba..000000000 --- a/_docs/txlcn/lessson02.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -title: 分布式事务从0到1-了解TX-LCN原理 -permalink: /docs/txlcn-lessson02/ ---- - -![](/img/WX20191220-102719.png) -本节课讲解的主要内容是TX-LCN分布式事务的原理介绍。 - -### TX-LCN的核心控制流程 - -协调控制流程 -![](/img/txlcn/yuanli.png) - -#### 各种事务模式的原理 - - -| id | name | balacne | -| ---- | ---- | ---- | -| 1 | A | 200 | -| 2 | B | 100 | - -#### TCC业务处理 -Try Confirm Cancle -如何实现A转账给B的呢? - -``` -//尝试方法 -function try(){ - //记录日志 - todo save A 转出了 100 元 - todo save B 转入了 100 元 - //执行转账 - update amount set balacne = balacne-100 where id = 1 - update amount set balacne = balacne+100 where id = 2 -} -//确认方法 -function confirm(){ - //清理日志 - clean save A 转出了 100 元 - clean save B 转出了 100 元 -} - -//取消方法 -function cancle(){ - //加载日志 - load log A - load log B - - //退钱 - update amount set balacne = balacne+100 where id = 1 - update amount set balacne = balacne-100 where id = 2 -} - - -``` - -特点: -* 该模式对代码的嵌入性高,要求每个业务需要写三种步骤的操作。 -* 该模式对有无本地事务控制都可以支持使用面广。 -* 数据一致性控制几乎完全由开发者控制,对业务开发难度要求高。 - -#### TXC逆向SQL - - -![](/img/txlcn/WX20191225-213128@2x.png) - -特点: -* 该模式同样对代码的嵌入性低。 -* 该模式仅限于对支持SQL方式的模块支持。 -* 该模式由于每次执行SQL之前需要先查询影响数据,因此相比LCN模式消耗资源与时间要多。 -* 该模式不会占用数据库的连接资源,但中间状态可见 - - -#### LCN代理连接 - -![](/img/txlcn/WX20191225-214414@2x.png) -特点: -* 该模式对代码的嵌入性为低。 -* 该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。 -* 该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障。 -* 该模式缺陷在于代理的连接需要随事务发起方一共释放连接,增加了连接占用的时间。 - - -### 负载问题 -负载情况下的事务控制,对于无状态的TXC TCC来说是不需要关心事务的。但是对LCN来说需要考虑负载调用同一个模块时若模块不同会可能触发锁的问题。 - -举例: - -目前TX-LCN支持的事务种类有三种,其中LCN模式是会占用资源,详情见LCN模式原理。 - -若存在这样的请求链,A模块先调用了B模块的one方法,然后在调用了two方法,如下所示: - -A ->B.one(); -A ->B.two(); -假如one与two方法的业务都是在修改同一条数据,假如两个方法的id相同,伪代码如下: -``` -void one(id){ - execute => update demo set state = 1 where id = {id} ; -} - -void two(id){ - execute => update demo set state = 2 where id = {id} ; -} -``` -若B模块做了集群存在B1、B2两个模块。那么就可能出现A分别调用了B1 B2模块,如下: - -A ->B1.one(); -A ->B2.two(); -在这样的情况下业务方将在LCN下会因为资源占用而导致执行失败而回滚事务。为了支持这样的场景,框架提供了重写了rpc的负载模式。 - -控制在同一次事务下同一个被负载的模块被重复调用时将只会请求到第一次被选中的模块。 - -### 保障机制与补偿 - -#### 超时机制 -当业务模块在接受到事务请求,并完成响应以后会自动加入定时任务,等待TM通知,若TM迟迟不通知则触发TC主动请求的状况,若TC没有请求到数据就记录补偿(回滚事务)。 -#### TM清理机制 -TM全局都在记录着事务的状态消息,只有当TM确认完全都通知到了TC模块才能清楚事务信息,不然将永久保存。 - -一些特殊的情况介绍: -1、通知事务的时候通知不到的情况。(需要超时机制,超时机制有分为两种可能 1、联系不上TM不清楚事务状态,2提前询问了TM,业务还没有确认最终状态) -2、通知事务组执行时没有响应。(1不清楚有没有执行完业务,2不清楚有没有收到消息) -3、若业务模块死掉了,TM的日志在没有全部确认清楚之前,是不能清理事务数据,TM清理数据需要全部都确认OK方可清理。 - -由上述情况可见,需要补偿的情况有 -1、上面的情况1中对联系不上TM的情况需要记录补偿记录。 -2、上面的情况2、3中描述的场景可能会存在业务模块在没有接受到TM消息的时候联系不上了,*若是服务挂了,那么就得需要在下次服务启动的时候通过切面日志来与TM通讯确认状态,然后在执行业务*。若是通讯出现了故障,那么会除非超时机制自动写补偿日志。 - -由于这样的情况的存在 *若是服务挂了,那么就得需要在下次服务启动的时候通过切面日志来与TM通讯确认状态,然后在执行业务* -所以只能在切面进入是记录数据,当出现时可通过切面记录来触发业务,然后再补偿事务。 - -##### 补偿出现的处理机制 -1、自动补偿 (需要开启) -2、手动补偿 (自行处理删除补偿记录即可) - - - -
- - - - - - - - \ No newline at end of file From 805bd5129bfbe3a8d1030b3fffed7d45fc7b2de0 Mon Sep 17 00:00:00 2001 From: wangliang <1991wangliang@gmail.com> Date: Wed, 25 Dec 2019 22:51:16 +0800 Subject: [PATCH 2/3] lesson02 --- _docs/txlcn/lesson01.md | 598 ++++++++++++++++++++++++++++++++++++++++ _docs/txlcn/lesson02.md | 146 ++++++++++ 2 files changed, 744 insertions(+) create mode 100644 _docs/txlcn/lesson01.md create mode 100644 _docs/txlcn/lesson02.md diff --git a/_docs/txlcn/lesson01.md b/_docs/txlcn/lesson01.md new file mode 100644 index 000000000..5d9682c89 --- /dev/null +++ b/_docs/txlcn/lesson01.md @@ -0,0 +1,598 @@ +--- +title: 分布式事务从0到1-认识分布式事务 +permalink: /docs/txlcn-lesson01/ +--- + + + +本节课讲解的主要内容是先介绍与分布式事务相关的一些理论 +ACID 隔离级别 spring事务传播行为 乐观锁悲观锁 BASE理论 ACP理论 拜占庭将军问题 共识算法 + +### 前言 +随着分布式技术的发展,分布式事务问题越来越显著。如何解决分布式事务问题变得非常紧迫。当目前业界较为成熟的分布式事务框架尚不存在,且又因为分布式事务相关的实现原理介绍不够完善,就让我们从今天开始结合原理与实践一起动手去解决分布式事务问题吧。 + +#### ACID理论 +在解决分布式事务这个问题之前,我们先来回顾一下什么事务,先理解事务的本质,先来看看本地事务的ACID理论。 + + +* 原子性(Atomicity) +原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 + +* 一致性(Consistency) +一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。 + +* 隔离性(Isolation) +事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离 + +* 持久性Durability) +这是最好理解的一个特性:持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。(完成的事务是系统永久的部分,对系统的影响是永久性的,该修改即使出现致命的系统故障也将一直保持) + +#### 数据库的隔离级别 + +- √为会发生,×为不会发生: + +| 隔离级别 | 脏读 | 不可重复读 | 幻读 | +| :---- | :---- | :---- | :---- | +| read uncommitted(未提交读) | √ | √ | √ | +| read committed(提交读) | x |√ | √ | +| repeatable read(可重复读 | x | x | √ | +| serialization(可串行化) | x | x| x | + +再总结mysql 的常用命令(下面会用到): +查看MySQL隔离级别: `SELECT @@tx_isolation` +会话层面设置隔离级别: `set session transaction isolation level 隔离级别` +开启事务: `start transaction` +提交事务:`commit` +回滚事务:`rollback` + + +##### 脏读演示 +表中的数据如下,设置隔离级别为未提交读 + +| id | name | balacne | +| ---- | ---- | ---- | +| 1 | 张三 | 1000 | +| 2 | 李四 | 0 | + +执行流程 + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
时间客户端A客户端B
T1 +set session transaction isolation level read uncommitted;start transaction(开启事务); +update account set balance = balance+1000 where id = 1; +select * from account where id = 1; +设置为未提交读,给张三账号+1000,输出为2000 + + +
T2 + + +set session transaction isolation level read uncommitted; +start transaction; +select * from account where id = 1; +查询余额输出为2000 +
T3 +rollback + + +
T4 + + +commit +
T5 + + +select * from account where id = 1; +查询余额输出为1000 +
+ + +再举一个例子 + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
时间客户端A客户端B
T1 +set session transaction isolation level read uncommitted; +start transaction; +update account set balance = balance-1000 where id = 1; +update account set balance = balance+1000 where id = 2; + + +
T2 + + +set session transaction isolation level read uncommitted; +start transaction; +select balance from account where id = 2; +update account set balance = balance -1000 where id = 2; +更新语句被阻塞 +
T3 +rollback + + +
T4 + + +commit +
+执行完成,数据库中的数据如下 + + +| id | name | balacne | +| ---- | ---- | ---- | +| 1 | 张三 | 1000 | +| 2 | 李四 | -1000 | + +解释如下: +T1: 1给2转账1000 +T2: 2的余额够1000,购买1000元商品,更新语句被阻塞 +T3: 1回滚,1的余额变成1000,2的余额变成0 +T4: 2成功扣款,余额为0-1000=-1000 + + +##### 不可重复读演示 +表中的数据如下,设置隔离级别为提交读 + +数据库中的数据如下 + +| id | name | balacne | +| ---- | ---- | ---- | +| 1 | 张三 | 1000 | +| 2 | 李四 | 0 | + + + +执行流程 + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
时间客户端A客户端B
T1 +set session transaction isolation level read committed; +start transaction; +select * from account where id = 2; +查询出余额输出为0; + + +
T2 + + +set session transaction isolation level read committed; +start transaction; +update account set balance = balance + 1000 where id = 2; +select * from account where id = 2; +commit; +查询余额输出1000 +
T3 +select * from account where id = 2; +commit; +查询余额输出1000 + + +
+ + +现在用上面的例子看一下可重复读是个什么过程? +表中的数据如下,设置隔离级别为可重复读 + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
时间客户端A客户端B
T1 +set session transaction isolation level repeatable read; +start transaction; +select * from account where id = 2; +查询出余额输出为0; + + +
T2 + + +set session transaction isolation level repeatable read; +start transaction; +update account set balance = balance + 1000 where id = 2; +select * from account where id = 2; +commit; +查询余额输出1000 +
T3 +select * from account where id = 2; +commit; +查询余额输出0 + + +
+ +仔细看这个例子和上面的例子在T3时间段的输出,理解了什么叫可重复读了吧?当我们将当前会话的隔离级别设置为可重复读的时候,当前会话可以重复读,就是每次读取的结果集都相同,而不管其他事务有没有提交。 +----------但是在可重复读的隔离级别上,会产生幻读的问题。 + +##### 幻读演示 + +数据库中的数据如下 + +| id | name | balacne | +| ---- | ---- | ---- | +| 1 | 张三 | 1000 | +| 2 | 李四 | 0 | + +先上一段《高性能MySQL》对于幻读的解释 +- 所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行。InnoDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。 + +用大白话解释一下,就是事务1查询id<10的记录时,返回了2条记录,接着事务2插入了一条id为3的记录,并提交。接着事务1查询id<10的记录时,返回了3条记录,说好的可重复读呢?结果却多了一条数据。 + +MySQL通过MVCC解决了这种情况下的幻读,我们可以验证一下 + + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
时间客户端A客户端B
T1 +set session transaction isolation level repeatable read; +start transaction; +select count(*) from account where id <=10; +输出2; + + +
T2 + + +set session transaction isolation level repeatable read; +start transaction; +insert into account(id,name,balance) values(“3”,“王五”,“0”) ; +select count(*) from account where id <=10; +commit; +输出3 +
T3 +select count(*) from account where id <=10; +commit; +输出2 + + +
+ +这种情况下的幻读被解决了,再举一例,表中的数据如下 + + +| id | name | balacne | +| ---- | ---- | ---- | +| 1 | 张三 | 1000 | +| 2 | 李四 | 0 | + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
时间客户端A客户端B
T1 +set session transaction isolation level repeatable read; +start transaction; +select count(*) from account where id =3; +输出0; + + +
T2 + + +set session transaction isolation level repeatable read; +start transaction; +insert into account(id,name,balance) values(“3”,“王五”,“0”) ; +commit; +
T3 +insert into account(id,name,balance) values(“3”,“王五”,“0”); +主键重复,插入失败 + + +
T4 +select count(*) from account where id =3; +输出0; + + +
T5 +rollback; + + +
+ +select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,这个就有问题了。 +很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。 + +注意:不可重复读和幻读的区别是:前者是指读到了已经提交的事务的更改数据(修改或删除),后者是指读到了其他已经提交事务的新增数据。 +对于这两种问题解决采用不同的办法,防止读到更改数据,只需对操作的数据添加行级锁,防止操作中的数据发生变化;而防止读到新增数据,往往需要添加表级锁,将整张表锁定,防止新增数据(oracle采用多版本数据的方式实现)。 + +当隔离级别设置为可串行化,强制事务串行执行,避免了前面说的幻读的问题。 + + + + + +##### Spring事务的七种传播行为 + +| 事务传播行为类型 | 说明 | +| ---- | ---- | +| PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 | +| PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 | +| PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 | +| PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 | +| PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 | +| PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 | +| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 | + + +spring隔离级别与事务传播行为控制 +`@Transactional(propagation = Propagation.NESTED,isolation = Isolation.READ_UNCOMMITTED)` + + +##### 乐观锁与悲观锁 + +悲观锁(Pessimistic Lock) + +顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。 + +乐观锁(Optimistic Lock) + +顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。 + + +两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。 + + +MySQL select…for update的Row Lock与Table Lock +使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。 +``` +-- (若主键1中存在数据,那么触发的就是RowLock) +select * from user where id = 1 for update +-- (触发的就是TableLock) +select * from user where name like = '%小明%' for update +``` + +##### BASE理论 +BASE理论 + BASE是Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 + +基本可用 + 基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性----注意,这绝不等价于系统不可用。比如: + +(1)响应时间上的损失。正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒 + +(2)系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面 + +软状态 + 软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时 + +最终一致性 + 最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。 + + +##### CAP定律 + +这个定理的内容是指的是在一个分布式系统中、Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。 + +一致性(C) + 在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本) + +可用性(A) + 在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性) + +分区容错性(P) + 以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。 + +##### 拜占庭将军问题 + +![](/img/txlcn/950B4878-0B07-4233-A5AA-F6E28A036A6E.png) + +##### 共识机制算法介绍 +https://www.cnblogs.com/davidwang456/articles/9001331.html + + +
+ +本文参考 +快速理解脏读,不可重复读,幻读 : https://blog.csdn.net/Vincent2014Linux/article/details/89669762 +Spring事务传播行为:https://blog.csdn.net/xzz1173724284/article/details/88954730 +乐观锁和悲观锁的区别: https://blog.csdn.net/coderDogg/article/details/85093741 +mysql(for update)悲观锁总结与实践: https://blog.csdn.net/zmx729618/article/details/52701972/ \ No newline at end of file diff --git a/_docs/txlcn/lesson02.md b/_docs/txlcn/lesson02.md new file mode 100644 index 000000000..9ccaa9bc4 --- /dev/null +++ b/_docs/txlcn/lesson02.md @@ -0,0 +1,146 @@ +--- +title: 分布式事务从0到1-了解TX-LCN原理 +permalink: /docs/txlcn-lesson02/ +--- + +![](/img/WX20191220-102719.png) +本节课讲解的主要内容是TX-LCN分布式事务的原理介绍。 + +### TX-LCN的核心控制流程 + +协调控制流程 +![](/img/txlcn/yuanli.png) + +#### 各种事务模式的原理 + + +| id | name | balacne | +| ---- | ---- | ---- | +| 1 | A | 200 | +| 2 | B | 100 | + +#### TCC业务处理 +Try Confirm Cancle +如何实现A转账给B的呢? + +``` +//尝试方法 +function try(){ + //记录日志 + todo save A 转出了 100 元 + todo save B 转入了 100 元 + //执行转账 + update amount set balacne = balacne-100 where id = 1 + update amount set balacne = balacne+100 where id = 2 +} +//确认方法 +function confirm(){ + //清理日志 + clean save A 转出了 100 元 + clean save B 转出了 100 元 +} + +//取消方法 +function cancle(){ + //加载日志 + load log A + load log B + + //退钱 + update amount set balacne = balacne+100 where id = 1 + update amount set balacne = balacne-100 where id = 2 +} + + +``` + +特点: +* 该模式对代码的嵌入性高,要求每个业务需要写三种步骤的操作。 +* 该模式对有无本地事务控制都可以支持使用面广。 +* 数据一致性控制几乎完全由开发者控制,对业务开发难度要求高。 + +#### TXC逆向SQL + + +![](/img/txlcn/WX20191225-213128@2x.png) + +特点: +* 该模式同样对代码的嵌入性低。 +* 该模式仅限于对支持SQL方式的模块支持。 +* 该模式由于每次执行SQL之前需要先查询影响数据,因此相比LCN模式消耗资源与时间要多。 +* 该模式不会占用数据库的连接资源,但中间状态可见 + + +#### LCN代理连接 + +![](/img/txlcn/WX20191225-214414@2x.png) +特点: +* 该模式对代码的嵌入性为低。 +* 该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。 +* 该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障。 +* 该模式缺陷在于代理的连接需要随事务发起方一共释放连接,增加了连接占用的时间。 + + +### 负载问题 +负载情况下的事务控制,对于无状态的TXC TCC来说是不需要关心事务的。但是对LCN来说需要考虑负载调用同一个模块时若模块不同会可能触发锁的问题。 + +举例: + +目前TX-LCN支持的事务种类有三种,其中LCN模式是会占用资源,详情见LCN模式原理。 + +若存在这样的请求链,A模块先调用了B模块的one方法,然后在调用了two方法,如下所示: + +A ->B.one(); +A ->B.two(); +假如one与two方法的业务都是在修改同一条数据,假如两个方法的id相同,伪代码如下: +``` +void one(id){ + execute => update demo set state = 1 where id = {id} ; +} + +void two(id){ + execute => update demo set state = 2 where id = {id} ; +} +``` +若B模块做了集群存在B1、B2两个模块。那么就可能出现A分别调用了B1 B2模块,如下: + +A ->B1.one(); +A ->B2.two(); +在这样的情况下业务方将在LCN下会因为资源占用而导致执行失败而回滚事务。为了支持这样的场景,框架提供了重写了rpc的负载模式。 + +控制在同一次事务下同一个被负载的模块被重复调用时将只会请求到第一次被选中的模块。 + +### 保障机制与补偿 + +#### 超时机制 +当业务模块在接受到事务请求,并完成响应以后会自动加入定时任务,等待TM通知,若TM迟迟不通知则触发TC主动请求的状况,若TC没有请求到数据就记录补偿(回滚事务)。 +#### TM清理机制 +TM全局都在记录着事务的状态消息,只有当TM确认完全都通知到了TC模块才能清楚事务信息,不然将永久保存。 + +一些特殊的情况介绍: +1、通知事务的时候通知不到的情况。(需要超时机制,超时机制有分为两种可能 1、联系不上TM不清楚事务状态,2提前询问了TM,业务还没有确认最终状态) +2、通知事务组执行时没有响应。(1不清楚有没有执行完业务,2不清楚有没有收到消息) +3、若业务模块死掉了,TM的日志在没有全部确认清楚之前,是不能清理事务数据,TM清理数据需要全部都确认OK方可清理。 + +由上述情况可见,需要补偿的情况有 +1、上面的情况1中对联系不上TM的情况需要记录补偿记录。 +2、上面的情况2、3中描述的场景可能会存在业务模块在没有接受到TM消息的时候联系不上了,*若是服务挂了,那么就得需要在下次服务启动的时候通过切面日志来与TM通讯确认状态,然后在执行业务*。若是通讯出现了故障,那么会除非超时机制自动写补偿日志。 + +由于这样的情况的存在 *若是服务挂了,那么就得需要在下次服务启动的时候通过切面日志来与TM通讯确认状态,然后在执行业务* +所以只能在切面进入是记录数据,当出现时可通过切面记录来触发业务,然后再补偿事务。 + +##### 补偿出现的处理机制 +1、自动补偿 (需要开启) +2、手动补偿 (自行处理删除补偿记录即可) + + + +
+ + + + + + + + \ No newline at end of file From 7d07c65d2bf3df5c809ea5e192d850c58ca8f340 Mon Sep 17 00:00:00 2001 From: wangliang <1991wangliang@gmail.com> Date: Thu, 26 Dec 2019 08:42:08 +0800 Subject: [PATCH 3/3] lesson02 --- _docs/txlcn/lesson02.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/_docs/txlcn/lesson02.md b/_docs/txlcn/lesson02.md index 9ccaa9bc4..272fbb4a5 100644 --- a/_docs/txlcn/lesson02.md +++ b/_docs/txlcn/lesson02.md @@ -3,7 +3,8 @@ title: 分布式事务从0到1-了解TX-LCN原理 permalink: /docs/txlcn-lesson02/ --- -![](/img/WX20191220-102719.png) + 本节课讲解的主要内容是TX-LCN分布式事务的原理介绍。 ### TX-LCN的核心控制流程 @@ -110,8 +111,12 @@ A ->B2.two(); 控制在同一次事务下同一个被负载的模块被重复调用时将只会请求到第一次被选中的模块。 + ### 保障机制与补偿 + + #### 超时机制 当业务模块在接受到事务请求,并完成响应以后会自动加入定时任务,等待TM通知,若TM迟迟不通知则触发TC主动请求的状况,若TC没有请求到数据就记录补偿(回滚事务)。 #### TM清理机制