分布式服务

分布式事务原理与解决方案实战

2022-08-26  本文已影响0人  一只阿木木

分布式事务:

1 CAP 定理

1.1 概念

CAP 理论在分布式系统中

对于共享数据系统,最多只能同时拥有CAP其中的两个,没法三者兼顾。

image

结论:分布式系统中,最重要的是满足业务需求,而不是追求抽象、绝对的系统特性。

2.2 中间件实例

2 BASE 理论

核心思想

基本可用(BasicallyAvailable)

分布式系统在出现故障时,允许损失部分的可用性来保证核心可用。

软状态(SoftState)

允许分布式系统存在中间状态,该中间状态不会影响到系统的整体可用性。

最终一致性(EventualConsistency)

分布式系统中的所有副本数据经过一定时间后,最终能够达到一致的状态

一致性模型数据的一致性模型可以分成以下 3 类:

分布式系统数据的强一致性、弱一致性和最终一致性可以通过Quorum NRW算法分析。

只要聊到做了分布式系统,必问分布式事务,若你对分布式事务一无所知的话,确实很坑,起码得知道有哪些方案,一般怎么来做,每个方案的优缺点是什么。

现在面试,分布式系统成了标配,而分布式系统带来的分布式事务也成了标配.
你做系统肯定要用事务,那你用事务的话,分布式系统之后肯定要用分布式事务.
先不说你搞过没有,起码你得明白有哪几种方案,每种方案可能有啥坑?比如TCC方案的网络问题、XA方案的一致性问题

分布式事务的几种解决方案

● 异步校对数据的方式
支付宝、微信支付主动查询支付状态、对账单的形式;
● 基于可靠消息(MQ)的解决方案
异步场景;通用性较强;拓展性较高
● TCC编程式解决方案
严选、阿里、蚂蚁金服自己封装的DTX

3 XA方案

即两阶段提交事务方案。
需要数据库厂商支持; JAVA组件有atomikos等。

3.1 程序理解

有一个事务管理器,负责协调多个数据库(资源管理器)的事务

  1. 事务管理器先问各个数据库预提交 ok 吗?
  2. 如果每个数据库都回复ok,即预提交成功,就正式提交事务,在各个数据库开始执行操作,这里失败会有失败异常重试,日志分析,人工重试。

3.3 适用场景

较适合单块应用中,跨多库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,不适合高并发场景。

如果要玩,那么基于Spring + JTA就可以搞定。

互联网公司基本都不用,因为某个系统内部如果出现跨多库的操作,是不合规的!现在的微服务,一个大的系统分成几十甚至上百个服务。一般规约每个服务只能操作自己对应的一个数据库。

如果你要操作别的服务对应的库,不允许直连别的服务的库。
要操作别人的服务的库,必须通过调用别的服务的接口

4 TCC方案

4.1 三阶段

4.2 跨行转账案例

涉及到两个银行的分布式事务,如果用TCC方案来实现,思路是这样的:

该方案说实话几乎很少使用,但也有使用场景.
因为这个事务的回滚实际上严重依赖于你自己写代码来回滚和补偿了,会造成补偿代码巨大,非常恶心!

比如说我们,一般来说和钱相关的支付、交易等相关的场景,我们会用TCC,严格严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性!

4.3 适用场景

除非你是真的一致性要求太高,是系统中核心之核心的场景!
常见的就是资金类的场景,那可以用TCC方案,自己编写大量的业务逻辑,自己判断一个事务中的各个环节是否ok,不ok就执行补偿/回滚代码

而且最好是你的各个业务执行的时间都比较短

但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,业务代码也很难维护

4.4 方案示意图

image

5 本地消息表

ebay搞出来的这么一套思想

5.1 简介

5.2 优点

这个方案保证了最终一致性
哪怕B事务失败了,但是A会不断重发消息,直到B那边成功为止

5.3 缺陷

最大的问题就在于严重依赖于数据库的消息表来管理事务,这个会导致高并发场景无力,难以扩展呢,一般确实很少用

6 可靠消息最终一致性方案

干脆不用本地的消息表了,直接基于MQ来实现事务。比如阿里的RocketMQ就支持消息事务!

6.1 简介

这个还是比较合适的,目前国内互联网公司大都是这么玩的,要不你举用RocketMQ支持的,要不你就自己基于类似ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的

7 最大努力通知方案

7.1 简介

实战方案:

1. 最终一致性

1.1 本地事务表 + 轮询补偿

交互流程

image

场景:重构业务新老系统双写库同步

