java事物(spring和分布式事务)


1 什么是事务呢?

事务,一般是指要做的或所做的事情。事务应该具有4个属性:原子性、一致性、隔离性、持久性。原子性,一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。一致性,一致性与原子性是密切相关的。” 解释都是一样的,无论是数据库还是java本身事物都是这样的。   也正是因为这样的特性,我们项目中恰好有这样的需求,所以才被广泛应用。mysql本身支持,但需要手动开启。   2 spring事物实现    spring主要作用在于项目构建黏合,也就是IOC管理对象,AOP作为其中重要的扩展(BeanPostPocessor的拓展),对业务来说,需要在java层保证一定的事物特性,或者说把数据库本身支持的事物操作交给spring来操作。   如果我们用java来写一个这,咋写呢,首先写一个interceptor吧,然后依靠aop的通知,around或者before和after这种通知,从准备到业务操作完是提交还是回滚,操作一把。   spring怎么操作的呢?分为两种,声明式和编程式,大部分都是用的编程式。一样的道理,写一个transactionInterceptor,然后获取mysql连接,开启数据库事物,然后等业务流程操作完进行commit或者cannal操作,然后依靠@Transaction注解标识类执行。源码里面是这么干的,简单粗暴。   还有一些微操,比如事物嵌套,会判定事物操作影响小的先回滚等操作。   3 为什么要用分布式事务?   大环境下人均分布式,服务请求路由到了不同的服务器上去执行,spring是通过本服务器连接数据库操作的,如果最后执行sql的服务器不是本身呢,就没办法控制流程总线了吧。而有些长业务强数据校验的场景又需要事物,比如我以前做的支付、对账、风控这些,都需要保证数据的一致性。     4 分布式的各种使用场景和痛点   数据库主键:最后持久层的数据还是会落到持久存储的库表,直接用库表主键做幂等。痛点:受困于架构瓶颈,数据库就是最大瓶颈(要不然也没那么多事了)->需要把更多事情交给java   MQ:就是最大可能重试,我以前这么想。在某些可放弃场景下不值当回滚整个场景下,比如我们整个支付流程没有异常,发送push推送服务器凉了,就干脆带个支持事物的MQ来做这种事情。有点就是比较轻,独立于业务,异步不影响主线程,像JMS协议下的rocketmq做事物的时候就是。以前常年这么干,但主流程不建议。   TCC:简单来说就是留三个接口,Try来做业务操作,commit做提交,cannal做回滚操作,也就是所谓2PC操作事物。这么做跟代码耦合度比较高,而且如果以前业务流程太长的话,改造起来比较困难,纯耦合写的话。   SAGA:这个就有点像nested的操作了,我个人感觉(好像是错的)。就是在整个流程开始的时候开启一个事物主线,各个单体操作各自提交时向它总线注册,最后如果失败,主线带着一起回滚。优点就是各个子业务速度较快,不受影响。缺点就是主线回滚的时候有些依赖场景可能会有问题。   5 分布式事务中间件   最近实验了一把事务中间件seata,整体使用还不错。   它的操作是这样的,三个组件,TransactionManage事务管理器,ResourceManage数据源,TransactionControl协调器。   有四种模式。XA、AT、TCC、SAGA。   AT:依靠数据库自身,在数据库开启个操作 TCC:见下面操作流程。XID这种 SAGA:搞了个事件驱动异步状态机,通过操作状态机流程来完成SAGA型事务操作。     seata工具原理(抄了个别人的,随便看看):  

Business 是业务入口,在程序中会通过注解来说明他是一个全局事务,这时他的角色为 TM(事务管理者)。

Business 会请求 TC(事务协调器,一个独立运行的服务),说明自己要开启一个全局事务,TC 会生成一个全局事务ID(XID),并返回给 Business。

Business 得到 XID 后,开始调用微服务,例如调用 Storage。

Storage 会收到 XID,知道自己的事务属于这个全局事务。Storage 执行自己的业务逻辑,操作本地数据库。

Storage 会把自己的事务注册到 TC,作为这个 XID 下面的一个分支事务,并且把自己的事务执行结果也告诉 TC。

此时 Storage 的角色是 RM(资源管理者),资源是指本地数据库。

Order、Account 的执行逻辑与 Storage 一致。

在各个微服务都执行完成后,TC 可以知道 XID 下各个分支事务的执行结果,TM(Business) 也就知道了。

Business 如果发现各个微服务的本地事务都执行成功了,就请求 TC 对这个 XID 提交,否则回滚。

TC 收到请求后,向 XID 下的所有分支事务发起相应请求。

各个微服务收到 TC 的请求后,执行相应指令,并把执行结果上报 TC。

(1)全局事务的回滚是如何实现的呢?

Seata 有一个重要的机制:回滚日志

每个分支事务对应的数据库中都需要有一个回滚日志表 UNDO_LOG,在真正修改数据库记录之前,都会先记录修改前的记录值,以便之后回滚。

在收到回滚请求后,就会根据 UNDO_LOG 生成回滚操作的 SQL 语句来执行。

如果收到的是提交请求,就把 UNDO_LOG 中的相应记录删除掉。

(2)RM 是怎么自动和 TC 交互的?

是通过监控拦截JDBC实现的,例如监控到开启本地事务了,就会自动向 TC 注册、生成回滚日志、向 TC 汇报执行结果。

(3)二阶段回滚失败怎么办?

例如 TC 命令各个 RM 回滚的时候,有一个微服务挂掉了,那么所有正常的微服务也都不会执行回滚,当这个微服务重新正常运行后,TC 会重新执行全局回滚。

            总结   老是有人说分布式事务太重了,能不用就不要用,其实个人感觉是不对的,技术可以一步步的改进,畏惧而不敢向前是做不好技术的,当然业务求稳我这样是不对的,哈哈。总体来说,分布式事务从以前一种繁琐的事情变得越来越简单还是不错的。推荐seata。