事务处理几乎在每一个信息系统中都会涉及,它存在的意义是为了保证系统中所有的数据都是符合期望的,且相互关联的数据之间不会产生矛盾,即数据状态的一致性 (C onsistency)。
按照数据库的经典理论,要达成这个目标,需要三方面共同努力来保障。
原子性
(A tomic):在同一项业务处理过程中,事务保证了对多个数据的修改,要么同时成功,要么同时被撤销。隔离性
(I solation):在不同的业务处理过程中,事务保证了各自业务正在读、写的数据互相独立,不会彼此影响。持久性
(D urability):事务应当保证所有成功被提交的数据修改都能够正确地被持久化,不丢失数据。
以上四种属性即事务的“ACID”特性,但笔者对这种说法其实不是太认同,因为这四种特性并不正交,A、I、D 是手段,C 是目的,前者是因,后者是果,弄到一块去完全是为了拼凑个单词缩写。
事务的概念虽然最初起源于数据库系统,但今天已经有所延伸,而不再局限于数据库本身了,所有需要保证数据一致性的应用场景,包括但不限于数据库、事务内存、缓存、消息队列、分布式存储,等等,都有可能会用到事务,后文里笔者会使用“数据源”来泛指所有这些场景中提供与存储数据的逻辑设备,但是上述场景所说的事务和一致性含义可能并不完全一致,说明如下。
- 当一个服务只使用一个数据源时,通过 A、I、D 来获得一致性是最经典的做法,也是相对容易的。此时,多个并发事务所读写的数据能够被数据源感知是否存在冲突,并发事务的读写在时间线上的最终顺序是由数据源来确定的,这种事务间一致性被称为“内部一致性”。
- 当一个服务使用到多个不同的数据源,甚至多个不同服务同时涉及多个不同的数据源时,问题就变得相对困难了许多。此时,并发执行甚至是先后执行的多个事务,在时间线上的顺序并不由任何一个数据源来决定,这种涉及多个数据源的事务间一致性被称为“外部一致性”。
外部一致性问题通常很难再使用 A、I、D 来解决,因为这样需要付出很大乃至不切实际的代价;但是外部一致性又是分布式系统中必然会遇到且必须要解决的问题,为此我们要转变观念,将一致性从“是或否”的二元属性转变为可以按不同强度分开讨论的多元属性,在确保代价可承受的前提下获得强度尽可能高的一致性保障,也正因如此,事务处理才从一个具体操作上的“编程问题”上升成一个需要全局权衡的“架构问题”。
人们在探索这些解决方案的过程中,产生了许多新的思路和概念,有一些概念看上去并不那么直观,在本章里,笔者会通过同一个场景事例讲解如何在不同的事务方案中处理来贯穿、理顺这些概念。
场景事例
Fenix's Bookstore 是一个在线书店。当每一本书被成功售出时,需要确保以下三件事情被正确地处理:
- 用户的账号扣减相应的商品款项。
- 商品仓库中扣减库存,将商品标识为待配送状态。
- 商家的账号增加相应的商品款项。
接下来,笔者将逐一介绍在“单个服务使用单个数据源”、“单个服务使用多个数据源”、“多个服务使用单个数据源”以及“多个服务使用多个数据源”下,我们可以采用哪些手段来保证数据在以上场景中被正确地读写。