image

项目背景

这是一个重构系统新老系统同时服役切量迁移的业务场景,老系统正在线上服役为各业务方提供接口查询功能,新系统重构完成后需要对接接入,调用流量要陆续从老系统切换到新系统,最终老系统迭代下线。

分布式事务

需要解决的分布式事务问题就是,双系统的数据是异构、分散的,线上不可停量,需要陆续切换完成,因此需要事先将老库存量数据洗入新库,此过程中增量数据写入是存在的,但是最终新老库是相对一致和统一的,该场景需要解决的是数据双库的双写问题

设计方案

image

场景Q&A

场景:第三方认证核验

image

项目背景

这是一个认证系统以来外部核验系统进行用户身份鉴权的场景,即认证系统记录认证发起记录,并请求到外部的核验系统发起一笔核验请求,用户在核验系统确认后核验结果返回到认证系统确认用户的真实数据状态。

分布式事务

该流程中认证系统是一个本地系统,存放用户发起的认证流水信息和核验状态,依赖外部核验系统返回该笔认证记录的核验状态,由于核验过程是异步的,用户可以选择任意时间完成或者永远不完成,需要保证每次认证流程只有一笔业务发起,而且需要根据业务时间进行核验流程的超时进行强制取消或者补偿查询对齐核验状态等,需要解决的分布式事务是认证流水、核验结果的一致性

设计方案

image image image

场景Q&A

1.2 本地事务表 + 事务消息

交互流程

image

场景:分库分表路由字段绑定

image

项目背景

该业务是在分库分表场景下,需要一个切分键字段进行数据分片路由,一般我们ToC业务的话会使用userId这样的字段进行切分。然而水平切分数据带来了数据库读写性能的同时也存在一个问题,那就是查询必须携带切分键才可以完成,因为要依赖它进行数据路由查询,比如在以userId进行数据路由切分时,如果想按照用户的身份证、姓名等进行匹配查询是无法实现的,因为我们事先是不知道userId、身份证、姓名的等值匹配关系。一般而言,我们可以通过HBase + ES的方案进行解决,即监听不同库的binlog日志,将其按照时间进行排序切分汇入HBase表中,加入要检索的索引到ES中解决分库分表下数据切片产生的聚合问题。

分布式事务

基于以上场景,除了通过HBase+ES实现之外,还可以通过切分键与非切分键进行数据绑定解决,但是由于切分键与非切分键的路由一般不一致,数据会分散到不同库,因此无法做本地事务,这是我们需要解决的分布式事务问题。

设计方案

image image

场景Q&A

1.3 TCC(Try-Commit-Cancel)

交互流程

image

TCC事务其实主要包含两个阶段:Try阶段、Confirm/Cancel阶段。

从TCC的逻辑模型上我们可以看到,TCC的核心思想是,Try阶段检查并预留资源,确保在Confirm阶段有资源可用,这样可以最大程度的确保Confirm阶段能够执行成功。这里的资源可以是DB,或者MQ,RPC,只要是分布式环境中的事务载体就可以,而且需要这些分布式事务的参与者具备正逆向条件,比如DB、MQ的事务可以支持2PC,可以根据协调者对其进行事务提交或者取消操作,RPC比如账户服务可以支持正向增加也可以支持逆向减少,除此之外,DB、MQ要自身支持事务的ACID,这是参与分布式事务的原子性保证,RPC底层也是DB,这里可以等同理解。以上参与者具备参与分布式事务的基本条件后便可以进行整合和使用,业务流程的驱动和事务的完整性由中间协调者来操作和推进。

场景:积分商品兑换

image

项目背景

这是一个电商系统比较经典的下单、扣款、发货流程,在下单之前会通过一系列商品在架状态、库存数量、购买限制等有效性过滤,然后在业务系统中进行一个商品兑换的场景。

分布式事务

由于是商品兑换,对于用户和系统本身来说是这个过程一个原子性的、一键完成的操作,因此下单+动账+发货是一个现实存在的分布式事务。这里假设订单数据和动账数据在一个本地数据库事务中,持久化开启数据库本地事务,该事务中记录订单生成数据和动账数据信息,以及发货状态的信息记录。这里需要解决的分布式事务是订单数据、动账数据、发货状态三种的最终一致

设计方案

image image image

场景Q&A

场景:广告任务结算

image

项目背景

当一个App有了足够多的用户体量,便开始有商家进行广告或商品的投放,平台通过包装运营活动、广告位等,将用户曝光与商家付费结合达到流量变现的目的。

