Spanner:TrueTime 和外部一致性

TrueTime 是一款高度可用的分布式时钟,面向所有 Google 服务器上的应用提供 1。TrueTime 使应用能够生成单调递增的时间戳:前提是任一时间戳 T' 在结束生成时,另一个时间戳 T 还没有开始生成,应用即可计算出一个保证大于 T' 的时间戳 T。这一保证适用于所有服务器和所有时间戳。

Spanner 使用 TrueTime 的这一特性为事务分配时间戳。具体而言,每个事务都分配有一个时间戳,它反映 Spanner 认为事务发生的时间。由于 Spanner 使用多版本并发控制,时间戳的排序保证使 Spanner 的客户端能够在整个数据库内(即使跨多个 Cloud 区域)执行一致的读取,而不会阻止写入。

外部一致性

如果未指定隔离级别,或者将隔离级别设置为串行化隔离,Spanner 会为客户端提供最严格的事务并发控制保证,这被称为外部一致性2。在外部一致性下,系统表现得就像所有事务都按顺序运行一样,尽管 Spanner 实际上为了获得更高的性能和可用性,将它们运行在多个服务器(也可能在多个数据中心)上。此外,如果一个事务在另一个事务开始提交之前完成,则系统保证客户端永远不会看到这样一种状态:即包含第二个事务的效果,但不包括第一个事务的效果。直观地说,Spanner 在语义上与单机数据库没有区别。尽管 Spanner 提供了如此强有力的保证,但仍然使应用能够实现与提供较弱保证(以获得更高性能)的数据库相媲美的性能。默认情况下,Spanner 让写入操作能够在不被只读事务阻塞的同时,还可避免可重复读隔离下可能出现的并发异常。

另一方面,可重复读隔离可确保事务内的所有读取操作都能看到事务开始时数据库的一致快照。在高读写并发场景中,许多事务读取的数据可能正在被其他事务修改,这种方法在该场景非常有用。如需了解详情,请参阅可重复读隔离

外部一致性大大简化了应用开发。例如,假设您已经在 Spanner 上创建了一个银行应用,并且您的一位客户的支票账户最开始有 50 美元,储蓄账户最开始有 50 美元。然后,您的应用开始执行一个工作流:首先提交事务 T1,将 200 美元存入储蓄账户;然后发出第二个事务 T 2,从支票账户中扣减 150 美元。此外,假设在一天结束时,一个账户的负余额将自动从其他账户进行补偿,并且如果在当天的任何时间,客户的所有账户的总余额为负值,则客户会受到处罚。外部一致性可保证,由于 T2 是在 T1 完成后开始提交,则数据库的所有读取方将观察到存款 T1 发生在扣款 T2 之前。换句话说,外部一致性可保证没有人会看到 T2 发生在 T1 之前这种状态;换言之,扣款不会导致资金不足造成的处罚。

使用单版本存储和严格两阶段锁定的传统数据库具有外部一致性。不幸的是,在此类系统中,每当您的应用想要读取最新数据(我们称之为“强读”)时,系统都会获取对数据的读取锁定,从而阻止写入到正在读取的数据。

时间戳和 MVCC

为了在读取的同时不阻止写入,Spanner 和许多其他数据库系统将保留多个不可变的数据版本(通常称为多版本并发控制)。写入会创建一个新的不可变版本,其时间戳是写入事务的时间戳。某个时间戳的“快照读取”将返回该时间戳之前最新版本的值,不需要阻止写入。因此,分配给版本的时间戳必须与事务所能被观察到的提交顺序保持一致,这一点非常重要。我们称这种特性为“恰当的时间戳分配”,它等同于外部一致性。

为什么适当的时间戳非常重要呢?回顾一下上一节中的银行业务示例。如果没有恰当的时间戳分配,分配给 T2 的时间戳可能会早于分配给 T1 的时间戳(例如,如果一个假想的系统使用本地时钟而不是 TrueTime,并且处理 T2 的服务器的时钟略微滞后)。那么,一个快照读取操作可能就会反映出 T2 的扣款,但却没有 T1 的存款,尽管客户在开始扣款之前就已经看到存款完成了。

实现适当的时间戳对于单机数据库来说轻而易举(例如,您可以从全局单调增加的计数器分配时间戳)。在分布广泛的系统(例如 Spanner)中高效地实现这一点要难得多,因为在这样的系统中,全世界的服务器都需要分配时间戳。

