分布式事务理解

事务是一组连续的操作,这一组操作要么都成功执行,要么都不能执行;

那么什么是数据库事务呢?

数据库事务一般具有以下几个特征:

原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失败的情况

一致性,在事务执行前后,数据库的一致性约束没有被破坏。

隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事务不能看到其他事务运行过程的中间状态。

持久性,事务完成之后,该事务对数据的更改会被持久化到数据库,且不会被回滚。

在事务执行过程中,如果出现故障,比如断电、宕机,这个时候就要利用日志(redo log或者undo log) 加上 checkpoint来保证事务的完整结束。

因此在单体架构中,我们的事务可以通过数据库的ACID来操作,不会出现什么问题。

问题:

随着规模扩大,数据量越来越大,数据库就有可能产生瓶颈,这个时候我们就要对逻辑服务进行拆分,部署在多台服务器,数据库一般也是多台,进行了分库分表等操作,这个时候单个数据库的ACID已经不能适应这种情况了。

因此对于这种分布式事务问题,就有了一种解决办法,CAP理论。

扫描二维码关注公众号,回复: 6054265 查看本文章

分布式事务产生的原因

什么是分布式事务:

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

分布式事务的基础:CAP理论:

CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的(管他是谁,我也不知道,反正很牛逼的一个人)他指出WEB服务无法同时满足一下3个属性:

一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)

可用性(Availability) : 每个操作都必须以可预期的响应结束

分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成

具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。

实际开发中,我们都会兼顾一致性和可用性,

比如12306的购票,其实就是兼顾了一致性和可用性。

在购票页面(已经没有票了,但还在排队还没有执行完成),

我们进去之后,显示了99张余票,点击购买,

其实此时已经没有票了,排队也轮不到咱,但是此时库存系统还没有更新,因此我们依旧可以进去买票。

但是点击购买之后,他会提示一个正在排队的队列,把我们加入到该队列中,

这样就保证了可用性,在排队过程中有一系列的队列(例如用redis的缓存队列),

在最终更新余票和提交订单的时候,会校验,这样也就保存了一致性。

因此一般都会两者兼顾。

可能12306用的是oracle,而且它是单体架构,堆服务器就行,所以他可能用oracle中的某些中间件来实现。

这里只是举一个列子。

 

cap理论详细说明:

C (一致性):对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。

