详解CockroachDB事务处理系统

本文提到的一些术语,比如Serializability和Linearizability,解释看Linearizability, Serializability and Strict Serializability。

本文中观点大部分都是参考了CockroachDB多篇官方blog,设计文档,代码以及相关资料,相对来说比较琐碎,而且有些地方没有交代的太清楚,这里尝试将这些资料融合起来。相信看完这篇文章,再看官方文档会更容易。

介绍

CockroachDB是一个支持SQL,支持分布式事务的ACID的分布式数据,支持ANSI SQL的最高隔离级别Serializability。

在一个分布式系统中,要支持Linearizability比较难,因为不同的机器之间时钟有误差,需要一个全局时钟。TiDB选择了和Percolator一样的方案,单点timestamp oracle提供时钟源。Google Spanner直接搞了一个基于硬件的TrueTime API提供相对来说比较精准的时钟。CockroachDB没有原子钟,也没有使用单点timestamp oracle,而是基于NTP来尽量同步机器之间的时钟偏移,NTP误差能达到250ms甚至更多,并且不能严格保证,这导致CockroachDB要保证Linearizability一致性很难,并且性能差。最终虽然CockroachDB支持Linearizability,但是官方不推荐。默认,CockroachDB支持Serializable隔离级别,但是不保证Linearizability。

Serializable

一个真实的数据库系统同一时刻会有很多并发的事务在执行,如何让这些事务觉得只有自己运行在数据库中不受其他事务的任何干扰是一个隔离级别的问题。Serializable就是不受任何干扰,弱一点的隔离级别有Repeatable Read, Read Committed, Read Uncommitted,Snapshot Isolation这些隔离级别多多少少会觉得受到了其他事务的干扰,如Repeatable Read有幻读问题,Snapshot Isolation有write skew问题,具体不赘述。可以参考a-critique-of-ansi-sql-isolation-levels

要实现一个支持Serializable隔离级别的数据库挺难的,很多数据库都不支持Serializable隔离级别,原因有几个,我觉得最重要的原因是性能不行。Oracle 11g默认隔离级别RC,最高隔离级别Snapshot Isolation,业界一些知名数据库对隔离级别的支持看When is "ACID" ACID? Rarely. 然而CockroachDB为了实现Serializable,花了大量的功夫。

一个事务通常包含多个读写操作,操作不同的行/列。数据库系统会对系统中的事务进行调度,事务会交叉执行,而不是一个接着一个。

 

一共三个事务,上图是数据库系统对这三个事务的一种调度。那么这个调度是不是Serializable的?这个有理论支持: serializability graph。这个理论引入了三种冲突,三种冲突都是对于不同的事务操作同一个数据而言:

  • RW: W覆盖了R读到的值

  • WR: R读到了W更新的值

  • WW: W覆盖了第一个W更新的值

对于任何一个事务调度结果,如果两个事务存在某种冲突,就在事务之间连上有向边(后面的事务指向前面的事务)。下图是上面事务调度的serializability graph。

已经证明如果一个事务调度的serializability graph中不存在环,那么这个事务调度就是Serializable的。那么CockroachDB是怎么做的?

CockroachDB事务处理系统

  • 多版本


CockroachDB的事务是Lock-Free的,不需要加任何读写锁,自然就需要维护数据的多个版本,版本通过timestamp来标识。

ACID中的A和I密切相关,都是通过并发控制协议保证的,下面先说明A是如何保证的,然后说明在并发的情况下,I是如何保证的。并发控制协议保证了A和I。

  • 原子性

一个分布式事务可能会对多个节点上的数据进行读写,如何保证原子性? 大家都知道分布式事务都是2PC,第一阶段做Prepare,把需要的读的数据读进来(怎么保证读到最新的数据,后面会说,这里先假设能读到),计算,最后把计算后的数据写入各个节点,但是不对外生效,即系统中其他事务暂且读不到这个数据。这种已经写入各个节点,但是没有生效的数据CockroachDB把它叫做write intent,这种write intent和实际的数据存储在一起,只是外部读不到而已。

