分布式事务外部一致性、隔离性和时间戳的理解

一致性与隔离性

Linearizability versus Serializability

Linearizability: single-operation, single-object, real-time order

Serializability: multi-operation, multi-object, arbitrary total order
——— [Linearizability versus Serializability]

一致性针对的是对单对象的单个操作之间的可见性。序列化(隔离性)指的是的事务(多个对象多个操作)之间的可见性。

如果把一致性与数据库事务结合,则一致性表示的是物理时间上不相交的两个事务的可见性,而物理时间上相交的两个事务的可见性由隔离性决定。
在这里插入图片描述
如果同时满足线性一致性和序列化性则称为Strict Serializability。

外部一致性

外部一致性即是在数据库事务之上实现线性一致性,即物理时间上不相交的两个事务之间的可见性。 至于外部一致性是否等于Strict Serializability,我是保持怀疑的,因为数据库可以实现SI的隔离性+线性一致性的,这样有时也称为外部一致性,所以我认为外部一致性更注重一致性部分,而非隔离性部分。

外部一致性的理解可以参考:
https://tyler-zx.blog.csdn.net/article/details/108915068

对于多种一致性的理解有很多解释,在这里阐述一下我的理解:

  • 传统ACID的一致性:数据库从一个一致性的状态转移到另一个一致性的状态。这里的一致性表达的是数据库处于一种符合数据库约束,也符合用户预期的一种状态,其一方面依赖数据库的保证,例如原子性,也依赖于业务特性和业务层代码实现。(这种一致性既适用于单机数据库也适用于分布式数据库)
  • 分布式副本一致性:CAP中的一致性和共识算法(raft、poxas)的一致性都可以算作是分布式副本的一致性,其描述的是在数据副本的复制过程中呈现的一致性。
  • 分布式事务外部一致性:非 lock base 的事务引擎下,在物理时间上先后发生的事务间(执行区间不重叠,它们的在时间线上的顺序由用户确定,数据库无法感知到他们之间的关系),数据如何相互可见的性质。外部一致性的目标是:假设事务 T1 执行完成后,T2 才启动,保证让 T2 看到 T1 的结果。
  • 分布式事务内部一致性:多个并发事务读写的数据(或数据范围),它们能够被数据库感知到冲突(读写之间也算冲突),因此它们在时间线上的顺序由数据库确定,这样的事务间一致性问题可以被认为是内部一致性问题,Ansi Sql Isolation 定义的隔离级别就是定义内部一致性的。

分布式事务的内外一致性描述的是多个事务间的数据的可见性“内部” 和 “外部” 最重要的区别是可见顺序由数据库(内部)确定,还是由用户(外部)确定

我们很容易把分布式副本一致性和分布式事务外部一致性搞混,因为他们的表现非常相像:首先客户端写入一条数据write(x, 4),然后客户端读取数据read(x),但是却返回空。两者虽然表现是一样的,但是产生的原因却完全不同。

  • 对于分布式副本一致性,产生这种现象的原因是:客户端写入数据后,数据副本还未复制到全部存储节点就返回客户端成功(弱一致性)。当客户端读取时,正好访问到未成功复制副本的存储节点,返回空。
  • 对于分布式事务外部一致性,产生的原因是:客户端写入数据发生在节点A,节点A利用本地时间戳localA_ts作为事务提交的时间戳commit_ts。客户端读取数据时发生在节点B,节点B利用本地时间戳localB_ts,作为事务读取的时间戳snapshot_read_ts。由于多节点的时钟偏移,很可能出现snapshot_read_ts < commit_ts,这样就会误以为读取是在写入之前发生的,从而返回空。

在分布式数据库中,为了避免数据副本不一致的情况,通常只读取leader副本(通过分区避免负载过大),保证读取到最新的数据,数据副本只是用来作为故障备份,所以基本没有分布式副本一致性的问题。但是对于使用MVCC并发控制的分布式数据库,由于多节点的时钟偏移,很可能出现分布式事务外部一致性的问题(对于单机数据库,则不存在事务外部一致性的问题,因为单机的时间戳是严格递增的)。

由于分布式副本一致性、分布事务外部一致性的表现是相像的,所以一些对一致性的描述经常使用混乱,既用于副本一致性也用于分布式事务外部一致性,比如可线性化、顺序一致性、因果一致性、强一致性、弱一致性,最终一致性,有些甚至本用于描述数据库事务ACID的一致性。但是我们只需要明白各种一致性的表现,以及在不同语境下产生原因即可。

