分布式事务

38 阅读7分钟

分布式事务

分布式事务产生

在分布式架构中发生事务性的问题,导致数据库数据异常.比如在我参与的项目万国头条中,出现过的当我们执行文章提交时,需要先根据文章id,找到我们所写的文章,然后审核文章内容以及标题标签以及所有图片;这一服务是我们发布文章时通过feign调用的,在这个过程中可能会出现的分布式问题就,我们写文章这边进行提交,后边进行审核,审核时出现问题导致审核时文章不通过,但是依旧正常运行,导致自媒体端这一部分观察到的数据是文章未通过审核,但我们app端以及数据库都显示出数据已经显示并已经发布到我们的app端用户上,这就产生了分布式事务的问题.

问题详情:在审核图片时,没有通过图片集合调用images.split(",")方法进行对逗号的切割,而是通过StingUtils.spilt(","),进行切割,导致所有的结果最终产生一个[,]的图片集合,导致图片审核有问题,但不影响运行,只是在发布文章时会造成文章未通过审核,代码继续运行时,依旧会在app端进行发布

传统式事务

传统事务的发生

假如我们进行的是订单下单与库存扣减操作,当我们项目在收到订单成功下单后,数据库会根据下单数量进行扣减,若在下单之后,扣减之前代码出现异常,这就导致订单已经下单但扣减库存的数据库操作并未执行成功,就造成了事务异常;
​
解决方案:根据事务的特性进行处理,给这一项目功能标记事务@Transactional注解,这就表示这一功能被事务捆绑,当出现异常时会回滚事务从而使得最终数据一致性;

事务的四大特性

1. 原子性:原子性表明被事务修饰的功能是一个不可分割的整体,当有一处发生异常时,会造成所有事务的回滚;
2. 一致性:一致性指的是如果没有发生异常,那对于数据的修改是符合预期的,主要是发生异常后,对于数据库的操作会一并回滚到上一次的数据修改前的数据
3. 隔离性:指的是对同一资源操作的事务不能发生
4. 持久性: 一旦数据提交或者回滚,数据的改变都是持久的,无论多少次查询,结果都是一致的

隔离性级别

读未提交
    会产生脏读、幻读、不可重复读的问题
读已提交
    会产生幻读、不可重复读的问题
    oracle默认
可重复读
    会产生幻读的问题
    mysql默认
串行化
    没有任何问题,但是相当于锁表,效率极低。所以这种方案不用
不同的隔离级别会产生不同的问题
脏读
    一个事务读到了另一个事务没有提交的数据
不可重复读
    同一个事务中,连续读取两次,得到结果不一样
幻读
    事务操作看到前和看到后的结果不一致,就像出现了幻觉一样
    1、在A事务中执行查询,看到两条数据
    2、在B事务中执行增加操作,成功插入一条数据
    3、在A事务中执行修改操作,发现修改影响行数为3行
演示
    设置事务隔离级别
    开启事务
    操作
    提交事务

CAP定理

三个指标

- Consistency(一致性): 用户访问分布式系统中的任意节点,得到的数据必须一致。
- Availability(可用性): 用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝。
- Partition tolerance (分区容错性):
Partition(分区):因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区。
Tolerance(容错):在集群出现分区时,整个系统也要持续对外提供服务。容忍出现分区的问题

image.png

CP

在一定时间内,等待集群节点进行数据同步后,对外提供访问

AP

在任何时间内,都对外访问,但是得到的数据可能不一样

CA不存在的理由

当我们需要数据同步时,总是需要时间执行,这其中需要通过多个节点要想数据一致,需要时间同步。那么在这个时间内,一定不能对外访问,所以A不成立。如果可以对外访问,那么数据就不一致,所以C就不成立了

为什么 P 一定要保证? 因为网络是不可靠的。客户因素,没办法避免

BASE理论

三个思想

Basically Available(基本可用)

分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
	譬如电商大促,为了应对大流量,暂时停止注册服务,这时注册服务就不可用,但是整个系统是可用的,所以叫基本可用

Soft State(软状态)

在一定时间内,允许出现中间状态,比如临时的不一致状态。
	比如订单状态:待付款、已付款待发货、已发货、已签收、已结束

Eventually Consistent(最终一致性):

虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。

分布式事务的解决思路

AP模式

最终一致性

各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
	譬如:增加了一条数据,后面有操作失败了,那么,补偿措施只需要把增加的那条数据删除即可
因为子事务提交了,那么提交后的状态是一个中间状态,也就是软状态

CP模式

强一致性

各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。
	事务执行过程中,会锁定操作的资源,那么这个资源暂时是不可用的。所以整个系统是基本可用
	

子事务也就是主事务(全局事务)中的分支事务,一般来说,全局事务包括两个以上的事务

@GlobalTransactional 即为全局事务的注解: 事务协调者,用来监控和通知各个分支事务

@Transactional 为普通事务,即子事务

Seata

官网

seata.io/zh-cn/

seata架构

TC (Transaction Coordinator)

事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
		监控和通知各个事务,包括分支事务和全局事务

image-20230926202950252

image.png

TM (Transaction Manager)

事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
	执行事务

RM (Resource Manager)

资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
				

AT模式

AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。

seata的AT模式

image-20230926203209233

image.png

	阶段一RM的工作
		- 注册分支事务
		- 记录undo-log(数据快照)
		- 执行业务sql并提交
		- 报告事务状态
	阶段二RM的工作
		删除undo-log即可
		一阶段都成功,则提交,删除undo-log
		一阶段有失败,则回滚,恢复undo-log日志,删除undo-log
	优点
		不会死锁
		最终一致性

部署Seata-TC服务

	1、解压 seata-server-1.4.2.zip
	2、配置
registry {
  # 注册中心
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "HZ"
    username = "nacos"
    password = "nacos"
  }
  
}

config {
  # 配置中心
  type = "nacos"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  }
  
}

注册中心

type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "HZ"
    username = "nacos"
    password = "nacos"
  }
}
config { 

配置中心

# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
# 事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000

# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898