2.2. CAP原理

简介: 本节学习分布式事务控制方案选型,基于CAP原理理解一致性与可用性的权衡。结合Seata框架,掌握AT、XA、TCC等模式原理与应用,实现微服务间事务一致性。

遇到了分布式事务的场景我们该如何去进行事务控制呢,本节学习如何选型分布式事务的控制方案。
什么是CAP原理
首先需要理解什么是CAP原理,明白了CAP原理有助于我们去选型分布式事务的控制方案。
CAP是 Consistency、Availability、Partition tolerance三个词语的缩写,分别表示一致性、可用性、分区容忍性。使用下图去理解CAP:下图表示客户端经过网关访问订单服务,库存服务

一致性: 向系统写一个新数据再次读取到的也一定是这个新数据。拿上图举例,请求订单服务下单,订单服务请求库存服务扣减库存,只要下单成功则库存扣减成功。
可用性:任何时间都可以访问订单服务和库存服务,系统保证可用。
分区容忍性:也叫分区容错性,分布式系统在部署时服务器可能部署在不同的网络分区,比如上图中订单服务在北京,库存服务在上海,由于处于不同的网络分区如果网络通信异常就会导致节点 之间无法通信,当出现由于网络问题导致节点 之间无法通信,此时仍然是对外提供服务的这叫做满足分区容忍性。
CAP理论要强调在分布式系统中C、A、P这三点不能全部满足。
由于是分布式系统就要满足分区容忍性,因为分布式系统难免存在网络分区,不能因为网络异常导致整个系统不可用,所以P是一定要满足的。满足P,那么C和A不能同时满足。拿上图举例说明:
当订单服务与库存服务出现网络通信异常,订单服务无法访问库存服务,此时如果要保证数据一致性则下单接口必须不可用,如果要保证可用性数据将出现不一致。

学习了CAP理论我们知道进行分布式事务控制要在C和A中作出取舍,进行分布式事务控制要么保证CP要么保证AP。具体要根据应用场景进行判断,下边举例说明CP和AP业务场景的例子。
符合CP的场景:满足C舍弃A,强调一致性。
金融系统:一般需要在多个账户之间进行交易或资金转移的操作通常需要满足CP,这是因为在这种场景下,数据的一致性是至关重要的,确保不会发生资金丢失、重复扣款或其他意外情况,源账号和目标账号的转账结果要么都成功要么都失败,不会存在一个成功一个失败的情况。
库存系统:在多个仓库之间进行库存转移或销售操作时,需要确保库存的一致性,防止商品超卖或库存混乱。
订票系统:需要确保预订信息的一致性,避免出现同一个资源被多次预订的问题。
Zookeeper:可作为注册中心,支持CP,拿主节点选举举例,当主节点异常进行选举,选举期间所有节点不可用,保证数据的一致性。
Redis:Redis主从模式是CP模式,当主从通信异常此时无法向主节点写数据。
Nacos:Nacos也支持CP,不过默认是AP模式,当客户端注册为非临时节点时为CP模式,注册为非临时节点就需要实时上报心跳,即使在一段时间内未收到心跳信息,该实例仍然会保留在服务列表中,适用于配置中心。
符合AP的场景:满足A舍弃C,强调可用性。
AP强调的是可用性,允许短暂的不一致但是要保证最终一致性,在实际应用中符合AP的场景较多。
订单退款:退款后状态为退款中,24小时后退款金额到帐。
积分系统:注册送积分,注册成功积分在24小时后到账。
跨行转账:一般转账支持CP,还有的支持AP,源账号扣减金额后需要等一段时间目标账户才到账,或者源账号扣款后由于目标账号有问题过一段时间将转账金额退回到源账户。
MySQL主从复制:支持AP,向主节点写数据,异步同步到的从节点。
Nacos:默认支持AP,即临时节点的情况,会实时上报心跳,如果一段时间内未收到心跳信息,Nacos 会将该实例标记为不可用并从服务列表中移除。
在生产中AP场景应用的更多,强调的是可用性,允许出现短暂不一致,最终达到数据一致性。
2.4. 安装Seata
2.4.1 认识Seata
解决分布式事务的方案有很多,但实现起来都比较复杂,因此我们一般会使用开源的框架来解决分布式事务问题。在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在2019年开源的 Seata 了。链接
其实分布式事务产生的一个重要原因,就是参与事务的多个分支事务互相无感知,不知道彼此的执行状态。因此解决分布式事务的思想非常简单:
就是找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证全局事务下的每一个分支事务同时成功或失败即可。大多数的分布式事务框架都是基于这个理论来实现的。
Seata也不例外,在Seata的事务管理中有三个重要的角色:
● TC (Transaction Coordinator) -事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚,相当于监控中心。
● TM (Transaction Manager) -事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
● RM (Resource Manager) -资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
Seata的工作架构如图所示:

