网上的文章对于java事物的总结,针对性太强,造成对java 全局性概念理解上存在问题,这里总结了下目前网上对java事物的相关解说,应该算是比较全面的了,希望对大家有些帮助!
首先,说说什么事务。我认为事务,就是一组操作数据库的动作集合。
事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态。
ACID,是指在数据库管理系统(DBMS)中,事务(transaction)所具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
在数据库系统中,一个事务是指:由一系列数据库操作组成的一个完整的逻辑过程。例如银行转帐,从原账户扣除金额,以及向目标账户添加金额,这两个数据库操作的总和,构成一个完整的逻辑过程,不可拆分。这个过程被称为一个事务,具有ACID特性。
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
事务的(ACID)特性是由关系数据库管理系统(RDBMS,数据库系统)来实现的。数据库管理系统采用日志来保证事务的原子性、一致性和持久性。日志记录了事务对数据库所做的更新,如果某个事务在执行过程中发生错误,就可以根据日志,撤销事务对数据库已做的更新,使数据库退回到执行事务前的初始状态。
一致性(Consistency)、可用性(Availability)和分区容忍性(Partitiontolerance)。CAP原理指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。这是Brewer教授于2000年提出的,后人也论证了CAP理论的正确性。
对于分布式的存储系统,一个数据往往会存在多份。简单的说,一致性会让客户对数据的修改操作(增/删/改),要么在所有的数据副本(replica)全部成功,要么全部失败。即,修改操作对于一份数据的所有副本(整个系统)而言,是原子(atomic)的操作。
如果一个存储系统可以保证一致性,那么则客户读写的数据完全可以保证是最新的。不会发生两个不同的客户端在不同的存储节点中读取到不同副本的情况。
可用性很简单,顾名思义,就是指在客户端想要访问数据的时候,可以得到响应。但是注意,系统可用(Available)并不代表存储系统所有节点提供的数据是一致的。这种情况,我们仍然说系统是可用的。往往我们会对不同的应用设定一个最长响应时间,超过这个响应时间的服务我们仍然称之为不可用的。
如果你的存储系统只运行在一个节点上,要么系统整个崩溃,要么全部运行良好。一旦针对同一服务的存储系统分布到了多个节点后,整个存储系统就存在分区的可能性。比如,两个存储节点之间联通的网络断开(无论长时间或者短暂的),就形成了分区。一般来讲,为了提高服务质量,同一份数据放置在不同城市非常正常的。因此节点之间形成分区也很正常。
Gilbert 和Lynch将分区容忍性定义如下:除全部网络节点全部故障以外,所有子节点集合的故障都不允许导致整个系统不正确响应。即使部分的组件不可用,施加的操作也可以完成。
一个数据存储系统不可能同时满足上述三个特性,只能同时满足其两个特性,也就是: CA,CP,AP。可以这么说,当前所有的数据存储解决方案,都可以归类的上述三种类型。
CA 满足数据的一致性和高可用性,但没有可扩展性,如传统的关系型数据,基本上满足是这个解决方案,如ORACLE , MYSQL 的单节点,满足数据的一致性和高可用性。
CP 满足数据的一致性和分区性,如Oracle RAC ,Sybase 集群。虽然Oracle RAC具备一点的扩展性,但当节点达到一定数目时,性能(也即可用性)就会下降很快,并且节点之间的网络开销还在,需要实时同步各节点之间的数据。
AP 在性能和可扩展性方面表现不错,但在数据一致性方面会用牺牲,各节点的之间数据同步没有哪么快,但能保存数据的最终一致性。当前热炒的NOSQL大多类是典型的AP类型数据库。
综合上述,架构师不要企图设计一套同是满足CAP三方面的数据库。只能在根据业务场景,对数据存储要求有所折衷。
接受最终一致性的理论支撑是BASE模型,BASE全称是BasicallyAvailable(基本可用), Soft-state(软状态/柔性事务), Eventually Consistent(最终一致性)。BASE模型在理论逻辑上是相反于ACID(原子性Atomicity、一致性Consistency、隔离性Isolation、持久性Durability)模型的概念,它牺牲高一致性,获得可用性和分区容忍性。
最终一致性:
最终一致性是指:经过一段时间以后,更新的数据会到达系统中的所有相关节点。这段时间就被称之为最终一致性的时间窗口
ACID一致性是有关数据库规则,如果数据表结构定义一个字段值是唯一的,那么一致性系统将解决所有操作中导致这个字段值非唯一性的情况,如果带有一个外键的一行记录被删除,那么其外键相关记录也应该被删除,这就是ACID一致性意思。
CAP理论的一致性是保证同样一个数据在所有不同服务器上的拷贝都是相同的,这是一种逻辑保证,而不是物理,因为光速限制,在不同服务器上这种复制是需要时间的,集群通过阻止客户端查看不同节点上还未同步的数据维持逻辑视图。
CAP原理指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。因此在进行分布式架构设计时,必须做出取舍。而对于分布式数 据系统,分区容忍性是基本要求 ,否则就失去了价值。因此设计分布式数据系统,就是在一致性和可用性之间取一个平衡。对于大多数web应 用,其实并不需要强一致性,因此牺牲一致性而换取高可用性,是目前多数分布式数据库产品的方向。(一般情况下CAP理论认为你不能拥有上述三种中两种,这是一个实践总结:当有网络分区情况下,也就是分布式系统中,你不能又要有完美一致性和100%的可用性,只能这两者选择一个。在单机系统中,你则需要在一致性和延迟性latency之间权衡)
当然,牺牲一致性,并不是完全不管数据的一致性,否则数据是混乱的,那么系统可用性再高分布式再好也没有了价值。牺牲一致性,只是不再要求关系型数 据库中的强一致性,而是只要系统能达到最终一致性即可,考虑到客户体验,这个最终一致的时间窗口,要尽可能的对用户透明,也就是需要保障“用户感知到的一致性”。通常是通过数据的多份异步复制来实现系统的高可用和数据的最终一致性的,“用户感知到的一致性”的时间窗口则 取决于数据复制到一致状态的时间。
JDBC 事务是用 Connection 对象控制的。JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:自动提交和手工提交。 java.sql.Connection 提供了以下控制事务的方法:
public void setAutoCommit(boolean)
public boolean getAutoCommit()
public void commit()
public void rollback()
使用 JDBC 事务界定时,您可以将多个 SQL 语句结合到一个事务中。JDBC 事务的一个缺点是事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库。
JTA是一种高层的,与实现无关的,与协议无关的API,应用程序和应用服务器可以使用JTA来访问事务。
JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据,这些数据可以分布在多个数据库上。JDBC驱动程序的JTA支持极大地增强了数据访问能力。
如果计划用 JTA 界定事务,那么就需要有一个实现 javax.sql.XADataSource 、 javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将可以参与 JTA 事务。一个 XADataSource 对象就是一个 XAConnection 对象的工厂。 XAConnection s 是参与 JTA 事务的 JDBC 连接。
您将需要用应用服务器的管理工具设置 XADataSource .从应用服务器和 JDBC 驱动程序的文档中可以了解到相关的指导。
J2EE应用程序用 JNDI 查询数据源。一旦应用程序找到了数据源对象,它就调用 javax.sql.DataSource.getConnection() 以获得到数据库的连接。
XA 连接与非 XA 连接不同。一定要记住 XA 连接参与了 JTA 事务。这意味着 XA 连接不支持 JDBC 的自动提交功能。同时,应用程序一定不要对 XA 连接调用 java.sql.Connection.commit() 或者 java.sql.Connection.rollback() .
相反,应用程序应该使用 UserTransaction.begin()、 UserTransaction.commit() 和 serTransaction.rollback() .
容器事务主要是J2EE应用服务器提供的,容器事务大多是基于JTA完成,这是一个基于JNDI的,相当复杂的API实现。相对编码实现JTA事务管理, 我们可以通过EJB容器提供的容器事务管理机制(CMT)完成同一个功能,这项功能由J2EE应用服务器提供。这使得我们可以简单的指定将哪个方法加入事 务,一旦指定,容器将负责事务管理任务。这是我们土建的解决方式,因为通过这种方式我们可以将事务代码排除在逻辑编码之外,同时将所有困难交给J2EE容 器去解决。使用EJB CMT的另外一个好处就是程序员无需关心JTA API的编码,不过,理论上我们必须使用EJB.
JDBC事务控制的局限性在一个数据库连接内,但是其使用简单。
JTA事务的功能强大,事务可以跨越多个数据库或多个DAO,使用也比较复杂。
容器事务,主要指的是J2EE应用服务器提供的事务管理,局限于EJB应用使用。
Java事务控制是构建J2EE应用不可缺少的一部分,合理选择应用何种事务对整个应用系统来说至关重要。一般说来,在单个JDBC 连接连接的情况下可以选择JDBC事务,在跨多个连接或者数据库情况下,需要选择使用JTA事务,如果用到了EJB,则可以考虑使用EJB容器事务
场景二:分库分表,分库分表之后,一般可以利用mycat等数据库中间件简化开发,但是数据库中间件也面临分布式事务的问题
场景三:SOA架构(跨应用)
分布式事务方案
强一致性
2PC 两阶段提交(XA事务,阻塞)
2PC协议:一种协议,在分布式系统保证事务的原子提交
两阶段提交是处理分布式事务的经典方法。对数据库分布式事务有了解的同学一定知道数据库支持的2PC,又叫做 XA Transactions。
MySQL从5.5版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持
主要通过增加事务协调者,对事务进行全局管理。
第一个阶段:
发出“准备”命令,所有事务参与者接受指令后进行资源准备,锁准备,undo log准备。如果都返回“准备成功”,如果不能执行,返回终止。
第二个阶段
协调者接受到第一个阶段的回复
如果都是ok,则发出“提交”命令,所有参与者进行commit操作。如果都成功,则事务结束,如果有失败情况,协调者发出“回滚”命令,所有事务参与者利用undo log进行回滚(这个在2PC不存在)。J2EE对JTA就是两阶段提交的实现。
如果有不ok,则发出撤销,所有事物撤销本地资源的锁定等操作
优点:
较强的一致性,适合于对数据一致性要求比较高对场景,当然并不是100%一致性
缺点:
整个过程耗时过程,锁定资源时间过长,同步阻塞(准备阶段回复后,一直等待协调者调用commit 或者rollback),CAP中达到了CP,牺牲了可用性,不适合高并发场景
协调者可能存在单点故障
Commit阶段可能存在部分成功,部分失败情况,并没有提及是否rollback
开源实现:
Spring jta
Bitronix
3PC三阶段提交(非阻塞,引入超时和准备阶段)
进入阶段3之后,如果协调者或者执行者因为网络等问题,接受不到docommit请求,超时后默认都执行doCommit请求,解决了2PC的第三个缺点
优点:降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务。
缺陷:脑裂问题依然存在,即在参与者收到PreCommit请求后等待最终指令,如果此时协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
TCC模式-本质也是2PC
TCC模式本质也是2PC,只是TCC在应用层控制,数据库只是负责第一个阶段。XA在数据库层控制两阶段提交。
TCC全局控制者是谁? XA有TM,RM,AP。TM负责全局管理。主业务方和从业务方
严格遵守ACID的分布式事务我们称为刚性事务,而遵循BASE理论(基本可用:在故障出现时保证核心功能可用,软状态:允许中间状态出现,最终一致性:不要求分布式事务打成中时间点数据都是一致性的,但是保证达到某个时间点后,数据就处于了一致性了)的事务我们称为柔性事务,其中TCC编程模式就属于柔性事务。
TCC是对二阶段的一个改进,try阶段通过预留资源的方式避免了同步阻塞资源的情况,但是TCC编程需要业务自己实现try,confirm,cancle方法,对业务入侵太大,实现起来也比较复杂。
分布式事务的基本原理本质上都是两阶段提交协议(2PC),TCC (try-confirm-cancel)其实也是一种 2PC,只不过 TCC 规定了在服务层面实现的具体细节,即参与分布式事务的服务方和调用方至少要实现三个方法:try 方法、confirm 方法、cancel 方法。
Saga模式
Saga模式是现实中可行的方案,采用事务补偿机制。每个本地事务都存储一个副本,如果出现失败,则利用补偿机制回滚。
最终一致性(BASE理论)
除了Saga模式,我们也有折中的方法,就是同步提交一个事务,异步通知其它数据源更新数据。这里我们放弃了事务参与者要么不执行,要么全部执行,采用异步方式达到最终一致性
本地消息表
ebay提出的方案,结构如下:
写业务逻辑和插入本地消息,必需在同一个事务
可以把本地消息数据发送到MQ或者利用quartz定时任务,发rest调用处理另外的事务,达到最终一致性
如果利用MQ,也是需要轮训处理失败的消息。我们项目中是直接插入消息表,然后利用quartz推送
MQ消息队列
我们可以利用消息队列(消息服务有等级:最多一次,至少一次,只有一次),异步操作,实现最终一致性。在用消息队列的时候,注意以下几点:
至少一次消息成功传递,需要持久化,防止消息总线宕机。
消息去除重复,防止多次操作。或者使用幂等消息
最常见的两种服务等级就是 At-Most-Once 和 At-Least-Once,前者能够保证发送方不对接收方是否能收到消息作保证,消息要么会被投递一次,要么不会被投递,这其实跟一次普通的网络请求没有太多的区别;At-Least-Once 能够解决消息投递失败的问题,它要求发送者检查投递的结果,并在失败或者超时时重新对消息进行投递,发送者会持续对消息进行推送,直到接受者确认消息已经被收到,相比于 At-Most-Once,At-Least-Once 因为能够确保消息的投递会被更多人使用。
除了这两种常见的服务等级之外,还有另一种服务等级,也就是 Exactly-Once,这种服务等级不仅对发送者提出了要求,还对消费者提出了要求,它需要接受者对接收到的所有消息进行去重,发送者和接受者一方对消息进行重试,另一方对消息进行去重,两者分别部署在不同的节点上,这样对于各个节点上的服务来说,它们之间的通信就是 Exactly-Once 的,但是需要注意的是,Exacly-Once 一定需要接收方的参与。
我们可以通过实现 AMQP 协议的消息队列来实现分布式事务,在协议的标准中定义了 tx_select、tx_commit 和 tx_rollback 三个事务相关的接口,其中 tx_select 能够开启事务,tx_commit 和 tx_rollback 分别能够提交或者回滚事务。支持事务消息的MQ有rocketMQ,但是rabbitmq和kafka不支持
使用消息服务实现分布式事务在底层的原理上与其他的方法没有太多的差别,只是消息服务能够帮助我们实现的消息的持久化以及重试等功能,能够为我们提供一个比较合理的 API 接口,方便开发者使用
分布式事务的实现方式是分布式系统中非常重要的一个问题,在微服务架构和 SOA 大行其道的今天,掌握分布式事务的原理和使用方式已经是作为后端开发者理所应当掌握的技能,从实现 ACID 事务的 2PC 与 3PC 到实现 BASE 补偿式事务的 Saga,再到最后通过事务消息的方式异步地保证消息最终一定会被消费成功,我们为了增加系统的吞吐量以及可用性逐渐降低了系统对一致性的要求。
在业务没有对一致性有那么强的需求时,作者一般会使用 Saga 协议对分布式事务进行设计和开发,而在实际工作中,需要强一致性事务的业务场景几乎没有,我们都可以实现最终一致性,在发生脑裂或者不一致问题时通过补偿的方式进行解决,这就能解决几乎全部的问题。