几乎所有的信息管理系统都涉及到事务。事务的目的是保证数据的一致性。这里所说的一致性是指数据库状态的一致性。说到数据库状态的一致性,相信大家都会想到ACID:原子(Atomic):在一个事件的多个数据库操作中,要么同时成功,要么同时失败,比如:转移业务。隔离性:不同业务之间的处理数据相互独立,互不影响。持久性:正常提交的数据可以持久化,不会丢失数据。比如mysql可以自然持久化,redis、rabbitmq也可以通过设置来持久化。一致性:最终的数据是正确的,所以C通过AID的方式来保证。在单体架构中,通常一组程序对应一个数据库,事务是基于数据库本身的能力。如果你在.NETCore中使用dapper或sqlsugar,你可以轻松地处理事务。可以参考以下文档:https://dapper-tutorial.net/transaction。https://www.donet5.com/Home/Doc?typeId=1183。但是在微服务架构和分布式场景下,事务的处理会变得复杂,会有多个节点,多个节点的同步性和可用性都是需要考虑的问题。在分布式中,有一个著名的CAP理论:C:数据一致性(Consistency):分布中存在多个节点。对于一个指定的客户端,保证从任何一个节点读取的数据都是最新写入的数据。A:可用性(Availability),非故障节点在合理的时间内返回合理的响应(不是错误和超时响应)。P:分区容错(PartitionTolerance),节点之间的数据传输是基于网络的,因为网络本身并不是100%可靠的,在极端情况下,网络会不可用,然后网络两端的节点会分开,这就是所谓的“网络分区”现象。当发生网络分区时,两部分的数据不一致。如果要保证数据的一致性,就必须让没有及时同步数据的节点不可用,这就牺牲了可用性,否则就牺牲了一致性。因此,在P一定存在的情况下,需要在C和A之间做出选择。我们在CAP和ACID中讨论的一致性称为“强一致性”(StrongConsistency),而牺牲C但必须保证最终结果一致的AP系统称为“弱一致性”。也称为最终一致性。最终一致性的概念是由eBay系统架构师DanPritchett在2008年ACM论文“Base:AnAcidAlternative”中提出的。本文主要讲几种保证一致性的方式:TCC、SAGA和消息队列。TCCTCC是Try-Confirm-Cancel的缩写,意思是整个过程分为三个阶段:Try:一个请求涉及到多个服务,多个服务会同时进行Try。该阶段为试行阶段。在这个阶段,进行数据校验和检查,保证一致性,准备资源。如果成功,您将进入确认阶段。确认:确认执行阶段,不做任何业务检查。多个服务的Try执行成功,多个服务进入Confirm阶段。该阶段直接使用Try阶段准备的资源完成业务处理。注意Confirm阶段可能会重复执行,所以需要满足幂等性。Cancel:如果一个服务在Try阶段失败,则所有服务进入Cancel阶段,该阶段释放Try阶段预留的业务资源。注意Cancel阶段也可能重复执行,所以同样需要满足幂等性。在.NETCore中,可以参考:https://github.com/simpleway2016/JMS。Seata可以在Java中使用:https://github.com/seata/seatahttps://seata.io/zh-cn/。因为TCC中第一步Try需要预留资源用于检查和验证,但在某些场景下,资源是我们无法控制的,比如支付,余额由银行管理,我们通常没有权限。所以此时不??适合做TCC,可以考虑用SAGA代替TCC。SAGASAGA起源于1987年普林斯顿大学的HectorGarciaMolina和KennethSalem在ACM上发表的一篇论文《SAGAS》。SAGA和TCC最大的区别是基于数据补偿机制而不是回滚。一个SAGA代表了一系列在多个服务中处理数据的操作,由一系列本地事务组成,ACID仍然可以在每个独立的本地事务中使用。SATA由两部分组成:一个大事务被分成若干个小事务,比如一个大事务T,被分为T1、T2、T3。每个子事务都有相应的补偿动作,例如上面的T1、T2、T3分别对应有C1、C2、C3的补偿动作。在ACID中,如果发生异常,很容易回滚,但是SAGA没有办法自己回滚,必须靠补偿动作来回滚。如果T1、T2、T3都提交成功,则整个交易T提交成功。如果T2执行过程中出现异常,有两种处理方式:Forward(连续重试):不断重试T2的操作,直到成功(不排除人工干预),T2重试成功后,继续执行以下T3。反向(补偿):当T2异常时,执行相应的补偿C2。C2必须执行成功(不排除人工操作),然后执行T1对应的补偿动作C1。上面提到的seata也可以支持SAGA模式。除了seata,还有一个用go语言写的DTM分布式事务框架:https://dtm.pub/https://github.com/dtm-labs/dtm。重要的是,DTM支持C#客户端:https://github.com/dtm-labs/dtmcli-csharp。消息队列消息队列相信大家都不陌生。我们零代码产品中调用外部接口的组件,在一些复杂的业务逻辑安排中会用到。对外部接口的调用使用消息队列。RabbitMQ的延迟队列增加了死信队列,可以用于重试操作,保证数据的最终一致性。另一种方法是使用事务消息表。比如有这样一个场景,要在系统列表中删除一条进程数据。这时候,你需要做的是:1、删除列表服务中的数据;。2.文件服务删除与该数据相关的附件。3、流程服务删除业务数据的所有流程信息。具体步骤如下:1、列表服务删除数据成功后,在数据库中创建交易消息表,记录交易ID、数据删除成功状态、业务数据ID、待删除附件状态、处理要删除的信息。删除状态等2、列表服务删除数据成功后,发送消息分别删除附件和进程信息。3.消息处理正确后,修改交易消息表的状态。4、单独创建一个消息服务程序,轮询扫描交易消息表。如果发现状态还没有变成completed,就会重新发送新的消息,这样就会多次执行附件删除和进程信息删除,这也是要求这些操作是幂等的。RabbitMQ本身不支持分布式事务,但是一些消息中间件支持,比如RocketMQ,原生支持分布式事务操作,使得事务处理更加方便。本文是对一些理论的整理。如果想掌握的更透彻,可以选择一个框架,找几个场景,写代码练习一下。