其中,TM和RM可以理解为Seata的客户端部分,引入到参与事务的微服务依赖中即可。将来TM和RM就会协助微服务,实现本地分支事务与TC之间交互,实现事务的提交或回滚。
而TC服务则是事务协调中心,是一个独立的微服务,需要单独部署。
Seata支持四种不同的分布式事务解决方案:
● XA:强一致性,唯一一个强一致,无业务侵入,注解即可
● AT:最终一致性,无业务侵入,注解即可,是默认的模式
● TCC:最终一致性,有业务侵入,适合复杂定制化业务
● SAGA:最终一致性,有业务侵入,适合长事务模式,较少使用
2.4.2 准备数据库表
Seata支持多种存储模式,但考虑到持久化的需要,我们一般选择基于数据库存储。执行课前资料提供的
《seata-tc.sql》,导入数据库表【默认已实现】:

2.4.3 准备配置文件
课前资料准备了一个seata目录,其中包含了seata运行时所需要的配置文件:

注意:需要修改自己虚拟机的ip地址和MySQL数据库的账户和密码。
我们将整个seata文件夹拷贝到虚拟机的/root目录【已实现】:

2.4.4 Docker部署
以下Docker部署均已实现,大家了解安装、运行步骤即可
如果镜像下载困难,也可以把课前资料提供的镜像上传到虚拟机并加载:

然后,将课前资料中的seata-1.5.2.tar上传至虚拟机的/root目录。
首先导入镜像文件:
docker load -i seata-1.5.2.tar
进入虚拟机的/root目录执行下面的命令:
注意修改虚拟机的ip地址
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.101.68 \
-v /root/seata:/seata-server/resources \
-d \
seataio/seata-server:1.5.2
查询容器

Seata控制台:http://192.168.101.68:7099/,账号/密码:admin/admin

2.5. 微服务集成Seata
接下来我们实现下单扣减库存的需求,参与分布式事务的每一个微服务都需要集成Seata,下单扣减库存涉及两个微服务,即交易微服务、商品微服务,下边我们以交易微服务为例在trade-service中集成Seata。
2.5.1.引入依赖
分别在交易服务、商品服务引入seata依赖【引入标准:每一个参与调用链路的微服务都需要】。


com.alibaba.cloud
spring-cloud-starter-alibaba-seata

2.5.2.Seata配置
分别在交易服务、商品服务引入seata配置。
内容如下:
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
type: nacos # 注册中心类型 nacos
nacos:
server-addr: 192.168.101.68:8848 # nacos地址
namespace: "" # namespace,默认为空
group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
application: seata-server # seata服务名称
tx-service-group: hmall # 事务组名称
service:
vgroup-mapping: # 事务组与tc集群的映射关系
hmall: "default"
tx-service-group: 事务组是seata进行全局事务管理的逻辑单元,通常按项目为单位进行定义
vgroup-mapping:事务组与tc集群的映射,配置事务组名称与seata TC集群的映射关系
"defualt":seata集群名称,seata服务实例上传至nacos,在nacos中查看seata集群如下:

2.5.3.seata数据库表
seata的客户端在解决分布式事务的时候需要记录一些中间数据,保存在数据库中。因此我们要先准备一个这样的表。将课前资料的seata-at.sql分别文件导入hm-trade、hm-item两个数据库中【已实现】:

OK,至此为止,微服务整合的工作就完成了。

2.5.4.实现分布式事务
前边在没有使用Seata是无法控制分布式事务的,接下来我们用seata控制分布式事务。
我们找到trade-service模块下的com.hmall.trade.service.impl.OrderServiceImpl类中的createOrder方法,也就是下单业务方法。
将其上的@Transactional注解改为Seata提供的@GlobalTransactional:

@GlobalTransactional注解就是在标记事务的起点,将来TM就会基于这个方法判断全局事务范围,初始化全局事务。
我们重启trade-service、item-service服务,再次测试分布式事务:
1.仍然在下单方法最后添加异常,扣减库存成功下单失败观察最终扣减库存是否会回滚。
2.下单正常但扣减库存失败观察最终下单数据是否回滚。
那么,Seata是如何解决分布式事务的呢?
2.6. Seata 工作模式
Seata支持四种不同的分布式事务解决方案,Seata默认使用的是AT模式。
● XA:强一致性,唯一一个强一致,无业务侵入,注解即可
● AT:最终一致性,无业务侵入,注解即可,是默认的模式
● TCC:最终一致性,有业务侵入,适合复杂定制化业务
● SAGA:最终一致性,有业务侵入,适合长事务模式,较少使用
这里我们以XA模式和AT模式来给大家讲解其实现原理。
2.6.1 AT模式
AT模式的基本流程图:

阶段一RM的工作:
● 注册分支事务
● 记录undo-log(数据快照)
● 执行业务sql并提交
● 报告事务状态
阶段二提交时RM的工作:
● 删除undo-log即可
阶段二回滚时RM的工作:
● 根据undo-log恢复数据到更新前
下边我们用一个真实的业务来梳理下AT模式的原理。
比如,现在有一个数据库表,记录用户余额:
id money
1 100
其中一个分支业务要执行的SQL为:
update tb_account set money = money - 10 where id = 1
AT模式下,当前分支事务执行流程如下:
一阶段:

  1. TM发起并注册全局事务到TC
  2. TM调用分支事务
  3. 分支事务准备执行业务SQL
  4. RM拦截业务SQL,根据where条件查询原始数据,形成快照。
    {
    "id": 1, "money": 100
    }
  5. RM执行业务SQL,提交本地事务,释放数据库锁。此时 money = 90
  6. RM报告本地事务状态给TC
    二阶段:
  7. TM通知TC事务结束
  8. TC检查分支事务状态
    a. 如果都成功,则立即删除快照
    b. 如果有分支事务失败,需要回滚。读取快照数据({"id": 1, "money": 100}),使用快照恢复数据库,此时数据库再次恢复为100。
    流程图:

演示AT模式
下边通过断点调试演示AT模式的整体流程:
首先在提交订单方法中模拟异常并打断点

当代码执行到断点处每个分支事务已经执行完成,
通过观察seata控制台,已经开启一个全局事务

分支事务如下

上图中其中一个分支事务27595734695333932即商品服务已经成功扣减库存,我们可以观察hm-item数据库的item表的stock字段值
当抛出异常后全局事务回滚,每个分支事务全部回滚
再次查询hm-item数据库的item表,发现stock库存值已恢复。
2.6.2 XA模式
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。
XA是规范,目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交。
正常情况:

异常情况:

一阶段:
● 事务协调者通知每个事务参与者执行本地事务
● 本地事务执行完成后报告事务执行状态给事务协调者,此时事务不提交,继续持有数据库锁
二阶段:
● 事务协调者基于一阶段的报告来判断下一步操作
● 如果一阶段都成功,则通知所有事务参与者,提交事务
● 如果一阶段任意一个参与者失败,则通知所有事务参与者回滚事务
Seata对原始的XA模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图:

RM一阶段的工作:

  1. 注册分支事务到TC
  2. 执行分支业务sql但不提交
  3. 报告执行状态到TC
    TC二阶段的工作:
  4. TC检测各分支事务执行状态
    a. 如果都成功,通知所有RM提交事务
    b. 如果有失败,通知所有RM回滚事务
    RM二阶段的工作:
    ● 接收TC指令,提交或回滚事务
    XA模式的优点是什么?
    ● 事务的强一致性,满足ACID原则
    ● 常用数据库都支持,实现简单,并且没有代码侵入
    XA模式的缺点是什么?
    ● 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
    ● 依赖关系型数据库实现事务
    简述AT模式与XA模式最大的区别是什么?
    ● XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
    ● XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
    ● XA模式强一致;AT模式最终一致
    可见,AT模式使用起来更加简单,无业务侵入,性能更好。因此企业90%的分布式事务都可以用AT模式来解决。
    2.6.3 TCC模式
    TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:
    ● Try:资源的检测和预留;
    ● Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
    ● Cancel:预留资源释放,可以理解为try的反向操作。
    举例,一个扣减用户余额的业务。假设账户A原来余额是100,需要余额扣减30元。
    ● 阶段一( Try ):检查余额是否充足,如果充足则冻结金额增加30元,可用余额扣除30
    初识余额:

余额充足,可以冻结:

此时,总金额 = 冻结金额 + 可用金额,数量依然是100不变。事务直接提交无需等待其它事务。
● 阶段二(Confirm):假如要提交(Confirm),则冻结金额扣减30
确认可以提交,不过之前可用金额已经扣减过了,这里只要清除冻结金额就好了:

此时,总金额 = 冻结金额 + 可用金额 = 0 + 70 = 70元
● 阶段二(Canncel):如果要回滚(Cancel)则冻结金额扣减30,可用余额增加30
需要回滚,那么就要释放冻结金额,恢复可用金额:

Seata中的TCC模型依然延续之前的事务架构,如图:

TCC模式的每个阶段是做什么的?
● Try:资源检查和预留
● Confirm:业务执行和提交
● Cancel:预留资源的释放
TCC的优点是什么?
● 一阶段完成直接提交事务,释放数据库资源,性能好
● 相比AT模型,无需生成快照,无需使用全局锁,性能最强
● 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
TCC的缺点是什么?
● 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
● 软状态,事务是最终一致
● 需要考虑Confirm和Cancel的失败情况,做好幂等处理
2.6.4 面试题
seata是怎么进行分布式事务控制的?/Seata的工作原理?
作业
对下单方法分布式事务控制
实现完整的分布式事务控制并进行测试,流程如下:

提示:
根据上图分析可知下单方法的分布式事务控制范围包括:下单、清理购物车、扣减库存,分别隶属交易服务、购物车服务、商品服务。
要想使用seata控制下单方法的分布式事务,需要将相关微服务集成 seata。
实现完成参考老师课堂上的方法进行分布式事务控制的测试。
对支付方法分布式事务控制
除下单业务外,用户如果选择余额支付,前端会将请求发送到pay-service模块。而这个模块要做三件事情:
● 直接从user-service模块调用接口,扣除余额付款
● 更新本地(pay-service)交易流水表状态
● 通知交易服务(trade-service)更新其中的业务订单状态
流程如图:

显然,这里也存在分布式事务问题。
前端会提交支付请求,业务接口的入口在com.hmall.pay.controller.PayController类的tryPayOrderByBalance方法:

对应的service方法如下:
@Override
@Transactional
public void tryPayOrderByBalance(PayOrderDTO payOrderDTO) {
// 1.查询支付单
PayOrder po = getById(payOrderDTO.getId());
// 2.判断状态
if(!PayStatus.WAIT_BUYER_PAY.equalsValue(po.getStatus())){
// 订单不是未支付,状态异常
throw new BizIllegalException("交易已支付或关闭!");
}
// 3.尝试扣减余额
userClient.deductMoney(payOrderDTO.getPw(), po.getAmount());
// 4.修改支付单状态
boolean success = markPayOrderSuccess(payOrderDTO.getId(), LocalDateTime.now());
if (!success) {
throw new BizIllegalException("交易已支付或关闭!");
}
// 5.修改订单状态
tradeClient.markOrderPaySuccess(po.getBizOrderNo());
}
将上边的方法改为使用seata控制分布式事务并进行测试。
测试方法:
首先通过交易服务提交订单,拿到业务订单号
然后通过支付服务创建支付单,如下:

再通过支付服务完成余额支付,如下:

测试完成注意观察相关数据库表的数据变化是否符合预期。

相关文章
|
Java 关系型数据库 中间件
分库分表(3)——ShardingJDBC实践
分库分表(3)——ShardingJDBC实践
1303 0
分库分表(3)——ShardingJDBC实践
|
2月前
|
消息中间件 Java 数据安全/隐私保护
RabbitMQ集群部署
本文介绍RabbitMQ集群部署及高可用方案,涵盖普通集群搭建、镜像模式配置与仲裁队列使用。通过Docker部署三节点集群,配置Erlang Cookie与rabbitmq.conf实现节点通信;利用镜像模式实现数据冗余,支持主从切换;引入3.8版本后的仲裁队列,简化高可用配置,提升系统容错能力。
|
4月前
|
存储 运维 Kubernetes
《聊聊分布式》从Paxos到Raft:分布式共识算法的演进与突破
共识算法是分布式系统的“大脑”,确保多节点协同工作。Paxos理论严谨但工程复杂,而Raft以可理解性为核心,通过清晰的角色划分和流程设计,显著降低实现与运维难度,成为etcd、Consul等主流系统的基石,体现了从理论到工程实践的成功演进。
|
消息中间件 数据库 RocketMQ
分布式事务常见解决方案
分布式事务常见解决方案
2827 0
|
6月前
|
SQL 关系型数据库 分布式数据库
一条SQL管理向量全生命周期,让AI应用开发更简单
本文探讨了AI应用开发中向量数据管理的挑战,介绍了PolarDB IMCI通过在数据库内核中集成向量索引与Embedding能力,实现向量全生命周期管理的创新方案。该方案有效解决了技术栈分裂、数据孤岛和运维复杂等痛点,提供了一体化、高性能、支持事务与实时检索的向量数据库服务,极大降低了AI应用的开发与维护门槛。
350 26
一条SQL管理向量全生命周期,让AI应用开发更简单
|
人工智能 数据可视化 数据挖掘
Quick BI 体验&征文有奖!
瓴羊生态推出Quick BI 征文激励计划,鼓励用户分享数据分析实践经验与技术洞察,征集高质量原创文章。内容围绕AI功能体验与BI案例实践,设季奖、年奖及参与奖,优秀作者可获现金奖励、产品内测资格及官方认证形象。投稿截止至2026年1月11日。
Quick BI 体验&征文有奖!
|
8月前
|
Java 数据安全/隐私保护 计算机视觉
银行转账虚拟生成器app,银行卡转账截图制作软件,java实现截图生成工具【仅供装逼娱乐用途】
本内容提供Java生成自定义图片的示例代码,涵盖基础图像创建、文本添加及保存功能,适合学习2D图形编程。包括教学示例图片生成、文本图层处理和数字水印技术实现方案。
|
开发框架 网络协议 .NET
C#/.NET/.NET Core优秀项目和框架2024年10月简报
C#/.NET/.NET Core优秀项目和框架2024年10月简报
395 3
|
负载均衡 应用服务中间件 nginx
基于Nginx和Consul构建自动发现的Docker服务架构——非常之详细
通过使用Nginx和Consul构建自动发现的Docker服务架构,可以显著提高服务的可用性、扩展性和管理效率。Consul实现了服务的自动注册与发现,而Nginx则通过动态配置实现了高效的反向代理与负载均衡。这种架构非常适合需要高可用性和弹性扩展的分布式系统。
382 3
|
SQL 关系型数据库 MySQL
postgresql|数据库|MySQL数据库向postgresql数据库迁移的工具pgloader的部署和初步使用
postgresql|数据库|MySQL数据库向postgresql数据库迁移的工具pgloader的部署和初步使用
1671 0