分布式事务

Posted xue_yun_xiang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式事务相关的知识,希望对你有一定的参考价值。

一、概述

1、事务四大特性

一致性
转账例子 ,钱总金额 不变

原子性
多条sql 语句 要么成功,要么都失败,如果有一条失败,其他成功的回滚

隔离性
在一个事务中(sqlsession)能够看到另外一个事务的数据

隔离级别:读未提交 读已提交 可重复读 序列化读
脏数据

脏读:一个事务可以看到另外一个事务未提交的数据
不可重复读:一个事务中执行查询同一行数据 ,两次结果不一致 针对 修改、更新
幻读:一个事务中执行查询同一个sql(select * from …tb) 多次,查询出来的总行数不一
致 针对增删

持久性:
服务器掉电不丢失

传播行为: 方法A开启事务调用 方法B 时,B是否开启事务
在B 方法上配置
required:必须有事务 如果A开始事务,方法B就是用A的事务
如果A没有开启事务,方法B自己开启事务
supports: 如果A开始事务,方法B就是用A的事务
如果A没有开启事务,方法B也不开启事务
requirs_new:
如果A开始事务,方法B也开启事务,使用新的事务
如果A没有开启事务,方法B自己开启事务

二、 实例:生成订单业务

1、之前的方法

开启事务就可以了
1.创建 订单,标记订单状态未支付
2.扣减库存
3.进行扣款 支付
4.修改订单状态

2、现在的方法:分布式

订单应用分析



发起远程调用库存服务

发起远程调用扣减金额服务

库存应用分析




削减金额




削减库存

三、启动工程

1、启动nacos注册中心

2、分别启动三个应用

启动顺序
1.启动账户服务
2.启动库存服务
3.启动订单服务

3、访问

http://localhost:8180/order/create?userId=1&productId=1&count=1&money=100

4、调试



后续将如果某一父发生异常,我们数据一致性还能否保证 ?

四、分布式事务

概述

分布式事务就是同一个业务处理多条sql,而多条sql发生在在多个应该中(或者多个数据源中),如何保证我们业务的一致性,原子性

CAP理论:C:一致性,A:可用性,P:分区容错性。
一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)
在分布式应用中,我们的业务不可能同时满足三个特性一致性,可用性,分区容错性;cap 理论是分布
式事务的 基础
cap 几乎不能满足组,分布式业务的要求,我就有了妥协

Base理论,BA:基本可用,S:中间状态,E:最终一致性。
CAP 理论的妥协,业务开始可以,没有一致性,先保证可用性,通过一定的时间,使用补偿机制到最后一致性
我们分布式业务无法满足cap三个特性,那我们就满足可用性,分区容错,达到基本可用的目的,通过一些后补的错谁最终完成 最终的一致性
先基本可用,不强调 强一致性,只强调最终的一致性我

分布式事务解决方案【重点】

2pc(两阶段提交)

两段提交分为两个阶段:
第一个阶段是准备阶段,参与者需要开启事务,执行SQL,保证数据库中已经存在相应的数据。参
与者会向TransactionManager(全局事务管理器)准备OK。
1.开启本地事务 执行slq
2.将执行sql 结果告诉 全局事务管理器
第二个阶段当TransactionManager收到了所有的参与者的通知之后,向所有的参与者发送
Commit请求。
3.全局事务管理器如果 返现所有的子业务报告sql 没有异常,则让所有的子业务 提交事务,有异
常,所有的子业务对应的事务都回滚

3pc(三阶段提交)

三阶段提交 其实就是在两阶段提交的基础上多个连个步骤
多了
1.对事务超时的判断 (假如如果有事务一直不执行,则将其他子业务执行的sql 通过undo 日志进行回
滚 )
2.对所有执行的sql 增加了 redo日志(执行的sql 记录下来) undo日志(课通过日志将数据复原)

2pc与3pc 的区别?

1.3pc 多了对事务超时的判断 (假如如果有事务一直不执行,则将其他子业务执行的sql 通过undo 日志进行回滚 )
2.3pc 多了对所有执行的sql 增加了 redo日志(执行的sql 记录下来) undo日志(课通过日志将数据复原)

TCC机制

Try,Confirm,Cancel 三个单词的缩写,他的执行需要和我们的代码耦合在一起

TCC(Try,Confirm,Cancel),和你的业务代码切合在一起。

Try:尝试去预执行具体业务代码。
try成功了:Confirm:再次执行Confirm的代码。
try失败了:Cancel:再次执行Cancel的代码。

Try: 尝试执行业务代码,对资源检查 与执行sql 检查资源是否足够
Confirm:确认 提交各个子模块的事务
Cancel:如果sql 有异常或者 资源不够充沛 则回滚

TCC机制 本质上和 2pc 流程基本上一致?

只不过TCC需要
1.将业务代码嵌套在 try 接口中 需要自己实现
2.需要检测资源是否可用

MQ分布式事务

使用消息队列(RabbitMQ)完成分布式事务
RabbitMQ在发送消息时,confirm机制,可以保证消息发送到MQ服务中,消费者有手动ack机
制,保证消费到MQ中的消息。这种模式全部需要自动手动来完成,非常的复杂,维护性很低

四种解决方法其实都是 2pc的变种,都是两阶段提交

五、seata

概述

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
地址https://seata.io/zh-cn/index.html
Seata 是springcloud alibaba 的一个组件,在使用是必须依赖springcloud alibaba 和注册中心(nacos)

