zl程序教程

您现在的位置是:首页 >  后端

当前栏目

分布式相关知识(分布式锁,事务实现方案,CAP)

事务分布式分布式 实现 方案 相关 知识 CAP
2023-09-27 14:22:12 时间

一  CAP理论

1.1 CAP理论

CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)这3个基本需求,最多只能同时满足其中的2个。

选项描述
Consistency(一致性)指数据在多个副本之间能够保持一致的特性(严格的一致性)
Availability(可用性)指系统提供的服务必须一直处于可用的状态,每次请求都能获取到非错的响应(不保证获取的数据为最新数据)
Partition tolerance(分区容错性)分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障

1.2 CAP案例场景

假如现在有这样的场景:

  • 用户访问了N1,修改了D1的数据。

  • 用户再次访问,请求落在了N2。此时D1和D2的数据不一致。

接下来:

  • 保证一致性:此时D1和D2数据不一致,要保证一致性就不能返回不一致的数据,可用性无法保证。

  • 保证可用性:立即响应,可用性得到了保证,但是此时响应的数据和D1不一致,一致性无法保证。

所以,可以看出,分区容错的前提下,一致性可用性是矛盾的。

 1.3 对应的模型应用

  • ZooKeeper 保证的是 CP

  • Eureka 保证的则是 AP

  • Nacos 不仅支持 CP 也支持 AP

二  分布式锁

2.1 分布式锁的实现方式

常见的分布式锁实现方案有三种:MySQL分布式锁ZooKepper分布式锁Redis分布式锁

2.1.1 MySQL分布式锁

数据库悲观锁实现的分布式锁可以使用 select ... for update 来实现分布式锁。

用数据库实现分布式锁比较简单,就是创建一张锁表,数据库对字段作唯一性约束加锁的时候,在锁表中增加一条记录即可;释放锁的时候删除记录就行。

如果有并发请求同时提交到数据库,数据库会保证只有一个请求能够得到锁。

这种属于数据库 IO 操作,效率不高,而且频繁操作会增大数据库的开销,因此这种方式在高并发、高性能的场景中用的不多

2.1.2 基于zk实现分布式锁

ZooKeeper的数据节点和文件目录类似,例如有一个lock节点,在此节点下建立子节点是可以保证先后顺序的,即便是两个进程同时申请新建节点,也会按照先后顺序建立两个节点。

 

 2.1.3 基于redis实现分布式锁

基于 Redis 分布式锁一般有以下这几种实现方式:setnx+expire。

加锁了之后如果机器宕机,那我这个锁就无法释放,所以需要加入过期时间,而且过期时间需要和setNx同一个原子操作,在Redis2.8之前需要用lua脚本,但是redis2.8之后redis支持nx和ex操作是同一原子操作。

 三  分布式事务

3.1 分布式事务

1.首先满足事务特性:ACID

 2.而在分布式环境下,会涉及到多个数据库

 分布式事务处理的关键是:

1.需要记录事务在任何节点所做的所有动作;

2.事务进行的所有操作要么全部提交,要么全部回滚。

目的是为了保证分布式系统中的数据一致性。

2.2 分布式事务的解决方案

2.2.1 方案1:2PC

1.分为两个阶段:

1.准备阶段:事务管理器要求每个涉及到事务的参与者进行预提交操作,参与者将操作成败通知协调者(即事务管理器)

2.提交阶段:事务协调器要求每个参与者提交数据,根据所有参与者的反馈情况决定各参与者是否要提交操作还是回滚操作。

 2.优点以及缺点

优点: 尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于MySQL是从5.5开始支持。

缺点:

1.单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。

2.同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。

3.数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能,比如在第二阶段中,假设协调者发出了事务commit的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

2.2.2 方案2:3PC

3pc分为3个阶段:CanCommitPreCommitDoCommit三个阶段。

1.CanCommit:准备阶段。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

2.PreCommit:预提交阶段。协调者根据参与者在准备阶段的响应判断是否执行事务还是中断事务,参与者执行完操作之后返回ACK响应,同时开始等待最终指令。

3.DoCommit:提交阶段。协调者根据参与者在准备阶段的响应判断是否执行事务还是中断事务:

3.1 如果所有参与者都返回正确的ACK响应,则提交事务

3.2 如果参与者有一个或多个参与者收到错误的ACK响应或者超时,则中断事务

3.3 如果参与者无法及时接收到来自协调者的提交或者中断事务请求时,在等待超时之后,会继续进行事务提交

总结:三阶段提交解决的只是两阶段提交中单体故障同步阻塞的问题,因为加入了超时机制,这里的超时的机制作用于 预提交阶段 和 提交阶段。如果等待 预提交请求 超时,参与者直接回到准备阶段之前。提交阶段: 如果等到提交请求超时,那参与者就会提交事务了。

无论是2PC还是3PC都不能保证分布式系统中的数据100%一致

 2.2.3 方案3:TCC

