diff --git a/ddia/ch1.md b/ddia/ch1.md index 5f947cf..e18ef92 100644 --- a/ddia/ch1.md +++ b/ddia/ch1.md @@ -385,7 +385,7 @@ Twitter的第一个版本使用了方法1,但系统努力跟上主页时间线 ## 参考文献 -[1]: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.68.9136&amp;amp;amp;amp;rep=rep1&amp;amp;amp;amp;type=pdf "Michael Stonebraker and Uğur Çetintemel: “'One Size Fits All': An Idea Whose Time Has Come and Gone,” at *21st International Conference on Data Engineering* (ICDE), April 2005." +[1]: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.68.9136&amp;amp;amp;amp;amp;rep=rep1&amp;amp;amp;amp;amp;type=pdf "Michael Stonebraker and Uğur Çetintemel: “'One Size Fits All': An Idea Whose Time Has Come and Gone,” at *21st International Conference on Data Engineering* (ICDE), April 2005." [2]: http://www.sei.cmu.edu/reports/92tr033.pdf "Walter L. Heimerdinger and Charles B. Weinstock: “A Conceptual Framework for System Fault Tolerance,” Technical Report CMU/SEI-92-TR-033, Software Engineering Institute, Carnegie Mellon University, October 1992." [3]: https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf "Ding Yuan, Yu Luo, Xin Zhuang, et al.: “Simple Testing Can Prevent Most Critical Failures: An Analysis of Production Failures in Distributed Data-Intensive Systems,” at 11th USENIX Symposium on Operating Systems Design and Implementation (OSDI), October 2014." [4]: http://techblog.netflix.com/2011/07/netflix-simian-army.html "Yury Izrailevsky and Ariel Tseitlin: “The Netflix Simian Army,” techblog.netflix.com, July 19, 2011." @@ -515,3 +515,11 @@ Twitter的第一个版本使用了方法1,但系统努力跟上主页时间线 (COMPSAC), July 2008. [doi:10.1109/COMPSAC.2008.50](http://dx.doi.org/10.1109/COMPSAC.2008.50) + + + +------ + +| 上一章 | 目录 | 下一章 | +| ----------------------------------- | ------------------------------- | ------------------------------------ | +| [第一部分:数据系统基础](part-i.md) | [设计数据密集型应用](README.md) | [第二章:数据模型与查询语言](ch2.md) | \ No newline at end of file diff --git a/ddia/ch2.md b/ddia/ch2.md index 81934bc..b4868ae 100644 --- a/ddia/ch2.md +++ b/ddia/ch2.md @@ -63,7 +63,7 @@ 现在大多数应用程序开发都是在面向对象的编程语言中完成的,这导致了对SQL数据模型的普遍批评:如果数据存储在关系表中,那么应用程序代码中的对象之间需要一个笨拙的转换层,表,行和列的数据库模型。模型之间的不连贯有时被称为**阻抗不匹配(impedance mismatch)**[^i]。 -[^i]: 从电子学借用一个术语。每个电路的输入和输出都有一定的阻抗(交流电阻)。当您将一个电路的输出连接到另一个电路的输入时,如果两个电路的输出和输入阻抗匹配,则连接上的功率传输将被最大化。阻抗不匹配可能导致信号反射和其他问题 +[^i]: 从电子学借用一个术语。每个电路的输入和输出都有一定的阻抗(交流电阻)。当您将一个电路的输出连接到另一个电路的输入时,如果两个电路的输出和输入阻抗匹配,则连接上的功率传输将被最大化。阻抗不匹配可能导致信号反射和其他问题 像ActiveRecord和Hibernate这样的对象关系映射(ORM)框架减少了这个翻译层需要的样板代码的数量,但是它们不能完全隐藏这两个模型之间的差异。 @@ -1085,3 +1085,10 @@ Datalog方法需要对本章讨论的其他查询语言采取不同的思维方 “[ROOT for Big Data Analysis](http://indico.cern.ch/getFile.py/access?contribId=13&resId=0&materialId=slides&confId=246453),” at *Workshop on the Future of Big Data Management*, London, UK, June 2013. + +------ + +| 上一章 | 目录 | 下一章 | +| -------------------------------------- | ------------------------------- | ---------------------------- | +| [第一章:可靠、可扩展、可维护](ch1.md) | [设计数据密集型应用](README.md) | [第三章:存储与检索](ch3.md) | + diff --git a/ddia/ch3.md b/ddia/ch3.md index 583e66c..3bf7367 100644 --- a/ddia/ch3.md +++ b/ddia/ch3.md @@ -854,3 +854,12 @@ WHERE product_sk = 31 AND store_sk = 3 Discovery*, volume 1, number 1, pages 29–53, March 2007. [doi:10.1023/A:1009726021843](http://dx.doi.org/10.1023/A:1009726021843) + + + +------ + +| 上一章 | 目录 | 下一章 | +| ------------------------------------ | ------------------------------- | ---------------------------- | +| [第二章:数据模型与查询语言](ch2.md) | [设计数据密集型应用](README.md) | [第四章:编码与演化](ch4.md) | + diff --git a/ddia/ch4.md b/ddia/ch4.md index f6c6988..eaca14c 100644 --- a/ddia/ch4.md +++ b/ddia/ch4.md @@ -698,3 +698,9 @@ actor模型是单个进程中并发的编程模型。逻辑被封装在角色中 “[Postscript: Maps](http://learnyousomeerlang.com/maps),” *learnyousomeerlang.com*, April 9, 2014. + +------ + +| 上一章 | 目录 | 下一章 | +| ---------------------------- | ------------------------------- | --------------------------------- | +| [第三章:存储与检索](ch3.md) | [设计数据密集型应用](README.md) | [第二部分:分布式数据](part-iimd) | \ No newline at end of file diff --git a/ddia/ch5.md b/ddia/ch5.md index cd9f111..acf8a8b 100644 --- a/ddia/ch5.md +++ b/ddia/ch5.md @@ -325,15 +325,15 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录 ***性能*** -在单活配置中,每个写入都必须穿过互联网,进入主库所在的数据中心。这可能会增加写入时间,并可能违背了设置多个数据中心的初心。在多活配置中,每个写操作都可以在本地数据中心进行处理,并与其他数据中心异步复制。因此,数据中心之间的网络延迟对用户来说是透明的,这意味着感觉到的性能可能会更好。 +​ 在单活配置中,每个写入都必须穿过互联网,进入主库所在的数据中心。这可能会增加写入时间,并可能违背了设置多个数据中心的初心。在多活配置中,每个写操作都可以在本地数据中心进行处理,并与其他数据中心异步复制。因此,数据中心之间的网络延迟对用户来说是透明的,这意味着感觉到的性能可能会更好。 ***容忍数据中心停机*** -在单主配置中,如果主库所在的数据中心发生故障,故障转移可以使另一个数据中心里的追随者成为领导者。在多活配置中,每个数据中心可以独立于其他数据中心继续运行,并且当发生故障的数据中心归队时,复制会自动赶上。 +​ 在单主配置中,如果主库所在的数据中心发生故障,故障转移可以使另一个数据中心里的追随者成为领导者。在多活配置中,每个数据中心可以独立于其他数据中心继续运行,并且当发生故障的数据中心归队时,复制会自动赶上。 ***容忍网络问题*** -数据中心之间的通信通常穿过公共互联网,这可能不如数据中心内的本地网络可靠。单主配置对这数据中心间的连接问题非常敏感,因为通过这个连接进行的写操作是同步的。采用异步复制功能的多活配置通常能更好地承受网络问题:临时的网络中断并不会妨碍正在处理的写入。 +​ 数据中心之间的通信通常穿过公共互联网,这可能不如数据中心内的本地网络可靠。单主配置对这数据中心间的连接问题非常敏感,因为通过这个连接进行的写操作是同步的。采用异步复制功能的多活配置通常能更好地承受网络问题:临时的网络中断并不会妨碍正在处理的写入。 有些数据库默认情况下支持多主配置,但使用外部工具实现也很常见,例如用于MySQL的Tungsten Replicator [26],用于PostgreSQL的BDR [27]以及用于Oracle的GoldenGate [19]。 @@ -361,7 +361,7 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录 但是,为了加速协作,您可能希望将更改的单位设置得非常小(例如,一个按键),并避免锁定。这种方法允许多个用户同时进行编辑,但同时也带来了多领导者复制的所有挑战,包括需要解决冲突[32]。 -### 处理写冲突 +### 处理写入冲突 多领导者复制的最大问题是可能发生写冲突,这意味着需要解决冲突。 @@ -375,7 +375,7 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录 在单主数据库中,第二个写入将被阻塞,并等待第一个写入完成,或中止第二个写入事务,强制用户重试。另一方面,在多活配置中,两个写入都是成功的,并且在稍后的时间点仅仅异步地检测到冲突。那时要求用户解决冲突可能为时已晚。 -原则上,可以使冲突检测同步 - 即等待写入被复制到所有副本,然后再告诉用户写入成功。但是,通过这样做,您将失去多领导者复制的主要优点:允许每个副本独立接受写入。如果您想要同步冲突检测,那么您可以使用单主程序复制。 +原则上,可以使冲突检测同步 - 即等待写入被复制到所有副本,然后再告诉用户写入成功。但是,通过这样做,您将失去多主复制的主要优点:允许每个副本独立接受写入。如果您想要同步冲突检测,那么您可以使用单主程序复制。 #### 避免冲突 @@ -383,38 +383,38 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录 例如,在用户可以编辑自己的数据的应用程序中,可以确保来自特定用户的请求始终路由到同一数据中心,并使用该数据中心的领导者进行读写。不同的用户可能有不同的“家庭”数据中心(可能根据用户的地理位置选择),但从任何用户的角度来看,配置基本上都是单一的领导者。 -但是,有时您可能需要更改指定的记录的领导 - 可能是因为一个数据中心出现故障,您需要将流量重新路由到另一个数据中心,或者可能是因为用户已经迁移到另一个位置,现在更接近不同的数据中心。在这种情况下,冲突避免会中断,你必须处理不同领导人同时写入的可能性。 +但是,有时您可能需要更改指定的记录的主库——可能是因为一个数据中心出现故障,您需要将流量重新路由到另一个数据中心,或者可能是因为用户已经迁移到另一个位置,现在更接近不同的数据中心。在这种情况下,冲突避免会中断,你必须处理不同主库同时写入的可能性。 -#### 趋于一致的状态 +#### 收敛至一致的状态 -单主程序数据库按顺序应用写操作:如果同一个字段有多个更新,则最后一个写操作将确定该字段的最终值。 +单主数据库按顺序应用写操作:如果同一个字段有多个更新,则最后一个写操作将确定该字段的最终值。 -在多领导者配置中,没有定义的写入顺序,所以不清楚最终值应该是什么。在图5-7中,标题1首先更新为B,然后更新为C;在领导者2中,首先更新为C,然后更新为B.两个订单都不是“更正确”的。 +在多主配置中,写入顺序没有定义,所以最终值应该是什么并不清楚。在[图5-7](img/fig5-7.png)中,在主库1中标题首先更新为B而后更新为C;在主库2中,首先更新为C,然后更新为B。两个顺序都不是“更正确”的。 -如果每个副本只是按照它看到写入的顺序写入,那么数据库最终将处于不一致的状态:最终值将是在领导者1处的C和在领导者2处的B.这是不可接受的 - 每个复制方案都必须确保数据在所有副本中最终都是相同的。因此,数据库必须以一种趋同的方式解决冲突,这意味着所有副本必须在所有更改都被复制时达到相同的最终值。 +如果每个副本只是按照它看到写入的顺序写入,那么数据库最终将处于不一致的状态:最终值将是在主库1的C和主库2的B。这是不可接受的,每个复制方案都必须确保数据在所有副本中最终都是相同的。因此,数据库必须以一种**收敛(convergent)**的方式解决冲突,这意味着所有副本必须在所有变更复制完成时收敛至一个相同的最终值。 实现冲突合并解决有多种途径: -* 给每个写入一个唯一的ID(例如,一个时间戳,一个长的随机数,一个UUID或者一个键和值的哈希),挑选最高ID的写入作为胜利者,并丢弃其他写入。如果使用时间戳,这种技术被称为最后一次写入胜利(LWW)。虽然这种方法很流行,但是很容易造成数据丢失[35]。我们将在本章末尾更详细地讨论LWW(第184页的“检测并发写入”)。 -* 为每个副本分配一个唯一的ID,并让始发于较高编号副本的写入始终优先于源自较低编号副本的写入。这种方法也意味着数据丢失。 +* 给每个写入一个唯一的ID(例如,一个时间戳,一个长的随机数,一个UUID或者一个键和值的哈希),挑选最高ID的写入作为胜利者,并丢弃其他写入。如果使用时间戳,这种技术被称为**最后写入胜利(LWW, last write wins)**。虽然这种方法很流行,但是很容易造成数据丢失[35]。我们将在本章末尾更详细地讨论LWW(第184页的“检测并发写入”)。 +* 为每个副本分配一个唯一的ID,ID编号更高的写入具有更高的优先级。这种方法也意味着数据丢失。 * 以某种方式将这些值合并在一起 - 例如,按字母顺序排序,然后连接它们(在图5-7中,合并的标题可能类似于“B / C”)。 -* 在保留所有信息的显式数据结构中记录冲突,并编写解决冲突的应用程序代码(可能通过提示用户)。 +* 在保留所有信息的显式数据结构中记录冲突,并编写解决冲突的应用程序代码(也许通过提示用户的方式)。 #### 自定义冲突解决逻辑 -作为解决冲突最合适的方法可能取决于应用程序,大多数领导者复制工具允许您使用应用程序代码编写冲突解决逻辑。该代码可以在写入或读取时执行: +作为解决冲突最合适的方法可能取决于应用程序,大多数多主复制工具允许使用应用程序代码编写冲突解决逻辑。该代码可以在写入或读取时执行: -* *写时执行* +***写时执行*** -只要数据库系统检测到复制更改日志中存在冲突,就会调用冲突处理程序。例如,Bucardo允许您为此编写一段Perl代码。这个处理程序通常不能提示用户 - 它在后台进程中运行,并且必须快速执行。 +只要数据库系统检测到复制更改日志中存在冲突,就会调用冲突处理程序。例如,Bucardo允许您为此编写一段Perl代码。这个处理程序通常不能提示用户——它在后台进程中运行,并且必须快速执行。 -* *读时执行* +***读时执行*** 当检测到冲突时,所有冲突写入被存储。下一次读取数据时,会将这些多个版本的数据返回给应用程序。应用程序可能会提示用户或自动解决冲突,并将结果写回数据库。例如,CouchDB以这种方式工作。 -请注意,冲突解决通常适用于单个行或文档层面,而不是整个事务[36]。因此,如果您有一笔交易,原本会进行几次不同的写入(请参阅第7章),则为了冲突解决的目的,每个写入仍被分开考虑。 +请注意,冲突解决通常适用于单个行或文档层面,而不是整个事务[36]。因此,如果您有一个事务会原子性地进行几次不同的写入(请参阅第7章),则对于冲突解决而言,每个写入仍需分开单独考虑。 @@ -424,9 +424,9 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录 > > 已经有一些有趣的研究来自动解决由于数据修改引起的冲突。有几行研究值得一提: > -> * 无冲突的复制数据类型(CRDT)[32,38]是可以由多个用户同时编辑的集合,映射,有序列表,计数器等的一系列数据结构,它们以合理的方式自动解决冲突。一些CRDT已经在Riak 2.0中实现[39,40]。 -> * 可合并的持久数据结构[41]显式跟踪历史记录,类似于Git版本控制系统,并使用三向合并功能(而CRDT使用双向合并)。 -> * 可进行的转换[42]是Etherpad [30]和Google Docs [31]等合作编辑应用背后的冲突解决算法。它是专为同时编辑项目的有序列表而设计的,例如构成文本文档的字符列表。 +> * **无冲突复制数据类型(Conflict-free replicated datatypes)**(CRDT)[32,38]是可以由多个用户同时编辑的集合,映射,有序列表,计数器等的一系列数据结构,它们以合理的方式自动解决冲突。一些CRDT已经在Riak 2.0中实现[39,40]。 +> * **可合并的持久数据结构(Mergeable persistent data structures)**[41]显式跟踪历史记录,类似于Git版本控制系统,并使用三向合并功能(而CRDT使用双向合并)。 +> * **可执行的转换(operational transformation)**[42]是Etherpad [30]和Google Docs [31]等合作编辑应用背后的冲突解决算法。它是专为同时编辑项目的有序列表而设计的,例如构成文本文档的字符列表。 > > 这些算法在数据库中的实现还很年轻,但很可能将来它们将被集成到更多的复制数据系统中。自动冲突解决方案可以使应用程序处理多领导者数据同步更为简单。 > @@ -437,7 +437,7 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录 有些冲突是显而易见的。在[图5-7](img/fig5-7.png)的例子中,两个写操作并发地修改了同一条记录中的同一个字段,并将其设置为两个不同的值。毫无疑问这是一个冲突。 -其他类型的冲突可能更为微妙,难以发现。例如,考虑一个会议室预订系统:它记录哪个房间是哪谁在什么时间预订的。应用需要确保每个房间只有一组人同时预定(即不得有相同房间的重叠预订)。在这种情况下,如果同时为同一个房间创建两个不同的预订,则可能会发生冲突。即使应用程序在允许用户进行预订之前检查可用性,如果两次预订是由两个不同的领导者进行的,则可能会有冲突。 +其他类型的冲突可能更为微妙,难以发现。例如,考虑一个会议室预订系统:它记录谁腚了哪个时间段的哪个房间。应用需要确保每个房间只有一组人同时预定(即不得有相同房间的重叠预订)。在这种情况下,如果同时为同一个房间创建两个不同的预订,则可能会发生冲突。即使应用程序在允许用户进行预订之前检查可用性,如果两次预订是由两个不同的领导者进行的,则可能会有冲突。 现在还没有一个现成的答案,但在接下来的章节中,我们将追溯到对这个问题有很好的理解。我们将在第7章中看到更多的冲突示例,在第12章中我们将讨论用于检测和解决复制系统中冲突的可扩展方法。 @@ -445,13 +445,15 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录 ### 多主复制拓扑 -复制拓扑描述写入从一个节点传播到另一个节点的通信路径。如果你有两个领导者,如图5-7所示,只有一个合理的拓扑结构:领导者1必须把他所有的写到领导者2,反之亦然。有两个以上的领导,各种不同的拓扑是可能的。图5-8举例说明了一些例子。 +复制拓扑描述写入从一个节点传播到另一个节点的通信路径。如果你有两个领导者,如[图5-7]()所示,只有一个合理的拓扑结构:领导者1必须把他所有的写到领导者2,反之亦然。有两个以上的领导,各种不同的拓扑是可能的。[图5-8]()举例说明了一些例子。 ![](img/fig5-8.png) **图5-8 三个可以设置多领导者复制的示例拓扑。** -最普遍的拓扑是全部到全部(图5-8 [c]),其中每个领导者将其写入每个其他领导。但是,也会使用更多受限制的拓扑:例如,默认情况下,MySQL仅支持圆形拓扑[34],其中每个节点接收来自一个节点的写入,并将这些写入(加上自己的任何写入)转发给另一个节点。另一种流行的拓扑结构具有星形的形状:v一个指定的根节点将写入转发给所有其他节点。星型拓扑可以推广到树。 +最普遍的拓扑是全部到全部(图5-8 [c]),其中每个领导者将其写入每个其他领导。但是,也会使用更多受限制的拓扑:例如,默认情况下,MySQL仅支持**环形拓扑(circular topology)**[34],其中每个节点接收来自一个节点的写入,并将这些写入(加上自己的任何写入)转发给另一个节点。另一种流行的拓扑结构具有星形的形状[^v]。个指定的根节点将写入转发给所有其他节点。星型拓扑可以推广到树。 + +[^v]: 不要与星型模式混淆(请参阅第93页的“星星和雪花:分析模式”),其中描述了数据模型的结构,而不是节点之间的通信拓扑。 在圆形和星形拓扑中,写入可能需要在到达所有副本之前通过多个节点。因此,节点需要转发从其他节点收到的数据更改。为了防止无限复制循环,每个节点被赋予一个唯一的标识符,并且在复制日志中,每个写入都被标记了所有已经通过的节点的标识符[43]。当一个节点收到用自己的标识符标记的数据更改时,该数据更改将被忽略,因为节点知道它已经被处理。 @@ -459,27 +461,29 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录 另一方面,全能拓扑也可能有问题。特别是,一些网络链接可能比其他网络链接更快(例如,由于网络拥塞),结果是一些复制消息可能“超过”其他复制消息,如图5-9所示。 -##### - ![](img/fig5-9.png) **图5-9 使用多主程序复制时,可能会在某些副本中写入错误的顺序。** -在图5-9中,客户端A向领导者1的表中插入一行,客户端B在领导者3上更新该行。然而,领导者2可以以不同的顺序接收写入:它可以首先接收更新(其中,从它的角度来看,是对数据库中不存在的行的更新),并且仅在稍后接收到相应的插入(其应该在更新之前)。 +在图5-9中,客户端A向*林登万(Leader One)*的表中插入一行,客户端B在主库3上更新该行。然而,主库2可以以不同的顺序接收写入:它可以首先接收更新(其中,从它的角度来看,是对数据库中不存在的行的更新),并且仅在稍后接收到相应的插入(其应该在更新之前)。 -这是一个因果关系的问题,类似于我们在第165页上的“一致前缀读取”中看到的:更新取决于先前的插入,所以我们需要确保所有节点先处理插入,然后再处理更新。仅仅在每一次写入时添加一个时间戳是不够的,因为时钟不可能被充分地同步,以便在主2处正确地排序这些事件(见第8章)。 +这是一个因果关系的问题,类似于我们在第165页上的“一致前缀读取”中看到的:更新取决于先前的插入,所以我们需要确保所有节点先处理插入,然后再处理更新。仅仅在每一次写入时添加一个时间戳是不够的,因为时钟不可能被充分地同步,以便在主库2处正确地排序这些事件(见第8章)。 -要正确命令这些事件,可以使用一种称为**版本向量(version vectors)**的技术,本章稍后将讨论这种技术(请参阅第174页的“检测并发写入”)。然而,冲突检测技术在许多多领导者复制系统中执行得不好。例如,在撰写本文时,PostgreSQL BDR不提供写操作的因果排序[27],而Tungsten Replicator for MySQL甚至不尝试检测冲突[34]。 +要正确排序这些事件,可以使用一种称为**版本向量(version vectors)**的技术,本章稍后将讨论这种技术(参阅“[检测并发写入](#检测并发写入)”)。然而,冲突检测技术在许多多领导者复制系统中执行得不好。例如,在撰写本文时,PostgreSQL BDR不提供写入的因果排序[27],而Tungsten Replicator for MySQL甚至不尝试检测冲突[34]。 如果您正在使用具有多领导者复制功能的系统,那么应该了解这些问题,仔细阅读文档,并彻底测试您的数据库,以确保它确实提供了您认为具有的保证。 + + ## 无主复制 -我们在本章到目前为止所讨论的复制方法 - 单主者和多主者复制 - 是基于客户端向一个节点(领导者)发送写请求的想法,数据库系统负责复制写入其他副本。领导决定了写入的顺序,而跟随者按相同的顺序应用领导的写入。 +我们在本章到目前为止所讨论的复制方法 ——单主复制、多主复制——都是这样的想法:客户端向一个主库发送写请求,而数据库系统负责将写入复制到其他副本。主库决定写入的顺序,而从库按相同顺序应用主库的写入。 -一些数据存储系统采用不同的方法,放弃领导者的概念,并允许任何副本直接接受来自客户端的写入。一些最早的复制数据系统是无领导的[1,44],但是在关系数据库主导时代,这个想法大多被遗忘。在亚马逊将其用于其内部的Dynamo系统之后,它再一次成为数据库的一种时尚架构[37] .(Dynamo不适用于Amazon以外的用户。 令人困惑的是,AWS提供了一个名为DynamoDB的托管数据库产品,它使用了完全不同的体系结构:它基于单主程序复制。) Riak,Cassandra和Voldemort是由Dynamo启发的无领导复制模型的开源数据存储,所以这类数据库也被称为*Dynamo风格*。 +一些数据存储系统采用不同的方法,放弃主库的概念,并允许任何副本直接接受来自客户端的写入。最早的一些的复制数据系统是**无领导的(leaderless)**[1,44],但是在关系数据库主导的时代,这个想法几乎已被忘却。在亚马逊将其用于其内部的Dynamo系统[^vi]之后,它再一次成为数据库的一种时尚架构[37] .(Dynamo不适用于Amazon以外的用户。 令人困惑的是,AWS提供了一个名为DynamoDB的托管数据库产品,它使用了完全不同的体系结构:它基于单主程序复制。) Riak,Cassandra和Voldemort是由Dynamo启发的无领导复制模型的开源数据存储,所以这类数据库也被称为*Dynamo风格*。 -在一些无领导者的实现中,客户端直接将其写入到几个副本中,而在另一些情况下,协调器节点代表客户端进行写入。但是,与领导者数据库不同,协调员不执行特定的写入顺序。我们将会看到,这种设计上的差异对数据库的使用方式有着深远的影响。 +[^vi]: Dynamo不适用于Amazon以外的用户。 令人困惑的是,AWS提供了一个名为DynamoDB的托管数据库产品,它使用了完全不同的体系结构:它基于单引导程序复制。 + +在一些无领导者的实现中,客户端直接将写入发送到到几个副本中,而另一些情况下,一个**协调者(coordinator)**节点代表客户端进行写入。但与主库数据库不同,协调员不执行特定的写入顺序。我们将会看到,这种设计上的差异对数据库的使用方式有着深远的影响。 ### 当节点故障时写入数据库 @@ -495,33 +499,36 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录 为了解决这个问题,当一个客户端从数据库中读取数据时,它不仅仅发送它的请求到一个副本:读请求也被并行地发送到多个节点。客户可能会从不同的节点获得不同的响应。即来自一个节点的最新值和来自另一个节点的陈旧值。版本号用于确定哪个值更新(请参阅第174页的“检测并发写入”)。 -#### 阅读修复和反熵 +#### 读修复和反熵 复制方案应确保最终将所有数据复制到每个副本。在一个不可用的节点重新联机之后,它如何赶上它错过的写入? 在Dynamo风格的数据存储中经常使用两种机制: -* *修复读* +* ***读修复(Read repair)*** 当客户端并行读取多个节点时,它可以检测到任何陈旧的响应。例如,在图5-10中,用户2345获得了来自Replica 3的版本6值和来自副本1和2的版本7值。客户端发现副本3具有陈旧值,并将新值写回复制品。这种方法适用于频繁阅读的值。 -* *反熵过程* +***反熵过程(Anti-entropy process)*** 此外,一些数据存储具有后台进程,该进程不断查找副本之间的数据差异,并将任何缺少的数据从一个副本复制到另一个副本。与基于领导者的复制中的复制日志不同,此反熵过程不会以任何特定的顺序复制写入,并且在复制数据之前可能会有显着的延迟。 并不是所有的系统都实现了这两个;例如,Voldemort目前没有反熵过程。请注意,如果没有反熵过程,某些副本中很少读取的值可能会丢失,从而降低了持久性,因为只有在应用程序读取值时才执行读取修复。 -#### 法定人数的读写 +#### 读写的法定人数 在图5-10的示例中,我们认为即使仅在三个副本中的两个上进行处理,写入仍然是成功的。如果三个副本中只有一个接受了写入,会怎样?我们能推多远呢? 如果我们知道,每个成功的写操作意味着在三个副本中至少有两个出现,这意味着至多有一个副本可能是陈旧的。因此,如果我们从至少两个副本读取,我们可以确定至少有一个是最新的。如果第三个副本停机或响应速度缓慢,则读取仍可以继续返回最新值。 -更一般地说,如果有n个副本,每个写入必须由w节点确认才能被认为是成功的,并且我们必须至少为每个读取查询r个节点。 (在我们的例子中,$n = 3,w = 2,r = 2$)。只要$w + r> n$,我们期望在读取时获得最新的值,因为至少有一个r节点从阅读必须是最新的。读取和写入服从这些r和w值被称为法定读取和写入。[44] (有时候这种法定人数被称为严格的法定人数,与马虎法定人数形成对比(见第183页“马虎法定人数和暗示交接法”)你可以认为r和w作为读或写所需的最低票数是有效的。 +更一般地说,如果有n个副本,每个写入必须由w节点确认才能被认为是成功的,并且我们必须至少为每个读取查询r个节点。 (在我们的例子中,$n = 3,w = 2,r = 2$)。只要$w + r> n$,我们期望在读取时获得最新的值,因为r个读取中至少有一个节点是最新的。遵循这些r值,w值的读写称为**法定人数(quorum)**[^vii]的读和写。[44] ,你可以认为,r和w是有效读写所需的最低票数。 -在Dynamo风格的数据库中,参数n,w和r通常是可配置的。一个常见的选择是使n为奇数(通常为3或5)并设置 $w = r =(n + 1)/ 2$(向上舍入)。但是,您可以根据需要更改数字。例如,设置$w = n$和$r = 1$的写入很少且读取次数较多的工作负载可能会受益。这使得读取速度更快,但具有只有一个失败节点导致所有数据库写入失败的缺点。 +[^vii]: 有时候这种法定人数被称为严格的法定人数,相对“松散的法定人数”而言(见“[松散法定人数与带提示的接力](#松散法定人数与带提示的接力)”) -*可能有多于n个节点的集群,但是任何给定的值只能存储在n个节点上。 这允许对数据集进行分区,从而支持比您可以放在一个节点上的数据集更大的数据集。 我们将在第6章回到分区。* +在Dynamo风格的数据库中,参数n,w和r通常是可配置的。一个常见的选择是使n为奇数(通常为3或5)并设置 $w = r =(n + 1)/ 2$(向上取整)。但是可以根据需要更改数字。例如,设置$w = n$和$r = 1$的写入很少且读取次数较多的工作负载可能会受益。这使得读取速度更快,但具有只有一个失败节点导致所有数据库写入失败的缺点。 + +> 集群中可能有多于n的节点。(集群的机器数可能多于副本书目),但是任何给定的值只能存储在n个节点上。 这允许对数据集进行分区,从而支持可以放在一个节点上的数据集更大的数据集。 将在第6章回到分区。 +> 仲裁条件$w + r> n$允许系统容忍不可用的节点,如下所示: @@ -543,66 +550,67 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录 如果你有n个副本,并且你选择w和r,使得$w + r> n$,你通常可以期望每个读取返回为一个键写的最近的值。情况就是这样,因为你写的节点集合和你读过的节点集合必须重叠。也就是说,您读取的节点中必须至少有一个具有最新值的节点(如图5-11所示)。 -通常,r和w被选为多数(超过 n/2 )节点,因为这确保了$w + r> n$,同时仍然容忍多达n / 2个节点故障。但是,法定人数不一定必须是大多数,只是读和写操作使用的节点集合至少需要在一个节点上重叠。其他法定人数的分配是可能的,这在分布式算法的设计中有一定的灵活性[45]。 +通常,r和w被选为多数(超过 n/2 )节点,因为这确保了$w + r> n$,同时仍然容忍多达$n/2$个节点故障。但是,法定人数不一定必须是大多数,只是读写使用的节点交集至少需要包括一个节点。其他法定人数的配置是可能的,这使得分布式算法的设计有一定的灵活性[45]。 -您也可以将w和r设置为较小的数字,以使$w + r≤n$(即法定条件不满足)。在这种情况下,读取和写入操作仍将被发送到n个节点,但操作成功需要少量的成功响应。 +您也可以将w和r设置为较小的数字,以使$w + r≤n$(即法定条件不满足)。在这种情况下,读取和写入操作仍将被发送到n个节点,但操作成功只需要少量的成功响应。 -对于较小的w和r,您更可能读取陈旧的值,因为您的读取更有可能不包含具有最新值的节点。另一方面,这种配置允许更低的延迟和更高的可用性:如果存在网络中断,并且许多副本变得无法访问,则可以继续处理读取和写入的机会更大。只有当可达副本的数量低于w或r时,数据库才分别变得不可用于写入或读取。 +较小的w和r更有可能会读取过时的数据,因为您的读取更有可能不包含具有最新值的节点。另一方面,这种配置允许更低的延迟和更高的可用性:如果存在网络中断,并且许多副本变得无法访问,则可以继续处理读取和写入的机会更大。只有当可达副本的数量低于w或r时,数据库才分别变得不可用于写入或读取。 -但是,即使在$w + r> n$的情况下,也可能存在返回陈旧值的边缘情况。这取决于实施,但可能的情况包括: +但是,即使在$w + r> n$的情况下,也可能存在返回陈旧值的边缘情况。这取决于实现,但可能的情况包括: -* 如果使用松散的法定人数(请参阅第181页上的“马虎的仲裁与暗示交接”),写入可能会以不同于r读取的节点结束,因此r节点和w之间不再有保证重叠节点[46]。 +* 如果使用松散的法定人数(见“[松散法定人数与带提示的接力](#松散法定人数与带提示的接力)”),w个写入和r个读取落在完全不同的节点上,因此r节点和w之间不再保证有重叠节点[46]。 * 如果两个写入同时发生,不清楚哪一个先发生。在这种情况下,唯一安全的解决方案是合并并发写入(请参阅第171页的“处理写入冲突”)。如果根据时间戳(最后写入成功)挑选出胜者,则由于时钟偏差[35],写入可能会丢失。我们将返回第184页上的“检测并发写入”中的此主题。 * 如果写操作与读操作同时发生,写操作可能仅反映在某些副本上。在这种情况下,不确定读取是返回旧值还是新值。 -* 如果在某些副本上写入成功,而在其他节点上写入失败(例如,因为某些节点上的磁盘已满),并且总体z上成功的次数少于w个副本,不会在成功的副本上回滚。这意味着如果一个写入报告失败了,后续的读取可能会或可能不会返回写入的值[47]。 -* 如果携带新值的节点失败,并且其数据从带有旧值的副本中恢复,则存储新值的副本数可能会低于w,从而打破法定条件。 -* 即使一切工作正常,也会出现边缘情况,在这种情况下,您可能会感到不安,因为我们将在第334页上的“线性化和法定人数”中看到。 +* 如果写操作在某些副本上成功,而在其他节点上失败(例如,因为某些节点上的磁盘已满),在小于w个副本上写入成功。所以整体判定写入失败,但整体写入失败并没有在写入成功的副本上回滚。这意味着如果一个写入虽然报告失败,后续的读取仍然可能会读取这次失败写入的值[47]。 +* 如果携带新值的节点失败,需要读取其他带有旧值的副本。并且其数据从带有旧值的副本中恢复,则存储新值的副本数可能会低于w,从而打破法定人数条件。 +* 即使一切工作正常,有时也会不幸地出现关于**时序(timing)**的边缘情况,在这种情况下,您可能会感到不安,因为我们将在第334页上的“[线性化和法定人数]()”中看到。 -因此,尽管法定人数似乎保证读取返回最新的写入值,但在实践中并不那么简单。 Dynamo风格的数据库通常针对可以容忍最终一致性的用例进行优化。参数w和r允许您调整陈旧值读取的概率,但不要把它们作为绝对保证。 +因此,尽管法定人数似乎保证读取返回最新的写入值,但在实践中并不那么简单。 Dynamo风格的数据库通常针对可以忍受最终一致性的用例进行优化。允许通过参数w和r来调整读取陈旧值的概率,但把它们当成绝对的保证是不明智的。 -尤其是,您通常没有得到第161页上的“与延迟有关的问题”(读取您的写入,单调读取或一致的前缀读取)中讨论的保证,因此前面提到的异常可能会发生在应用程序中。更强有力的担保通常需要交易或共识。我们将在第七章和第九章回到这些话题。 +尤其是,通常没有得到第161页上的“与延迟有关的问题”(读取您的写入,单调读取或一致的前缀读取)中讨论的保证,因此前面提到的异常可能会发生在应用程序中。更强有力的保证通常需要**事务**或**共识**。我们将在[第七章](ch7.md)和[第九章](ch9.md)回到这些话题。 #### 监控陈旧度 -从操作的角度来看,监视你的数据库是否返回最新的结果是很重要的。即使您的应用程序可以容忍陈旧的读取,您也需要了解复制的健康状况。如果显着落后,应该提醒您,以便您可以调查原因(例如,网络中的问题或超载节点)。 +从运维的角度来看,监视你的数据库是否返回最新的结果是很重要的。即使应用可以容忍陈旧的读取,您也需要了解复制的健康状况。如果显着落后,应该提醒您,以便您可以调查原因(例如,网络中的问题或超载节点)。 对于基于领导者的复制,数据库通常会公开复制滞后的度量标准,您可以将其提供给监视系统。这是可能的,因为写入按照相同的顺序应用于领导者和追随者,并且每个节点在复制日志中具有一个位置(在本地应用的写入次数)。通过从领导者的当前位置中减去随从者的当前位置,您可以测量复制滞后量。 -然而,在无领导者复制的系统中,没有固定的写入顺序,这使得监控变得更加困难。而且,如果数据库只使用读取修复(没有反熵),那么对于一个值可能会有多大的限制是没有限制的 - 如果一个值很少被读取,那么由一个陈旧副本返回的值可能是古老的。 +然而,在无领导者复制的系统中,没有固定的写入顺序,这使得监控变得更加困难。而且,如果数据库只使用读取修复(没有反熵过程),那么对于一个值可能会有多大的限制是没有限制的 - 如果一个值很少被读取,那么由一个陈旧副本返回的值可能是古老的。 已经有一些关于衡量无主复制数据库中的复制陈旧度的研究,并根据参数n,w和r来预测陈旧读取的预期百分比[48]。不幸的是,这还不是很常见的做法,但是将过时测量值包含在数据库的标准度量标准中是一件好事。最终的一致性是故意模糊的保证,但是对于可操作性来说,能够量化“最终”是很重要的。 -### 松散仲裁与意见交换 +### 松散法定人数与带提示的接力 -具有适当配置的仲裁的数据库可以容忍个别节点的故障,而不需要故障切换。他们也可以容忍个别节点变慢,因为请求不必等待所有n个节点响应 - 当w或r节点响应时它们可以返回。这些特性使得数据库具有无需复制的吸引力,适用于需要高可用性和低延迟的用例,并且可以容忍偶尔的陈旧读取。 +合理配置的法定人数可以使数据库无需故障切换即可容忍个别节点的故障。也可以容忍个别节点变慢,因为请求不必等待所有n个节点响应——当w或r节点响应时它们可以返回。对于需要高可用、低延时、且能够容忍偶尔读到陈旧值的应用场景来说,这些特性使无主复制的数据库很有吸引力。 然而,法定人数(如迄今为止所描述的)并不像它们可能的那样具有容错性。网络中断可以很容易地将客户端从大量的数据库节点上切断。虽然这些节点是活着的,而其他客户端可能能够连接到它们,但是从数据库节点切断的客户端,它们也可能已经死亡。在这种情况下,剩余的可用节点可能会少于可用节点,因此客户端可能无法达到法定人数。 -在一个大型的群集中(节点数量明显多于n个),在网络中断期间,客户端可能连接到某些数据库节点,而不是为了为特定值组装法定数量的节点。在这种情况下,数据库设计人员需要权衡一下: +在一个大型的群集中(节点数量明显多于n个),网络中断期间客户端可能连接到某些数据库节点,而不是为了为特定值组成法定人数的节点们。在这种情况下,数据库设计人员需要权衡一下: * 将错误返回给我们无法达到w或r节点的法定数量的所有请求是否更好? * 或者我们是否应该接受写入,然后将它们写入一些可达的节点,但不在n值通常存在的n个节点之间? -后者被认为是一个马虎的法定人数[37]:写和读仍然需要w和r成功的响应,但是那些可能包括不在指定的n个“主”节点中的值。比方说,如果你把自己锁在房子外面,你可能会敲开邻居的门,问你是否可以暂时停留在沙发上。 +后者被认为是一个**松散的法定人数(sloppy quorum)**[37]:写和读仍然需要w和r成功的响应,但是那些可能包括不在指定的n个“主”节点中的值。比方说,如果你把自己锁在房子外面,你可能会敲开邻居的门,问你是否可以暂时停留在沙发上。 -一旦网络中断得到解决,代表另一个节点临时接受的一个节点的任何写入都被发送到适当的“本地”节点。这就是所谓的提示。 (一旦你再次找到你的房子的钥匙,你的邻居礼貌地要求你离开沙发回家。) +一旦网络中断得到解决,代表另一个节点临时接受的一个节点的任何写入都被发送到适当的“本地”节点。这就是所谓的**带提示的接力(hinted handoff)**。 (一旦你再次找到你的房子的钥匙,你的邻居礼貌地要求你离开沙发回家。) -松散仲裁提高写入可用性特别有用:只要有任何w节点可用,数据库就可以接受写入。然而,这意味着即使当$w + r> n$时,也不能确定读取某个键的最新值,因为最新的值可能已经临时写入了n之外的某些节点[47]。 +松散法定人数提高写入可用性特别有用:只要有任何w节点可用,数据库就可以接受写入。然而,这意味着即使当$w + r> n$时,也不能确定读取某个键的最新值,因为最新的值可能已经临时写入了n之外的某些节点[47]。 -因此,在传统意义上,一个马虎的法定人数实际上不是一个法定人数。这只是一个保证,即数据存储在w节点的地方。不能保证r节点的读取直到提示已经完成。 +因此,在传统意义上,一个松散的法定人数实际上不是一个法定人数。这只是一个保证,即数据存储在w节点的地方。不能保证r节点的读取直到提示已经完成。 -在所有常见的Dynamo实现中,松散的仲裁是可选的。在Riak中,它们默认是启用的,而在Cassandra和Voldemort中它们默认是禁用的[46,49,50]。 +在所有常见的Dynamo实现中,松散法定人数是可选的。在Riak中,它们默认是启用的,而在Cassandra和Voldemort中它们默认是禁用的[46,49,50]。 #### 多数据中心操作 我们先前讨论了跨数据中心复制作为多主复制的用例(请参阅第162页的“多重复制复制”)。无主复制还适用于多数据中心操作,因为它旨在容忍冲突的并发写入,网络中断和延迟尖峰。 Cassandra和Voldemort在正常的无主模型中实现了他们的多数据中心支持:副本的数量n包括所有数据中心的节点,在配置中,您可以指定每个数据中心中您想拥有的副本的数量。无论数据中心如何,每个来自客户端的写入都会发送到所有副本,但客户端通常只等待来自其本地数据中心内的法定节点的确认,从而不会受到跨数据中心链路延迟和中断的影响。对其他数据中心的高延迟写入通常被配置为异步发生,尽管配置有一定的灵活性[50,51]。 + Riak将客户端和数据库节点之间的所有通信保持在一个数据中心本地,因此n描述了一个数据中心内的副本数量。数据库集群之间的跨数据中心复制在后台异步发生,其风格类似于多领导者复制[52]。 -### 侦测并发写入 +### 检测并发写入 -Dynamo风格的数据库允许多个客户端同时写入相同的Key,这意味着即使使用严格的法定人数也会发生冲突。这种情况与多领导者复制相似(请参阅第171页的“处理写冲突”),但在Dynamo样式的数据库中,在读取修复或提示性切换期间也可能会产生冲突。 +Dynamo风格的数据库允许多个客户端同时写入相同的Key,这意味着即使使用严格的法定人数也会发生冲突。这种情况与多领导者复制相似(请参阅第171页的“处理写冲突”),但在Dynamo样式的数据库中,在**读修复**或**带提示的接力**期间也可能会产生冲突。 问题在于,由于可变的网络延迟和部分故障,事件可能在不同的节点以不同的顺序到达。例如,图5-12显示了两个客户机A和B同时写入三节点数据存储区中的键X: @@ -616,30 +624,30 @@ Dynamo风格的数据库允许多个客户端同时写入相同的Key,这意 如果每个节点只要接收到来自客户端的写入请求就简单地覆盖了某个键的值,那么节点就会永久地不一致,如图5-12中的最终获取请求所示:节点2认为X的最终值是B,而其他节点认为值是A. -为了最终达成一致,副本应该趋于相同的价值。他们如何做到这一点?有人可能希望复制的数据库能够自动处理,但不幸的是,大多数的实现都很糟糕:如果你想避免丢失数据,你(应用程序开发人员)需要知道很多有关数据库冲突处理的内部信息。 +为了最终达成一致,副本应该趋于相同的值。如何做到这一点?有人可能希望复制的数据库能够自动处理,但不幸的是,大多数的实现都很糟糕:如果你想避免丢失数据,你(应用程序开发人员)需要知道很多有关数据库冲突处理的内部信息。 -我们在第171页的“处理写冲突”中简要介绍了一些解决冲突的技术。在总结本章之前,让我们来更详细地探讨这个问题。 +在“[处理写冲突](#处理写入冲突)”一节中已经简要介绍了一些解决冲突的技术。在总结本章之前,让我们来更详细地探讨这个问题。 -#### 最后写入胜(丢弃并发写入) +#### 最后写入为准(丢弃并发写入) -实现最终融合的一种方法是声明每个副本只需要存储最“最近”的值,并允许“更旧”的值被覆盖和抛弃。然后,只要我们有一种明确的方式来确定哪个写是“最近的”,并且每个写入最终都被复制到每个副本,那么复制最终会收敛到相同的值。 +实现最终融合的一种方法是声明每个副本只需要存储最***“最近”***的值,并允许***“更旧”***的值被覆盖和抛弃。然后,只要我们有一种明确的方式来确定哪个写是“最近的”,并且每个写入最终都被复制到每个副本,那么复制最终会收敛到相同的值。 -正如“最近”的引用所表明的,这个想法其实颇具误导性。在图5-12的例子中,当客户端向数据库节点发送写入请求时,客户端都不知道另一个客户端,因此不清楚哪一个先发生了。事实上,说“发生”是没有意义的:我们说写入是并发的,所以它们的顺序是不确定的。 +正如**“最近”**的引号所表明的,这个想法其实颇具误导性。在图5-12的例子中,当客户端向数据库节点发送写入请求时,客户端都不知道另一个客户端,因此不清楚哪一个先发生了。事实上,说“发生”是没有意义的:我们说写入是**并发(concurrent)**的,所以它们的顺序是不确定的。 -即使写入没有自然排序,我们也可以强制任意排序。例如,我们可以为每个写入附加一个时间戳,挑选最“最近”的最大时间戳,并丢弃具有较早时间戳的任何写入。这种冲突解决算法被称为最后写入胜利(LWW),是Cassandra [53]唯一支持的冲突解决方法,也是Riak [35]中的一个可选特征。 +即使写入没有自然的排序,我们也可以强制任意排序。例如,可以为每个写入附加一个时间戳,挑选最**“最近”**的最大时间戳,并丢弃具有较早时间戳的任何写入。这种冲突解决算法被称为**最后写入为准(LWW, last write wins)**,是Cassandra [53]唯一支持的冲突解决方法,也是Riak [35]中的一个可选特征。 -LWW实现了最终收敛的目标,但是以持久性为代价:如果同一个Key有多个并发写入,即使它们都被报告为客户端成功(因为它们被写入w个副本),其中一个写道会生存下来,其他的将被无声丢弃。此外,LWW甚至可能会删除不是并发的写入,我们将在第291页的“有序事件的时间戳”中讨论。 +LWW实现了最终收敛的目标,但以**持久性**为代价:如果同一个Key有多个并发写入,即使它们都被报告为客户端成功(因为它们被写入w个副本),其中一个写道会生存下来,其他的将被无声丢弃。此外,LWW甚至可能会删除不是并发的写入,我们将在第291页的“[有序事件的时间戳]()”中讨论。 -有一些情况,如缓存,其中丢失的写入可能是可以接受的。如果丢失数据是不可接受的,LWW是解决冲突的一个很烂的选择。 +有一些情况,如缓存,其中丢失的写入可能是可以接受的。如果丢失数据不可接受,LWW是解决冲突的一个很烂的选择。 与LWW一起使用数据库的唯一安全方法是确保一个Key只写入一次,然后视为不可变,从而避免对同一个密钥进行并发更新。例如,推荐使用Cassandra的方法是使用UUID作为键,从而为每个写操作提供一个唯一的键[53]。 #### “此前发生”的关系和并发 -我们如何判断两个操作是否是并发的?发展一个直觉,让我们看看一些例子: +我们如何判断两个操作是否是并发的?为了建立一个直觉,让我们看看一些例子: -* 在图5-9中,两个写入不是并发的:A的插入发生在B的增量之前,因为B递增的值是A插入的值。换句话说,B的操作建立在A的操作上,所以B的操作必须有后来发生。我们也可以说B是因果依赖于A -* 另一方面,图5-12中的两个写入是并发的:当每个客户端启动操作时,它不知道另一个客户端也正在执行操作同样的Key。因此,操作之间不存在因果关系。 +* 在[图5-9](fig5-9.png)中,两个写入不是并发的:A的插入发生在B的增量之前,因为B递增的值是A插入的值。换句话说,B的操作建立在A的操作上,所以B的操作必须有后来发生。我们也可以说B是**因果依赖(causally dependent)**于A +* 另一方面,[图5-12](fig5-12.png)中的两个写入是并发的:当每个客户端启动操作时,它不知道另一个客户端也正在执行操作同样的Key。因此,操作之间不存在因果关系。 如果操作B了解操作A,或者依赖于A,或者以某种方式构建于操作A之上,则操作A在另一个操作B之前发生。在另一个操作之前是否发生一个操作是定义什么并发的关键。事实上,我们可以简单地说,如果两个操作都不在另一个之前发生,那么两个操作是并发的(即,两个操作都不知道另一个)[54]。 @@ -649,25 +657,25 @@ LWW实现了最终收敛的目标,但是以持久性为代价:如果同一 > #### 并发性,时间和相对性 > -> 如果两个操作“同时”发生,似乎应该称为并发 - 但事实上,它们是否在时间上重叠并不重要。由于分布式系统中的时钟问题,实际上很难判断两个事件是否同时发生,这个问题我们将在第8章中详细讨论。 +> 如果两个操作**“同时”**发生,似乎应该称为并发——但事实上,它们在字面时间上重叠与否并不重要。由于分布式系统中的时钟问题,现实中是很难判断两个事件是否**同时**发生的,这个问题我们将在[第8章](ch8.md)中详细讨论。 > -> 为了定义并发性,确切的时间并不重要:如果两个操作都不知道对方,我们只需调用两个并发操作,而不管它们发生的物理时间。人们有时把这个原理和狭义相对论的物理学联系起来[54],它引入了信息不能比光速更快的思想。因此,如果事件之间的时间短于光通过它们之间的距离,那么发生一定距离的两个事件不可能相互影响。 +> 为了定义并发性,确切的时间并不重要:如果两个操作都意识不到对方的存在,就称这两个操作**并发**,而不管它们发生的物理时间。人们有时把这个原理和狭义相对论的物理学联系起来[54],它引入了信息不能比光速更快的思想。因此,如果事件之间的时间短于光通过它们之间的距离,那么发生一定距离的两个事件不可能相互影响。 > -> 在计算机系统中,即使光速原则上允许一个操作影响另一个操作,但两个操作可能是并行的。例如,如果网络缓慢或中断,两个操作可能会发生一段时间,并且仍然是并发的,因为网络问题阻止一个操作能够知道另一个操作。 +> 在计算机系统中,即使光速原则上允许一个操作影响另一个操作,但两个操作也可能是**并行的**。例如,如果网络缓慢或中断,两个操作间可能会出现一段时间间隔,且仍然是并发的,因为网络问题阻止一个操作意识到另一个操作的存在。 -#### 捕捉此前发生关系 +#### 捕捉"此前发生"关系 -让我们来看一个算法,它确定两个操作是并发的,还是一个在另一个之前。为了简单起见,我们从一个只有一个副本的数据库开始。一旦我们已经制定了如何在单个副本上完成这项工作,我们可以将该方法概括为具有多个副本的无领导者数据库。 +来看一个算法,它确定两个操作是否为并发的,还是一个在另一个之前。为了简单起见,我们从一个只有一个副本的数据库开始。一旦我们已经制定了如何在单个副本上完成这项工作,我们可以将该方法概括为具有多个副本的无领导者数据库。 -图5-13显示了两个客户端同时向同一购物车添加项目。 (如果这样的例子让你觉得太麻烦了,那么可以想象,两个空中交通管制员同时把飞机添加到他们正在跟踪的部门。)最初,购物车是空的。在它们之间,客户端向数据库发出五次写入: +[图5-13]()显示了两个客户端同时向同一购物车添加项目。 (如果这样的例子让你觉得太麻烦了,那么可以想象,两个空中交通管制员同时把飞机添加到他们正在跟踪的区域)最初,购物车是空的。在它们之间,客户端向数据库发出五次写入: -1. 客户1将牛奶加入购物车。这是第一次写入该Key,所以服务器成功存储并为其分配版本1.服务器还将值与版本号一起回送给客户端。 -2. 客户2将鸡蛋加入购物车,不知道客户1同时添加了牛奶(客户2认为其蛋是购物车中的唯一物品)。服务器为此写入分配版本2,并将蛋和牛奶存储为两个单独的值。然后它将这两个值和版本号2一起返回给客户端。 -3. 客户1不知道客户2的写入,想要将面粉加入购物车,因此认为当前的购物车内容应该是[牛奶,面粉]。它将此值与服务器先前向客户端1提供的版本号1一起发送到服务器。服务器可以从版本号中知道[牛奶,面粉]的写入取代了[牛奶]的先前值,但与[鸡蛋]同时出现。因此,服务器将版本3分配给[牛奶,面粉],覆盖版本1值[牛奶],但保留版本2值[蛋]并将剩余的值返回给客户端。 -4. 同时,客户2想要加入火腿,不知道客户1刚刚加了面粉。客户端2在最后一个响应中从服务器收到了两个值[牛奶]和[蛋],所以客户现在合并这些值,并添加火腿形成一个新的值,[鸡蛋,牛奶,火腿]。它将这个值发送到服务器以及以前的版本2.服务器检测到版本2会覆盖[eggs],但与[milk,flour]同时发生,所以剩下的两个值是[milk,flour]版本3,和[鸡蛋,牛奶,火腿]与版本4。 -5. 最后,客户1想要加培根。它以前在版本3中从服务器接收[牛奶,面粉]和[鸡蛋],所以它合并这些,添加培根,并将最终值[牛奶,面粉,鸡蛋,培根]连同版本号3.这会覆盖[牛奶,面粉](请注意[鸡蛋]已经在最后一步被覆盖),但与[鸡蛋,牛奶,火腿]并发,所以服务器保留这两个并发值。 +1. 客户端1将牛奶加入购物车。这是该键的第一次写入,服务器成功存储了它并为其分配版本号1,最后将值与版本号一起回送给客户端。 +2. 客户端2将鸡蛋加入购物车,不知道客户端1同时添加了牛奶(客户端2认为它的鸡蛋是购物车中的唯一物品)。服务器为此写入分配版本号2,并将鸡蛋和牛奶存储为两个单独的值。然后它将这两个值**都**反回给客户端2,并附上版本号2。 +3. 客户端1不知道客户端2的写入,想要将面粉加入购物车,因此认为当前的购物车内容应该是 [牛奶,面粉]。它将此值与服务器先前向客户端1提供的版本号1一起发送到服务器。服务器可以从版本号中知道[牛奶,面粉]的写入取代了[牛奶]的先前值,但与[鸡蛋]的值是**并发**的。因此,服务器将版本3分配给[牛奶,面粉],覆盖版本1值[牛奶],但保留版本2的值[蛋],并将所有的值返回给客户端1。 +4. 同时,客户端2想要加入火腿,不知道客端户1刚刚加了面粉。客户端2在最后一个响应中从服务器收到了两个值[牛奶]和[蛋],所以客户端2现在合并这些值,并添加火腿形成一个新的值,[鸡蛋,牛奶,火腿]。它将这个值发送到服务器,带着之前的版本号2。服务器检测到新值会覆盖版本2 [eggs],但新值也会与v3 [milk,flour]**并发**,所以剩下的两个值是v3 [milk,flour],和v4:[鸡蛋,牛奶,火腿]。 +5. 最后,客户端1想要加培根。它以前在v3中从服务器接收[牛奶,面粉]和[鸡蛋],所以它合并这些,添加培根,并将最终值[牛奶,面粉,鸡蛋,培根]连同版本号v3发往服务器。这会覆盖v3[牛奶,面粉](请注意[鸡蛋]已经在最后一步被覆盖),但与v4[鸡蛋,牛奶,火腿]并发,所以服务器保留这两个并发值。 ![](img/fig5-13.png) @@ -675,52 +683,48 @@ LWW实现了最终收敛的目标,但是以持久性为代价:如果同一 图5-13中的操作之间的数据流如图5-14所示。 箭头表示哪个操作发生在其他操作之前,意味着后面的操作知道或依赖于较早的操作。 在这个例子中,客户端永远不会完全掌握服务器上的数据,因为总是有另一个操作同时进行。 但是,旧版本的值最终会被覆盖,并且不会丢失任何写入。 - - ![](img/fig5-14.png) **图5-14 图5-13中的因果依赖关系图。** -请注意,服务器可以通过查看版本号来确定两个操作是否是并发的 - 它不需要解释该值本身(因此该值可以是任何数据结构)。该算法的工作原理如下: +请注意,服务器可以通过查看版本号来确定两个操作是否是并发的——它不需要解释该值本身(因此该值可以是任何数据结构)。该算法的工作原理如下: -* 服务器为每个密钥保留一个版本号,每次写入密钥时都增加版本号,并将新版本号与写入的值一起存储。 -* 当客户端读取密钥时,服务器将返回所有未覆盖的值以及最新的版本号。在写之前,客户必须先读钥匙。 -* 客户端写入密钥时,必须包含之前读取的版本号,并且必须将之前读取的所有值合并在一起。 (来自写入请求的响应可以像读取一样,返回所有当前值,这使得我们可以像购物车示例那样链接多个写入。) +* 服务器为每个键保留一个版本号,每次写入键时都增加版本号,并将新版本号与写入的值一起存储。 +* 当客户端读取键时,服务器将返回所有未覆盖的值以及最新的版本号。客户端在写入前必须读取。 +* 客户端写入键时,必须包含之前读取的版本号,并且必须将之前读取的所有值合并在一起。 (来自写入请求的响应可以像读取一样,返回所有当前值,这使得我们可以像购物车示例那样连接多个写入。) * 当服务器接收到具有特定版本号的写入时,它可以覆盖该版本号或更低版本的所有值(因为它知道它们已经被合并到新的值中),但是它必须保持所有值更高版本号(因为这些值与传入的写入同时发生)。 当一个写入包含前一次读取的版本号时,它会告诉我们写入的是哪一种状态。如果在不包含版本号的情况下进行写操作,则与所有其他写操作并发,因此它不会覆盖任何内容 - 只会在随后的读取中作为其中一个值返回。 #### 合并同时写入的值 -这种算法可以确保没有数据被无声地丢弃,但不幸的是,客户端需要做一些额外的工作:如果多个操作并发发生,则客户端必须通过合并并行写入的值来进行清理。 Riak称这些并发值兄弟姐妹。 +这种算法可以确保没有数据被无声地丢弃,但不幸的是,客户端需要做一些额外的工作:如果多个操作并发发生,则客户端必须通过合并并发写入的值来擦屁股。 Riak称这些并发值**兄弟(siblings)**。 -合并兄弟值本质上是与多领导者复制中的冲突解决相同的问题,我们先前讨论过(请参阅第171页的“处理写冲突”)。一个简单的方法是根据版本号或时间戳(最后写入胜利)选择一个值,但这意味着丢失数据。所以,你可能需要在应用程序代码中做更聪明的事情。 +合并兄弟值,本质上是与多领导者复制中的冲突解决相同的问题,我们先前讨论过(请参阅第171页的“处理写冲突”)。一个简单的方法是根据版本号或时间戳(最后写入胜利)选择一个值,但这意味着丢失数据。所以,你可能需要在应用程序代码中做更聪明的事情。 -以购物车为例,合并兄弟姐妹的一种合理方法就是参加工会。在图5-14中,最后的两个兄弟姐妹是[牛奶,面粉,鸡蛋,熏肉]和[鸡蛋,牛奶,火腿]。注意牛奶和鸡蛋出现在两个,即使他们每个只写一次。合并的价值可能是像[牛奶,面粉,鸡蛋,培根,火腿],没有重复。 +以购物车为例,一种合理的合并兄弟方法就是集合求并。在图5-14中,最后的两个兄弟是[牛奶,面粉,鸡蛋,熏肉]和[鸡蛋,牛奶,火腿]。注意牛奶和鸡蛋出现在两个,即使他们每个只写一次。合并的价值可能是像[牛奶,面粉,鸡蛋,培根,火腿],没有重复。 -然而,如果你想让人们也可以从他们的手推车中删除东西,而不是仅仅添加东西,那么把兄弟姐妹联合起来可能不会产生正确的结果:如果你合并了两个兄弟手推车,并且只有一个那么被删除的项目会重新出现在兄弟姐妹的联合中[37]。为了防止这个问题,一个项目在删除时不能简单地从数据库中删除;相反,系统必须留下一个具有合适版本号的标记,以指示合并兄弟时该项目已被删除。这种删除标记被称为墓碑。 (我们之前在第72页的“哈希索引”中的日志压缩环境中看到了墓碑。) +然而,如果你想让人们也可以从他们的手推车中**删除**东西,而不是仅仅添加东西,那么把兄弟求并可能不会产生正确的结果:如果你合并了两个兄弟手推车,并且只在其中一个兄弟值里删掉了它,那么被删除的项目会重新出现在兄弟的并集中[37]。为了防止这个问题,一个项目在删除时不能简单地从数据库中删除;相反,系统必须留下一个具有合适版本号的标记,以指示合并兄弟时该项目已被删除。这种删除标记被称为**墓碑(tombstone)**。 (我们之前在第72页的“哈希索引”中的日志压缩的上下文中看到了墓碑。) -因为在应用程序代码中合并兄弟是复杂且容易出错的,所以设计数据结构可以自动执行这种合并,如“自动冲突解决”(第174页)中讨论的。例如,Riak的数据类型支持使用称为CRDT的数据结构家族[38,39,55]可以以合理的方式自动合并兄弟,包括保留删除。 +因为在应用程序代码中合并兄弟是复杂且容易出错的,所以有一些数据结构被设计出来用于自动执行这种合并,如“[自动冲突解决]()”中讨论的。例如,Riak的数据类型支持使用称为CRDT的数据结构家族[38,39,55]可以以合理的方式自动合并兄弟,包括保留删除。 #### 版本向量 -图5-13中的示例只使用一个副本。如果有多个副本,但没有领导者,算法如何改变? +图5-13中的示例只使用一个副本。如果有没有主库,有多个副本,算法如何改变? -图5-13使用单个版本号来捕获操作之间的依赖关系,但是当多个副本接受写入连接时,这是不够的。相反,我们需要使用每个副本的版本号以及每个密钥。每个副本在处理写入时增加自己的版本号,并且跟踪从其他副本中看到的版本号。这个信息指出了要覆盖哪些值以及保留哪些值作为兄弟。 +图5-13使用单个版本号来捕获操作之间的依赖关系,但是当多个副本并发接受写入时,这是不够的。相反,除了对每个键使用版本号之外,还需要在**每个副本**中版本号。每个副本在处理写入时增加自己的版本号,并且跟踪从其他副本中看到的版本号。这个信息指出了要覆盖哪些值,以及保留哪些值作为兄弟。 -所有副本的版本号集合称为版本向量[56]。这个想法的一些变体正在使用,但最有趣的可能是在Riak 2.0 [58,59]中使用的虚线版本矢量[57]。我们不会深入细节,但是它的工作方式与我们在购物车示例中看到的非常相似。 +所有副本的版本号集合称为**版本向量(version vector)**[56]。这个想法的一些变体正在使用,但最有趣的可能是在Riak 2.0 [58,59]中使用的**分散版本矢量(dotted version vector)**[57]。我们不会深入细节,但是它的工作方式与我们在购物车示例中看到的非常相似。 -与图5-13中的版本号一样,当读取值时,版本向量会从数据库副本发送到客户端,并且随后写入值时需要将其发送回数据库。 (Riak将版本向量编码为一个字符串,它称为因果上下文)。版本向量允许数据库区分重写和并发写入。 +与图5-13中的版本号一样,当读取值时,版本向量会从数据库副本发送到客户端,并且随后写入值时需要将其发送回数据库。 (Riak将版本向量编码为一个字符串,它称为**因果上下文(causal context)**)。版本向量允许数据库区分覆盖写入和并发写入。 -另外,就像在单个副本的例子中,应用程序可能需要合并兄弟。版本向量结构确保从一个副本读取并随后写回到另一个副本是安全的。这样做可能会导致兄弟姐妹被创建,但只要兄弟姐妹合并正确,就不会丢失数据。 +另外,就像在单个副本的例子中,应用程序可能需要合并兄弟。版本向量结构确保从一个副本读取并随后写回到另一个副本是安全的。这样做可能会创建兄弟,但只要兄弟姐妹合并正确,就不会丢失数据。 -> #### 版本矢量和矢量时钟 +> #### 版本向量和向量时钟 > -> 版本矢量有时也被称为矢量时钟,即使它们不完全相同。 细微差别 - 请参阅参考资料的细节[57,60,61]。 简而言之,在比较副本的状态时,版本向量是正确的数据结构。 +> 版本向量有时也被称为矢量时钟,即使它们不完全相同。 差别很微妙——请参阅参考资料的细节[57,60,61]。 简而言之,在比较副本的状态时,版本向量是正确的数据结构。 > - - ## 本章小结 在本章中,我们考察了复制的问题。复制可以用于几个目的: @@ -1005,3 +1009,8 @@ LWW实现了最终收敛的目标,但是以持久性为代价:如果同一 +-------- + +| 上一章 | 目录 | 下一章 | +| :--------------------------: | :-----------------------------: | :--------------------: | +| [第四章:编码与演化](ch4.md) | [设计数据密集型应用](README.md) | [第六章:分片](ch6.md) | \ No newline at end of file diff --git a/ddia/ch6.md b/ddia/ch6.md index 1ce57fa..7cc15fb 100644 --- a/ddia/ch6.md +++ b/ddia/ch6.md @@ -388,6 +388,11 @@ Couchbase不会自动重新平衡,这简化了设计。通常情况下,它 33. Shivnath Babu and Herodotos Herodotou: “[Massively Parallel Databases and MapReduce Systems](http://research.microsoft.com/pubs/206464/db-mr-survey-final.pdf),” *Foundations and Trends in Databases*, volume 5, number 1, pages 1–104, November 2013.[doi:10.1561/1900000036](http://dx.doi.org/10.1561/1900000036) -##### -##### \ No newline at end of file + +------ + +| 上一章 | 目录 | 下一章 | +| :--------------------: | :-----------------------------: | :--------------------: | +| [第五章:复制](ch5.md) | [设计数据密集型应用](README.md) | [第七章:事务](ch7.md) | + diff --git a/ddia/ch7.md b/ddia/ch7.md index 9eb0e7e..7af0e6a 100644 --- a/ddia/ch7.md +++ b/ddia/ch7.md @@ -1,4 +1,4 @@ -# 7. 事务 +# 7. 事务 ![](img/ch7.png) @@ -6,7 +6,7 @@ > > ​ ——James Corbett等人,Spanner:Google的全球分布式数据库(2012) --------- +------ [TOC] @@ -14,12 +14,12 @@ 在数据系统的严酷现实中,很多事情可能会出错: -* 数据库软件或硬件可能随时发生故障(包括写操作过程中)。 -* 应用程序可能随时崩溃(包括一系列操作的中途)。 -* 网络中断可能会意外地切断来自数据库的应用程序,或从另一个数据库节点切断应用程序。 -* 多个客户端可能会同时写入数据库,覆盖彼此的更改。 -* 客户可能读取的数据无意义,因为它只是部分更新。 -* 客户之间的竞争条件可能导致令人惊讶的错误。 +- 数据库软件或硬件可能随时发生故障(包括写操作过程中)。 +- 应用程序可能随时崩溃(包括一系列操作的中途)。 +- 网络中断可能会意外地切断来自数据库的应用程序,或从另一个数据库节点切断应用程序。 +- 多个客户端可能会同时写入数据库,覆盖彼此的更改。 +- 客户可能读取的数据无意义,因为它只是部分更新。 +- 客户之间的竞争条件可能导致令人惊讶的错误。 为了可靠,系统必须处理这些故障并确保它们不会导致整个系统的灾难性故障。但是,实现容错机制是很多工作。它需要仔细考虑所有可能出错的事情,并进行大量的测试以确保解决方案真正起作用。 @@ -106,11 +106,11 @@ ACID意义上的隔离意味着同时执行的事务是相互隔离的:它们 回顾一下,在ACID中,原子性和隔离性描述了如果客户在同一事务中进行多次写入时数据库应该做的事情: -* 原子性 +- 原子性 如果在一系列写操作的中途发生错误,则应中止事务处理,并废除写入该处的写操作。换句话说,数据库不必担心部分失败,通过提供全或无的保证。 -* 隔离 +- 隔离 同时运行的交易不应该互相干扰。例如,如果一个事务进行多次写入,则另一个事务应该看到全部或者全部写入,而不是一些子集。 @@ -146,9 +146,9 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true 当一个对象被改变时,原子性和隔离也是适用的。例如,假设您正在向数据库写入一个20 KB的JSON文档: -* 如果在发送第一个10 KB之后网络连接中断,数据库是否存储了不可解析的10 KB JSON片段? -* 如果在数据库正在覆盖磁盘上的前一个值的过程中电源发生故障,是否最终将新旧值拼接在一起? -* 如果另一个客户端在写入过程中读取该文档,是否会看到部分更新的值? +- 如果在发送第一个10 KB之后网络连接中断,数据库是否存储了不可解析的10 KB JSON片段? +- 如果在数据库正在覆盖磁盘上的前一个值的过程中电源发生故障,是否最终将新旧值拼接在一起? +- 如果另一个客户端在写入过程中读取该文档,是否会看到部分更新的值? 这些问题会令人难以置信的混淆,因此存储引擎几乎普遍的目的是在一个节点上的单个对象(例如键值对)上提供原子性和隔离性。原子性可以通过使用日志来实现崩溃恢复(请参阅第82页的“使B树可靠”),并且可以使用每个对象的锁来实现隔离(每次只允许一个线程访问对象) )。 @@ -178,13 +178,11 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true 虽然重试一个中止的事务是一个简单而有效的错误处理机制,但它并不完美: -* 如果事务实际上成功了,但是在服务器试图确认成功提交给客户端(所以客户端认为失败)时网络发生故障,那么重试事务会导致它被执行两次,除非你有一个额外的应用程序,级别的重复数据删除机制已到位。 -* 如果错误是由于过载造成的,则重试交易将使问题变得更糟,而不是更好。为了避免这种反馈周期,您可以限制重试次数,使用指数回退,并处理与过载相关的错误(与可能的情况不同)。 -* 仅在暂时性错误(例如,由于死锁,异常情况,临时性网络中断和故障转移)之后才值得重试。在发生永久性错误(例如,违反约束)之后,重试将毫无意义。 -* 如果事务在数据库之外也有副作用,即使事务被中止,也可能发生这些副作用。例如,如果您正在发送电子邮件,则每次重试交易时都不会再发送电子邮件。如果您想确保几个不同的系统提交或放弃在一起,两阶段提交可以提供帮助(我们将在第354页的“原子提交和两阶段提交(2PC)”中讨论这个问题)。 -* 如果客户端进程在重试时失败,则任何试图写入数据库的数据都将丢失。 - - +- 如果事务实际上成功了,但是在服务器试图确认成功提交给客户端(所以客户端认为失败)时网络发生故障,那么重试事务会导致它被执行两次,除非你有一个额外的应用程序,级别的重复数据删除机制已到位。 +- 如果错误是由于过载造成的,则重试交易将使问题变得更糟,而不是更好。为了避免这种反馈周期,您可以限制重试次数,使用指数回退,并处理与过载相关的错误(与可能的情况不同)。 +- 仅在暂时性错误(例如,由于死锁,异常情况,临时性网络中断和故障转移)之后才值得重试。在发生永久性错误(例如,违反约束)之后,重试将毫无意义。 +- 如果事务在数据库之外也有副作用,即使事务被中止,也可能发生这些副作用。例如,如果您正在发送电子邮件,则每次重试交易时都不会再发送电子邮件。如果您想确保几个不同的系统提交或放弃在一起,两阶段提交可以提供帮助(我们将在第354页的“原子提交和两阶段提交(2PC)”中讨论这个问题)。 +- 如果客户端进程在重试时失败,则任何试图写入数据库的数据都将丢失。 ## 弱隔离级别 @@ -236,8 +234,8 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true 通过防止脏写,这个隔离级别避免了一些并发问题: -* 如果事务更新多个对象,脏写入会导致不好的结果。例如,考虑图7-5,图7-5说明了一个二手车销售网站,Alice和Bob两个人同时试图购买同一辆车。购买汽车需要两次数据库写入:网站上的商品需要更新以反映买家,销售发票需要发送给买家。在图7-5的情况下,销售授予鲍勃(因为他执行获奖更新列表表),但发票发送给爱丽丝(因为她执行获奖更新发票表)。阅读承诺这样的事故。 -* 但是,提交读取并不能防止图7-1中两个计数器增量之间的竞争状态。在这种情况下,第二次写入发生在第一个事务提交后,所以它不是一个肮脏的写。这仍然是不正确的,但是出于不同的原因,在第242页的“防止丢失的更新”中,我们将讨论如何使这种计数器增量安全。 +- 如果事务更新多个对象,脏写入会导致不好的结果。例如,考虑图7-5,图7-5说明了一个二手车销售网站,Alice和Bob两个人同时试图购买同一辆车。购买汽车需要两次数据库写入:网站上的商品需要更新以反映买家,销售发票需要发送给买家。在图7-5的情况下,销售授予鲍勃(因为他执行获奖更新列表表),但发票发送给爱丽丝(因为她执行获奖更新发票表)。阅读承诺这样的事故。 +- 但是,提交读取并不能防止图7-1中两个计数器增量之间的竞争状态。在这种情况下,第二次写入发生在第一个事务提交后,所以它不是一个肮脏的写。这仍然是不正确的,但是出于不同的原因,在第242页的“防止丢失的更新”中,我们将讨论如何使这种计数器增量安全。 ![](img/fig7-5.png) @@ -277,11 +275,11 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true 在Alice的情况下,这不是一个长期的问题,因为如果她几秒钟后重新加载在线银行网站,她很可能会看到一致的帐户余额。但是,有些情况不能容忍这种暂时的不一致: -* *备份* +- *备份* 进行备份需要复制整个数据库,这可能需要花费数小时才能完成。在备份过程正在运行的过程中,将继续写入数据库。因此,您可能会得到包含较旧版本数据的备份部分以及包含较新版本的其他部分。如果您需要从这样的备份中恢复,那么不一致(如消失的钱)就会变成永久的。 -* *分析查询和完整性检查* +- *分析查询和完整性检查* 有时,您可能需要运行一个查询,扫描大部分的数据库。这样的查询在分析中很常见(请参阅第90页的“事务处理或分析?”),也可能是定期完整性检查(即监视数据损坏)的一部分。如果这些查询在不同时间点观察数据库的某些部分,则这些查询可能会返回无意义的结果。 @@ -326,8 +324,8 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true 换句话说,如果以下两个条件都成立,则可见一个对象: -* 读者事务开始时,创建该对象的事务已经提交。 -* 对象未被标记为删除,或者如果是,请求删除的事务在读者事务开始时尚未提交。 +- 读者事务开始时,创建该对象的事务已经提交。 +- 对象未被标记为删除,或者如果是,请求删除的事务在读者事务开始时尚未提交。 长时间运行的事务可能会长时间继续使用快照,并继续读取(从其他事务的角度来看)早已被覆盖或删除的值。由于从不更新值,而是每次更改值时创建一个新版本,数据库可以提供一致的快照,同时只产生一个小的开销。 @@ -358,9 +356,9 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true 如果应用程序从数据库中读取一些值,修改它并写回修改的值(读取 - 修改 - 写入周期),则可能会发生丢失的更新问题。如果两个事务同时执行,则其中一个修改可能会丢失,因为第二个写入不包括第一个修改。 (我们有时会说后面写的是先前写的。)这种模式发生在各种不同的情况下: -* 增加计数器或更新账户余额(需要读取当前值,计算新值并写回更新后的值) -* 将本地更改设置为复杂的值,例如,将元素添加到JSON文档中的列表(需要解析文档,进行更改并写回修改的文档) -* 两个用户同时编辑wiki页面,每个用户通过将整个页面内容发送到服务器来保存其更改,覆盖数据库中当前的任何内容 +- 增加计数器或更新账户余额(需要读取当前值,计算新值并写回更新后的值) +- 将本地更改设置为复杂的值,例如,将元素添加到JSON文档中的列表(需要解析文档,进行更改并写回修改的文档) +- 两个用户同时编辑wiki页面,每个用户通过将整个页面内容发送到服务器来保存其更改,覆盖数据库中当前的任何内容 由于这是一个普遍的问题,所以已经开发了各种解决方案。 @@ -400,9 +398,7 @@ FOR UPDATE; COMMIT; ``` -* FOR UPDATE子句指示数据库应该对所有行进行锁定由此查询返回。 - - +- FOR UPDATE子句指示数据库应该对所有行进行锁定由此查询返回。 这是有效的,但要做到这一点,你需要仔细考虑你的应用逻辑。忘记在代码的某个地方添加一个必要的锁,很容易引入竞争条件。 @@ -555,8 +551,8 @@ COMMIT; 在本章中,我们已经看到了几个易于出现竞争条件的交易的例子。读取提交和快照隔离级别会阻止某些竞争条件,但其他竞争条件则不会。我们遇到了一些特别棘手的例子,写有歪斜和幻影。这是一个可悲的情况: -* 隔离级别难以理解,并且在不同的数据库中不一致地实现(例如,“可重复读取”的含义差别很大)。 -* 如果您查看应用程序代码,很难判断在特定的隔离级别运行是否安全 - 特别是在大型应用程序中,您可能并不知道可能同时发生的所有事情。 +- 隔离级别难以理解,并且在不同的数据库中不一致地实现(例如,“可重复读取”的含义差别很大)。 +- 如果您查看应用程序代码,很难判断在特定的隔离级别运行是否安全 - 特别是在大型应用程序中,您可能并不知道可能同时发生的所有事情。 没有好的工具来帮助我们检测竞争状况。原则上,静态分析可能有助于[26],但研究技术还没有找到实际应用的方法。并发问题的测试是很难的,因为它们通常是非确定性的 - 只有在不及时的情况下才会出现问题。 @@ -566,9 +562,9 @@ COMMIT; 但是,如果可序列化隔离比弱隔离级别的混乱好得多,那么为什么不是每个人都使用它?为了回答这个问题,我们需要看看实现可串行化的选项,以及它们如何执行。目前大多数提供可串行化的数据库都使用了三种技术之一,本章后面将会介绍这些技术。 -* 以串行顺序从字面上执行事务(请参见第252页的“实际的串行执行”) -* 两相锁定(参见第257页上的“两相锁定(2PL)”),几十年来唯一可行的选择 -* 开放式并发控制技术,例如可序列化的快照隔离(请参阅“可序列化的快照隔离(SSI)” +- 以串行顺序从字面上执行事务(请参见第252页的“实际的串行执行”) +- 两相锁定(参见第257页上的“两相锁定(2PL)”),几十年来唯一可行的选择 +- 开放式并发控制技术,例如可序列化的快照隔离(请参阅“可序列化的快照隔离(SSI)” 目前,我们将主要在单节点数据库的背景下讨论这些技术;在第9章中,我们将研究如何将它们推广到涉及分布式系统中多个节点的事务。 @@ -584,8 +580,8 @@ COMMIT; 两个事态发展引起了这个反思: -* RAM变得足够便宜,现在许多用例可以将整个活动数据集保存在内存中(请参阅第88页的“将所有内容保留在内存中”)。当事务需要访问的所有数据都在内存中时,事务处理的执行速度要比等待数据从磁盘加载时快得多。 -* 数据库设计人员意识到OLTP事务通常很短,只能进行少量的读写操作(请参阅“事务处理或分析?”)。相比之下,长时间运行的分析查询通常是只读的,因此它们可以在串行执行循环之外的一致快照(使用快照隔离)上运行。 +- RAM变得足够便宜,现在许多用例可以将整个活动数据集保存在内存中(请参阅第88页的“将所有内容保留在内存中”)。当事务需要访问的所有数据都在内存中时,事务处理的执行速度要比等待数据从磁盘加载时快得多。 +- 数据库设计人员意识到OLTP事务通常很短,只能进行少量的读写操作(请参阅“事务处理或分析?”)。相比之下,长时间运行的分析查询通常是只读的,因此它们可以在串行执行循环之外的一致快照(使用快照隔离)上运行。 串行执行事务的方法在VoltDB / H-Store,Redis和Datomic中实现[46,47,48]。设计用于单线程执行的系统有时可以比支持并发的系统更好,因为它可以避免锁定的协调开销。但是,其吞吐量仅限于单个CPU内核的吞吐量。为了充分利用单一线索,交易需要与传统形式不同的结构。 @@ -609,9 +605,9 @@ COMMIT; 存储过程在关系型数据库中已经存在了一段时间了,自1999年以来它们一直是SQL标准(SQL/PSM)的一部分。由于各种原因,它们的名声不太好: -* 每个数据库供应商都有自己的存储过程语言(Oracle有PL/SQL,SQL Server有T-SQL,PostgreSQL有PL/pgSQL等)。这些语言并没有跟上通用编程语言的发展,所以从今天的角度来看它们看起来相当丑陋和陈旧,而且它们缺乏大多数编程语言中能找到的库的生态系统。 -* 与应用程序服务器相,比在数据库中运行的代码难以管理,调试更为困难,版本控制和部署起来也比较尴尬,更难测试,难与监控系统集成。 -* 数据库通常比应用程序服务器对性能敏感的多,因为单个数据库实例通常由许多应用程序服务器共享。数据库中一个写得不好的存储过程(例如,使用大量内存或CPU时间)会比在应用程序服务器中写入相同的代码造成更多的麻烦。 +- 每个数据库供应商都有自己的存储过程语言(Oracle有PL/SQL,SQL Server有T-SQL,PostgreSQL有PL/pgSQL等)。这些语言并没有跟上通用编程语言的发展,所以从今天的角度来看它们看起来相当丑陋和陈旧,而且它们缺乏大多数编程语言中能找到的库的生态系统。 +- 与应用程序服务器相,比在数据库中运行的代码难以管理,调试更为困难,版本控制和部署起来也比较尴尬,更难测试,难与监控系统集成。 +- 数据库通常比应用程序服务器对性能敏感的多,因为单个数据库实例通常由许多应用程序服务器共享。数据库中一个写得不好的存储过程(例如,使用大量内存或CPU时间)会比在应用程序服务器中写入相同的代码造成更多的麻烦。 但是这些问题都是可以克服的。现代的存储过程实现放弃了PL/SQL,而是使用现有的通用编程语言:VoltDB使用Java或Groovy,Datomic使用Java或Clojure,而Redis使用Lua。 @@ -635,10 +631,10 @@ VoltDB还使用存储过程进行复制:不是将事务的写入从一个节 事务的串行执行已成为在一定的约束条件下实现可序列化的隔离的一种可行方法: -* 每笔交易都必须小而快,因为只需一个缓慢的交易即可拖延所有交易处理。 -* 仅限于使用活动数据集可以放入内存的情况。很少访问的数据可能会被移动到磁盘,但是如果需要在单线程事中访问,系统会变得非常慢[^x]。 -* 写入吞吐量必须足够低才能在单个CPU内核上处理,否则事务需要进行分区而不需要跨分区协调。 -* 交叉分区交易是可能的,但是它们的使用程度有很大的限制。 +- 每笔交易都必须小而快,因为只需一个缓慢的交易即可拖延所有交易处理。 +- 仅限于使用活动数据集可以放入内存的情况。很少访问的数据可能会被移动到磁盘,但是如果需要在单线程事中访问,系统会变得非常慢[^x]。 +- 写入吞吐量必须足够低才能在单个CPU内核上处理,否则事务需要进行分区而不需要跨分区协调。 +- 交叉分区交易是可能的,但是它们的使用程度有很大的限制。 [^x]: 如果事务需要访问不在内存中的数据,最好的解决方案可能是放弃事务,异步地将数据提取到内存中,同时继续处理其他事务,然后在数据加载时重新启动事务。这种方法被称为反高速缓存,正如前面在第88页“将所有内容保存在内存”中所述。 @@ -654,8 +650,8 @@ VoltDB还使用存储过程进行复制:不是将事务的写入从一个节 两相锁定类似,但使锁定要求更强。只要没有人写信,就允许多个事务同时读取同一个对象。但只要有人想写(修改或删除)对象,就需要独占访问权限: -* 如果事务A读取了一个对象,并且事务B想要写入该对象,那么B必须等到A提交或中止才能继续。 (这确保B不能在A后面意外地改变对象。) -* 如果事务A写入了一个对象,并且事务B想要读取该对象,则B必须等到A提交或中止才能继续。 (如图7-1所示,读取旧版本的对象在2PL下是不可接受的。) +- 如果事务A读取了一个对象,并且事务B想要写入该对象,那么B必须等到A提交或中止才能继续。 (这确保B不能在A后面意外地改变对象。) +- 如果事务A写入了一个对象,并且事务B想要读取该对象,则B必须等到A提交或中止才能继续。 (如图7-1所示,读取旧版本的对象在2PL下是不可接受的。) 在第二方物流中,作家不只是阻碍其他作家,他们也阻挡读者,反之亦然。快照隔离使得读者永远不会阻止写入者,编写者也不会阻止读取者(请参阅“实施快照隔离”在本页221),该功能捕获快照隔离和两阶段锁定之间的这一关键区别。另一方面,因为2PL提供了可串行性,它可以防止早先讨论的所有竞争条件,包括丢失更新和写入歪斜。 @@ -667,10 +663,10 @@ VoltDB还使用存储过程进行复制:不是将事务的写入从一个节 读写器的阻塞是通过锁定数据库中的每个对象来实现的。锁可以处于共享模式或独占模式。锁使用如下: -* 如果事务要读取对象,则必须先以共享模式获取锁。允许多个事务同时保持共享模式下的锁定,但是如果另一个事务已经在对象上拥有独占锁定,则这些事务必须等待。 -* 如果一个事务要写入一个对象,它必须首先以独占模式获取该锁。没有其他事务可以同时持有锁(共享或独占模式),所以如果对象上存在任何锁,事务必须等待。 -* 如果事务首先读取并写入对象,则可能会将其共享锁升级为独占锁。升级工作与直接获得排他锁相同。 -* 事务获得锁之后,必须继续保持锁直到事务结束(提交或中止)。这就是“两阶段”这个名字的来源:第一阶段(当事务正在执行时)是获取锁的时间,第二阶段(在事务结束时)是所有的锁被释放。 +- 如果事务要读取对象,则必须先以共享模式获取锁。允许多个事务同时保持共享模式下的锁定,但是如果另一个事务已经在对象上拥有独占锁定,则这些事务必须等待。 +- 如果一个事务要写入一个对象,它必须首先以独占模式获取该锁。没有其他事务可以同时持有锁(共享或独占模式),所以如果对象上存在任何锁,事务必须等待。 +- 如果事务首先读取并写入对象,则可能会将其共享锁升级为独占锁。升级工作与直接获得排他锁相同。 +- 事务获得锁之后,必须继续保持锁直到事务结束(提交或中止)。这就是“两阶段”这个名字的来源:第一阶段(当事务正在执行时)是获取锁的时间,第二阶段(在事务结束时)是所有的锁被释放。 由于使用了这么多的锁,因此事务A可能很容易发生,等待事务B释放它的锁,反之亦然。这种情况叫做死锁。数据库自动检测事务之间的死锁,并中止其中的一个,以便其他人可以取得进展。被中止的事务需要被应用程序重试。 @@ -703,8 +699,8 @@ WHERE room_id = 123 AND 谓词锁限制访问,如下所示: -* 如果事务A想要读取匹配某些条件的对象,就像在该SELECT查询中那样,它必须获取对查询条件的共享模式谓词锁定。如果另一个事务B当前对与这些条件匹配的任何对象具有排他锁,那么A必须等到B释放它的锁之后才允许它进行查询。 -* 如果事务A想要插入,更新或删除任何对象,则必须首先检查旧值或新值是否与任何现有的谓词锁匹配。如果事务B持有匹配的谓词锁,那么A必须等到B已经提交或中止后才能继续。 +- 如果事务A想要读取匹配某些条件的对象,就像在该SELECT查询中那样,它必须获取对查询条件的共享模式谓词锁定。如果另一个事务B当前对与这些条件匹配的任何对象具有排他锁,那么A必须等到B释放它的锁之后才允许它进行查询。 +- 如果事务A想要插入,更新或删除任何对象,则必须首先检查旧值或新值是否与任何现有的谓词锁匹配。如果事务B持有匹配的谓词锁,那么A必须等到B已经提交或中止后才能继续。 这里的关键思想是,谓词锁定甚至适用于数据库中尚不存在的对象,但可能在将来添加(幻像)对象。如果两阶段锁定包含谓词锁定,则数据库将阻止所有形式的写入歪斜和其他竞争条件,因此其隔离可以串行化。 @@ -716,8 +712,8 @@ WHERE room_id = 123 AND 在房间预订数据库中,您可能会在room_id列上有一个索引,并且/或者在start_time和end_time上有索引(否则前面的查询在大型数据库上的速度会非常慢): -* 假设您的索引位于room_id上,并且数据库使用此索引查找123号房间的现有预订。现在数据库可以简单地将共享锁附加到此索引条目,指示交易已搜索123号房间的预订。 -* 或者,如果数据库使用基于时间的索引来查找现有预订,那么它可以将共享锁附加到该索引中的一系列值,指示交易已搜索与中午的时间段重叠的预订到下午1点2018年1月1日 +- 假设您的索引位于room_id上,并且数据库使用此索引查找123号房间的现有预订。现在数据库可以简单地将共享锁附加到此索引条目,指示交易已搜索123号房间的预订。 +- 或者,如果数据库使用基于时间的索引来查找现有预订,那么它可以将共享锁附加到该索引中的一系列值,指示交易已搜索与中午的时间段重叠的预订到下午1点2018年1月1日 无论哪种方式,搜索条件的近似值都附加到其中一个索引上。现在,如果另一个交易想要插入,更新或删除同一个房间和/或重叠时间段的预订,则它将不得不更新索引的相同部分。在这样做的过程中,它会遇到共享锁,它将被迫等到锁被释放。 @@ -758,10 +754,8 @@ WHERE room_id = 123 AND 数据库如何知道查询结果是否可能已经改变?有两种情况需要考虑: -* 检测陈旧的MVCC对象版本的读取(在读取之前发生未提交的写入) -* 检测影响先前读取的写入(写入发生在读取之后) - - +- 检测陈旧的MVCC对象版本的读取(在读取之前发生未提交的写入) +- 检测影响先前读取的写入(写入发生在读取之后) #### 检测陈旧的MVCC读取 @@ -857,212 +851,165 @@ WHERE room_id = 123 AND ## 参考文献 - -1. Donald D. Chamberlin, Morton M. Astrahan, Michael W. Blasgen, et al.: - “[A History and Evaluation of System R](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.84.348&rep=rep1&type=pdf),” *Communications of the ACM*, - volume 24, number 10, pages 632–646, October 1981. - [doi:10.1145/358769.358784](http://dx.doi.org/10.1145/358769.358784) - -1. Jim N. Gray, Raymond A. Lorie, Gianfranco R. Putzolu, and Irving L. Traiger: - “[Granularity of Locks and Degrees of Consistency in a Shared Data Base](http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.92.8248&rep=rep1&type=pdf),” in *Modelling in Data - Base Management Systems: Proceedings of the IFIP Working Conference on Modelling in Data Base - Management Systems*, edited by G. M. Nijssen, pages - 364–394, Elsevier/North Holland Publishing, 1976. Also in *Readings in Database Systems*, 4th edition, edited by Joseph M. - Hellerstein and Michael Stonebraker, MIT Press, 2005. ISBN: 978-0-262-69314-1 - -1. Kapali P. Eswaran, Jim N. Gray, Raymond A. Lorie, and Irving L. Traiger: - “[The Notions of Consistency and Predicate Locks in a Database System](http://research.microsoft.com/en-us/um/people/gray/papers/On%20the%20Notions%20of%20Consistency%20and%20Predicate%20Locks%20in%20a%20Database%20System%20CACM.pdf),” *Communications of the - ACM*, volume 19, number 11, pages 624–633, November 1976. - -1. “[ACID Transactions Are Incredibly Helpful](http://web.archive.org/web/20150320053809/https://foundationdb.com/acid-claims),” FoundationDB, LLC, 2013. - -1. John D. Cook: - “[ACID Versus BASE for Database Transactions](http://www.johndcook.com/blog/2009/07/06/brewer-cap-theorem-base/),” *johndcook.com*, July 6, 2009. - -1. Gavin Clarke: - “[NoSQL's CAP Theorem Busters: We Don't Drop ACID](http://www.theregister.co.uk/2012/11/22/foundationdb_fear_of_cap_theorem/),” *theregister.co.uk*, November 22, 2012. - -1. Theo Härder and Andreas Reuter: - “[Principles of Transaction-Oriented Database Recovery](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.87.2812&rep=rep1&type=pdf),” *ACM Computing Surveys*, - volume 15, number 4, pages 287–317, December 1983. - [doi:10.1145/289.291](http://dx.doi.org/10.1145/289.291) - -1. Peter Bailis, Alan Fekete, Ali Ghodsi, et al.: - “[HAT, not CAP: Towards Highly Available Transactions](http://www.bailis.org/papers/hat-hotos2013.pdf),” - at *14th USENIX Workshop on Hot Topics in Operating Systems* (HotOS), May 2013. - -1. Armando Fox, Steven D. Gribble, Yatin Chawathe, et al.: - “[Cluster-Based Scalable Network Services](http://www.cs.berkeley.edu/~brewer/cs262b/TACC.pdf),” at - *16th ACM Symposium on Operating Systems Principles* (SOSP), October 1997. - -1. Philip A. Bernstein, Vassos Hadzilacos, and Nathan Goodman: +1. Donald D. Chamberlin, Morton M. Astrahan, Michael W. Blasgen, et al.: + “[A History and Evaluation of System R](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.84.348&rep=rep1&type=pdf),” *Communications of the ACM*, + volume 24, number 10, pages 632–646, October 1981. + [doi:10.1145/358769.358784](http://dx.doi.org/10.1145/358769.358784) +2. Jim N. Gray, Raymond A. Lorie, Gianfranco R. Putzolu, and Irving L. Traiger: + “[Granularity of Locks and Degrees of Consistency in a Shared Data Base](http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.92.8248&rep=rep1&type=pdf),” in *Modelling in Data + Base Management Systems: Proceedings of the IFIP Working Conference on Modelling in Data Base + Management Systems*, edited by G. M. Nijssen, pages + 364–394, Elsevier/North Holland Publishing, 1976. Also in *Readings in Database Systems*, 4th edition, edited by Joseph M. + Hellerstein and Michael Stonebraker, MIT Press, 2005. ISBN: 978-0-262-69314-1 +3. Kapali P. Eswaran, Jim N. Gray, Raymond A. Lorie, and Irving L. Traiger: + “[The Notions of Consistency and Predicate Locks in a Database System](http://research.microsoft.com/en-us/um/people/gray/papers/On%20the%20Notions%20of%20Consistency%20and%20Predicate%20Locks%20in%20a%20Database%20System%20CACM.pdf),” *Communications of the + ACM*, volume 19, number 11, pages 624–633, November 1976. +4. “[ACID Transactions Are Incredibly Helpful](http://web.archive.org/web/20150320053809/https://foundationdb.com/acid-claims),” FoundationDB, LLC, 2013. +5. John D. Cook: + “[ACID Versus BASE for Database Transactions](http://www.johndcook.com/blog/2009/07/06/brewer-cap-theorem-base/),” *johndcook.com*, July 6, 2009. +6. Gavin Clarke: + “[NoSQL's CAP Theorem Busters: We Don't Drop ACID](http://www.theregister.co.uk/2012/11/22/foundationdb_fear_of_cap_theorem/),” *theregister.co.uk*, November 22, 2012. +7. Theo Härder and Andreas Reuter: + “[Principles of Transaction-Oriented Database Recovery](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.87.2812&rep=rep1&type=pdf),” *ACM Computing Surveys*, + volume 15, number 4, pages 287–317, December 1983. + [doi:10.1145/289.291](http://dx.doi.org/10.1145/289.291) +8. Peter Bailis, Alan Fekete, Ali Ghodsi, et al.: + “[HAT, not CAP: Towards Highly Available Transactions](http://www.bailis.org/papers/hat-hotos2013.pdf),” + at *14th USENIX Workshop on Hot Topics in Operating Systems* (HotOS), May 2013. +9. Armando Fox, Steven D. Gribble, Yatin Chawathe, et al.: + “[Cluster-Based Scalable Network Services](http://www.cs.berkeley.edu/~brewer/cs262b/TACC.pdf),” at + *16th ACM Symposium on Operating Systems Principles* (SOSP), October 1997. +10. Philip A. Bernstein, Vassos Hadzilacos, and Nathan Goodman: *Concurrency Control and Recovery in Database Systems*. Addison-Wesley, 1987. ISBN: 978-0-201-10715-9, available online at *research.microsoft.com*. - -1. Alan Fekete, Dimitrios Liarokapis, Elizabeth O'Neil, et al.: +11. Alan Fekete, Dimitrios Liarokapis, Elizabeth O'Neil, et al.: “[Making Snapshot Isolation Serializable](https://www.cse.iitb.ac.in/infolab/Data/Courses/CS632/2009/Papers/p492-fekete.pdf),” *ACM Transactions on Database Systems*, volume 30, number 2, pages 492–528, June 2005. [doi:10.1145/1071610.1071615](http://dx.doi.org/10.1145/1071610.1071615) - -1. Mai Zheng, Joseph Tucek, Feng Qin, and Mark Lillibridge: +12. Mai Zheng, Joseph Tucek, Feng Qin, and Mark Lillibridge: “[Understanding the Robustness of SSDs Under Power Fault](https://www.usenix.org/system/files/conference/fast13/fast13-final80.pdf),” at *11th USENIX Conference on File and Storage Technologies* (FAST), February 2013. - -1. Laurie Denness: +13. Laurie Denness: “[SSDs: A Gift and a Curse](https://laur.ie/blog/2015/06/ssds-a-gift-and-a-curse/),” *laur.ie*, June 2, 2015. - -1. Adam Surak: +14. Adam Surak: “[When Solid State Drives Are Not That Solid](https://blog.algolia.com/when-solid-state-drives-are-not-that-solid/),” *blog.algolia.com*, June 15, 2015. - -1. Thanumalayan Sankaranarayana Pillai, Vijay Chidambaram, +15. Thanumalayan Sankaranarayana Pillai, Vijay Chidambaram, Ramnatthan Alagappan, et al.: “[All File Systems Are Not Created Equal: On the Complexity of Crafting Crash-Consistent Applications](http://research.cs.wisc.edu/wind/Publications/alice-osdi14.pdf),” at *11th USENIX Symposium on Operating Systems Design and Implementation* (OSDI), October 2014. - -1. Chris Siebenmann: +16. Chris Siebenmann: “[Unix's File Durability Problem](https://utcc.utoronto.ca/~cks/space/blog/unix/FileSyncProblem),” *utcc.utoronto.ca*, April 14, 2016. - -1. Lakshmi N. Bairavasundaram, Garth R. +17. Lakshmi N. Bairavasundaram, Garth R. Goodson, Bianca Schroeder, et al.: “[An Analysis of Data Corruption in the Storage Stack](http://research.cs.wisc.edu/adsl/Publications/corruption-fast08.pdf),” at *6th USENIX Conference on File and Storage Technologies* (FAST), February 2008. - -1. Bianca Schroeder, Raghav Lagisetty, and Arif Merchant: +18. Bianca Schroeder, Raghav Lagisetty, and Arif Merchant: “[Flash Reliability in Production: The Expected and the Unexpected](https://www.usenix.org/conference/fast16/technical-sessions/presentation/schroeder),” at *14th USENIX Conference on File and Storage Technologies* (FAST), February 2016. - -1. Don Allison: +19. Don Allison: “[SSD Storage – Ignorance of Technology Is No Excuse](https://blog.korelogic.com/blog/2015/03/24),” *blog.korelogic.com*, March 24, 2015. - -1. Dave Scherer: +20. Dave Scherer: “[Those Are Not Transactions (Cassandra 2.0)](http://web.archive.org/web/20150526065247/http://blog.foundationdb.com/those-are-not-transactions-cassandra-2-0),” *blog.foundationdb.com*, September 6, 2013. - -1. Kyle Kingsbury: +21. Kyle Kingsbury: “[Call Me Maybe: Cassandra](http://aphyr.com/posts/294-call-me-maybe-cassandra/),” *aphyr.com*, September 24, 2013. - -1. “[ACID Support in Aerospike](http://www.aerospike.com/docs/architecture/assets/AerospikeACIDSupport.pdf),” Aerospike, Inc., June 2014. - -1. Martin Kleppmann: +22. “[ACID Support in Aerospike](http://www.aerospike.com/docs/architecture/assets/AerospikeACIDSupport.pdf),” Aerospike, Inc., June 2014. +23. Martin Kleppmann: “[Hermitage: Testing the 'I' in ACID](http://martin.kleppmann.com/2014/11/25/hermitage-testing-the-i-in-acid.html),” *martin.kleppmann.com*, November 25, 2014. - -1. Tristan D'Agosta: +24. Tristan D'Agosta: “[BTC Stolen from Poloniex](https://bitcointalk.org/index.php?topic=499580),” *bitcointalk.org*, March 4, 2014. - -1. bitcointhief2: +25. bitcointhief2: “[How I Stole Roughly 100 BTC from an Exchange and How I Could Have Stolen More!](http://www.reddit.com/r/Bitcoin/comments/1wtbiu/how_i_stole_roughly_100_btc_from_an_exchange_and/),” *reddit.com*, February 2, 2014. - -1. Sudhir Jorwekar, Alan Fekete, Krithi Ramamritham, and S. Sudarshan: +26. Sudhir Jorwekar, Alan Fekete, Krithi Ramamritham, and S. Sudarshan: “[Automating the Detection of Snapshot Isolation Anomalies](http://www.vldb.org/conf/2007/papers/industrial/p1263-jorwekar.pdf),” at *33rd International Conference on Very Large Data Bases* (VLDB), September 2007. - -1. Michael Melanson: +27. Michael Melanson: “[Transactions: The Limits of Isolation](http://www.michaelmelanson.net/2014/03/20/transactions/),” *michaelmelanson.net*, March 20, 2014. - -1. Hal Berenson, Philip A. Bernstein, Jim N. Gray, et al.: +28. Hal Berenson, Philip A. Bernstein, Jim N. Gray, et al.: “[A Critique of ANSI SQL Isolation Levels](http://research.microsoft.com/pubs/69541/tr-95-51.pdf),” at *ACM International Conference on Management of Data* (SIGMOD), May 1995. - -1. Atul Adya: “[Weak Consistency: A Generalized Theory and Optimistic Implementations for Distributed Transactions](http://pmg.csail.mit.edu/papers/adya-phd.pdf),” +29. Atul Adya: “[Weak Consistency: A Generalized Theory and Optimistic Implementations for Distributed Transactions](http://pmg.csail.mit.edu/papers/adya-phd.pdf),” PhD Thesis, Massachusetts Institute of Technology, March 1999. - -1. Peter Bailis, Aaron Davidson, Alan Fekete, et al.: +30. Peter Bailis, Aaron Davidson, Alan Fekete, et al.: “[Highly Available Transactions: Virtues and Limitations (Extended Version)](http://arxiv.org/pdf/1302.0309.pdf),” at *40th International Conference on Very Large Data Bases* (VLDB), September 2014. - -1. Bruce Momjian: +31. Bruce Momjian: “[MVCC Unmasked](http://momjian.us/main/presentations/internals.html#mvcc),” *momjian.us*, July 2014. - -1. Annamalai Gurusami: +32. Annamalai Gurusami: “[Repeatable Read Isolation Level in InnoDB – How Consistent Read View Works](https://blogs.oracle.com/mysqlinnodb/entry/repeatable_read_isolation_level_in),” *blogs.oracle.com*, January 15, 2013. - -1. Nikita Prokopov: +33. Nikita Prokopov: “[Unofficial Guide to Datomic Internals](http://tonsky.me/blog/unofficial-guide-to-datomic-internals/),” *tonsky.me*, May 6, 2014. - -1. Baron Schwartz: +34. Baron Schwartz: “[Immutability, MVCC, and Garbage Collection](http://www.xaprb.com/blog/2013/12/28/immutability-mvcc-and-garbage-collection/),” *xaprb.com*, December 28, 2013. - -1. J. Chris Anderson, Jan Lehnardt, and Noah Slater: +35. J. Chris Anderson, Jan Lehnardt, and Noah Slater: *CouchDB: The Definitive Guide*. O'Reilly Media, 2010. ISBN: 978-0-596-15589-6 - -1. Rikdeb Mukherjee: +36. Rikdeb Mukherjee: “[Isolation in DB2 (Repeatable Read, Read Stability, Cursor Stability, Uncommitted Read) with Examples](http://mframes.blogspot.co.uk/2013/07/isolation-in-cursor.html),” *mframes.blogspot.co.uk*, July 4, 2013. - -1. Steve Hilker: +37. Steve Hilker: “[Cursor Stability (CS) – IBM DB2 Community](http://www.toadworld.com/platforms/ibmdb2/w/wiki/6661.cursor-stability-cs.aspx),” *toadworld.com*, March 14, 2013. - -1. Nate Wiger: +38. Nate Wiger: “[An Atomic Rant](http://www.nateware.com/an-atomic-rant.html),” *nateware.com*, February 18, 2010. - -1. Joel Jacobson: +39. Joel Jacobson: “[Riak 2.0: Data Types](http://blog.joeljacobson.com/riak-2-0-data-types/),” *blog.joeljacobson.com*, March 23, 2014. - -1. Michael J. Cahill, Uwe Röhm, and Alan Fekete: +40. Michael J. Cahill, Uwe Röhm, and Alan Fekete: “[Serializable Isolation for Snapshot Databases](http://www.cs.nyu.edu/courses/fall12/CSCI-GA.2434-001/p729-cahill.pdf),” at *ACM International Conference on Management of Data* (SIGMOD), June 2008. [doi:10.1145/1376616.1376690](http://dx.doi.org/10.1145/1376616.1376690) - -1. Dan R. K. Ports and Kevin Grittner: +41. Dan R. K. Ports and Kevin Grittner: “[Serializable Snapshot Isolation in PostgreSQL](http://drkp.net/papers/ssi-vldb12.pdf),” at *38th International Conference on Very Large Databases* (VLDB), August 2012. - -1. Tony Andrews: +42. Tony Andrews: “[Enforcing Complex Constraints in Oracle](http://tonyandrews.blogspot.co.uk/2004/10/enforcing-complex-constraints-in.html),” *tonyandrews.blogspot.co.uk*, October 15, 2004. - -1. Douglas B. Terry, Marvin M. Theimer, Karin Petersen, et al.: +43. Douglas B. Terry, Marvin M. Theimer, Karin Petersen, et al.: “[Managing Update Conflicts in Bayou, a Weakly Connected Replicated Storage System](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.141.7889&rep=rep1&type=pdf),” at *15th ACM Symposium on Operating Systems Principles* (SOSP), December 1995. [doi:10.1145/224056.224070](http://dx.doi.org/10.1145/224056.224070) - -1. Gary Fredericks: +44. Gary Fredericks: “[Postgres Serializability Bug](https://github.com/gfredericks/pg-serializability-bug),” *github.com*, September 2015. - -1. Michael Stonebraker, Samuel Madden, Daniel J. Abadi, et al.: +45. Michael Stonebraker, Samuel Madden, Daniel J. Abadi, et al.: “[The End of an Architectural Era (It’s Time for a Complete Rewrite)](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.137.3697&rep=rep1&type=pdf),” at *33rd International Conference on Very Large Data Bases* (VLDB), September 2007. - -1. John Hugg: +46. John Hugg: “[H-Store/VoltDB Architecture vs. CEP Systems and Newer Streaming Architectures](https://www.youtube.com/watch?v=hD5M4a1UVz8),” at *Data @Scale Boston*, November 2014. - -1. Robert Kallman, Hideaki Kimura, Jonathan Natkins, et al.: +47. Robert Kallman, Hideaki Kimura, Jonathan Natkins, et al.: “[H-Store: A High-Performance, Distributed Main Memory Transaction Processing System](http://www.vldb.org/pvldb/1/1454211.pdf),” *Proceedings of the VLDB Endowment*, volume 1, number 2, pages 1496–1499, August 2008. - -1. Rich Hickey: +48. Rich Hickey: “[The Architecture of Datomic](http://www.infoq.com/articles/Architecture-Datomic),” *infoq.com*, November 2, 2012. - -1. John Hugg: +49. John Hugg: “[Debunking Myths About the VoltDB In-Memory Database](http://voltdb.com/blog/debunking-myths-about-voltdb-memory-database),” *voltdb.com*, May 12, 2014. - -1. Joseph M. Hellerstein, Michael Stonebraker, and James Hamilton: +50. Joseph M. Hellerstein, Michael Stonebraker, and James Hamilton: “[Architecture of a Database System](http://db.cs.berkeley.edu/papers/fntdb07-architecture.pdf),” *Foundations and Trends in Databases*, volume 1, number 2, pages 141–259, November 2007. [doi:10.1561/1900000002](http://dx.doi.org/10.1561/1900000002) - -1. Michael J. Cahill: +51. Michael J. Cahill: “[Serializable Isolation for Snapshot Databases](http://cahill.net.au/wp-content/uploads/2010/02/cahill-thesis.pdf),” PhD Thesis, University of Sydney, July 2009. - -1. D. Z. Badal: +52. D. Z. Badal: “[Correctness of Concurrency Control and Implications in Distributed Databases](http://ieeexplore.ieee.org/abstract/document/762563/),” at *3rd International IEEE Computer Software and Applications Conference* (COMPSAC), November 1979. - -1. Rakesh Agrawal, Michael J. Carey, and Miron Livny: +53. Rakesh Agrawal, Michael J. Carey, and Miron Livny: “[Concurrency Control Performance Modeling: Alternatives and Implications](http://www.eecs.berkeley.edu/~brewer/cs262/ConcControl.pdf),” *ACM Transactions on Database Systems* (TODS), volume 12, number 4, pages 609–654, December 1987. [doi:10.1145/32204.32220](http://dx.doi.org/10.1145/32204.32220) - -1. Dave Rosenthal: +54. Dave Rosenthal: “[Databases at 14.4MHz](http://web.archive.org/web/20150427041746/http://blog.foundationdb.com/databases-at-14.4mhz),” *blog.foundationdb.com*, December 10, 2014. + + +------ + +| 上一章 | 目录 | 下一章 | +| ---------------------- | ------------------------------- | ---------------------------------- | +| [第六章:分片](ch6.md) | [设计数据密集型应用](README.md) | [第八章:分布式系统的麻烦](ch7.md) | \ No newline at end of file diff --git a/gis/intro.md b/gis/intro.md deleted file mode 100644 index 3cfd24e..0000000 --- a/gis/intro.md +++ /dev/null @@ -1,241 +0,0 @@ -# PostGIS简明教程 - -PostGIS是PostgreSQL强大扩展能力的最佳示例,它已经成为GIS行业的事实标准,值得用几本书去专门讲。但这里不妨先管中窥豹一下。 - -## 1. 安装与配置 - -安装与配置并不是PostGIS的学习重点,然而它确实是许多新人入门的最大拦路虎。 - -建议直接使用现成的二进制包,发行版来安装PostGIS,而不是手工编译,这会轻松很多。 - -在Mac上可以通过homebrew一键安装PostGIS - -```bash -/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" -``` - -在CentOS上可以通过yum安装,在Ubuntu可以通过apt-get安装,不再赘述 - -连接PostgreSQL并执行以下查询,确认PostGIS扩展已经正确地安装,可以被数据库识别: - -```bash -vonng=# SELECT name,default_version FROM pg_available_extensions WHERE name ~ 'gis'; - name | default_version -------------------------+----------------- - postgis | 2.4.3 - postgis_tiger_geocoder | 2.4.3 - postgis_topology | 2.4.3 - btree_gist | 1.5 - postgis_sfcgal | 2.4.3 -``` - - - -## 2. 创建GIS数据库 - -PostGIS是PostgreSQL的一个扩展,连接并执行以下命令,可在当前数据库中加载PostGIS插件。 - -```sql -CREATE EXTENSION postgis; -CREATE EXTENSION postgis_topology; -CREATE EXTENSION postgis_sfcgal; -CREATE EXTENSION fuzzystrmatch; -CREATE EXTENSION address_standardizer; -CREATE EXTENSION address_standardizer_data_us; -CREATE EXTENSION postgis_tiger_geocoder; -``` - -执行完毕后,执行`postgis_full_version`查看当前PostGIS版本。 - -```sql -gis=# SELECT postgis_full_version(); - -POSTGIS="2.4.3 r16312" PGSQL="100" GEOS="3.6.2-CAPI-1.10.2 4d2925d6" PROJ="Rel. 4.9.3, 15 August 2016" GDAL="GDAL 1.11.5, released 2016/07/01" LIBXML="2.9.7" LIBJSON="0.12.1" RASTER -``` - -现在GIS数据库已经准备好了。让我们进入主题吧。 - -## 3. 几何对象 - -PostGIS支持很多几何类型:点,线,多边形,复合几何体等,并提供了大量实用的相关函数。 - -注意,虽然PostGIS中的几何类型与PostgreSQL内建的几何类型非常像,但它们并不是一回事。所有PostGIS中的对象命名通常都以`ST`开头,是空间类型(Spatial Type)的缩写。 - -对于PostGIS而言,所有几何对象都有一个公共父类`Geometry`,这种面向对象的组织形式允许在数据库中进行一些灵活的操作:例如在数据表中的同一列中存储不同的几何对象。 - -每种几何对象实际上都是PostGIS底层C++几何库geos中对象包装,这些几何类型按照面向对象的继承关系组成了一颗树: - -![](img/gis-type.png) - - - -### 几何对象的创建 - -几何对象可以通过PostGIS内建的函数进行创建,例如: - -```sql ->>> SELECT ST_Point(1.0, 2.0); -0101000000000000000000F03F0000000000000040 -``` - -注意,当查询原始集合类型时,PostgreSQL会返回几何对象的二进制数据的十六进制表示。这允许各类ETL工具以同样的方式高效处理PostGIS类型,但二进制表示对人类很不友好,可以通过`ST_AsText`获取人类可读的格式。 - -```sql ->>> SELECT ST_AsText(ST_Point(1.0, 2.0)); -POINT(1 2) -``` - -当然,如同PostgreSQL内建的类型一样,PostGIS类型也可以使用字面值的方式创建。 - -```SQL -CREATE TABLE geom ( - geom GEOMETRY -); - -INSERT INTO geom VALUES -('Point(1 2)'), -('LineString(0 0,1 1,2 1,2 3)'), -('Polygon((0 0, 1 0, 1 1,0 1,0 0))'), -('MultiPoint(1 2,3 4)'); -``` - -通常在使用PostGIS中,几何类型使用统一的`Geometry`类型。如果需要判断具体的几何类型,则可以使用`ST_GeometryType`。 - -```sql -geo=# SELECT ST_GeometryType(geom), ST_AsText(geom) FROM geom; - - st_geometrytype | st_astext ------------------+-------------------------------- - ST_Point | POINT(1 2) - ST_LineString | LINESTRING(0 0,1 1,2 1,2 3) - ST_Polygon | POLYGON((0 0,1 0,1 1,0 1,0 0)) - ST_MultiPoint | MULTIPOINT(1 2,3 4) -``` - -### 点 - -让我们从最简单的**点(Point)**开始。PostGIS的点默认是二维空间中的点,具有两个`double`类型的分量`x,y`。使用`ST_X, ST_Y`可以从点中取出对应的坐标分量 - -```sql -geo=# SELECT ST_X(geom), ST_Y(geom) FROM geom WHERE ST_GeometryType(geom) = 'ST_Point'; - st_x | st_y -------+------ - 1 | 2 -``` - -在介绍更多几何类型前, - - - -## 4. Play with Point - -单纯使用PostGIS的Point,就已经可以实现许多有趣的功能了。 - -### 计算两点距离 - -```sql -geo=# SELECT ST_Point(1,1) <-> ST_Point(2,2); -1.4142135623730951 -``` - -运算符`<->`可以计算左右两侧两点之间的距离。 - -### 应用:查找最近的餐馆 - -现在我们有一张包含全国所有餐馆的表,有五千万条记录: - -```sql -CREATE TABLE poi( - id BIGSERIAL, - name TEXT, - position GEOMETRY -) -``` - -如果我现在在国贸`(116.458855, 39.909863)`,想要找到距离这里最近的10家餐厅。应该如何查询呢? - -```sql -SELECT name FROM poi -ORDER BY position <-> ST_Point(116.458855, 39.909863) LIMIT 10; -``` - -```sql - QUERY PLAN ---------------------------------------------------------------------------------------------- - Limit (cost=4610514.44..4610514.47 rows=10 width=31) - -> Sort (cost=4610514.44..4767389.77 rows=62750132 width=31) - Sort Key: (("position" <-> '0101000000CAA65CE15D1D5D40946B0A6476F44340'::geometry)) - -> Seq Scan on poi (cost=0.00..3254506.65 rows=62750132 width=31) -``` - -执行需要一次扫表,需要几分钟的时间。对于只有几千行、每天查询几十次来说,这也没什么大不了的。但对于几千万的数据量,几万的查询QPS,就需要索引了。 - -在`position`列上创建GIST索引: - -```sql -CREATE INDEX CONCURRENTLY idx_poi_position_gist ON poi USING gist(position); -``` - -然后再执行同样的查询,变为了索引扫描。 - -```sql - QUERY PLAN ------------------------------------------------------------------------------------------------------- - Limit (cost=0.42..9.73 rows=10 width=31) - -> Index Scan using idx_poi_position_gist on poi (cost=0.42..58440964.86 rows=62750132 width=31) - Order By: ("position" <-> '0101000000CAA65CE15D1D5D40946B0A6476F44340'::geometry) -``` - -结果,在0.1毫秒内就返回了结果! - -```sql -geo=# SELECT name -FROM poi -ORDER BY position <-> ST_Point(116.458855, 39.909863) -LIMIT 10; - name ------------------------------- - 苹果智元咨询北京有限公司 - 住友商社 - 国贸 - 路易·费罗(国贸店) - addidas(国贸店) - 博艺府家 - 北京尚正明远信息技术研究中心 - 北京竹露桐花商贸有限公司 - 文心雕龙 - -(10 rows) - -Time: 0.993 ms -``` - -也许需要成百上千行应用代码实现的功能,现在一行SQL就可以搞定,而且性能相当瞩目。 - - - -## 线段 - -### 表示道路 - -* 找出城市里最长的道路 - - -* 计算城市道路里程 -* 计算全国道路里程 - - - - -## 多边形 - -### 带洞的多边形 - -表示复杂的地理对象,例如:工人体育馆 - -### 地理围栏 - -例如你有用户的位置轨迹数据,现在希望研究用户经过了哪些商圈。 - - -