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

12张图让你彻底理解分布式事务的场景和解决方案

时间:2023-03-16 10:08:21 科技观察

作者亲自开发了一个简单、稳定、可扩展的高并发场景下的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。开源半年多以来,已成功为十几家中小企业提供精准定时调度解决方案,经受住了生产环境的考验。为了造福更多童鞋,这里提供开源框架地址:https://github.com/sunshinelyz/mykit-delayPS:欢迎Star源码,也可以pr出你的精彩代码。写这篇文章的背景是一个和我关系很好的朋友去一家大型互联网公司面试。面试官问他分布式事务。不幸的是,他确实很好地掌握了分布式事务。不是很深入,面试的结果比较遗憾。不过这位朋友还是挺乐观的,让我写一个关于【分布式事务】的系列文章,如果我想改善自己在分布式事务上的不足,那我就写一篇关于【分布式事务】的专题。内容规划从原理、框架源码到企业级实现。这篇文章可以算是整个话题的开始。希望能给朋友们带来实质性的帮助。LocalTransaction本地事务流程在介绍分布式事务之前,我们先来看一下本地事务。首先,让我们拍张照片。从上图我们可以看出,本地事务是由资源管理器(如DBMS,数据库管理系统)在本地管理的。本地事务的优缺点本地事务有相应的优缺点。优点:支持严格的ACID属性。交易执行可靠、效率高(仅限本地操作)。事务只能在RM(ResourceManager)中进行操作。编程模型很简单。缺点:缺乏分布式事务处理能力。数据隔离的最小单位由RM(资源管理器)决定,开发者无法确定数据隔离的最小单位。例如:数据库中的一条记录等。ACID属性说到事务,就不得不提到事务的ACID属性。A(Atomic):原子性,构成事务的所有操作要么执行要么根本不执行,不可能部分成功部分失败。C(Consistency):一致性,事务执行前后,数据库的一致性约束没有被打破。举个例子:张三给李四转了100块钱,转账前后数据的正确状态叫做一致性,如果张三转了100块钱,但是李四的账户没有增加100块钱,那么就有一个数据错误,则未实现一致性。I(隔离):隔离。数据库中的事务通常是并发的。隔离是指两个并发事务的执行不会相互干扰。一个事务看不到其他事务的中间状态。通过配置事务隔离级别,可以避免脏读、重复读等问题。D(耐久性):持久性。事务完成后,事务对数据所做的修改会持久化到数据库中,不会回滚。分布式事务随着业务的快速发展,网站系统往往从单体架构逐渐演进到分布式、微服务架构,而对于数据库,则从单机数据库架构向分布式数据库架构转变。此时,我们会将一个大型的应用系统拆分成多个可以独立部署的应用服务,需要服务之间进行远程协作来完成事务操作。一开始我们可以用下图来表示我们系统的单体架构。上图中,我们将同一个项目中的不同模块组织到不同的包中进行管理,所有的程序代码仍然放在同一个项目中。后来由于业务发展,我们将其扩展为分布式微服务架构。至此,我们将一个大项目拆分成可以独立部署的小微服务,每个微服务都有自己的数据库,如下图。再比如,在我们的程序中,经常会在同一个事务中执行类似下面的代码来完成我们的需求。@Transactional(rollbackFor=Exception.class)publicvoidsubmitOrder(){orderDao.update();//更新订单信息accountService.update();//修改资金账户金额pointService.update();//修改积分accountingService.insert();//插入交易流merchantNotifyService.notify();//通知支付结果}上面代码中的业务只是在submitOrder()方法中添加了@Transactional注解,可以避免分布式分布式场景下的分布业务问题?很明显不是。如果以上代码对应:订单信息、资金账户信息、积分信息、交易流水等信息存储在不同的数据中,支付完成后,通知的目标系统的数据也存储在不同的数据库中。这时候就会出现分布式事务问题。跨JVM进程的分布式事务产生的场景当我们将单个项目拆分成分布式和微服务项目时,各个服务通过远程REST或RPC调用相互协作完成业务操作。一个典型的场景是:商城系统中的订单微服务和库存微服务。当用户下订单时,他们将访问订单微服务。订单微服务生成订单记录时,会调用库存微服务扣除库存。每个微服务部署在不同的JVM进程中。这时候就会出现跨JVM进程导致的分布式事务问题。跨数据库实例单体系统访问多个数据库实例,即跨数据源访问会产生分布式事务。比如我们系统中的订单数据库和交易数据库是放在不同的数据库实例中的。当用户发起退款时,会同时操作用户的订单数据库和交易数据库,退款操作会在交易数据库中进行。在数据库中将订单状态更改为已退款。由于数据分布在不同的数据库实例中,因此需要通过不同的数据库连接会话来操作数据库中的数据。这时候分布式事务就产生了。多服务单数据库多个微服务访问同一个数据库。比如订单微服务和库存微服务访问同一个数据库也会产生分布式事务。原因是当多个微服务访问同一个数据库时,本质上是通过不同的数据库会话来操作数据库。正式事务。注:跨库实例场景和多服务单库场景本质上是因为会产生不同的数据库会话来操作数据库中的数据,从而产生分布式事务。这两种情况相对容易被忽略。分布式事务的解决方案了解了分布式事务发生的场景之后,接下来我们来说说分布式事务的具体解决方案。2PC方案2PC是两阶段提交协议,将整个事务过程分为两个阶段,准备阶段和提交阶段。2指的是两个阶段,P指的是准备阶段,C指的是commit阶段。提交阶段。这里,我们以MySQL数据库为例。MySQL数据库支持两阶段提交协议,分为成功和失败两种情况。成功与失败的具体过程如下:Prepare阶段:事务管理器向每个参与者发送Prepare消息,每个数据库参与者在本地执行事务并写入本地Undo/Redo日志。此时交易不提交。(Undolog是记录修改前的数据,用于数据库回滚,Redolog是记录修改后的数据,用于事务提交后写入数据文件)Commitphase(提交阶段):如果事务管理器收到参与者执行失败或超时的消息,则直接向每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据事务管理器的指令执行提交或回滚操作,并释放事务过程使用的锁资源。使用2PC方案时需要注意的是锁资源必须在final阶段释放。可靠消息最终一致性方案可靠消息最终一致性方案是指当事务发起者完成本地事务并发送消息时,事务参与者(消息消费者)必须能够成功接收到消息并处理事务。该方案强调,只要向交易参与者发送消息,最终的交易就必须是一致的。事务发起者(消息生产者)向消息中间件发送消息,事务参与者从消息中间件接收消息,事务发起者和消息中间件之间,事务参与者(消息消费者)和消息中间件之间都是通过网络进行通信,网络通信的不确定性会导致分布式事务问题。因此,我们会在具体方案中引入消息确认服务和消息恢复服务。使用可靠消息最终一致性方案需要注意几个问题:本地事务和消息发送的原子性。交易参与者接收消息的可靠性问题。消息重复消费的问题(需要做到幂等)。TCC方案TCC分为三个阶段:Try阶段是业务检查(一致性)和资源预留(隔离)。该阶段只是初步操作,与后续的Confirm才能真正构成完整的业务逻辑。Confirm阶段是对提交进行确认,Try阶段的所有分支事务执行成功后才会执行Confirm。通常,在使用TCC时,认为Confirm阶段不会出现错误。即:只要Try成功,Confirm就一定成功。如果Confirm阶段出现错误,则需要重试机制或人工处理。Cancel阶段是在需要回滚业务执行错误的状态下执行分支事务的业务取消,释放预留资源。通常,如果采用TCC,则认为Cancel阶段必须成功。如果真的在Cancel阶段出了问题,需要重试机制或者人工处理。使用TCC分布式方案时,需要注意空回滚、幂等、挂起等问题。Best-effortnotificationscheme该方案主要用于保证多个不同系统之前数据的最终一致性,如下图所示。使用尽力而为通知方案需要注意幂等性和数据回溯操作。好了,今天就到这里。后面我们会详细介绍各个分布式事务的解决方案。下次见!!本文转载自微信♂「冰河技术」,可通过以下二维码关注。转载本文请联系冰川科技公众号。