在项目ITOO2.0之前,分布式事务一直是讨论的主流问题之一。对于什么是事务,以及事务的ACDI特性,我就不在这里费口舌了~
先简单介绍一下分布式事务:分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
其中最经典的例子就是跨行转账问题,各大银行之间的系统一定都是分布式系统,银行之间的业务操作也一定要执行分布式事务。没有分布式事务,谁还敢去银行存钱?
为什么要用它:分布式事务旨在协助在分布式环境中跨异类的事务识别资源的事务。在分布式系统的支持下,应用程序可以将不同的活动合并为一个事务单元。因为分布式事务跨多个数据库资源,故强制 ACID 属性维护所有资源上的数据一致性十分重要。
当时我们只是对事务做到了理解和简单的实现,分布式事务到底是什么,怎么用还不太清楚。
实现项目的时候,我们只是先现在底层封装了一系列操作数据库的方法,然后定义了一个接口,里边有VS自带的SaveChanges方法。在BLL层每个逻辑块结束时再对SaveChanges方法进行调用。在严格意义上来讲,我个人觉得这其实并不属于分布式事务,只是一个简单的事务。
关于这个问题,师哥们提出了两级提交这个概念,也叫两阶段提交。这个“两”应该也不是绝对的,只是理解起来比较容易。两级提交是分布式事务实现的关键。
两级提交:
两级提交的过程涉及到协调者和参与者。协调者可以看做成事务的发起者,同时也是事务的一个参与者。对于一个分布式事务来说,一个事务是涉及到多个参与者的。
阶段一:首先,协调者在自身节点的日志中写入一条日志记录,然后所有参与者发送消息prepare T,询问这些参与者(包括自身),是否能够提交这个事务;参与者在接受到这个prepare T 消息以后,会根据自身的情况,进行事务的预处理,如果参与者能够提交该事务,则会将日志写入磁盘,并返回给协调者一个ready T信息,同时自身进入预提交状态;如果不能提交该事务,则记录日志,并返回一个not
commit T信息给协调者,同时撤销在自身上所做的数据库改。这个参与者能够推迟发送响应的时间,但最终还是需要发送的。
阶段二:协调者要收集参与者的结果。
返回信息为ready T,则协调者会将commit T日志写入磁盘,并向所有参与者发送一个commit T信息,提交该事务;
返回信息为not commit T,则该事务不能被提交,协调者会将abort T 记录到日志中,并向所有参与者发送一个abort T 信息,让所有参与者撤销在自身上所有的预操作;
若协调者迟迟未收到某个参与者发来的信息,则认为该参与者发送了一个VOTE_ABORT信息--决定中止,从而取消该事务的执行。
如图:
异常处理:
操作日志是保证提交成功与否的信息关键,当参与者commit T时,若其发生了死机,则可以通过日志进行恢复,询问协调者是否提交成功。
但若协调者发出commit T后发生死机,该事务就会处于一个未知状态。这时就需要数据库管理员的介入,防止数据库进入一个不一致的状态。当然,如果所有节点或者网络的异常最终都会恢复,那么这个问题就不存在了,协调者和参与者最终会重启,其他节点也最终也会收到commit T的信息。
下面给大家分享一个System.TransactionScope类(使代码块成为事务代码)实现分布式事务的一个Demo:
//若dal.Insert提交失败,则不能继续执行;若employeeDAL.Insert(emp)提交失败,则整体提交失败,dal.Insert也不能真正执行。 using (TransactionScope ts = new(TransactionScope()) { DepartmentDAL dal = new DepartmentDAL()//公寓DAL; dal.Insert("公寓1号"); EmployeeDAL employeeDAL = new EmployeeDAL();//员工DAL Employee emp = new Employee();//员工类 employeeDAL.Insert(emp); ts.Complete();//提交 }
分布式事务的使用十分重要,而且随着分布式系统的兴起,它的应用范围也会更广。在我们的项目中,如何做好、实现好还需要继续分析和实践探索。总之,在分布式的路程上,我们还需要走的路很长~