分布式事务:
在分布式架构中一个业务需要跨对个微服务,需要这多个微服务执行的状态保持一致.
分布式事务理论基础:
CAP定理:
C: 一致性
在微服务架构中,从任何节点读取到的数据应当是一致的.
A: 可用性
在服务不宕机的情况下,必须返回预期的结果
P: 分区容错
在微服务架构中是避免不了的.
BASE理论:
- Basically Available 基本可用
- Soft State 软状态
- Eventually Consistent 最终一致性
方案:
AP: 可用性
软状态: 允许不同的分支事务短时间内状态不一致
但软状态过后需要保证最终一致.
各个分支执行自己的操作,执行完毕后,直接提交事务即可.
如果有分支出现失败的请求,事务协调者会通过各个分支,进行补救
CP: 强一致性
在所有的分支事务执行完毕后,等待协调事务发送最终提交或回滚事务的通知
===========================Seata======================
seata-TC: 事务协调者
连接mysql数据库: 将事务的各种状态信息保存到mysql数据库表中
记录全局事务
记录全局事务包含的分支事务
seata-TM: 事务管理器
开启全局事务
提交全局事务
seata-RM: 资源管理器
----------------------------------
分支事务管理
----------------------------------
Seata-分布式事务模式:
XA模式: CP思想(强一致性)
AT模式: AP思想(最终一致)
软状态: 每个分支事务都可以独立提交
TM--->TC: 开启全局事务
TM--->分支
获取全局事务锁
undo_log快照:
before-image: 在分支事务对数据库中的数据操作前进行前置快照
after-image: 在分支事务对数据库中的数据操作后进行后置快照
如果各个分支都执行成功了---> 删除快照
如果执行失败--->恢复快照(执行事务的补偿过程)
释放全局事务锁
注意事项: 主要使用在关系型数据库中
TCC模式: AP思想
try:
判断cancel是否执行,如果未执行,正常执行即可
校验,冻结
如果执行过:
不再执行任何逻辑,删除冻结数据
confirm: 执行成功
删除冻结数据
cancel: 执行失败
执行补偿过程
判断try是否执行了,如果执行了
恢复冻结数据
删除冻结数据
如果try没有执行:
空回滚: 添加一条冻结数据,并设置状态
SAGA模式: AP思想
1.CPA定理是什么?
1.Consistency(一致性):
用户访问分布式系统中的任意节点,得到的数据必须一致。
2.Availability (可用性):
用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝。
3.Partition(分区):
因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区。
Tolerance(容错):在集群出现分区时,整个系统也要持续对外提供服务
2.Base理论是什么?
BASE理论是对CAP的一种解决思路,包含三个思想:
1.Basically Available(基本可用):
分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
2.Soft State(软状态):
在一定时间内,允许出现中间状态,比如临时的不一致状态。
3.Eventually Consistent(最终一致性):
虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
3.CP和AP指导思想是什么?
CP:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。
但事务等待过程中,处于弱可用状态。
AP:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
4.Seata结构?
三个重要角色:
TC (Transaction Coordinator) 事务协调者:
维护全局和分支事务的状态,协调全局事务提交或回滚。
TM (Transaction Manager) 事务管理器:
定义全局事务的范围、开始全局事务、提交或回滚全局事务。
RM (Resource Manager) 资源管理器:
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
5.Seata搭建过程?
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!--版本较低,1.3.0,因此排除-->
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<!--seata starter 采用1.4.2版本-->
<version>${seata.version}</version>
</dependency>
2)配置TC地址
在order-service中的application.yml中,配置TC服务信息,通过注册中心nacos,结合服务名称获取TC地址:
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
type: nacos # 注册中心类型 nacos
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
namespace: "" # namespace,默认为空
group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
application: seata-tc-server # seata服务名称
username: nacos
password: nacos
tx-service-group: seata-demo # 事务组名称
service:
vgroup-mapping: # 事务组与cluster的映射关系
seata-demo: SH
3)其他服务
其它两个微服务也都参考order-service的步骤来做,完全一样。
6.Seata的四种模式?
1.XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
2.TCC模式:最终一致的分阶段事务模式,有业务侵入
3.AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
4.SAGA模式:长事务模式,有业务侵入
7.四种模式执行原理?
1)XA模式执行原理:
XA是规范,目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交。
一阶段:事务协调者通知每个事物参与者执行本地事务
本地事务执行完成后报告事务执行状态给事务协调者,此时事务不提交,继续持有数据库 锁
二阶段:事务协调者基于一阶段的报告来判断下一步操作
如果一阶段都成功,则通知所有事务参与者,提交事务
如果一阶段任意一个参与者失败,则通知所有事务参与者回滚事务
2)AT模式执行原理:
一阶段:
1、TM发起并注册全局事务到TC
2、TM调用分支事务
3、分支事务准备执行业务SQL
4、RM拦截业务SQL,根据where条件查询原始数据,形成快照。
5、RM执行业务SQL,提交本地事务,释放数据库锁。此时 money = 90
6、RM报告本地事务状态给TC
二阶段:
1、TM通知TC事务结束
2、TC检查分支事务状态:
a)如果都成功,则立即删除快照
b)如果有分支事务失败,需要回滚。读取快照数据(`{"id": 1, "money": 100}`),将快照恢复到数据库。此时数据库再次恢复为100
3)TCC模式执行原理:
TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需 要实现三个方法:
① Try:资源的检测和预留;
② Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
③ Cancel:预留资源释放,可以理解为try的反向操作。
4)SAGA模式执行原理:
在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。
分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
Saga也分为两个阶段:
一阶段:直接提交本地事务
二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚
8.三种模式实现方式?
一、XA模式实现方式:
1)修改application.yml文件(每个参与事务的微服务),开启XA模式:
seata:
data-source-proxy-mode: XA
2)给发起全局事务的入口方法添加@GlobalTransactional注解:
3)重启服务并测试
重启order-service,再次测试,发现无论怎样,三个微服务都能成功回滚。
二、AT模式实现方式:
AT模式中的快照生成、回滚等动作都是由框架自动完成,没有任何代码侵入,因此实现非常简单。只不过,AT模式需要一个表来记录全局锁、另一张表来记录数据快照undo_log。
1)导入数据库表,记录全局锁
2)修改application.yml文件,将事务模式修改为AT模式即可:
seata:
data-source-proxy-mode: AT # 默认就是AT
3)重启服务并测试<br />三、TCC模式实现方式:<br /> 1)思路分析:<br /> Try业务:<br /> 1、记录冻结金额和事务状态到account_freeze表<br /> 2、扣减account表可用金额<br /> Confirm业务:<br /> 1、根据xid删除account_freeze表的冻结记录<br /> Cancel业务:<br /> 1、修改account_freeze表,冻结金额为0,state为2<br /> 2、修改account表,恢复可用金额<br /> 如何判断是否空回滚?<br /> cancel业务中,根据xid查询account_freeze,如果为null则说明try还没做,需要空回滚<br /> 如何避免业务悬挂?<br /> try业务中,根据xid查询account_freeze ,如果已经存在则证明Cancel已经执行,拒绝<br /> 执行try业务 。<br /> 2)声明TCC接口:<br /> TCC的Try、Confirm、Cancel方法都需要在接口中基于注解来声明,<br /> 我们在account-service项目中的`cn.itcast.account.service`包中新建一个接口,声明TCC三个接口:
@LocalTCC // 设置TCC模式
public interface TCCAccountService {
/**
* 从用户账户中扣款
*/
// 此注解使用在try方法上,方法名称可以自定义
// 预留冻结
@TwoPhaseBusinessAction(name = "deduct",commitMethod = "confirm",rollbackMethod = "cancel")
void deduct(
@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money") int money);
// confirm: 提交
// BusinessActionContext: 业务上下文对象
void confirm(BusinessActionContext context);
// cancel: 事务补偿
void cancel(BusinessActionContext context);
}
3)编写实现类
在account-service服务中的cn.itcast.account.service.impl
包下新建一个类,实现TCC业务:
@Service
public class TCCAccountServiceImpl implements TCCAccountService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper;
// try阶段
@Override
@Transactional
public void deduct(String userId, int money) {
// 0.获取全局事务ID
String xid = RootContext.getXID();
// 1.查询account_freeze_tbl表,判断cancel是否执行
AccountFreeze accountFreeze = freezeMapper.selectById(xid);
if (accountFreeze!=null){
// cancel执行过了.不再执行try
if (accountFreeze.getState()==2){
freezeMapper.deleteById(xid);
}
return;
}
// 1.冻结金额 -- 向冻结表中添加一条数据
AccountFreeze freeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freeze.setXid(xid);
freezeMapper.insert(freeze);
// 2.从account表中减去冻结金额
accountMapper.deduct(userId, money);
}
@Override
@Transactional
public void confirm(BusinessActionContext context) {
// 删除冻结数据
freezeMapper.deleteById(context.getXid());
}
@Override
@Transactional
public void cancel(BusinessActionContext context) {
// 查看冻结数据状态,并执行补偿
// 1.通过业务上下文对象获取
String userId = context.getActionContext("userId").toString();
Integer money = (Integer) context.getActionContext("money");
String xid = context.getXid();
// 2.判断try是否执行,如果未执行,则进行空回滚
AccountFreeze accountFreeze = freezeMapper.selectById(xid);
if (accountFreeze==null){
// 添加一条记录,标准cancel执行过
AccountFreeze freeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.CANCEL);
freeze.setXid(xid);
freezeMapper.insert(freeze);
return;
}
// 3.恢复可用金额
accountMapper.refund(userId, money);
// 4.移除冻结金额,修改事务状态
freezeMapper.deleteById(context.getXid());
}
}
4)编写实现类
在account-service服务中的cn.itcast.account.web.AccountController类中修改TCC类的调用:
/**
* @author 黑马帅帅
*/
@RestController
@RequestMapping("account")
public class AccountController {
//@Autowired
//private AccountService accountService;
@Autowired
private TCCAccountService accountService;
@RequestMapping("/{userId}/{money}")
public ResponseEntity<Void> deduct(@PathVariable("userId") String userId, @PathVariable("money") Integer money){
accountService.deduct(userId, money);
return ResponseEntity.noContent().build();
}
}