半年前看过一下spanner论文,对于其中True time相关的内容相当的疑惑,虽然True Time本身只有3个接口,Wound wait的方式也非常简单,但是因为基础较为欠缺,依然对很多问题很疑惑。

  1. 外部一致性是什么?
  2. 为什么需要时钟?
  3. True Time解决了什么问题

True time的理论非常简单,具体介绍可以参考spanner论文,或者这篇博客:
True Time

1. 外部一致性是什么?

这个词好像只在spanner的论文中看到过,按照论文中的说法就是,事务x结束之后,事务y再开始执行,那么y一定可以看到x的修改。好像挺好理解的,有点儿听君一席话,如听一席话的感觉;好像又不太好理解,这也没有明确的下一个约束的定义。
其实可以把这个外部一致性拆分成两个约束,Serializable(可串行化)和Linearizable(可线性化)。
spanner是一个分布式数据库,整个数据库的数据被被分片到多个shard上,而为了高可用,每个shard中存放了数据的三副本,使用paxos协议来管理三个数据副本,即每个shard是一个paxos group。
其中可串行化的概念来自于数据库隔离级别,其中最高的级别就是可串行化,意味着多个事务并发执行的结果和这些事务一个一个执行的结果是一致的,这是对spanner中分布式事务隔离级别的约束。
可线性化这个概念和事务无关,现在忘掉事务,只考虑单纯的get和set,没get都能读取到之前成功set的值,就是可线性化,即每次保证能够读取到新数据,因为spanner中数据是多副本存在的,读取可能从多个副本中任意读取,可线性化意味着,每次向一个shard读取数据,不论读的哪个副本,都能读取到最新值。
可线性化+可串行化=外部一致性

并发的顺序系统一般都是不做约束的,事务1发起,事务2发起,事务1结束,事务2结束,那么不保证事务2一定可以看到事务1的修改,这是并发。

2. 为什么需要时钟

时钟是为事务服务的,现在可以忘掉paxos,多副本,可线性化相关的问题。
就单机数据库而言,要实现可串行化的隔离级别,在不考虑性能的前提下,最简单的方案就是SS2PL,读写数据加锁就行,通过锁来互斥有冲突的事务,让它们最终可以有序执行,达成可串行化。
但是这样的实现性能很成问题,所以大多数数据库都进行了优化,引入了数据多版本(Multi Version),以此来优化读事务,即读数据读的是快照数据,不受写的影响,写数据是增加新的版本,不是在原来的数据上做修改,不影响旧版本数据读取,读不阻塞写,写不阻塞读。
既然有了多版本,那么就需要版本号,在单机数据库上很好处理这个事情,每个事务有互不相同ID,一般来说事务id是递增发放的。那么写事务写的新数据版本号就是该事物的id。那么读取数据时又该怎么判断哪些数据是可以读取的呢?这个和隔离级别有关,比如在read commited隔离级别下,可以在每次查询语句执行时取一下当前数据库中最大已提交的事务id,该事务就应该读取所有版本号不大于该id的数据中的最新版本,就能读取到已提交的最新数据,如果是read repeated,那就在读事务开始的时候去一次当前数据库的最大已完成事务id即可,后续的所有查询语句都带上该id,就能保证查询到的是同一个时间节点的快照数据。
说完了多版本,其实时钟就是为多版本服务的,刚才介绍多版本数据的版本号时使用事务id举的例子,但是在分布式数据库中,没法像单机数据库中使用事务id号作为版本号,除非搞个中心发号器,那rpc开销就大了,而且也存在单点问题,发号器崩了集群就完蛋,达不到分布式容错的目的。所以一般在分布式数据库中一般使用时间戳来作为版本号,通过时钟来给数据库中的数据定序,判断可见行。

3. True Time解决了什么问题