那么这个事务的状态存在哪?事实上,在一个事务开始的时候,会往底层存储系统中写入一条记录,这个记录叫做Transaction Record,record会记录事务ID,事务状态,Pending(正在运行)还是Committed,还是Aborted,而write intent里存在key指向这个Transaction Record。提交事务,只需要把Transaction Record中的事务状态改成Committed即可,回滚事务改成Aborted即可。一旦事务状态修改成功,就可以返回给客户端,遗留的write intent会异步处理:commit时,将write intent的值覆盖原始值,删掉write intent,rollback时直接删掉write intent即可。

随后客户端过来读的时候,如果碰到了write intent(之前说了,write intent是异步删除),就会沿着write intent找到Transaction Record,看看事务的状态,如果状态是committed,返回write intent中的值,如果Abort就会返回原始的值。如果是Pending,说明这个事务还在正常跑,遇到了写写冲突,如何解决写写冲突? 这个牵扯到隔离级别和并发控制协议,看下面。

  • 隔离性

之前提到,数据是多版本的,版本通过timestamp来标识。timestamp是读写事务/写事务在事务开始的时候从本机拿到的wall time(实际上是HLC,一种基于物理时钟的可以捕获因果关系的逻辑时钟),这个timestamp只是这个事务最后commit的候选timestamp而已,不一定是最终的commit的timestamp(根本原因是机器之间存在时钟offset,后面会讲到),这里先假定,拿到了一个最终的timestamp。timestamp越大,说明版本越新。这个事务的所有写入的数据都会打上这个timestamp作为版本标识。在这样一个系统中,serializability graph大概是下面的样子:

frameborder="0" scrolling="no" style="border-width: medium; width: 600px; height: 450px;">

上面这个图是无环的。下面这个图是有环的:

frameborder="0" scrolling="no" style="border-width: medium; width: 600px; height: 450px;">

回到Serializability,为了实现Serializability,需要保证事务的调度是无环的。CockroachDB通过在timestamp的反方向避免之前提到的三种冲突,从而在图中就不会有和timestamp走向一致的边,进而保证无环。最后,CockroachDB的serializability graph长如下样子:

frameborder="0" scrolling="no" style="border-width: medium; width: 600px; height: 450px;">

CockroachDB保证如下约束:

  • RW: W的时间戳只能比R的大,这只会产生回头边(通过在每个节点维护一个Read Timestamp Cache)。

  • WR: R只会读比自己timestamp小的最大的版本,这也只会产生回头边。

  • WW:第二W的timestamp比第一个W的timestamp大,这也只会产生回头边。

也就是说,只要保证一个事务只与timestamp更小的事务冲突,就能保证无环。

  • Recoverable

残酷的是,仅仅保证无环能实现Serializability,同时还需要保持数据库的一致性,即ACID中的C。考虑如下场景:

T1,T2两个事务,timestamp(T1) < timestamp(T2),T1更新A,还没有提交,T2读A。这是一个WR冲突,但是由于这个冲突是回头边,所以是允许的。为了维护上面提到的RW约束,T2必须读T1的更新(W的timestamp必须比R大,然而T1比T2小)。然而,T2读T1对A的更新有什么问题?

  • T2读T1的更新。如果最后T2 commit,随后T1回滚,这个会违反T1的原子性:T1没有写成功的值被T2读到了。

CockroachDB使用一种比较苛刻的调度来处理这种场景:所有的操作只能在已经committed的数据上进行!下面讲讲CockroachDB的这种苛刻的调度是如何保证的,这里就需要用到前面原子性的知识。

  • Strict Scheduling

从上一节以及原子性章节可以得知,一个事务碰到了一个write intent,那么说明有可能写write intent的事务还没有结束(因为write intent是异步清除的),这就说明有可能碰到了uncommitted的数据。这时,当前事务会去检查write intent所在的事务的状态,如果已经提交了,将write intent覆盖旧值然后清除write intent即可。如果已经回滚了,那么直接清除write intent就行。如果是Pending,正在运行呢?这个时候,就要看事务的优先级了,优先级低的事务需要abort,事务开始时赋予的优先级是random的。CockroachDB会保证被abort的事务在restart之后优先级会提高。