A (可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的。其实就是就算微服务挂了,也要保证用户无感知。而不是浏览器报错返回400,500,页面丢失,,,等一些用户体验不好的情况发生。

P (分区容错性):当出现网络分区后,系统能够继续工作。打个比方,这里个集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。也就是当各系统模块间通信出现问题时,设计一个策略,使系统仍可对外提供满足一致性或可用性。

熟悉CAP的人都知道,三者不能共有,如果感兴趣可以搜索CAP的证明,在分布式系统中,网络无法100%可靠,分区其实是一个必然现象(服务化肯定是要划分多个服务的),如果我们选择了CA而放弃了P,那么当发生分区现象时,为了保证一致性,这个时候必须拒绝请求,但是A又不允许,所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构。

对于CP来说,放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致。

对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展。

顺便一提,CAP理论中是忽略网络延迟,也就是当事务提交时,从节点A复制到节点B,但是在现实中这个是明显不可能的,所以总会有一定的时间是不一致。同时CAP中选择两个,比如你选择了CP,并不是叫你放弃A。因为P出现的概率实在是太小了,大部分的时间你仍然需要保证CA。就算分区出现了你也要为后来的A做准备,比如通过一些日志的手段,是其他机器回复至可用。

BASE

BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。是对CAP中AP的一个扩展

1:基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。

2:软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。

3:最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。

BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和 ACID 是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。

是否真的要分布式事务

在说方案之前,首先你一定要明确你是否真的需要分布式事务?

上面说过出现分布式事务的两个原因,其中有个原因是因为微服务过多。我见过太多团队一个人维护几个微服务,太多团队过度设计,搞得所有人疲劳不堪,而微服务过多就会引出分布式事务,这个时候我不会建议你去采用下面任何一种方案,而是请把需要事务的微服务聚合成一个单机服务,使用数据库的本地事务。因为不论任何一种方案都会增加你系统的复杂度,这样的成本实在是太高了,千万不要因为追求某些设计,而引入不必要的成本和复杂度。

如果你确定需要引入分布式事务可以看看下面几种常见的方案。

常见的分布式事务解决方案:

1:2PC(两阶段提交)(2 Phase Commitment Protocol

基于XA协议的两阶段提交

两阶段提交这种解决方案属于牺牲了一部分可用性来换取的一致性。

简单理解:

其实类似于交通指挥,有一个纽带,来协调各个节点。(协调者-参与者)

两阶段提交,将事务的提交分为两个阶段,

第一阶段:准备阶段(prepare)

                 协调者问所有的参与者是不是可以提交事物了(请参与者开始投票)

                 所有参与者向协调者投票(YES/NO)

第二阶段:提交(commit)/回滚(rollback)阶段

                 协调者根据参与者的投票结果,做出是否事务可以全局提交的指令。并通知所有的参与者执行该决定。

                 也就是说,如果第一阶段所有的参与者提交的是YES,那么协调者就通知所有参与者提交事务。

                 只要有一个参与者投票选择放弃(NO)事务,则事务必须被放弃。

优点: 尽量保证了数据的强一致。

缺点: 整个事务的执行需要由协调者在多个节点之间去协调,增加了事务的执行时间,性能低下,不适合高并发高性能场景。

2:TCC事务补偿机制

TCC是Try、Commit、Cancel的缩写

2pc是数据库层面控制的,tcc和2pc类似,是基于2pc实现的业务层事务控制方案。

以在线下单为例,

Try阶段会去扣库存,

Confirm阶段则是去更新订单状态,

如果更新订单失败,则进入Cancel阶段,会去恢复库存。

总之,TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,复杂度也不一样,因此,这种模式并不能很好地被复用。

具体的实现:

1:Try 检查及预留业务资源
                  完成提交事务前的检查,并预留好资源。

2:Confirm 确定执行业务操作
                  对try阶段预留的资源正式执行。

3:Cancel 取消执行业务操作
                 对try阶段预留的资源释放。

优点:最终保证数据的一致性,在业务层实现事务控制,灵活性好。XA两阶段提交资源层面的,而TCC实际上把资源层面二阶段提交上提到了业务层面来实现。有效了的避免了XA两阶段提交占用资源锁时间过长导致的性能地下问题。

缺点:开发成本高,每个事务操作每个参与者都需要实现try/confirm/cancel三个接口。

3:基于异步消息的事物机制

3.1本地消息表(使用最多的)

核心是将需要分布式处理的任务通过消息日志的方式来异步执行。

消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。 人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。 

一个很经典的图:画出了大致的流程

将分布式事务拆分成本地事务进行处理。

分布式事务就是跨多个系统的事务,可以拆分为多个子系统的本地事务;

分布式事务=A系统本地事务 + B系统本地事务 + 消息通知(MQ);

准备阶段:

消息生产方:准备一个消息表,并记录消息发送状态。

注意:消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。

           然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。

消息消费方:也要准备一个消息表,并记录消息状态。

需要处理这个消息,并完成自己的业务逻辑。

此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。

如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。

生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

为什么我们需要本地消息表呢(这个表增加不少的工作,而且是非业务的工作, 有些难以接受, 是否可以把这个工作作出通用的方法呢?)? 因为,我们可以保证消息发送出去,但是不是说消息发送出去就完了,因为消息可能被mq弄丢了啊等等。如果消息能够确保被mq 接收而且 永久保存,那么我们其实是不需要本地消息表的,本地消息表的作用,无非就是 永久化 消息。

优点:开发简单,mq性能较高

缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

业务耦合,因为频繁轮询数据库,增大了数据库负载,此时数据库的性能瓶颈尤为明显,不适合大高并发场景,中等的规模还是可以满足的

3.2:MQ 事务消息

有一些第三方的MQ是支持事务消息的,

比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

分布式事务本身是一个技术难题,是没有一种完美的方案应对所有场景的,具体还是要根据业务场景去选择。

https://www.cnblogs.com/bigben0123/p/9453830.html

猜你喜欢

转载自blog.csdn.net/u010953880/article/details/88050295