spanner的目标就是提供一个事务型,且保证外部一致性的数据库。写事务通过二阶段事务中加索(2PL)来实现,读事务通过多版本数据来实现快照读。看起来很美好,问题是分布式数据库中没法直接用事务id来作为版本号,所以在spanner中使用了时间戳作为版本号。
先来考虑一下,如果使用普通的时间戳来作为版本号,会存在什么问题?一般服务器会使用NTP协议来同步时钟,根据网络情况的不同,不同机器间的时钟会有一些误差,误差一般在百毫秒级。具体的数字先不管,我们知道服务器的时钟不准就行了,有的快有的慢,这会导致什么问题呢?比如有个服务器A时钟比较快,比绝对时钟快10分钟,服务器B的时钟和绝对时钟一致,那么在0(A:10,B:0)分钟的时候,有个客户端想A发起一个写事务W,然后提交,不考虑网络延迟和执行时间,那么A的提交时间就是10,提交W后返回给客户端,完成了该事物W。一分钟后,另一个客户端发起一个读数据的事务R,先在B上读取一批数据,B给给这个读事务分配了一个时间戳1(当前绝对时间),并把数据和时间戳返回给客户端,客户端该事务的后续读请求都带上该时间戳,以保证可重复读(同一个事物用于都读同一个版本),那么客户端带上1这个时间戳去A上读,并不能在A上读取到W写入的数据,因为W写入的数据版本号是10,不满足外部一致性(R在W结束之后才开始,但是并没有读取到W的写入)

具体的反例还有很多,简单的说就是存在一些问题:
(1)时钟太快,把数据提交在了未来,时钟慢的机器根本就读不到提交的数据
(2)时钟太慢,某些事物的协调者时钟太慢(数据的版本时间戳由二阶段提交的协调者确定),导致后提交的事务的时间戳比先提交事务的时间戳小,后提交进去的数据变成了旧数据。
对于二阶段提交的事务,可以尝试多种提交时间戳的选择(选协调者的时间,选所有参与者中最大的时间等),但是因为不同服务器的时钟之间存在误差,都会遇到上述的两个问题,就不一一举例子了。

现在来看一下True Time是怎么解决时钟存在误差的问题的。首先spanner中使用了原子钟和GPS时钟,这样spanner的所有数据中心之间的时间误差都能控制在很小的范围内,论文中表明误差不超过7ms。True Time提供了如下三个接口:

Method Returns
TT.now() TTinterval: [earliest, latest]
TT.after(t) true if t has definitely passed
TT.before(t) true if t has definitely not arrived

这三个接口的返回结果可以保证和语义绝对一致。调用了now(),会返回一个时间段,表明当前绝对时间一定在该范围内,after表明当前绝对时间一定超过了给定时间t,before表明当前绝对时间一定没有超过给定时间t。

spanner中提交时间遵循一下规则:

  1. 提交时间一定大于所有参与者prepare的时间
  2. 提交时间一定大于协调者本地时间,即选择MAX(TT.now().lastest, PT1, PT2…)作为提交时间,其中TT.now().lastest是在获取协调者上当前可能的最大时间,PT为prepare time,分别表示每个参与者prepare事务的时间
  3. 一定要等到TT.after©返回true时才能提交。(C表示协调者选定的提交时间)

来看个简单的例子,有S1,S2,S3共3个服务器,Tabs表示标准时间轴,其中事务T1有两个参与者,S1和S2,事务T2有两个参与者S2和S3,两个事务的协调者都是S2,现在发起T1事务,首先在两个参与者节点上prepare,S1的PT(prepare time)是15,S2的是7,prepare结束后都汇报给协调者S2,S2获取了两个PT分别是15和7,按照第一条规则,选择最大的PT,即15,然后在本地调用一次TT.now(),S2的本地时钟是7,假设TT.now().latest值为14,那么该选择的时间戳就是MAX(15,14)=15,然后按照第三条规则,等待到TT.after(15)返回true时,才能进行提交,最终可能在绝对时间为19时,完成T1提交。T2同理。

现在来看看上面的三个约束:
第一个条件很好理解,二阶段提交,提交时间肯定应该晚于所有参与者prepare的时间,这是符合认知的
第二个,提交时间一定大于协调者本地时间,这个也很好理解,总不能选择一个时间戳小于本地时间吧,这不就相当于写了一个旧的数据到数据库里面吗?(改变过去不符合逻辑)
第三个,即一定要等到绝对时间已经超过了选定的提交时间戳C之后才能提交。TT给定的语义是绝对准确的,一个服务器调用TT.after©返回True,那么集群中所有节点调用该接口都一定返回True,即表示,当等待到TT.after©返回True时,集群中所有节点的时间都已经超过了C,这就能保证,在该事务提交之后,再发起的新事务时间戳一定大于C,后续的新事务一定可以看到该事务的修改。