到这里,CockroachDB如何提供Serializability隔离级别就讲完了,注意,这里的前提是每个事务都被赋予了一个合适的timestamp,什么叫做合适的 timestamp? 一个分布式读/读写事务需要能读到最新的已经committed的数据。

  • CockroachDB如何为事务赋予时间戳

CockroachDB使用NTP进行时钟同步,NTP基本能保证机器之间的时钟offset小于250ms,但是这也不绝对,这受到网络延时,系统load等因素的影响。从前面可以看出,CockroachDB的Serializability依赖于集群内机器之间的时钟clock在一个范围ε内。这个范围可以配置,默认250ms。任何一个时刻,在一台机器上拿到wall time为t,那么集群中可能存在的最大wall time是t+ε。

一个事务T开始时,先拿一个本地Wall time(实际上是HLC),记作t,根据NTP定义,集群内机器此刻最大的Wall time为t+ε,如果事务执行过程中读到的数据对象处于[t,t+ε]之间,我们是不知道这个值到底是在T开始之后才commit的,还是T开始之前就commit的。所以T需要restart,重新设置t为碰到的这个timestamp。

总结

总体来看,CockroachDB的并发控制协议是一个Lock-Free的,不加锁的,乐观的协议。对于数据竞争比较强的应用不太适合,需要频繁的restart事务。并且,NTP这个东西不能总是保证机器之间时钟误差在一个范围内,一旦超过这个范围,就会违反Serializability。

参考文献

Serializable, Lockless, Distributed: Isolation in CockroachDB

How CockroachDB Does Distributed, Atomic Transactions

CockroachDB beta-20160829

cockroachdb/cockroach

Living Without Atomic Clocks

Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases

原文地址:http://www.cnblogs.com/foxmailed/p/6885368.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325214.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《金色梦乡》金句摘抄(四)

System.out.println("《金色梦乡》"); System.out.println("小说类型的书就是比散文类型的书好看"); System.out.println("通俗易懂");森田森吾的手朝副驾驶座这边伸了过来。空气似乎在某处破裂了&#xff0c;由此而产生的震动转化为波纹&#x…

JavaFX官方教程(二)之JavaFX体系结构

翻译自 JavaFX体系结构 本章提供了JavaFX体系结构和生态系统的高级描述。 图2-1说明了JavaFX平台的架构组件。图中的部分描述了每个组件以及这些部件如何互连。JavaFX公共API下面是运行JavaFX代码的引擎。它由包含JavaFX高性能图形引擎的子组件组成&#xff0c;称为Prism; …

Work Time Manager【开源项目】- 创建自己日志组件 2.0重构

这次我们真是开始来聊聊开源项目里&#xff0c;小而有用的模块或者组件的开发思想。 同时&#xff0c;软件已经更新到1.60的版本了&#xff0c;支持新用户注册&#xff0c;可以不再使用统一的test账户了。 您可以通过以下路径进行下载&#xff1a; 1、在GitHub上fellow一下项目…

《金色梦乡》金句摘抄(五)

System.out.println("《金色梦乡》"); System.out.println("小说类型的书就是比散文类型的书好看"); System.out.println("通俗易懂");路笔直地朝前延伸。腿脚的疲惫和喘息的痛苦让青柳雅春很想瘫坐休息。河里的鱼儿们都带有商标&#xff0c;有本…

JavaFX官方教程(三)之JavaFX示例应用程序入门

翻译自 JavaFX示例应用程序入门 此示例应用程序集旨在帮助您开始使用常见的JavaFX任务&#xff0c;包括使用布局&#xff0c;控件&#xff0c;样式表&#xff0c;FXML和视觉效果。 Hello World&#xff0c;JavaFX Style JavaFX中的表单设计 用CSS设计的花式设计 使用F…

SSH(Spring+Struts2+Hibernate)框架搭建步骤(含配置文件以及运行结果)