分布式事务的时间戳

分布式事务中的时间戳是影响分布式外部一致性的重要因素,同时它也会影响隔离性的表现。数据库通过可以时间戳来判断当前记录的可见性,即MVCC(快照隔离级别)。如果当前记录是已经提交的事务,则其体现的是一致性;如果当前记录是并发执行的事务,则其体现的是隔离性。

分布式节点不可能保证每个机器上的时钟完全同步,所以这为实现外部一致性带来了困难。已知的时间戳方案中,仅有 TSO 和 TrueTime 能够保证线性一致性;

可参考:

分布式事务中的时间戳

分布式数据库中的一致性与时间戳

分布式系统中的时间、时钟和事件顺序

深度解析:分布式存储系统实现快照隔离的常见时钟方案

Distributed System Clocks分布式系统时钟解决方案

分布式系统中各类时间戳

逻辑时钟和复合逻辑时钟

分布式时钟同步之:逻辑时钟、向量时钟、混合逻辑时钟(三)

基于HLC的分布式事务实现深度剖析

分布式事务中的时间戳

逻辑时钟/复合逻辑时钟可以保证有关系的事务之间的外部一致性,但是对于独立的事务之间无法保证外部一致性,所以称为因果一致性。

HLC/LC 用在分布式事务中,我们将时间戳附加到所有事务相关的 RPC 中,也就是 Begin、Prepare 和 Commit 这几个消息中:

  • Begin:取本地时间戳 local_ts 作为事务读时间戳 snapshot_ts
  • Snapshot Read: 用 snapshot_ts 读取其他节点数据(MVCC)
  • Prepare:收集所有事务参与者的当前时间戳,记作 prepare_ts
  • Commit:计算推高后的本地时间戳,即 commit_ts = max{ prepare_ts } + 1

比如:分布式数据库包括Node1、Node2…多个节点,假设同一个客户端可以连接到不同的Node上进行数据库操作。

  • 独立的事务:在Node1上开启事务TA,在Node1写入数据并提交。随后Node2上启动事务TB,TB需要读取Node1上TA写入的数据。因为TB使用Node2上的snapshot_ts去读取Node1上的数据,所以TB不一定能读取到TA写入的数据。
  • 有关系的事务:在Node1上开启事务TA,事务TA在Node1、Node2上写入数据并提交,所以Node1需要发消息给Node2。随后Node2开始事务TB读取Node1上TA写入的数据。因为消息传递时,逻辑时钟/复合逻辑时钟进行了更新,所以TB的snapshot_ts肯定小于TA的commit_ts。

综上可知,如果两个事务所涉及的节点有重叠,则事务之间产生了关系(因果),则在消息传递时产生了逻辑时钟/复合逻辑时钟的更新,从而保证了事务之间的因果一致性,即有关系的事务之间的外部一致性。

然而一些在外部世界看起来有因果关系的事情,在数据库中是不会产生因果关系的。比如客户端调用微服务节点 A 负责用户注册,A连接数据库节点1写入数据,之后客户端向微服务节点 B 发送消息,通知节点 B 进行下订单,B连接数据库节点2读取数据,此时 B 却查不到这条用户的记录。

复合逻辑时钟

纯逻辑时钟是一个递增的计数器,与物理时钟毫不相关。而复合逻辑时钟将物理时钟和逻辑时钟拼接,从而使符合逻辑时钟和物理时钟保持在一定误差范围内,且保持了逻辑时钟的特性。其具有以下性质:

  • 满足LC的因果一致性happens-before,即若事件​e​先行发生于​f​,则有​hlc.e​ < ​hlc.f​。
  • 单个时钟O(1)的存储空间(VC是O(n),n为分布式系统下的节点个数)。
  • 单个时钟的大小有确定的边界(不会无穷大)。
  • 尽可能接近物理时钟PT,也就是说,HLC和PT的差值有个确定的边界。这条规则的好处是,只要两次操作间隔大于这个确定的边界,就可以保证外部一致性,无论是否是当前分布式系统内的。

复合逻辑时钟和物理时钟保持在一定误差范围内,同时多个节点的物理时钟利用NTP校准也保持在一定的误差范围内,从而就可以模仿spanner的TrueTime,使两个事务操作保持一定的时间差,实现多个独立事务之间的外部一致性。