Spanner 依赖 TrueTime 生成单调递增的时间戳。Spanner 通过两种方式使用这些时间戳。首先,它将它们用作写入事务的恰当时间戳,而不需要进行全局通信。其次,它将它们用作强读的时间戳,这使强读可以在一轮通信中执行,即使强读跨越多个服务器也是如此。

常见问题解答

Spanner 提供什么样的一致性保证?

默认情况下,Spanner 提供外部一致性,这是事务处理系统中最严格的一致性特性。Spanner 中所有使用串行化隔离的事务都满足该一致性特性,而不仅仅是那些在同一分区内的事务。如需了解详情,请参阅隔离级别概览

外部一致性表明,Spanner 执行事务的方式,与串行执行事务的系统几乎没有区别,并且,该串行顺序与事务所能被观察到的提交顺序保持一致。由于为事务生成的时间戳对应于该串行顺序,因此,如果任一客户端观察到事务 T2 在另一事务 T1 完成后才开始提交,系统将分配给 T2 一个高于 T1 时间戳的时间戳。

Spanner 是否提供可线性化?

是。默认情况下,Spanner 提供外部一致性,这是一个比线性一致性更强的特性,因为线性一致性并没有对事务的行为做出任何说明。线性一致性是支持原子读写操作的并发对象的一种特性。在数据库中,“对象”通常是单行甚至单个单元格。外部一致性是事务处理系统的一个特性,其中,客户端会动态地合成任意对象上包含多个读写操作的事务。可线性化可被视为外部一致性的一个特例,其中,事务只能包含单个对象上的单个读取或写入操作。

Spanner 是否提供可序列化?

是。默认情况下,Spanner 提供外部一致性,这是一个比串行化更严格的特性。如果一个事务处理系统执行事务的方式,与按顺序串行执行事务的系统几乎没有区别,那么它就是串行化的。Spanner 还保证该串行顺序与事务可被观察到的提交顺序保持一致。

再回顾一下前面使用的银行业务示例。在一个提供串行化但没有外部一致性的系统中,即使客户按顺序先后执行了 T1 和 T2,系统仍可能被允许对它们重新排序,这可能会导致因资金不足而产生扣款罚金。

Spanner 是否提供强一致性?

是。Spanner 提供外部一致性,这是一个比强一致性更强的特性。Spanner 中读取操作的默认模式是“强一致”,它保证读取操作能观察到在该操作开始之前提交的所有事务的效果,无论是哪个副本接收到该读取请求。

强一致性和外部一致性有什么区别?

如果被复制的对象是线性一致的,则该复制协议表现出强一致性。与线性一致性一样,强一致性也弱于外部一致性,因为它没有对事务的行为做出任何强制规定。

Spanner 是否提供最终(或延迟)一致性?

Spanner 提供外部一致性,这是一个比最终一致性强得多的特性。最终一致性是用较弱的保证来换取更高的性能。最终一致性是存在问题的,因为它意味着读取方可以观察到数据库从未存在的状态(例如,即使事务 A 发生在 B 之前,读取操作仍可能观察到事务 B 已提交但事务 A 尚未提交的状态)。

Spanner 提供过时数据读取,其可带来与最终一致性相似的性能优势,但具有强得多的一致性保证。过时数据读取返回的是较早时间戳的数据,它不会阻塞写入,因为数据的先前版本是不可变的。

后续步骤

备注

  • 1J.C. Corbett, J.Dean, M.Epstein, A.Fikes, C.Frost, J.Furman, S.Ghemawat, A.Gubarev, C.Heiser, P.Hochschild, W.Hsieh, S.Kanthak, E.Kogan, H.Li, A.Lloyd, S.Melnik, D.Mwaura, D.Nagle, S.Quinlan, R.Rao, L.Rolig, Y.Saito, M.Szymaniak, C.Taylor, R.Wang,和 D.Woodford.Spanner:Google 的全球分布式数据库。第十届 USENIX 操作系统设计与实现专题论文集 (OSDI 12) 第 261-264 页,加利福尼亚州好莱坞,2012 年 10 月。
  • 2Gifford, D. K.K. 《分散式计算机系统中的信息存储》。博士论文,斯坦福大学,1981 年。