1.创建web项目2.导入ssh 所需要的多有jar包&#xff0c;到web-inf下面的lib里面3.将导入过来的jar包都build--path一下4.切换到myeclipse database视图中&#xff0c;添加链接数据库的链接5.新建一个数据库连接&#xff08;如果忘记了数据库链接时你可以去下面的网址中查看&…

Unity 游戏用XLua的HotFix实现热更原理揭秘

本文通过对XLua的HoxFix使用原理的研究揭示出来这样的一套方法。这个方法的第一步&#xff1a;通过对C#的类与函数设置Hotfix标签。来标识需要支持热更的类和函数。第二步&#xff1a;生成函数连接器来连接LUA脚本与C#函数。第三步&#xff1a;在C#脚本编译结束后&#xff0c;使…

外边距

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title><style>#div1{border: 1px solid red;background-color: darkseagreen;width: 300px;height: 300px;/*外边距 当前块标签外部的和父级块标签之间的距离…

JavaFX官方教程(四)之Hello World,JavaFX样式

翻译自 Hello World&#xff0c;JavaFX Style 教你创建和构建JavaFX应用程序的最佳方法是使用“Hello World”应用程序。本教程的另一个好处是&#xff0c;它使您能够测试您的JavaFX技术是否已正确安装。 本教程中使用的工具是NetBeans IDE 7.4。在开始之前&#xff0c;请确…

比较器的比较规则

返回负数的时候 第一个参数排在前面 返回正数的时候 第二个参数排在前面 返回0的时候 谁在前面无所谓

WebAssembly,开发者赢了

自从WebAssembly标准发布以及各大浏览器完成对其默认支持之后&#xff0c;WebAssembly成为前端热门话题。在WebAssembly之前&#xff0c;类似的前端二进制标准有火狐主导的asm.js和Chrome主导的PNaCl。二者均用于将后端C/C代码用于前端&#xff0c;作为它们折中方案&#xff0c…

《金色梦乡》金句摘抄(六)

System.out.println("《金色梦乡》"); System.out.println("小说类型的书就是比散文类型的书好看"); System.out.println("通俗易懂");越不注重外表越容易老&#xff0c;到时候变老的速度可比从山上滚下来还快。那还真是个让人记忆深刻的分手方式…

输入标签from

<!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body>账号:<input type"text" /><br />密码:<input type"password" /><br /><!--设置…

JavaFX官方教程(五)之在JavaFX中创建表单

翻译自 在JavaFX中创建表单 在开发应用程序时&#xff0c;创建表单是一项常见活动。本教程将向您介绍屏幕布局的基础知识&#xff0c;如何将控件添加到布局窗格以及如何创建输入事件。 在本教程中&#xff0c;您将使用JavaFX构建如图4-1所示的登录表单。 图4-1登录表单 本入…

SSL-练习题目:种树 题解

种树(normal) Time Limit:1000MS Memory Limit:65536K Total Submit:213 Accepted:114 Description 在6*6的方格地盘中&#xff0c;种植24颗树&#xff0c;使每行、每列都有4颗树。   求出所有可能的种植方案总数。   种植方案的说明&#xff1a;输出一个6*6的矩阵&am…

哈希表的key的类型(传值与传引用)

放入哈希表的东西&#xff0c;如果是基础类型 内部按值传递&#xff0c;内存占用就是这个东西的大小. 放入哈希表的东西&#xff0c;如果不是基础类型 内部按引用传递&#xff0c;内存占用是这个东西内存地

《金色梦乡》金句摘抄(七)

System.out.println("《金色梦乡》"); System.out.println("小说类型的书就是比散文类型的书好看"); System.out.println("通俗易懂");轰叔的笑容中带着经验丰富的匠人才有的自信与骄傲&#xff0c;让青柳雅春佩服不已。但他的动作却只是用一根食…

来腾讯云开发者实验室 学习.NET

腾讯云开发者实验室为开发者提供了一个零门槛的在线实验平台,开发者实验室提供的能力&#xff1a; 零门槛扫码即可免费领取实验机器&#xff0c;支持使用自有机器参与&#xff0c;实验完成后支持保留实验成果&#xff1b; 在线WEBIDE支持shell命令操作&#xff0c;支持机器文件…