CockroachDB
在这里插入图片描述
CockroachDB 实现 Snapshot Read:假定存在假设:当前物理时间​pt.e​+​MaxOffset​一定是当前系统的最大时间,发生在​pt.e​+​MaxOffset​之后的事务的物理时间一定大于当前事务(根据 NTP 时间同步特性​ε​ ≥ |​ptnode1​ – ​ptnode2​|得出该假设成立,​ε​代表集群中最大时钟漂移,也就是​MaxOffset​),CockroachDB 启动事务​e​,根据 HLC 的特性​ε​ ≥ |​pt.e​ – ​l.e​| ,可得推论:任意时刻当前集群的整体物理时间不可能超过​hlc.e​+​MaxOffset​。那么当 CockroachDB 执行 Snapshot Read 的时候有

  • 若事务​g​满足​hlc.e​ + ​MaxOffset​ < ​hlc.g​。根据 HLC 算法的特性 2(对任一事件​e​,一定有​l.e​ ≥ ​pt.e​),可得出:​pt.e​ < ​pt.g​,​e hb g​,事务​e​发生在​g​之前。
  • 若事务​g​满足​hlc.e​ < ​hlc.g​ <= ​hlc.e​ + ​MaxOffset​,那么此时事务​e​陷入一个叫 Uncertain Read 的状态,意思是不确定事务​g​的物理时间​pt.g​一定大于 ​pt.e​。例如:(1)hlc.e​=(10,10,2),​hlc.g​=(11,11,0),假设​MaxOffset​=5,此时​hlc.g​ > ​hlc.e​,但是​pt.g​ > ​pt.e​。(2)​hlc.e​=(10,10,2),​hlc.g​=(9,11,0),假设​MaxOffset​=5,此时​hlc.g​ > ​hlc.e​,但是​pt.g​ < ​pt.e​。在这种两种情况下 CockroachDB 无法获得一致的 Snapshot,因此当前事务​e​必须 Restart 并等待时间足够长之后,获取一个新的时间戳 ​hlc.g​ +1 重新执行。
  • 若事务​g​满足​hlc.g​ < ​hlc.e​, 那么根据 HLC 算法的 Snapshot Read,事务​e​可以直接执行 Snapshot Read。注意​hlc.g​ < ​hlc.e并不代表事务g提交在事务e启动之前,这有可能造成不可重复读或者幻读吗?不会的,因为不可重复读或者幻读至少需要两次读取操作。事务e第一次读取时,由于存在消息传递,所以事务g所在节点的时钟被更新,所以事务g的commit_ts一定会大于事务e的snapshot_ts,所以事务e第二次读取时不会看到事务g提交写入的数据。事务g不提交,则其写入的数据没有commit_ts,也是无法读取的。

所以复合逻辑时钟既保持了有关系的事务间的外部一致性,也可以保证独立事务之间的外部一致性,只要两个事务操作保持一定的时间差。

但是CockroachDB并没有达到可线性化
在这里插入图片描述

TrueTime

关于Spanner中的TrueTime和Linearizability
在这里插入图片描述
个人理解:
Spanner的TrueTime是利用GPS时钟和原子钟实现的, 可以提供非常精确的时钟, 使得每台机器上的本地时间戳和absolute time误差在ε之间。

TrueTime API TT.now()返回一个区间[earlist, latest], 保证absolute time是在区间以内,即earliest ≤ absolute time ≤ latest。所以latest - earlist = 2ε,(earlist, + latest) / 2为本地时间戳。

当A事务进行提交时,选择TT.now().latest作为事务提交时间戳commitA_ts,并且等待2ε时间后再进行提交。这样做的原因是让提交时的absolute time大于commitA_ts。那么当另一个节点的B事务在absolute time之后再开启,那么其提交时选定的事务提交时间戳commitB_ts必定是大于absolute time的。

为什么要求B事务一定要在A事务提交之后再开启呢?因为B事务如果在A事务提交之前开启,那么B事务开启时的absolute time可能小于commitA_ts,那么B事务选定的commitB_ts可能大于absolute time 且小于commitA_ts。

TrueTime的面向的对象是时间上没有重叠的两个事务,先提交的事务的时间戳,必定小于后提交事务的时间戳。这保证了连续写事务的线性一致性。

当写事务A提交之后,absolute time > commitA_ts,则此时开启一个读事务,将TT.now().latest作为其snapshot_read_ts,则可以保证snapshot_read_ts > absolute time > commitA_ts,即读取到写事务的数据。但是spanner里面还做了一些其他的优化,细节可看论文。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值