基于TCC实现的分布式事务也可以看做是对业务的一种补偿机制。TCC 是业务层面的分布式事务,保证最终一致性,不会一直持有资源的锁。

 1.Try:尝试待执行的业务。订单系统将当前订单状态设置为支付中,库存系统校验当前剩余库存数量是否大于1,然后将可用库存数量设置为库存剩余数量-1,。

2.Confirm:确认执行业务,如果Try阶段执行成功,接着执行Confirm 阶段,将订单状态修改为支付成功,库存剩余数量修改为可用库存数量。

3.Cancel:取消待执行的业务,如果Try阶段执行失败,执行Cancel 阶段,将订单状态修改为支付失败,可用库存数量修改为库存剩余数量。

3.1优点: 把数据库层的二阶段提交交给应用层来实现,规避了数据库的 2PC 性能低下问题

3.2缺点:TCC 的 Try、Confirm 和 Cancel 操作功能需业务提供,开发成本高。TCC 对业务的侵入较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作。

2.2.4 方案4:本地消息表

本地消息表的核心思想是将分布式事务拆分成本地事务进行处理。

思想: 可以在订单库新增一个消息表,将新增订单和新增消息放到一个事务里完成,然后通过轮询的方式去查询消息表,将消息推送到MQ,库存服务去消费MQ。

执行流程:

  1. 订单服务,添加一条订单和一条消息,在一个事务里提交

  2. 订单服务,使用定时任务轮询查询状态为未同步的消息表,发送到MQ,如果发送失败,就重试发送

  3. 库存服务,接收MQ消息,修改库存表,需要保证幂等操作

  4. 如果修改成功,调用rpc接口修改订单系统消息表的状态为已完成或者直接删除这条消息

  5. 如果修改失败,可以不做处理,等待重试。

 本地消息表这种方案实现了最终一致性,需要在业务系统里增加消息表,业务逻辑中多一次插入的DB操作,所以性能会有损耗,而且最终一致性的间隔主要有定时任务的间隔时间决定。

2.2.5 方案5:MQ消息事务

消息事务的原理是将两个事务通过消息中间件进行异步解耦。

执行流程:

  1. 发送prepare消息到消息中间件

  2. 发送成功后,执行本地事务

  3. 如果事务执行成功,则commit,消息中间件将消息下发至消费端

  4. 如果事务执行失败,则回滚,消息中间件将这条prepare消息删除

  5. 消费端接收到消息进行消费,如果消费失败,则不断重试

 优点与缺点

消息事务依赖于消息中间件的事务消息,例如我们熟悉的RocketMQ就支持事务消息(半消息),也就是只有收到发送方确定才会正常投递的消息。https://mp.weixin.qq.com/s/qPUnyjXOe-4bwvwJ-PPe7A

这种方案也是实现了最终一致性,对比本地消息表实现方案,不需要再建消息表,对性能的损耗和业务的入侵更小。

2.2.6 方案6:最大努力通知

最大努力通知相比实现会简单一些,适用于一些对最终一致性实时性要求没那么高的业务,比如支付通知,短信通知。

执行流程:

  1. 业务系统调用支付平台支付接口, 并在本地进行记录,支付状态为支付中

  2. 支付平台进行支付操作之后,无论成功还是失败,同步给业务系统一个结果通知

  3. 如果通知一直失败则根据重试规则异步进行重试,达到最大通知次数后,不再通知

  4. 支付平台提供查询订单支付操作结果接口

  5. 业务系统根据一定业务规则去支付平台查询支付结果

 2.2.7 方案7:分布式方案seata

1.seata是一款解决分布式事务的解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

2.seata的几种术语:

一个中心:全局事务id

TC(Transaction Coordinator):事务协调者。管理全局的分支事务的状态,用于全局性事务的提交和回滚。
TM(Transaction Manager):事务管理者。用于开启、提交或回滚事务。
RM(Resource Manager):资源管理器。用于分支事务上的资源管理,向 TC 注册分支事务,上报分支事务的状态,接收 TC 的命令来提交或者回滚分支事务。

3.一个典型的分布式事务过程:

流程: 

1.服务A中的 TM 向 TC 申请开启一个全局事务,TC 就会创建一个全局事务并返回一个唯一的 XID

2.服务A中的 RM 向 TC 注册分支事务,然后将这个分支事务纳入 XID 对应的全局事务管辖中

3.服务A开始执行分支事务

4.服务A开始远程调用B服务,此时 XID 会根据调用链传播

5.服务B中的 RM 也向 TC 注册分支事务,然后将这个分支事务纳入 XID 对应的全局事务管辖中

6.服务B开始执行分支事务

7.全局事务调用处理结束后,TM 会根据有误异常情况,向 TC 发起全局事务的提交或回滚

8.TC协调其管辖之下的所有分支事务,决定是提交还是回滚