(1)原子性(atomicity):个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
比方说:买东西要么交钱收货一起都执行,要么发不出货,就退钱
(2)一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一个事务查看数据时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。
如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
(3)隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
比方说:一个人买东西这个事情,是不影响其他人买东西。
隔离性又分为四个级别:读未提交 (read uncommitted)、读已提交 (read committed,解决脏读)、可重复读 (repeatable read,解决虚读)、串行化 (serializable,解决幻读)。
(4)持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
比方说:一个人买东西的时候需要记录在账本上,即使老板忘记了那也有据可查。
大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务 (Local Transaction)。本地事务的 ACID 特性是数据库直接提供支持。
大部分人对 MySQL 都比较熟悉,以 MySQL 的 InnoDB (InnoDB 是 MySQL 的一个存储引擎)为例,InnoDB 是通过 日志和锁 来保证的事务的 ACID 特性,具体如下:
Undo Log 如何保障事务的原子性呢?
具体的方式为:在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 Undo Log),然后进行数据的修改。如果出现了错误或者用户执行了 Rollback 语句,系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。
Redo Log 如何保障事务的持久性呢?
具体的方式为:Redo Log 记录的是新数据的备份(和 Undo Log 相反)。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到崩溃之前的状态。
在分布式系统上一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务节点上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
典型的分布式事务场景:
CAP 定理是由加州大学伯克利分校 Eric Brewer 教授提出来的,他指出 WEB 服务无法同时满足一下 3 个属性:
数据一致性分为强一致性、弱一致性、最终一致性。
系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果,如果超时,那么系统就被认为是不可用的
分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
分区容错性是不能被抛弃的那个,只能在一致性、可用性寻找平衡。大部分互联网公司采可能会优先采用这基于 BASE 理论的柔性事务,强调的是可用性。
即使无法做到强一致性,但应用可以采用适合的方式达到最终一致性。
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写。
1.基本可用(Basically Available)
指分布式系统在出现不可预知故障的时候,允许损失部分可用性。
2.软状态( Soft State)
指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。
3.最终一致( Eventual Consistency)
强调的是所有的数据更新操作,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
BAS 柔性事务方案有:
CAP 理论的事务方案(强一致)有:
TCC 方案的全称是:Try、Confirm、Cancel。
TCC 方案严重依赖回滚和补偿代码,最终的结果是:回滚代码逻辑复杂,业务代码很难维护。所以,TCC 方案的使用场景较少,主要用于支付、交易相关的场景。
(1)A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;
(2)如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息;
(3)如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;
(4)mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。
(5)这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。
这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。
TCC 和可靠消息最终一致性方案是在生产中最常用。
对于那些特别严格的场景,用的是 TCC 来保证强一致性;阿里开源了分布式事务框架,seata类似TCC事务,经历过阿里生产环境大量考验的框架。seata支持Dubbo,Spring Cloud。
对于数据一致性要求没有那些特别严格的场景,可以使用可靠消息最终一致性方案,如果基于 RocketMQ 来实现了分布式事务框架,
RocketMQ提供了分布式事务支持,已经把可靠消息服务需要实现的功能逻辑已经做好了。
也可以基于 ActiveMQ,RabbitMQ,等,自己开发一个可靠消息服务,收到消息之后,尝试投递到 MQ,如果投递失败,重试投递