分布式事务

当用户进行浏览、关注、商品购买、视频观看、App下载等多种任务,我们会根据商家配置等付费规则进行商家广告费用的扣减,同时还要为用户完成任务进行奖励结算,此时的分布式事务便是商家账户扣减与用户账户增加的数据一致性问题

设计方案

image image image

场景Q&A

场景:运营活动抽奖

image

项目背景

抽奖是运营活动中比较常见的方式,对于用户来说需要做的是参加设定人物获取积分,攒够积分就可以开始抽奖,抽中后即等待奖品入账,一般券会立刻入账使用,而实物商品则需要耐心等待物流送到用户手上。

分布式事务

关于抽奖,涉及账户动账、库存参与次数扣减、抽奖等多个环节,该场景要解决的分布式事务是账户动账、活动库存变更、抽奖下单数据一致性

设计方案

image image image

场景Q&A

2. 弱一致性

2.1 最大努力通知 + 消息重试控制

场景:数据变更同步下游业务方

image

项目背景

系统数据发生变更时,会对外部系统产生一定影响,外部系统需要知道这种数据变化,这便是数据状态同步的场景。一般来说数据交互可以有推(Push)、拉(Pull) 两种形式,这里先说推模式,即数据变更方负责将变化通知到数据关注方。

分布式事务

这里要保证的是数据变更在多个应用中的状态一致

设计方案

image image

场景Q&A

这里是弱一致性的实现,没有做本地事务表和定时任务轮询对比各事务状态进行补偿操作。完全依赖于MQ的失败重试驱动,若RPC调用失败即数据同步业务方失败,MQ会一直进行重试操作,随着重试次数增加,重试间隔也会增加,这里也可以业务自行进行最大努力尝试次数的控制,超过多少次尝试仍失败则放弃,因此不能保证最终一致

场景:数据变更广播下游业务方

image

项目背景

这里是数据同步的说拉模式,即数据关注方对数据变更方进行数据状态变更的监听,这种处理方式处理的主动权在于数据关注方,数据变更方只负责和保证一定通知到数据变更情况,是否能够同步成功则由监听方处理和兼容。

分布式事务

这里要保证的是数据变更在多个应用中的状态一致

设计方案

image image

场景Q&A

这里也是弱一致性的实现,没有做本地事务表和定时任务轮询对比各事务状态进行补偿操作。完全依赖于MQ消费方的处理,若消费方处理失败或在消息队列规定时间内没有消费完毕,则数据无法保证最终一致

2.2 战略放弃 + 报警 + 人工修复

场景:秒杀库存回滚

image

项目背景

在秒杀场景中,最复杂的除了解决高并发问题外,最核心的就是活动商品的库存控制、变更问题,一般商品库存会初始化到Redis缓存中进行管理,秒杀方法会对Redis缓存库存数量进行校验、扣减操作,通过MQ异步扣减DB库存,既利用Reids原子操作进行库存数量操作,又利用缓存抗住高并发请求,起到异步削峰的作用,这是秒杀的正向流程。而逆向流程是用户秒杀到商品预占了库存,但是没有及时进行订单支付或者进行了订单取消,此时要发起对库存的恢复操作。

分布式事务

这里的分布式事务是Redis缓存库存与DB库存数量一致性问题

设计方案

image image

场景Q&A

这里也是一个弱一致性的实现,业务场景我们保证不超卖即可,对于极端情况出现的库存数量无效多扣减做战略性放弃,一般情况下不会影响大多业务使用,如果非要吹毛求疵达到账户金额那种强一致性,思路也很简单,可以借助定时任务轮询对比缓存与DB库存数量进行校验,这里还要考虑到其他在行流程如超市关单库存恢复,仍然在行的秒杀活动等,保证数据处理不多加不多减。

3. 总结

3.1 分布式角色

3.2 技术保证

3.2 强弱一致选择

3.3 幂等&防重

3.4 尽早干预&补偿一致

指的是代码逻辑上尽早对串行处理的做个子事务进行回滚或逆向操作,这样可以尽快结束分布式事务,而不需要等待相对更为延迟的定时任务或其他补偿机制来驱动,这里可以使用旁路方法或不阻塞主方法放到MQ或异步线程中进行处理,比如秒杀下单发货因为库存不足或商品下架可以立刻进行发起关单退款的逆向流程

补偿机制一般可以通过定时任务、MQ重试来进行子事务驱动整个分布式事务的完结

上一篇 下一篇

猜你喜欢

热点阅读