当前位置: 首页 > 科技观察

再说分布式事务,你知道多少?

时间:2023-03-17 15:26:28 科技观察

前言三年前,我写了第一篇分布式事务相关的文章。然后有人问你分布式事务,我就把这篇文章扔给他。后来写了一些分布式事务相关的文章:实战中如何完成分布式事务一站式分布式事务解决方案Seata-Server深入解析一站式分布式事务解决方案Seata-Cient分布式的解密transactionframework-Fescar时隔三年回头看之前的文章,之前的知识确实缺失了很多,所以今天再次和大家总结一下分布式事务相关的东西。首先,让我们谈谈交易的定义。交易的英文是transaction。我们可以发现,这个词的中文解释是交易、交易等,所以我们可以知道交易一定离不开交易,交易的定义是什么?有句话说得好,一只手付款,一只手发货,这就是交易的规则,这也是交易的定义。那么交易的官方定义是什么?事务是一系列已完成或未完成操作的集合。它是一个不可分割的工作单元,是数据库环境中最小的工作单元。可以发现,这和我们单手支付,单手发货基本是一样的。确实,在真实的开发环境中,交易保障在交易业务中显得尤为重要,而一些社交业务与资金关系不大。比如点赞数和评论数,不会特别关注交易。这些应该侧重于性能。交易类型前面的文章没有介绍交易的类型。直到之前看了一篇文章,才知道交易分为5种。这里我也给大家介绍一下:扁平化交易在我们日常使用中基本上都是扁平化的。事务以begin开始,以commit或rollback.begin;doxxxx;commit/rollback;结束。带有保存点的平面事务增加了一个保存点机制,保存在内存中。如果数据库出现故障,保存点将丢失。begininsertintoxxxsavepointainsertintoyyyrollbacktoa这里回滚到a只会回滚到savepointa,并不是整个事务都会回滚链式事务当我们提交事务时,相当于执行了COMMITANDCHAIN,也就是开启一个链式事务,即当我们提交一个事务时,会开启一个具有相同隔离级别的事务。如果回滚,只会回滚当前节点。通过下面的sql语句,可以查看mysql中当前是否启用链模式,如何启用select@@completion_typeset@@completion_type=1//0没有链,嵌套事务嵌套事务可以是树,其中Leaf节点可以是扁平交易,也可以是嵌套交易,但都称为子交易。某个节点的回滚只影响当前节点下的所有交易。子节点的提交会按照父节点的提交进行提交。也就是说,所有交易的保存只有在顶层提交后才能生效。Mysql不支持嵌套事务。Oracle支持分布式事务。它通常是运行在分布式环境中的平面事务。它需要根据数据的位置访问网络中的不同节点。一般来说,对于分布式事务,还需要满足单机的ACID属性,要么都发生,要么都失败。然而,实际实现可能远比理想复杂,因此要求通常会降低。单机事务上面提到了五种事务,前四种其实都可以归结为单机事务,而单机事务是后端程序员经常遇到的,所以简单说一下单机的核心要点——单独事务:ACIDACID是独立事务的四个特征,这四个特征可以定义在什么情况下可以算作一个事务。A:原子性——事务中的操作要么全部提交,要么全部回滚。C:一致性——数据库必须在一个事务执行前后处于一致的状态(数据库中的数据要么是事务前的状态,要么是事务后的状态)。I:隔离性——在并发环境下,当不同的事务同时操作同一个数据时,每个事务都有自己完整的数据空间。D:持久性——当一个事务成功结束时,它对数据库所做的更新必须永久保存。即使发生系统崩溃,重启数据库系统后,数据库也可以恢复到事务成功结束时的状态事务的隔离是通过数据库锁的机制实现的,通过redolog(重做日志)实现持久化.原子性是通过Undolog实现的。一致性是靠以上三个方面的特性来实现的redolog,undolog,binlog通常很容易混淆,这里简单介绍下undolog:用于回滚和mvcc,可以理解为是用来记录数据的以前的版本。redolog:为了加快磁盘刷盘速度,数据库采用WAL方式,即先顺序预写日志,然后异步刷盘,redolog是顺序写入日志的产物。binlog:以上是innodb引擎的日志,binlog是mysql-server的日志,用于主从同步。如果代码使用java程序员,如果你使用spring,那么使用本地事务有两种方式:声明式事务其实就是直接加注解,spring自己会做一个切面代理然后使用事务。这个方法用的应该也是最多的,因为最简单,但是如果你只想控制这个方法的部分代码进入交易,或者调用同一个类的方法来使用交易,那么不能使用此方法。程序化事务程序化事务实现起来有点麻烦,需要大量的手写代码,但是可以解决上面提到的问题。这种方式更加灵活多变。为什么分布式事务需要分布式事务?我们会发现最近几年越来越多的人提到分布式事务。为什么是这样?《架构即未来》一书中提到,AKF模型是软件架构的延伸,软件的基本模型如上图所示。如果我们要对一个软件进行扩展,我们需要在AKF模型的基础上,从三个维度进行扩展。X轴:X轴的意思是把之前的单机变成集群,从一台机器扩容到N台。Y轴:在之前的单体服务中,所有的业务代码都写在一个服务中,Y轴的作用就是将这些业务模块分离出来,拆分成微服务。Z轴:很多业务瓶颈在数据库。通常我们可以做数据库分库分表,单元化等,在Y轴上,我们之前的单个服务拆分为多个服务,所以有可能之前的事务内容可能出现在多个服务中,比如作为订单扣除积分和优惠券。以前是服务。如果有积分服务和优惠券服务,那么我们之前的本地事务就失效了,我们需要引入分布式服务来保证一个订单中的所有扣费成功。在Z轴,如果我们做分库分表,也会破坏本地事务。如果大家在同一个库,我们自然可以分库分表,但是如果操作不同的库,那么事务就无法保证了,所以我们还需要引入分布式事务。而AKF是现代软件扩展的基本模型。三者中的两个可能需要引入分布式事务。因此,如果要对软件进行扩展,分布式事务是必不可少的。分布式理论知识CAP和BASE的理论知识应该很多人都知道,但是这里还是要介绍一下,因为分布式事务离不开这两个东西。CAPCAP定理,又称布鲁尔定理。CAP是分布式系统的入门理论。C(一致性):对于给定的客户端,读操作可以返回最新的写操作。对于分布在不同节点上的数据,如果在某个节点上更新了数据,那么如果在其他节点上也能读取到最新的数据,就称为强一致性。如果一个节点没有读取到,则为分布式不一致。A(可用性):非故障节点在合理的时间内返回合理的响应(不是错误和超时响应)。可用性的两个关键是合理的时间和合理的响应。合理的时间是指不能无限期地阻止请求,应该在合理的时间内返回。合理的响应是指系统应该清楚地返回结果并且结果是正确的。这里正确的意思是,比如应该返回50,而不是40。P(partitiontolerance):当发生网络分区时,系统可以继续工作。比如这个集群有多台机器,一台机器网络有问题,但是集群还能正常工作。BASEBASE是BasiclyAvailable(基本可用)、Softstate(软状态)和Eventuallyconsistent(最终一致性)这三个词组的缩写。它是AP在CAP中的扩展。1.基本可用性:当分布式系统发生故障时,允许丢失部分可用功能,以保证核心功能可用。2、软状态:允许系统存在一个中间状态,这个状态不影响系统的可用性,这里指的是CAP中的不一致性。3、最终一致性:最终一致性是指经过一段时间后,所有节点的数据都会达到一致性。BASE解决了CAP中没有网络延迟的理论,在BASE中使用软状态和最终一致性来保证延迟后的一致性。BASE和ACID是相反的。完全不同于ACID的强一致性模型。而是通过牺牲强一致性来获得可用性,允许数据在一段时间内不一致,但最终达到一致状态。分布式事务解决方案刚性事务刚性事务追求强一致性事务。DTP/XADTP(全称DistributedTransactionProcessingTheXASpecification)的XA规范的创建者是X/Open,也就是现在的OpenGroup。熟悉数据库自带的分布式事务支持的同学其实都知道这就是XA。你可以把这里的AP理解为我们自己的业务服务,RM就是我们使用的数据库。一般都是用mysql,mysql也是自带的。支持XA,TM可以单独服务,也可以由我们自己的业务服务来承担。它的作用是做事务管理。如果使用mysql,使用XA,代码如下:XABEGIN'123';insertintoxxx;XAEND'123';//如果链接断开,这笔交易将丢失XAPREPARE'123';//第二阶段准备会议PersistentXACOMMIT'123';//准备所有成功/或如果失败则回滚。当然,XA其实也有很多缺点:1.数据加锁:数据在整个事务流程结束前被加锁,读写是按隔离级别的定义来约束的。2、协议阻塞:XAprepare后,分支事务进入阻塞阶段,必须阻塞等待,才能收到XAcommit或XArollback。3、性能差:性能损失主要来自两个方面:一方面,交易协调过程增加了单笔交易的RT;另一方面,并??发事务数据的锁冲突降低了吞吐量。由于实现刚性事务的成本很高,而灵活事务不愿意为现在的互联网业务承担如此大的成本和性能损失,愿意牺牲一定的一致性来保证性能,所以我们这里称之为灵活事务。消息的最终一致性适用于很多异步任务。在我们的场景中,比如异步审核票据,异步发放积分(适用于加法业务)。并且有两种方式可以实现消息的最终一致性。消息表qmq实现事务消息的方式就是使用消息表。首先我们需要有这样一个消息表,用来保存在和我们业务相同的事务中。CREATETABLE`msg_queue`(`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'primarykey',`topic`varchar(64)NOTNULL,`nameServer`varchar(64)NOTNULL,`status`smallint(6)NOTNULLDEFAULT'0'COMMENT'MessageStatus',`error`intunsignedNOTNULLDEFAULT'0'COMMENT'ErrorTimes',`create_time`datetimeNOTNULLCOMMENT'CreationTime',`update_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COMMENT='记录业务系统消息';从上图可以看出,我们会有一个单独的任务,不断的扫描我们的消息,如果消息表没有被删除,说明之前消息没有发送成功,所以我们需要发送。这里需要说的是,我们必须保证消息是幂等的,因为这里发送完消息就删除,并不能保证数据一致。一条消息可能会被发送多次才能被删除。使用的代码如下:@Transactionalpublicvoidpay(Orderorder){saveOrder(order);messageProducer.sendMessage(buildMessage(order));//写在最后}rocketmq事务消息rockemq的事务消息如图所示分为四个阶段:第一阶段:先发送一个prepare消息,得到一个prepare消息id。第二阶段:执行本地事务,如果执行成功,则发送commit/failure,然后回滚。第三阶段:rocketmq-server根据消息的结果,如果成功则将消息投递给消费者,如果失败则删除消息。第四阶段:如果第二阶段没有上报新闻结果,则需要进行复审。尽力而为通知适用于外部第三方系统希望确保最终一致性的开放平台。比如微信,支付宝的开放平台。下面我贴出微信支付平台的执行过程:从上面可以看出besteffortnotification需要保证两点:重试次数有限,一般重试策略采用指数退避,需要提供查询防止通知失败的接口。TCC通常由付款的学生使用。虽然它是一个灵活的东西,但目标是要有一个刚性的效果。隔离性比较强。例如:有积分服务、优惠券服务、余额服务。如果用户想同时扣除这三项,怎么保证呢?是否可以使用其他模式?消息最终一致性和尽力而为通知不适用,无法保证隔离类型,用户重用资产。XA性能很差。所以这里我们选择使用TCC,分为三种方法:try:lock,一般是带字段或者记录。(进化成saga直接trycommitmerge)commit:commitresourcescancel:releaseresourcesSAGASAga是30年前一篇数据库伦理文章中提到的概念。它的核心思想是将一个长事务拆分成多个本地短事务,由Saga事务协调器协调。如果正常结束,则正常完成。如果某个步骤失败,将以相反的顺序调用一次补偿操作。适用于无法提供TCC接口(遗留系统、外部系统)。一般来说,提供提交和回滚接口就可以了。这里,可以看成是一个业务接口。生成订单对应的删除订单为rollback,无需单独命名为Rollback接口。无论隔离,都会在一个阶段生效。想异步执行,想支持前向重试(为什么不能tcc和try前向重试,资源已经被业务隔离,需要释放Isolation)。当然,SEATA引入了多种分布式事务,有的同学会想,我说了那么多,但是怎么实现呢?那么我这里推荐使用seata。Seata是一个开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单性。使用的分布式事务服务。为用户提供AT、TCC、SAGA和XA交易模式。有兴趣的可以访问seata官网:https://seata.io/zh-cn。这里就不详细介绍了,很多介绍还是看我之前的文章吧。终于隔了这么久,又写了分布式事务。这次是对前面几篇的补充,当然也算是对分布式事务的一个总结。希望大家能在自己的业务中找到适合自己的分布式事务方式。