前几天面试,面试官让我谈谈分布式事务的ACID,当时我才发现一谈论ACID立马想到的都是关系型数据库、基于单机的分布式事务,而对于分布式事务,我仅仅依稀记得什么CAP定理、BASE特性。看来有必要总结一番了
理解事务
首先,单看事务这个概念,它是一组操作,这组操作只能有两个完成状态——要么成功,要么失败。
完成一个事务,最终能够从一个一致性状态(某个正确的状态),转移到另一个一致性状态。我们将事务可以细分为四个特性:原子性、隔离性、持久性、一致性。
总结:事务就是将一组操作看作一个原子操作执行,这些原子操作之间是隔离的,不能被打断,并且一旦执行完毕就永久生效。执行完毕一个原子操作,执行前正确的状态将转移至另一个正确的状态
上面这四个特性都是上层的东西、是概念性的东西,支持事务的产品需要进行实现。但是产品考虑到本身的定位、性能无法完全的实现以上四个特性。(标准是好的,但是也要考虑场景和成本)
例如,一些关系型数据库如mysql、oracle等在隔离性进行了妥协,没有采用严格的隔离性,而是提出隔离级别的概念包括读未提交、读已提交、可重复读、串行化。
再比如,内存数据库redis的事务更像是一个打包执行的命令,它不提供严格意义上的原子性、隔离性、持久性,包括一致性也没有实际的体现。它只是实现了“自解释”的事务罢了。
以上提到的产品实现了不严格的事务,单个服务器内容的事务称为单机事务或本地事务,而分布式事务则更加复杂,分布式事务中的各个操作往往分散在各个的单机节点中,往往依赖各节点的本地进行实现。
总结:事务是一个上层概念,ACID也是通过提取特性,为事务的实现提供依据,但是各个产品往往考虑到自身定位、性能而不去实现严格的事务,往往将其中某些特性进行弱化。分布式事务的实现也可以依据ACID的标准,但是往往也不能完全具有严格的ACID特性。
业务层事务与分布式事务
数据库产品一般都实现了事务如oracle、innoDB等。我们称他们为一个数据库事务,最简单情况就是单数据源(DB)的DAO接口调用,这个事务我们的业务逻辑实现直接依赖数据库事务即可,但是随着业务逐渐复杂,我们的业务逻辑往往需要操作多个数据源,甚至多个子系统,这个时候数据库事务就无法进行保证。
业务层(service)事务可以用来协调多个DAO接口调用的事务,如果我们去自己实现,往往需要通过try/catch包裹多个commit和rollback。如果service接口内部出现异常,那么将在catch块中依次回滚。引入spring框架后,我们也可以将逻辑层事务控制的任务交给spring提供的transactionManager进行管理,它将为各个声明了事务的service方法基于springAOP的动态代理技术,切入事务控制的逻辑。
transactionManager本身是不支持事务的,它只是一个协调者,根据不同的时机负责开启事务以及调用提交或者回滚接口。它底层还是依赖数据库实例的事务功能。
总结:最简单的单数据源场景,我们可以直接依赖数据库事务,而多数据源场景,我们需要引入一个事务协调者,控制各个数据库提交和回滚的时机。
上面的事务也可以看作分“步(step)”式事务,而现在我们要谈论的基于子系统调用的分布式(distribution)事务和上面的思路类似。基于子系统实现的分布式事务,其实就是依赖各个子系统本地的事务,我们需要找到一个更上层的事务协调者(如日志、中间件),来协调各个系统事务的步伐。
分布式事务的目的是使得各个子系统构成的完整系统能够从一个一致性状态(正确的状态)到达另一个一致性状态。不止是多个数据源之间的事务协助,它是各个子系统之间的基于某种业务逻辑的事务协助
个人理解,分布式事务的实现思路也是类似业务层事务的实现思路,我们需要通过实现一个事务协调者,通过try/catch检测接口调用的情况。只不过这个接口不再是本地调用,需要通过网络实现远程调用,还需要考虑超时的问题,并且提供相应的容错机制。
ACID、CAP和BASE
CAP理论是设计分布式系统的基础理论,它指出一个分布式系统中,一致性(consistency)、可用性(availability)和分区容错性(partition tolerance)。最多只能满足其中两个。
一致性指的就是数据的强一致性,任意时刻,各个分布式子系统的数据必须是一样的。客户对数据进行修改操作,要么在所有的数据部分全部成功,要么全部失败——修改操作对于一份数据的所有副本(整个系统)而言,是原子的操作
可用性指的是正常响应,你调用分布式系统的一个接口,必须立刻得到