控制器

Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事
务的提交或回滚;这是一个通讯员 负责协调各个子模块的事务
Transaction Manager(TM) : 控制全局事务的边界,负责开启一个全局事务,并最终发起全局
提交或全局回滚的决议;全局事务管理器是分布式全局事务的发起者,负责全局事务对额发起 和提交
Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指
令,驱动分支(本地)事务的提交和回滚。资源管理器,本地事务,负责本地事务的提交和回滚

原理

四种模式

AT

最简单的分布式事务模式,底层使用的 就是两阶段提交 ,使用seata 中的AT 只需要一个注解即可

两阶段提交协议的演变:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:提交异步化,非常快速地完成。回滚通过一阶段的回滚日志进行反向补偿。

TCC

TCC 模式,我们必须实现seata 为我们提供的TCC接口,所有的业务都需要自己实现,并且也要配合本地事务
好处:性能非常的好,但是需要工程师手写代码,难度稍微大一些

saga

长事务模式,适合在一个事务开启到提交耗费时间很长的场景
长事务:就是事务从开启到提交,时间耗费特别长的,一般长事务都特别耗费性能,并发能力非常低

XA

XA 模式其实就是AT模式的升级,更加的可靠,但是性能有所减低,使用起来也是很简单,只需要一条注解

六、实战

搭建seata服务

1、解压seata安装包

2、修改seata 配置文件

修改conf/file.conf


修改conf/registry.confseata

需要向nacos 进行注册

3、创建 seat-server 数据库,导入 conf/db_store.sql

4、启动seata服务



一定要在nacos 检查 一下

在生成订单为服务应用中使用开启seate 中的AT 事务

1、在订单的所有应用中的每一个数据库都导入conf/db_undo_log.sql

undo_log表就是为了 rm记录日志

2、在三个订单的应用中都要加入seate 依赖

添加依赖

<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>$seata.version</version>
</dependency>

3、配置文件application.yaml增加


4、将seata conf/file.conf conf/registry.conf 分别拷贝到 三个应用的resource 中

5、在三个应用中启动类都屏蔽 数据源自动装配

// 取消springboot数据源的自动装配 exclude = DataSourceAutoConfiguration.class
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

6、在三个应用中配置自定义数据源 conf/DataSourceProxyConfig

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
*配置类:
* 作用就是转配数据源 将druid 数据源进行一个封装 代理
*/
@Configuration
public class DataSourceProxyConfig 
	@Value("$mybatis.mapperLocations")
	private String mapperLocations;
/**
* 创建DruidDataSource druid数据源(连接池)
* @return
*/
	@Bean
	@ConfigurationProperties(prefix = "spring.datasource")
	public DataSource druidDataSource()
		return new DruidDataSource();
	
/**
3.在订单服务中发起 分布式事务
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class) // 开启seata 分布
式事务 // 默认情况下使用AT 模式开启分布式事务
* 创建 druid数据源 的代理
* @param dataSource
* @return
*/
	@Bean
	public DataSourceProxy dataSourceProxy(DataSource dataSource) 
		return new DataSourceProxy(dataSource);
	
/**
* 配置自定义SqlSessionFactory 使用代理数据源创建SqlSession 所有分布式事务的提交
和回滚都通过 DataSourceProxy
* @param dataSourceProxy
* @return
* @throws Exception
*/
	@Bean
	public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxydataSourceProxy) throws Exception 
	SqlSessionFactoryBean sqlSessionFactoryBean = new
	SqlSessionFactoryBean();
	sqlSessionFactoryBean.setDataSource(dataSourceProxy);
	sqlSessionFactoryBean.setMapperLocations(newPathMatchingResourcePatternResolver()
.getResources(mapperLocations));
	sqlSessionFactoryBean.setTransactionFactory(new
	SpringManagedTransactionFactory());
	return sqlSessionFactoryBean.getObject();
	

7、在订单服务中发起 分布式事务

@GlobalTransactional(name = “fsp-create-order”,rollbackFor = Exception.class) // 开启seata 分布式事务 // 默认情况下使用AT 模式开启分布式事务

@Override
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class) //
开启seata 分布式事务 // 默认情况下使用AT 模式开启分布式事务
public void create(Order order) 
LOGGER.info("------->下单开始");
//本应用创建订单
orderDao.create(order);
//远程调用库存服务扣减库存
LOGGER.info("------->order-service中扣减库存开始");
// 扣减库存 需要商品id 订单中商品的数量
storageService.decrease(order.getProductId(),order.getCount());
LOGGER.info("------->order-service中扣减库存结束");
//远程调用账户服务扣减余额
LOGGER.info("------->order-service中扣减余额开始");
4.重新启动 订单的三个应用,并检查
5.测试
http://localhost:8180/order/create?userId=1&productId=1&count=1&money=100
// 远程扣减金额 使用feign 发起远程调用
// 参数 用户id , 订单的金额
accountService.decrease(order.getUserId(),order.getMoney());
LOGGER.info("------->order-service中扣减余额结束");
//修改订单状态为已完成
LOGGER.info("------->order-service中修改订单状态开始");
// 修改时 最好传订单id 但是在这里 是使用用户id + 订单的状态来修改的
orderDao.update(order.getUserId(),0);
LOGGER.info("------->order-service中修改订单状态结束");
LOGGER.info("------->下单结束");

8、重新启动 订单的三个应用,并检查

9、测试

访问http://localhost:8180/order/create?userId=1&productId=1&count=1&money=100

以上是关于分布式事务的主要内容,如果未能解决你的问题,请参考以下文章

java事务相关

分布式事务入门篇

从单体事务到分布式事务

分布式技术专题「架构实践于案例分析」总结和盘点目前常用分布式事务特别及问题分析(上)

TX-LCN:分布式事务框架

TX-LCN:分布式事务框架