在分布式系统中,为了保证数据的高可用,通常,我们会将数据保留多个副本(replica),这些副本会放置在不同的物理的机器上。为了对用户提供正确的 CRUD 等语义,我们需要保证这些放置在不同物理机器上的副本是一致的。分布式事务在现在遍地都是分布式部署的系统中几乎是必要的。
我们的项目用到了数据库,也和事务有关,我们先分析一下项目的问题,再描述一下事务。
如上图,如果用户打车成功,需要修改司机状态、下单、记录支付日志,而每个操作都是调用了不同的服务,比如此时 hailtaxi-driver 服务执行成功了,但是 hailtaxi-order 有可能执行失败了,这时候
如何实现跨服务事务回滚呢?这就要用到分布式事务。
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组SQL语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity):事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态,事务的中间状态不能被观察到的。
隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离性又分为四个级别:读未提交 (read uncommitted)、读已提交(read committed,解决脏读)、可重复读(repeatable read,解决虚 读)、串行化(serializable,解决幻读)。
持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数 据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
任何事务机制在实现时,都应该考虑事务的ACID特性,包括:本地事务、分布式事务,及时不能都很好的满足,也要考虑支持到什么程度。
大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务( Local Transaction )。本地事务的ACID特性是数据库直接提供支持。本地事务应用架构如下所示:
很多java应用都整合了spring,并使用其声明式事务管理功能来完成事务功能。一般使用的步骤如下:
1、配置事务管理器。spring提供了一个 PlatformTransactionManager 接口,其有2个重要的实现类:
DataSourceTransactionManager :用于支持本地事务,事实上,其内部也是通过操作
java.sql.Connection 来开启、提交和回滚事务。
JtaTransactionManager :用于支持分布式事务,其实现了JTA规范,使用XA协议进行两阶段提交。需要注意的是,这只是一个代理,我们需要为其提供一个JTA provider,一般是Java EE容器提供的事务协调器(Java EE server’s transaction coordinator),也可以不依赖容器,配置一个本地的JTAprovider。
2、 在需要开启的事务的bean的方法上添加 @Transitional 注解可以看到,spring除了支持本地事务,也支持分布式事务,下面我们先对分布式事务的典型应用场景进行介绍。
当下互联网发展如火如荼,绝大部分公司都进行了数据库拆分和服务化(SOA)。在这种情况下,完成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务,用需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据的操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性。
跨库事务指的是,一个应用某个功能需要操作多个库,不同的库中存储不同的业务数据。下图演示了一个服务同时操作2个库的情况:
通常一个库数据量比较大或者预期未来的数据量比较大,都会进行水平拆分,也就是分库分表。如下图,将数据库B拆分成了2个库:
对于分库分表的情况,一般开发人员都会使用一些数据库中间件来降低sql操作的复杂性。如,对于sql: insert into user(id,name) values (1,“gupaoedu”),(2,“gpvp”) 。这条sql是操作单库的语法,单库情况下,可以保证事务的一致性。
但是由于现在进行了分库分表,开发人员希望将1号记录插入分库1,2号记录插入分库2。所以数据库中间件要将其改写为2条sql,分别插入两个不同的分库,此时要保证两个库要不都成功,要不都失败,因此基本上所有的数据库中间件都面临着分布式事务的问题。
微服务架构是目前一个比较一个比较火的概念。例如上面提到的一个案例,某个应用同时操作了9个库,这样的应用业务逻辑必然非常复杂,对于开发人员是极大的挑战,应该拆分成不同的独立服务,以简化业务逻辑。拆分后,独立服务之间通过RPC框架来进行远程调用,实现彼此的通信。下图演示了一个3个服务之间彼此调用的架构:
Service A完成某个功能需要直接操作数据库,同时需要调用Service B和Service C,而Service B又同时操作了2个数据库,Service C也操作了一个库。需要保证这些跨服务的对多个数据库的操作要不都成功,要不都失败,实际上这可能是最典型的分布式事务场景。
上述讨论的分布式事务场景中,无一例外的都直接或者间接的操作了多个数据库。如何保证事务的ACID特性,对于分布式事务实现方案而言,是非常大的挑战。同时,分布式事务实现方案还必须要考虑性能的问题,如果为了严格保证ACID特性,导致性能严重下降,那么对于一些要求快速响应的业务,是无法接受的。
分布式事务可以有多种分类,比如柔性事务和强一致性事务,这些事务操作会遵循一定的定理,比如CAP原理、BASE理论
由于对系统或者数据进行了拆分,我们的系统不再是单机系统,而是分布式系统,针对分布式系统的
CAP原理包含如下三个元素:
C:Consistency,一致性。在分布式系统中的所有数据 备份,在同一时刻具有同样的值,所有节 点在同一时刻读取的数据都是最新的数据副本。
A:Availability,可用性,好的响应性能。完全的可用性指的是在任何故障模型下,服务都会在有 限的时间内处理完成并进行响应。
P: Partition tolerance,分区容忍性。尽管网络上有部分消息丢失,但系统仍然可继续工作。
CAP原理指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。因此在进行分布式架构设计时,必须做出取舍。而对于分布式数据系统,分区容忍性是基本要求,否则就失去了价值。因此设计分布式数据系统,就是在一致性和可用性之间取一个平衡。对于大多数web应用,其实并不需要强一致性,因此牺牲一致性而换取高可用性,是目前多数分布式数据库产品的方向。
当然,牺牲一致性,并不是完全不管数据的一致性,否则数据是混乱的,那么系统可用性再高分布式再好也没有了价值。牺牲一致性,只是不再要求关系型数据库中的强一致性,而是只要系统能达到最终一致性即可,考虑到客户体验,这个最终一致的时间窗口,要尽可能的对用户透明,也就是需要保障“用户感知到的一致性”。通常是通过数据的多份异步复制来实现系统的高可用和数据的最终一致性的,“用户感知到的一致性”的时间窗口则取决于数据复制到一致状态的时间。
BASE理论是指,Basically Available(基本可用)、Soft-state( 软状态/柔性事务)、EventualConsistency(最终一致性)。是基于CAP定理演化而来,是对CAP中一致性和可用性权衡的结果。核心思想:即使无法做到强一致性,但每个业务根据自身的特点,采用适当的方式来使系统达到最终一致性。
1、基本可用 BA:(Basically Available ):
指分布式系统在出现故障的时候,允许损失部分可用性,保证核心可用。但不等价于不可用。比如:搜索引擎0.5秒返回查询结果,但由于故障,2秒响应查询结果;网页访问过大时,部分用户提供降级服务等。简单来说就是基本可用。
2、软状态 S:( Soft State):
软状态是指允许系统存在中间状态,并且该中间状态不会影响系统整体可用性。即允许系统在不同节点间副本同步的时候存在延时。简单来说就是状态可以在一段时间内不同步。
3、最终一致性 E:(Eventually Consistent):系统中的所有数据副本经过一定时间后,最终能够达到一致的状态,不需要实时保证系统数据的强一致性。最终一致性是弱一致性的一种特殊情况。BASE理论面向的是大型高可用可扩展的分布式系统,通过牺牲强一致性来获得可用性。ACID是传统数据库常用的概念设计,追求强一致性模型。简单来说就是在一定的时间窗口内, 最终数据达成一致即可。
何谓刚柔事务?刚性事务它的事务是原子的,要么都成功要么都失败,也就是需要保障ACID理论,而柔性事务只需要保障数据最终一致即可,需要遵循BASE理论。
柔性事务分为:
金融项目对柔性事务用的比较多,例如支付宝、微信支付、银联支付几乎所有架构是SOA架构,因此传统单机环境下数据库的ACID事务满足了分布式环境下的业务需要,以上几种事务类似就是针对分布式环境下业务需要设定的。
补偿型
WS-BusinessActivity 提供了一种基于补偿的 long-running 的事务处理模型。服务器 A 发起事务,服务器 B 参与事务,服务器 A 的事务如果执行顺利,那么事务 A 就先行提交,如果事务 B 也执行顺利,则事务 B 也提交,整个事务就算完成。但是如果事务 B 执行失败,事务 B 本身回滚,这时事务 A 已经被提交,所以需要执行一个补偿操作,将已经提交的事务 A 执行的操作作反操作,恢复到未执行前事务 A 的状态。这样的 SAGA 事务模型,是牺牲了一定的隔离性和一致性的,但是提高了 long-running 事务的可用性。
异步确保型
通过将一系列同步的事务操作变为基于消息执行的异步操作, 避免了分布式事务中的同步阻塞操作的影
最大努力通知型(多次尝试)
这是分布式事务中要求最低的一种, 也可以通过消息中间件实现, 与前面异步确保型操作不同的一点是, 在消息由 MQ Server 投递到消费者之后, 允许在达到最大重试次数之后正常结束事务
分布式事务解决方案几乎都是柔性事务,常见的有2PC/3PC、TCC、MQ最终一致性解决方案,至于工作中用哪种方案,需要根据业务场景选取, 2PC/3PC、TCC 数据强一致性高,而MQ是最终数据一致。
DTP模型中有5个基本元素:
应用程序(Application Program ,简称AP):用于定义事务边界(即定义事务的开始和结束),并且在事务边界内对资源进行操作。
资源管理器(Resource Manager,简称RM):如数据库、文件系统等,并提供访问资源的方式。
事务管理器(Transaction Manager ,简称TM):负责分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚等。
通信资源管理器(Communication Resource Manager,简称CRM):控制一个TM域(TM domain)内或者跨TM域的分布式应用之间的通信。
通信协议(Communication Protocol,简称CP):提供CRM提供的分布式应用节点之间的底层通信服务。
1)2PC
两阶段提交又称2PC,2PC是一个非常经典的强一致、中心化的原子提交协议 。这里所说的中心化是指协议中有两类节点:一个是中心化 协调者节点(coordinator)和 N个参与者节点 (partcipant)。两个阶段 :第一阶段:投票阶段 和第二阶段:提交/执行阶段。 举例 订单服务A,需要调用 支付服务B 去支付,支付成功则处理购物订单为待发货状态,否则就需要将购物订单处理为失败状态。
那么看2PC阶段是如何处理的
1: 事务询问
协调者 向所有的 参与者 发送事务预处理请求,称之为Prepare,并开始等待各 参与者 的响应。
2: 执行本地事务
各个 参与者 节点执行本地事务操作,但在执行完成后并不会真正提交数据库本地事务,而是先向 协调者报告说:“我这边可以处理了/我这边不能处理”。
3: 各参与者向协调者反馈事务询问的响应
如果 参与者 成功执行了事务操作,那么就反馈给协调者 Yes 响应,表示事务可以执行,如果没有 参与者 成功执行事务,那么就反馈给协调者 No 响应,表示事务不可以执行。
2PC:
1: 所有的参与者反馈给协调者的信息都是Yes,那么就会执行事务提交
协调者 向 所有参与者 节点发出Commit请求.
2: 事务提交
参与者 收到Commit请求之后,就会正式执行本地事务Commit操作,并在完成提交之后释放整个事务执行期间占用的事务资源。
2)3PC
三阶段提交又称3PC,其在两阶段提交的基础上增加了CanCommit阶段,并引入了超时机制。一旦事务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,这样相对有效地解决了协调者单点故障的问题。
但是性能问题和不一致问题仍然没有根本解决。下面我们还是一起看下三阶段流程的是什么样的?
这个阶段类似于2PC中的第二个阶段中的Ready阶段,是一种事务询问操作,事务的协调者向所有参与者询问“你们是否可以完成本次事务?”,如果参与者节点认为自身可以完成事务就返回“YES”,否则“NO”。而在实际的场景中参与者节点会对自身逻辑进行事务尝试,简单来说就是检查下自身状态的健康性,看有没有能力进行事务操作。
在阶段一中,如果所有的参与者都返回Yes的话,那么就会进入PreCommit阶段进行事务预提交。此时分布式事务协调者会向所有的参与者节点发送PreCommit请求,参与者收到后开始执行事务操作,并将Undo和Redo信息记录到事务日志中。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。
否则,如果阶段一中有任何一个参与者节点返回的结果是No响应,或者协调者在等待参与者节点反馈的过程中超时(2PC中只有协调者可以超时,参与者没有超时机制)。整个分布式事务就会中断,协调者就会向所有的参与者发送“abort”请求。
在阶段二中如果所有的参与者节点都可以进行PreCommit提交,那么协调者就会从“预提交状态”-》“提交状态”。然后向所有的参与者节点发送"doCommit"请求,参与者节点在收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的Ack消息后完成事务。
相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。
2PC VS 3PC
相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而2PC只有协调者才拥有超时机制。这解决了一个什么问题呢?这个优化点,主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。另外,通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。
以上就是3PC相对于2PC的一个提高(相对缓解了2PC中的前两个问题),但是3PC依然没有完全解决数据不一致的问题。
TCC与2PC、3PC一样,也是分布式事务的一种实现方案。TCC(Try-Confirm-Cancel)又称补偿事务。其核心思想是:“针对每个操作都要注册一个与其对应的确认和补偿(撤销操作)”。它分为三个操作:
Try阶段:主要是对业务系统做检测及资源预留。
Confirm阶段:确认执行业务操作。
Cancel阶段:取消执行业务操作。
TCC事务的处理流程与2PC两阶段提交类似,不过2PC通常都是在跨库的DB层面,而TCC本质上就是一个应用层面的2PC,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。
不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口还必须实现幂等。
在OLTP系统领域,我们在很多业务场景下都会面临事务一致性方面的需求,而大型互联网平台往往是由一系列分布式系统构成的,开发语言平台和技术栈也相对比较杂,尤其是在SOA和微服务架构盛行的
今天,一个看起来简单的功能,内部可能需要调用多个“服务”并操作多个数据库或分片来实现,情况往往会复杂很多,如果对数据一致性要求很高,可以采用上面的2PC或者3PC以及TCC方案,如果数据强一致性要求没那么高,可以采用消息中间件(MQ)实现事务最终一致。
在支付系统中,常常使用的分布式事务解决方案就是基于MQ实现的,它对数据强一致性要求没那么高,但要求数据最终一致即可。例如:向借呗申请借钱,借呗审核通过后支付宝的余额才会增加,但借呗和支付宝有可能不是同一个系统,这时候如何实现事务呢?实现方案如下图:
1:找花呗借钱
2:花呗借钱审核通过,同步生成借款单
3:借款单生成后,向MQ发送消息,通知支付宝转账
4:支付宝读取MQ消息,并增加账户余额
上图最复杂的其实是如何保障2、3在同一个事务中执行(本地事务和MQ消息发送在同一个事务执行),借款结束后,借呗数据处理就完成了,接下来支付宝才能读到消息,然后执行余额增加,这才完成整个操作。如果中途操作发生异常,例如支付宝余额增加发生问题怎么办?此时需要人工解决,没有特别好的办法,但这种事故概率极低。
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,
Seata 正式宣布对外开源,开放以来,广受欢迎,不到一年已经成为最受欢迎的分布式事务解决方案。
Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。其中AT模式最受欢迎,使用也非常简单,但它内在的原理不简单。
AT模式前提:
1:基于支持本地 ACID 事务的关系型数据库。
2:Java 应用,通过 JDBC 访问数据库。
整体机制:2PC协议的演变
1PC:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
2PC:
1)提交异步化,非常快速地完成。
2)回滚通过一阶段的回滚日志进行反向补偿。
以一个示例来说明整个 AT 分支的工作过程。
业务表: product
执行修改操作:(SQL语句如下)
update product set name = 'GTS' where name = 'TXC';
1PC:
a.解析 SQL 语句,得到类型为 UPDATE ,表为 product ,条件 where name = ‘TXC’ 。
b.根据解析的 SQL 语句进行要操作的结果查询:
select * product where name ='TXC'
select id, name, since from product where id = 1;
查询修改后的结果:
d.镜像备份
将修改前的结果和修改后的结果添加到数据库表 undo_log 中。
e.提交前,向TC注册分支申请 product 表中id=1的数据的全局锁。
f.本地事务提交。
8.本地事务提交结果上报给TC。
2PC-提交:
a.收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
b.异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
2PC-回滚:
a.收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
b.通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
c.数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
d.根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
update product set name = 'TXC' where id = 1;
e.提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
undo_log 表,在每个需要执行分布式事务操作的数据库中添加
-- 注意此处0.7.0+ 增加字段 context
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版本的时候,可以先参考下官方给出的版本匹配(Seata版本也可以按自己的要求选择):
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
我们当前 SpringCloud Alibaba 的版本是 2.2.5.RELEASE ,对应Seata版本是1.3.0,所以我们首先安装Seata-Server1.3.0:
docker run --name seata-server -p 8091:8091 -d seataio/seata-server:1.3.0
我们接下来开始在项目中集成使用Seata的AT模式实现分布式事务控制,关于如何集成,官方也给出了很多例子,可以通过https://github.com/seata/seata-samples查看:
集成SpringBoot可以按照如下步骤实现:
1:引入依赖包spring-cloud-starter-alibaba-seata
2:配置Seata
3:创建代理数据源
4:@GlobalTransactional全局事务控制
1)依赖引入
我们首先在 hailtaxi-driver 中引入依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
<version>2.2.5.RELEASEversion>
dependency>
2)配置Seata
依赖引入后,我们需要在项目中配置SeataClient端信息,关于SeataClient端配置信息,官方也给出了很多版本的模板,可以打开https://github.com/seata/seata/tree/1.4.0/script,如下图:
我们可以选择spring,把 application.yml 文件直接拷贝到工程中,文件如下:
seata:
enabled: true
application-id: applicationName
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: true
data-source-proxy-mode: AT
use-jdk-proxy: false
excludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExclude
client:
rm:
async-commit-buffer-limit: 1000
report-retry-count: 5
table-meta-check-enable: false
report-success-enable: false
saga-branch-register-enable: false
saga-json-parser: fastjson
lock:
retry-interval: 10
retry-times: 30
retry-policy-branch-rollback-on-conflict: true
tm:
commit-retry-count: 5
rollback-retry-count: 5
default-global-transaction-timeout: 60000
degrade-check: false
degrade-check-period: 2000
degrade-check-allow-times: 10
undo:
data-validation: true
log-serialization: jackson
log-table: undo_log
only-care-update-columns: true
log:
exceptionRate: 100
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 127.0.0.1:8091
enable-degrade: false
disable-global-transaction: false
transport:
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
server-executor-thread-prefix: NettyServerBizHandler
share-boss-worker: false
client-selector-thread-prefix: NettyClientSelector
client-selector-thread-size: 1
client-worker-thread-prefix: NettyClientWorkerThread
worker-thread-size: default
boss-thread-size: 1
type: TCP
server: NIO
heartbeat: true
serialization: seata
compressor: none
enable-client-batch-send-request: true
config:
type: file
consul:
server-addr: 127.0.0.1:8500
apollo:
apollo-meta: http://192.168.1.204:8801
app-id: seata-server
namespace: application
apollo-accesskey-secret: ""
etcd3:
server-addr: http://localhost:2379
nacos:
namespace:
serverAddr: 127.0.0.1:8848
group: SEATA_GROUP
username: ""
password: ""
zk:
server-addr: 127.0.0.1:2181
session-timeout: 6000
connect-timeout: 2000
username: ""
password: ""
custom:
name: ""
registry:
type: file
load-balance: RandomLoadBalance
load-balance-virtual-nodes: 10
file:
name: file.conf
consul:
server-addr: 127.0.0.1:8500
etcd3:
serverAddr: http://localhost:2379
eureka:
weight: 1
service-url: http://localhost:8761/eureka
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group : "SEATA_GROUP"
namespace:
username: ""
password: ""
redis:
server-addr: localhost:6379
db: 0
password:
timeout: 0
sofa:
server-addr: 127.0.0.1:9603
region: DEFAULT_ZONE
datacenter: DefaultDataCenter
group: SEATA_GROUP
addressWaitTime: 3000
application: default
zk:
server-addr: 127.0.0.1:2181
session-timeout: 6000
connect-timeout: 2000
username: ""
password: ""
custom:
name: ""
关于配置文件内容参数比较多,我们需要掌握核心部分:
seata_transaction: default:事务分组,前面的seata_transaction可以自定义,通过事务分组很方便找到集群节点信息。
tx-service-group: seata_transaction:指定应用的事务分组,和上面定义的分组前部分保持一致。
default: 192.168.211.145:8091:服务地址,seata-server服务地址。
3)代理数据源
通过代理数据源可以保障事务日志数据和业务数据能同步,关于代理数据源早期需要手动创建,但是随着Seata版本升级,不同版本实现方案不一样了,下面是官方的介绍:
1.1.0: seata-all取消属性配置,改由注解@EnableAutoDataSourceProxy开启,并可选择jdk
proxy或者cglib proxy
1.0.0: client.support.spring.datasource.autoproxy=true
0.9.0: support.spring.datasource.autoproxy=true
我们当前的版本是1.3.0,所以我们创建代理数据源只需要在启动类上添加@EnableAutoDataSourceProxy 注解即可,在 com.itheima.DriverApplication 上添加该注解:
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan(basePackages = "com.itheima.driver.mapper")
@EnableAutoDataSourceProxy
public class DriverApplication {
//...略
}
4)全局事务控制
上面3个操作同时在 hailtaxi-order 工程中执行,打车成功创建订单是由客户发起,在 hailtaxi- order 中执行,并且feign调用 hailtaxi-driver ,所以 hailtaxi-order 是全局事务入口,我们在OrderInfoServiceImpl.addOrder() 方法上添加@GlobalTransactional ,那么此时该方法就是全局事务的入口,为了测试事务,我们在代码中添加一个异常,代码如下:
5)分布式事务测试
我们此时执行请求 http://localhost:18082/order/seata ,执行失败,并且数据库数据没有发生变更,但在 hailtaxi-driver 执行成功了,受影响行数为1,效果如下,说明分布式事务控制成功。
Seata和我们SpringBoot项目一样,也有注册中心,它支持的注册中心非常多,默认采用的是直连方式:
1:eureka
2:consul
3:apollo
4:etcd
5:zookeeper
6:sofa
7:redis
8:file (直连)
我们在生产环境中一般使用非直连的注册中心,因为直连seata-server无法实现高可用,我们这里以Zookeeper为例。
服务端注册中心(位于seata-server的registry.conf配置文件中的registry.type参数),为了实现seataserver集群高可用不会使用file类型,一般会采用第三方注册中心,例如zookeeper、redis、eureka、nacos等。 我们这里使用zookeeper,seata-server的registry.conf配置如下:
type = "zk"
zk {
cluster = "default"
serverAddr = "192.168.211.145:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
此时我们再重新启动Seata-server,数据就会注册到Zookeeper中。
项目中,我们需要修改注册中心地址,修改 application.yaml 的registry部分,配置如下
注释掉项目中file.conf文件的default.grouplist=“192.168.211.145:8091”
seata-server目前使用的是一个单节点,能否抗住高并发是一个值得思考的问题。生产环境项目几乎都需要确保能扛高并发、具备高可用的能力,因此生产环境项目一般都会做集群。而我们刚才的seataserver只有一个节点,肯定是无法做到高可用,要想实现高可用,必须要做集群操作。
上面配置也只是将注册中心换成了zookeeper,而且是单机版的,如果要想实现高可用,就得实现集群,集群就需要做会话共享操作了,我们需要准备2个seata-server,并且配置会话共享,会话共享支持3种方式,1)file【集群不可用】 2)redis 3)db 我们这里选择redis存储会话信息实现共享。 修改seataserver的file.conf,修改如下:
mode = "redis"
## redis store property
redis {
host = "192.168.211.145"
port = "6379"
password = ""
database = "0"
minConn = 1
maxConn = 10
queryLimit = 100
}
seata-server启动测试 第1台:启动命令 seata-server.bat -p 8091 -m redis 第2台:启动命令 seataserver.bat -p 8092 -m redis
命令中 -p 表示端口号,-m表示会话存储的方式,redis表示用redis存储。
测试地址 http://localhost:18082/order/seata 此时可以发现没有任何问题,停掉任意一个节点,分布式事务仍然生效。