论文中的TT保证外部一致性(此处不考虑paxos多副本)的证明如图,s1是事务1选择的提交时间戳,tabs(e1commit)表示事务1提交的绝对时间,这是有第三条约束保证的,s1一定小于事务1提交的绝对时间,事务1的提交时间小于事务2的开始时间,这是外部一致性的假设,事务2的开始绝对时间一定小于提交时的绝对时间(因为运行了一段时间),提交时间一定要大于TT.now().latest,这就保证了事务2的提交时间s2大于当前的绝对时间,最终推理出s1<s2,所以事务2一定可以看到事务1的修改。

如何理解Ture Time相关推荐

  1. Python逻辑运算符 and ,or not 的理解

    要理解Python逻辑运算符 and ,or not  这三个 ,你需要知道  在 python里面,0.''.[].().{}.None为 false,其它任何东西都为true 1 and(这个会j ...

  2. exist后select加数字的理解

      查看文章     相关子查询中exists后select 加数字的理解 2010-07-23 17:16 前提:两个基础表 SQL> select * from courses; COURS ...

  3. 相关子查询中exists后select 加数字的理解

      查看文章     相关子查询中exists后select 加数字的理解 2010-07-23 17:16 前提:两个基础表 SQL> select * from courses; COURS ...

  4. 深入理解JS的面向对象(更新中)

    面向对象是软件开发方法.面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统.交互式界面.应用结构.应用平台.分布式系统.网络管理结构.CAD技术.人工智能等领域.面向对象是一种对现实世 ...

  5. 对python的认识800字_我对python里True和False的理解

    我说一下的的拙见- and 和 or 是用来处理多个条件,得出结果的,要处理的东西可以化为最简单的True和False. 什么是T,什么是F,就不说了,百度里一大堆,什么集合,01,哈希,几乎所有都可 ...

  6. JavaScript的几个概念简单理解(深入解释见You Don't know JavaScript这本书)

    ES201X是JavaScript的一个版本. ES2015新的feature let, const Scope, 块作用域 Hoisting Closures DataStructures: Obj ...

  7. C#一元运算重载的深入理解

    C#一元运算重载的深入理解 using System; using System.Diagnostics; using System.Text; using System.Collections; u ...

  8. 如何理解java反射_怎么理解java反射

    怎么理解java反射? 概述 Java 反射是可以让我们在运行时获取类的方法.属性.父类.接口等类的内部信息的机制.也就是说,反射本质上是一个"反着来"的过程.我们通过new创建一 ...

  9. PX4/Pixhawk---uORB深入理解和应用

     The Instructions of uORB 『PX4/Pixhawk』   『软件体系结构』 『uORB』 『主题发布』 『主题订阅』 1 简介 1.1 PX4/Pixhawk的软件体系结 ...

最新文章

  1. 一步一步深入spring(6)--使用基于XML配置的spring实现的AOP
  2. 【机器视觉】 dev_set_shape算子
  3. 成功抓取豆瓣读书的所有书籍
  4. IPv6应用普及,任重而道远
  5. android覆盖扩散动画,[Android]多层波纹扩散动画——自定义View绘制
  6. API设计原则 - Qt官网的设计实践总结
  7. 中国风喜庆传统新年元旦海报PSD分层模板
  8. Spring(一)概述
  9. 小D课堂 - 新版本微服务springcloud+Docker教程_5-01分布式核心知识之熔断、降级
  10. windows7安装中注入USB3.0和NVME驱动
  11. PHP生成二维码名片带LOGO并解决LOGO失真
  12. 5个免费可商用的图片素材网站,赶快收藏
  13. 公式图片识别转换工具
  14. Visual Studio Ultimate 2015 旗舰版 Preview
  15. 智能智慧型停车场管理系统解决方案
  16. SpringBoot项目运行时出现A cookie header was received警告问题
  17. (称重问题)假设你有8个球,其中一个略微重一些,但是找出这个球的惟一方法是将两个球放在天平上对比
  18. Java IDEA的使用
  19. JSD1806——8——SpringMVC
  20. RC电路(积分电路,微分电路)

热门文章

  1. Python模拟鼠标按键(长按)
  2. (VC++2013)MFC自绘圆形按钮
  3. 【无标题】百度地图的基本使用
  4. linux pppd源码下载_LINUX下的拨号利器:wvdial和pppd —— 转载
  5. Unity 画面质量设置
  6. 2022上海省赛(A,E,G,H,M,N)
  7. 蚂蚁金服首席数据科学家漆远:AI技术开放,与业界融合共创
  8. 全套3D游戏建模自学资料
  9. drag方法——>拖拽
  10. Java8 :流式数据处理