update punction marks

This commit is contained in:
Gang Yin 2021-12-23 10:57:51 +08:00
parent 37f3ed9380
commit c8ed9a15d0
18 changed files with 96 additions and 96 deletions

34
ch1.md
View File

@ -128,7 +128,7 @@
* 将人们最容易犯错的地方与可能导致失效的地方**解耦decouple**。特别是提供一个功能齐全的非生产环境**沙箱sandbox**,使人们可以在不影响真实用户的情况下,使用真实数据安全地探索和实验。 * 将人们最容易犯错的地方与可能导致失效的地方**解耦decouple**。特别是提供一个功能齐全的非生产环境**沙箱sandbox**,使人们可以在不影响真实用户的情况下,使用真实数据安全地探索和实验。
* 在各个层次进行彻底的测试【3】从单元测试、全系统集成测试到手动测试。自动化测试易于理解已经被广泛使用特别适合用来覆盖正常情况中少见的**边缘场景corner case**。 * 在各个层次进行彻底的测试【3】从单元测试、全系统集成测试到手动测试。自动化测试易于理解已经被广泛使用特别适合用来覆盖正常情况中少见的**边缘场景corner case**。
* 允许从人为错误中简单快速地恢复,以最大限度地减少失效情况带来的影响。 例如,快速回滚配置变更,分批发布新代码(以便任何意外错误只影响一小部分用户),并提供数据重算工具(以备旧的计算出错)。 * 允许从人为错误中简单快速地恢复,以最大限度地减少失效情况带来的影响。 例如,快速回滚配置变更,分批发布新代码(以便任何意外错误只影响一小部分用户),并提供数据重算工具(以备旧的计算出错)。
* 配置详细和明确的监控,比如性能指标和错误率。 在其他工程学科中这指的是**遥测telemetry**(一旦火箭离开了地面,遥测技术对于跟踪发生的事情和理解失败是至关重要的)监控可以向我们发出预警信号,并允许我们检查是否有任何地方违反了假设和约束。当出现问题时,指标数据对于问题诊断是非常宝贵的。 * 配置详细和明确的监控,比如性能指标和错误率。 在其他工程学科中这指的是**遥测telemetry**(一旦火箭离开了地面,遥测技术对于跟踪发生的事情和理解失败是至关重要的)监控可以向我们发出预警信号,并允许我们检查是否有任何地方违反了假设和约束。当出现问题时,指标数据对于问题诊断是非常宝贵的。
* 良好的管理实践与充分的培训——一个复杂而重要的方面,但超出了本书的范围。 * 良好的管理实践与充分的培训——一个复杂而重要的方面,但超出了本书的范围。
### 可靠性有多重要? ### 可靠性有多重要?
@ -285,7 +285,7 @@
* 简单性Simplicity * 简单性Simplicity
从系统中消除尽可能多的**复杂度complexity**,使新工程师也能轻松理解系统(注意这和用户接口的简单性不一样 从系统中消除尽可能多的**复杂度complexity**,使新工程师也能轻松理解系统(注意这和用户接口的简单性不一样)
* 可演化性evolability * 可演化性evolability
@ -299,26 +299,26 @@
运维团队对于保持软件系统顺利运行至关重要。一个优秀运维团队的典型职责如下或者更多【29】 运维团队对于保持软件系统顺利运行至关重要。一个优秀运维团队的典型职责如下或者更多【29】
* 监控系统的运行状况,并在服务状态不佳时快速恢复服务 * 监控系统的运行状况,并在服务状态不佳时快速恢复服务
* 跟踪问题的原因,例如系统故障或性能下降 * 跟踪问题的原因,例如系统故障或性能下降
* 及时更新软件和平台,比如安全补丁 * 及时更新软件和平台,比如安全补丁
* 了解系统间的相互作用,以便在异常变更造成损失前进行规避。 * 了解系统间的相互作用,以便在异常变更造成损失前进行规避。
* 预测未来的问题,并在问题出现之前加以解决(例如,容量规划) * 预测未来的问题,并在问题出现之前加以解决(例如,容量规划)
* 建立部署,配置、管理方面的良好实践,编写相应工具 * 建立部署,配置、管理方面的良好实践,编写相应工具
* 执行复杂的维护任务,例如将应用程序从一个平台迁移到另一个平台 * 执行复杂的维护任务,例如将应用程序从一个平台迁移到另一个平台
* 当配置变更时,维持系统的安全性 * 当配置变更时,维持系统的安全性
* 定义工作流程,使运维操作可预测,并保持生产环境稳定。 * 定义工作流程,使运维操作可预测,并保持生产环境稳定。
* 铁打的营盘流水的兵,维持组织对系统的了解。 * 铁打的营盘流水的兵,维持组织对系统的了解。
良好的可操作性意味着更轻松的日常工作,进而运维团队能专注于高价值的事情。数据系统可以通过各种方式使日常任务更轻松: 良好的可操作性意味着更轻松的日常工作,进而运维团队能专注于高价值的事情。数据系统可以通过各种方式使日常任务更轻松:
* 通过良好的监控,提供对系统内部状态和运行时行为的**可见性visibility** * 通过良好的监控,提供对系统内部状态和运行时行为的**可见性visibility**
* 为自动化提供良好支持,将系统与标准化工具相集成 * 为自动化提供良好支持,将系统与标准化工具相集成
* 避免依赖单台机器(在整个系统继续不间断运行的情况下允许机器停机维护) * 避免依赖单台机器(在整个系统继续不间断运行的情况下允许机器停机维护)
* 提供良好的文档和易于理解的操作模型“如果做X会发生Y” * 提供良好的文档和易于理解的操作模型“如果做X会发生Y”
* 提供良好的默认行为,但需要时也允许管理员自由覆盖默认值 * 提供良好的默认行为,但需要时也允许管理员自由覆盖默认值
* 有条件时进行自我修复,但需要时也允许管理员手动控制系统状态 * 有条件时进行自我修复,但需要时也允许管理员手动控制系统状态
* 行为可预测,最大限度减少意外 * 行为可预测,最大限度减少意外
### 简单性:管理复杂度 ### 简单性:管理复杂度
@ -357,7 +357,7 @@
一个应用必须满足各种需求才称得上有用。有一些**功能需求**functional requirements即它应该做什么比如允许以各种方式存储检索搜索和处理数据以及一些**非功能性需求*nonfunctional即通用属性例如安全性、可靠性、合规性、可伸缩性、兼容性和可维护性。在本章详细讨论了可靠性可伸缩性和可维护性。 一个应用必须满足各种需求才称得上有用。有一些**功能需求**functional requirements即它应该做什么比如允许以各种方式存储检索搜索和处理数据以及一些**非功能性需求*nonfunctional即通用属性例如安全性、可靠性、合规性、可伸缩性、兼容性和可维护性。在本章详细讨论了可靠性可伸缩性和可维护性。
**可靠性Reliability** 意味着即使发生故障,系统也能正常工作。故障可能发生在硬件(通常是随机的和不相关的)软件通常是系统性的Bug很难处理和人类(不可避免地时不时出错)。 **容错技术** 可以对终端用户隐藏某些类型的故障。 **可靠性Reliability** 意味着即使发生故障,系统也能正常工作。故障可能发生在硬件(通常是随机的和不相关的)软件通常是系统性的Bug很难处理和人类不可避免地时不时出错**容错技术** 可以对终端用户隐藏某些类型的故障。
**可伸缩性Scalability** 意味着即使在负载增加的情况下也有保持性能的策略。为了讨论可伸缩性,我们首先需要定量描述负载和性能的方法。我们简要了解了推特主页时间线的例子,介绍描述负载的方法,并将响应时间百分位点作为衡量性能的一种方式。在可伸缩的系统中可以添加 **处理容量processing capacity** 以在高负载下保持可靠。 **可伸缩性Scalability** 意味着即使在负载增加的情况下也有保持性能的策略。为了讨论可伸缩性,我们首先需要定量描述负载和性能的方法。我们简要了解了推特主页时间线的例子,介绍描述负载的方法,并将响应时间百分位点作为衡量性能的一种方式。在可伸缩的系统中可以添加 **处理容量processing capacity** 以在高负载下保持可靠。

View File

@ -277,7 +277,7 @@ Unix和关系数据库以非常不同的哲学来处理信息管理问题。Unix
在这个典型的Web应用模型中数据库充当一种可以通过网络同步访问的可变共享变量。应用程序可以读取和更新变量而数据库负责维持它的持久性提供一些诸如并发控制和容错的功能。 在这个典型的Web应用模型中数据库充当一种可以通过网络同步访问的可变共享变量。应用程序可以读取和更新变量而数据库负责维持它的持久性提供一些诸如并发控制和容错的功能。
但是,在大多数编程语言中,你无法订阅可变变量中的变更 —— 你只能定期读取它。与电子表格不同,如果变量的值发生变化,变量的读者不会收到通知(你可以在自己的代码中实现这样的通知 —— 这被称为**观察者模式** —— 但大多数语言没有将这种模式作为内置功能 但是,在大多数编程语言中,你无法订阅可变变量中的变更 —— 你只能定期读取它。与电子表格不同,如果变量的值发生变化,变量的读者不会收到通知(你可以在自己的代码中实现这样的通知 —— 这被称为**观察者模式** —— 但大多数语言没有将这种模式作为内置功能)
数据库继承了这种可变数据的被动方法:如果你想知道数据库的内容是否发生了变化,通常你唯一的选择就是轮询(即定期重复你的查询)。 订阅变更只是刚刚开始出现的功能(请参阅“[变更流的API支持](ch11.md#变更流的API支持)”)。 数据库继承了这种可变数据的被动方法:如果你想知道数据库的内容是否发生了变化,通常你唯一的选择就是轮询(即定期重复你的查询)。 订阅变更只是刚刚开始出现的功能(请参阅“[变更流的API支持](ch11.md#变更流的API支持)”)。
@ -461,7 +461,7 @@ COMMIT;
即使我们可以抑制数据库客户端与服务器之间的重复事务我们仍然需要担心终端用户设备与应用服务器之间的网络。例如如果终端用户的客户端是Web浏览器则它可能会使用HTTP POST请求向服务器提交指令。也许用户正处于一个信号微弱的蜂窝数据网络连接中它们成功地发送了POST但却在能够从服务器接收响应之前没了信号。 即使我们可以抑制数据库客户端与服务器之间的重复事务我们仍然需要担心终端用户设备与应用服务器之间的网络。例如如果终端用户的客户端是Web浏览器则它可能会使用HTTP POST请求向服务器提交指令。也许用户正处于一个信号微弱的蜂窝数据网络连接中它们成功地发送了POST但却在能够从服务器接收响应之前没了信号。
在这种情况下,可能会向用户显示错误消息,而他们可能会手动重试。 Web浏览器警告说“你确定要再次提交这个表单吗” —— 用户选“是”,因为他们希望操作发生Post/Redirect/Get模式【54】可以避免在正常操作中出现此警告消息但POST请求超时就没办法了从Web服务器的角度来看重试是一个独立的请求从数据库的角度来看这是一个独立的事务。通常的除重机制无济于事。 在这种情况下,可能会向用户显示错误消息,而他们可能会手动重试。 Web浏览器警告说“你确定要再次提交这个表单吗” —— 用户选“是”因为他们希望操作发生Post/Redirect/Get模式【54】可以避免在正常操作中出现此警告消息但POST请求超时就没办法了从Web服务器的角度来看重试是一个独立的请求从数据库的角度来看这是一个独立的事务。通常的除重机制无济于事。
#### 操作标识符 #### 操作标识符
@ -490,7 +490,7 @@ COMMIT;
抑制重复事务的这种情况只是一个更普遍的原则的一个例子,这个原则被称为**端到端原则end-to-end argument**它在1984年由SaltzerReed和Clark阐述【55】 抑制重复事务的这种情况只是一个更普遍的原则的一个例子,这个原则被称为**端到端原则end-to-end argument**它在1984年由SaltzerReed和Clark阐述【55】
> 只有在通信系统两端应用的知识与帮助下,所讨论的功能才能完全地正确地实现。因而将这种被质疑的功能作为通信系统本身的功能是不可能的(有时,通信系统可以提供这种功能的不完备版本,可能有助于提高性能 > 只有在通信系统两端应用的知识与帮助下,所讨论的功能才能完全地正确地实现。因而将这种被质疑的功能作为通信系统本身的功能是不可能的(有时,通信系统可以提供这种功能的不完备版本,可能有助于提高性能)
> >
在我们的例子中**所讨论的功能**是重复抑制。我们看到TCP在TCP连接层次抑制了重复的数据包一些流处理器在消息处理层次提供了所谓的恰好一次语义但这些都无法阻止当一个请求超时时用户亲自提交重复的请求。TCP数据库事务以及流处理器本身并不能完全排除这些重复。解决这个问题需要一个端到端的解决方案从终端用户的客户端一路传递到数据库的事务标识符。 在我们的例子中**所讨论的功能**是重复抑制。我们看到TCP在TCP连接层次抑制了重复的数据包一些流处理器在消息处理层次提供了所谓的恰好一次语义但这些都无法阻止当一个请求超时时用户亲自提交重复的请求。TCP数据库事务以及流处理器本身并不能完全排除这些重复。解决这个问题需要一个端到端的解决方案从终端用户的客户端一路传递到数据库的事务标识符。

10
ch2.md
View File

@ -50,7 +50,7 @@
采用NoSQL数据库的背后有几个驱动因素其中包括 采用NoSQL数据库的背后有几个驱动因素其中包括
* 需要比关系数据库更好的可伸缩性,包括非常大的数据集或非常高的写入吞吐量 * 需要比关系数据库更好的可伸缩性,包括非常大的数据集或非常高的写入吞吐量
* 相比商业数据库产品,免费和开源软件更受偏爱 * 相比商业数据库产品,免费和开源软件更受偏爱
* 关系模型不能很好地支持一些特殊的查询操作 * 关系模型不能很好地支持一些特殊的查询操作
* 受挫于关系模型的限制性渴望一种更具多动态性与表现力的数据模型【5】 * 受挫于关系模型的限制性渴望一种更具多动态性与表现力的数据模型【5】
@ -208,7 +208,7 @@ CODASYL中的查询是通过利用遍历记录列和跟随访问路径表在数
在关系数据库中,查询优化器自动决定查询的哪些部分以哪个顺序执行,以及使用哪些索引。这些选择实际上是“访问路径”,但最大的区别在于它们是由查询优化器自动生成的,而不是由程序员生成,所以我们很少需要考虑它们。 在关系数据库中,查询优化器自动决定查询的哪些部分以哪个顺序执行,以及使用哪些索引。这些选择实际上是“访问路径”,但最大的区别在于它们是由查询优化器自动生成的,而不是由程序员生成,所以我们很少需要考虑它们。
如果想按新的方式查询数据,你可以声明一个新的索引,查询会自动使用最合适的那些索引。无需更改查询来利用新的索引(请参阅“[数据查询语言](#数据查询语言)”)关系模型因此使添加应用程序新功能变得更加容易。 如果想按新的方式查询数据,你可以声明一个新的索引,查询会自动使用最合适的那些索引。无需更改查询来利用新的索引(请参阅“[数据查询语言](#数据查询语言)”)关系模型因此使添加应用程序新功能变得更加容易。
关系数据库的查询优化器是复杂的已耗费了多年的研究和开发精力【18】。关系模型的一个关键洞察是只需构建一次查询优化器随后使用该数据库的所有应用程序都可以从中受益。如果你没有查询优化器的话那么为特定查询手动编写访问路径比编写通用优化器更容易——不过从长期看通用解决方案更好。 关系数据库的查询优化器是复杂的已耗费了多年的研究和开发精力【18】。关系模型的一个关键洞察是只需构建一次查询优化器随后使用该数据库的所有应用程序都可以从中受益。如果你没有查询优化器的话那么为特定查询手动编写访问路径比编写通用优化器更容易——不过从长期看通用解决方案更好。
@ -581,7 +581,7 @@ CREATE INDEX edges_heads ON edges (head_vertex);
关于这个模型的一些重要方面是: 关于这个模型的一些重要方面是:
1. 任何顶点都可以有一条边连接到任何其他顶点。没有模式限制哪种事物可不可以关联。 1. 任何顶点都可以有一条边连接到任何其他顶点。没有模式限制哪种事物可不可以关联。
2. 给定任何顶点,可以高效地找到它的入边和出边,从而遍历图,即沿着一系列顶点的路径前后移动(这就是为什么[例2-2]()在`tail_vertex`和`head_vertex`列上都有索引的原因 2. 给定任何顶点,可以高效地找到它的入边和出边,从而遍历图,即沿着一系列顶点的路径前后移动(这就是为什么[例2-2]()在`tail_vertex`和`head_vertex`列上都有索引的原因)
3. 通过对不同类型的关系使用不同的标签,可以在一个图中存储几种不同的信息,同时仍然保持一个清晰的数据模型。 3. 通过对不同类型的关系使用不同的标签,可以在一个图中存储几种不同的信息,同时仍然保持一个清晰的数据模型。
这些特性为数据建模提供了很大的灵活性,如[图2-5](img/fig2-5.png)所示。图中显示了一些传统关系模式难以表达的事情例如不同国家的不同地区结构法国有省和州美国有不同的州和州国中国的怪事先忽略主权国家和国家错综复杂的烂摊子不同的数据粒度Lucy现在的住所被指定为一个城市而她的出生地点只是在一个州的级别 这些特性为数据建模提供了很大的灵活性,如[图2-5](img/fig2-5.png)所示。图中显示了一些传统关系模式难以表达的事情例如不同国家的不同地区结构法国有省和州美国有不同的州和州国中国的怪事先忽略主权国家和国家错综复杂的烂摊子不同的数据粒度Lucy现在的住所被指定为一个城市而她的出生地点只是在一个州的级别
@ -590,7 +590,7 @@ CREATE INDEX edges_heads ON edges (head_vertex);
### Cypher查询语言 ### Cypher查询语言
Cypher是属性图的声明式查询语言为Neo4j图形数据库而发明【37】它是以电影“黑客帝国”中的一个角色来命名的而与密码术中的密码无关【38】 Cypher是属性图的声明式查询语言为Neo4j图形数据库而发明【37】它是以电影“黑客帝国”中的一个角色来命名的而与密码术中的密码无关【38】
[例2-3]()显示了将[图2-5](img/fig2-5.png)的左边部分插入图形数据库的Cypher查询。可以类似地添加图的其余部分为了便于阅读而省略。每个顶点都有一个像`USA`或`Idaho`这样的符号名称,查询的其他部分可以使用这些名称在顶点之间创建边,使用箭头符号:`Idaho - [WITHIN] ->USA`创建一条标记为`WITHIN`的边,`Idaho`为尾节点,`USA`为头节点。 [例2-3]()显示了将[图2-5](img/fig2-5.png)的左边部分插入图形数据库的Cypher查询。可以类似地添加图的其余部分为了便于阅读而省略。每个顶点都有一个像`USA`或`Idaho`这样的符号名称,查询的其他部分可以使用这些名称在顶点之间创建边,使用箭头符号:`Idaho - [WITHIN] ->USA`创建一条标记为`WITHIN`的边,`Idaho`为尾节点,`USA`为头节点。
@ -790,7 +790,7 @@ RDF有一些奇怪之处因为它是为了在互联网上交换数据而设
### SPARQL查询语言 ### SPARQL查询语言
**SPARQL**是一种用于三元组存储的面向RDF数据模型的查询语言【43】它是SPARQL协议和RDF查询语言的缩写发音为“sparkle”SPARQL早于Cypher并且由于Cypher的模式匹配借鉴于SPARQL这使得它们看起来非常相似【37】。 **SPARQL**是一种用于三元组存储的面向RDF数据模型的查询语言【43】它是SPARQL协议和RDF查询语言的缩写发音为“sparkle”SPARQL早于Cypher并且由于Cypher的模式匹配借鉴于SPARQL这使得它们看起来非常相似【37】。
与之前相同的查询 - 查找从美国转移到欧洲的人 - 使用SPARQL比使用Cypher甚至更为简洁请参阅[例2-9]())。 与之前相同的查询 - 查找从美国转移到欧洲的人 - 使用SPARQL比使用Cypher甚至更为简洁请参阅[例2-9]())。

4
ch3.md
View File

@ -49,7 +49,7 @@ $ db_get 42
{"name":"San Francisco","attractions":["Golden Gate Bridge"]} {"name":"San Francisco","attractions":["Golden Gate Bridge"]}
``` ```
底层的存储格式非常简单一个文本文件每行包含一条逗号分隔的键值对忽略转义问题的话大致与CSV文件类似。每次对 `db_set` 的调用都会向文件末尾追加记录,所以更新键的时候旧版本的值不会被覆盖 —— 因而查找最新值的时候,需要找到文件中键最后一次出现的位置(因此 `db_get` 中使用了 `tail -n 1 ` ) 底层的存储格式非常简单一个文本文件每行包含一条逗号分隔的键值对忽略转义问题的话大致与CSV文件类似。每次对 `db_set` 的调用都会向文件末尾追加记录,所以更新键的时候旧版本的值不会被覆盖 —— 因而查找最新值的时候,需要找到文件中键最后一次出现的位置(因此 `db_get` 中使用了 `tail -n 1 ` )
```bash ```bash
$ db_set 42 '{"name":"San Francisco","attractions":["Exploratorium"]}' $ db_set 42 '{"name":"San Francisco","attractions":["Exploratorium"]}'
@ -236,7 +236,7 @@ Lucene是Elasticsearch和Solr使用的一种全文搜索的索引引擎它使
[^ii]: 向B树中插入一个新的键是相当符合直觉的但删除一个键同时保持树平衡就会牵扯很多其他东西了【2】。 [^ii]: 向B树中插入一个新的键是相当符合直觉的但删除一个键同时保持树平衡就会牵扯很多其他东西了【2】。
这个算法可以确保树保持平衡具有n个键的B树总是具有 $O(log n)$ 的深度。大多数数据库可以放入一个三到四层的B树所以你不需要追踪多个页面引用来找到你正在查找的页面分支因子为500的4KB页面的四层树可以存储多达256TB的数据 这个算法可以确保树保持平衡具有n个键的B树总是具有 $O(log n)$ 的深度。大多数数据库可以放入一个三到四层的B树所以你不需要追踪多个页面引用来找到你正在查找的页面分支因子为500的4KB页面的四层树可以存储多达256TB的数据
#### 让B树更可靠 #### 让B树更可靠

10
ch4.md
View File

@ -248,7 +248,7 @@ Avro的关键思想是Writer模式和Reader模式不必是相同的 - 他们只
使用Avro向前兼容性意味着你可以将新版本的模式作为Writer并将旧版本的模式作为Reader。相反向后兼容意味着你可以有一个作为Reader的新版本模式和作为Writer的旧版本模式。 使用Avro向前兼容性意味着你可以将新版本的模式作为Writer并将旧版本的模式作为Reader。相反向后兼容意味着你可以有一个作为Reader的新版本模式和作为Writer的旧版本模式。
为了保持兼容性,你只能添加或删除具有默认值的字段我们的Avro模式中的字段`favoriteNumber`的默认值为`null`。例如假设你添加了一个有默认值的字段这个新的字段将存在于新模式而不是旧模式中。当使用新模式的Reader读取使用旧模式写入的记录时将为缺少的字段填充默认值。 为了保持兼容性你只能添加或删除具有默认值的字段我们的Avro模式中的字段`favoriteNumber`的默认值为`null`。例如假设你添加了一个有默认值的字段这个新的字段将存在于新模式而不是旧模式中。当使用新模式的Reader读取使用旧模式写入的记录时将为缺少的字段填充默认值。
如果你要添加一个没有默认值的字段新的Reader将无法读取旧Writer写的数据所以你会破坏向后兼容性。如果你要删除没有默认值的字段旧的Reader将无法读取新Writer写入的数据因此你会打破向前兼容性。在一些编程语言中null是任何变量可以接受的默认值但在Avro中并不是这样如果要允许一个字段为`null`,则必须使用联合类型。例如,`union {null, long, string} field;`表示field可以是数字或字符串也可以是`null`。如果要将null作为默认值则它必须是union的分支之一[^iv]。这样的写法比默认情况下就允许任何变量是`null`显得更加冗长,但是通过明确什么可以和什么不可以是`null`有助于防止出错【22】。 如果你要添加一个没有默认值的字段新的Reader将无法读取旧Writer写的数据所以你会破坏向后兼容性。如果你要删除没有默认值的字段旧的Reader将无法读取新Writer写入的数据因此你会打破向前兼容性。在一些编程语言中null是任何变量可以接受的默认值但在Avro中并不是这样如果要允许一个字段为`null`,则必须使用联合类型。例如,`union {null, long, string} field;`表示field可以是数字或字符串也可以是`null`。如果要将null作为默认值则它必须是union的分支之一[^iv]。这样的写法比默认情况下就允许任何变量是`null`显得更加冗长,但是通过明确什么可以和什么不可以是`null`有助于防止出错【22】。
@ -266,11 +266,11 @@ Avro的关键思想是Writer模式和Reader模式不必是相同的 - 他们只
* 有很多记录的大文件 * 有很多记录的大文件
Avro的一个常见用途 - 尤其是在Hadoop环境中 - 用于存储包含数百万条记录的大文件,所有记录都使用相同的模式进行编码(我们将在[第十章](ch10.md)讨论这种情况在这种情况下该文件的作者可以在文件的开头只包含一次Writer模式。 Avro指定了一个文件格式对象容器文件来做到这一点。 Avro的一个常见用途 - 尤其是在Hadoop环境中 - 用于存储包含数百万条记录的大文件,所有记录都使用相同的模式进行编码(我们将在[第十章](ch10.md)讨论这种情况)在这种情况下该文件的作者可以在文件的开头只包含一次Writer模式。 Avro指定了一个文件格式对象容器文件来做到这一点。
* 支持独立写入的记录的数据库 * 支持独立写入的记录的数据库
在一个数据库中不同的记录可能会在不同的时间点使用不同的Writer模式来写入 - 你不能假定所有的记录都有相同的模式。最简单的解决方案是在每个编码记录的开始处包含一个版本号并在数据库中保留一个模式版本列表。Reader可以获取记录提取版本号然后从数据库中获取该版本号的Writer模式。使用该Writer模式它可以解码记录的其余部分例如Espresso 【23】就是这样工作的 在一个数据库中不同的记录可能会在不同的时间点使用不同的Writer模式来写入 - 你不能假定所有的记录都有相同的模式。最简单的解决方案是在每个编码记录的开始处包含一个版本号并在数据库中保留一个模式版本列表。Reader可以获取记录提取版本号然后从数据库中获取该版本号的Writer模式。使用该Writer模式它可以解码记录的其余部分例如Espresso 【23】就是这样工作的
* 通过网络连接发送记录 * 通过网络连接发送记录
@ -286,7 +286,7 @@ Avro的关键思想是Writer模式和Reader模式不必是相同的 - 他们只
现在如果数据库模式发生变化例如一个表中添加了一列删除了一列则可以从更新的数据库模式生成新的Avro模式并在新的Avro模式中导出数据。数据导出过程不需要注意模式的改变 - 每次运行时都可以简单地进行模式转换。任何读取新数据文件的人都会看到记录的字段已经改变但是由于字段是通过名字来标识的所以更新的Writer模式仍然可以与旧的Reader模式匹配。 现在如果数据库模式发生变化例如一个表中添加了一列删除了一列则可以从更新的数据库模式生成新的Avro模式并在新的Avro模式中导出数据。数据导出过程不需要注意模式的改变 - 每次运行时都可以简单地进行模式转换。任何读取新数据文件的人都会看到记录的字段已经改变但是由于字段是通过名字来标识的所以更新的Writer模式仍然可以与旧的Reader模式匹配。
相比之下如果你为此使用Thrift或Protocol Buffers则字段标签可能必须手动分配每次数据库模式更改时管理员都必须手动更新从数据库列名到字段标签的映射(这可能会自动化,但模式生成器必须非常小心,不要分配以前使用的字段标签这种动态生成的模式根本不是Thrift或Protocol Buffers的设计目标而是Avro的。 相比之下如果你为此使用Thrift或Protocol Buffers则字段标签可能必须手动分配每次数据库模式更改时管理员都必须手动更新从数据库列名到字段标签的映射这可能会自动化但模式生成器必须非常小心不要分配以前使用的字段标签这种动态生成的模式根本不是Thrift或Protocol Buffers的设计目标而是Avro的。
#### 代码生成和动态类型的语言 #### 代码生成和动态类型的语言
@ -416,7 +416,7 @@ Web服务仅仅是通过网络进行API请求的一系列技术的最新版本
所有这些都是基于 **远程过程调用RPC** 的思想该过程调用自20世纪70年代以来一直存在【42】。 RPC模型试图向远程网络服务发出请求看起来与在同一进程中调用编程语言中的函数或方法相同这种抽象称为位置透明。尽管RPC起初看起来很方便但这种方法根本上是有缺陷的【43,44】。网络请求与本地函数调用非常不同 所有这些都是基于 **远程过程调用RPC** 的思想该过程调用自20世纪70年代以来一直存在【42】。 RPC模型试图向远程网络服务发出请求看起来与在同一进程中调用编程语言中的函数或方法相同这种抽象称为位置透明。尽管RPC起初看起来很方便但这种方法根本上是有缺陷的【43,44】。网络请求与本地函数调用非常不同
* 本地函数调用是可预测的,并且成功或失败仅取决于受你控制的参数。网络请求是不可预知的:由于网络问题,请求或响应可能会丢失,或者远程计算机可能很慢或不可用,这些问题完全不在你的控制范围之内。网络问题是常见的,所以你必须预测他们,例如通过重试失败的请求。 * 本地函数调用是可预测的,并且成功或失败仅取决于受你控制的参数。网络请求是不可预知的:由于网络问题,请求或响应可能会丢失,或者远程计算机可能很慢或不可用,这些问题完全不在你的控制范围之内。网络问题是常见的,所以你必须预测他们,例如通过重试失败的请求。
* 本地函数调用要么返回结果,要么抛出异常,或者永远不返回(因为进入无限循环或进程崩溃)。网络请求有另一个可能的结果:由于超时,它可能会返回没有结果。在这种情况下,你根本不知道发生了什么:如果你没有得到来自远程服务的响应,你无法知道请求是否通过(我们将在[第八章](ch8.md)更详细地讨论这个问题 * 本地函数调用要么返回结果,要么抛出异常,或者永远不返回(因为进入无限循环或进程崩溃)。网络请求有另一个可能的结果:由于超时,它可能会返回没有结果。在这种情况下,你根本不知道发生了什么:如果你没有得到来自远程服务的响应,你无法知道请求是否通过(我们将在[第八章](ch8.md)更详细地讨论这个问题)
* 如果你重试失败的网络请求,可能会发生请求实际上正在通过,只有响应丢失。在这种情况下,重试将导致该操作被执行多次,除非你在协议中引入去重机制(**幂等**即idempotence。本地函数调用没有这个问题。 (在[第十一章](ch11.md)更详细地讨论幂等性) * 如果你重试失败的网络请求,可能会发生请求实际上正在通过,只有响应丢失。在这种情况下,重试将导致该操作被执行多次,除非你在协议中引入去重机制(**幂等**即idempotence。本地函数调用没有这个问题。 (在[第十一章](ch11.md)更详细地讨论幂等性)
* 每次调用本地功能时,通常需要大致相同的时间来执行。网络请求比函数调用要慢得多,而且其延迟也是非常可变的:好的时候它可能会在不到一毫秒的时间内完成,但是当网络拥塞或者远程服务超载时,可能需要几秒钟的时间完成一样的东西。 * 每次调用本地功能时,通常需要大致相同的时间来执行。网络请求比函数调用要慢得多,而且其延迟也是非常可变的:好的时候它可能会在不到一毫秒的时间内完成,但是当网络拥塞或者远程服务超载时,可能需要几秒钟的时间完成一样的东西。
* 调用本地函数时,可以高效地将引用(指针)传递给本地内存中的对象。当你发出一个网络请求时,所有这些参数都需要被编码成可以通过网络发送的一系列字节。如果参数是像数字或字符串这样的基本类型倒是没关系,但是对于较大的对象很快就会变成问题。 * 调用本地函数时,可以高效地将引用(指针)传递给本地内存中的对象。当你发出一个网络请求时,所有这些参数都需要被编码成可以通过网络发送的一系列字节。如果参数是像数字或字符串这样的基本类型倒是没关系,但是对于较大的对象很快就会变成问题。

8
ch5.md
View File

@ -234,7 +234,7 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
从异步从库读取第二个异常例子是,用户可能会遇到 **时光倒流moving backward in time** 从异步从库读取第二个异常例子是,用户可能会遇到 **时光倒流moving backward in time**
如果用户从不同从库进行多次读取,就可能发生这种情况。例如,[图5-4](img/fig5-4.png)显示了用户2345两次进行相同的查询首先查询了一个延迟很小的从库然后是一个延迟较大的从库(如果用户刷新网页,而每个请求被路由到一个随机的服务器,这种情况是很有可能的第一个查询返回最近由用户1234添加的评论但是第二个查询不返回任何东西因为滞后的从库还没有拉取写入内容。在效果上相比第一个查询第二个查询是在更早的时间点来观察系统。如果第一个查询没有返回任何内容那问题并不大因为用户2345可能不知道用户1234最近添加了评论。但如果用户2345先看见用户1234的评论然后又看到它消失那么对于用户2345就很让人头大了。 如果用户从不同从库进行多次读取,就可能发生这种情况。例如,[图5-4](img/fig5-4.png)显示了用户2345两次进行相同的查询首先查询了一个延迟很小的从库然后是一个延迟较大的从库如果用户刷新网页而每个请求被路由到一个随机的服务器这种情况是很有可能的第一个查询返回最近由用户1234添加的评论但是第二个查询不返回任何东西因为滞后的从库还没有拉取写入内容。在效果上相比第一个查询第二个查询是在更早的时间点来观察系统。如果第一个查询没有返回任何内容那问题并不大因为用户2345可能不知道用户1234最近添加了评论。但如果用户2345先看见用户1234的评论然后又看到它消失那么对于用户2345就很让人头大了。
![](img/fig5-4.png) ![](img/fig5-4.png)
@ -578,7 +578,7 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
后者被认为是一个**宽松的法定人数sloppy quorum**【37】写和读仍然需要w和r成功的响应但这些响应可能来自不在指定的n个“主”节点中的其它节点。比方说如果你把自己锁在房子外面你可能会敲开邻居的门问你是否可以暂时呆在沙发上。 后者被认为是一个**宽松的法定人数sloppy quorum**【37】写和读仍然需要w和r成功的响应但这些响应可能来自不在指定的n个“主”节点中的其它节点。比方说如果你把自己锁在房子外面你可能会敲开邻居的门问你是否可以暂时呆在沙发上。
一旦网络中断得到解决,代表另一个节点临时接受的一个节点的任何写入都被发送到适当的“主”节点。这就是所谓的**提示移交hinted handoff**(一旦你再次找到你的房子的钥匙,你的邻居礼貌地要求你离开沙发回家 一旦网络中断得到解决,代表另一个节点临时接受的一个节点的任何写入都被发送到适当的“主”节点。这就是所谓的**提示移交hinted handoff**(一旦你再次找到你的房子的钥匙,你的邻居礼貌地要求你离开沙发回家)
宽松的法定人数对写入可用性的提高特别有用只要有任何w节点可用数据库就可以接受写入。然而这意味着即使当$w + r> n$时也不能确定读取某个键的最新值因为最新的值可能已经临时写入了n之外的某些节点【47】。 宽松的法定人数对写入可用性的提高特别有用只要有任何w节点可用数据库就可以接受写入。然而这意味着即使当$w + r> n$时也不能确定读取某个键的最新值因为最新的值可能已经临时写入了n之外的某些节点【47】。
@ -675,7 +675,7 @@ LWW实现了最终收敛的目标但以**持久性**为代价:如果同一
* 服务器为每个键保留一个版本号,每次写入键时都增加版本号,并将新版本号与写入的值一起存储。 * 服务器为每个键保留一个版本号,每次写入键时都增加版本号,并将新版本号与写入的值一起存储。
* 当客户端读取键时,服务器将返回所有未覆盖的值以及最新的版本号。客户端在写入前必须读取。 * 当客户端读取键时,服务器将返回所有未覆盖的值以及最新的版本号。客户端在写入前必须读取。
* 客户端写入键时,必须包含之前读取的版本号,并且必须将之前读取的所有值合并在一起(针对写入请求的响应可以像读取请求一样,返回所有当前值,这使得我们可以像购物车示例那样将多个写入串联起来 * 客户端写入键时,必须包含之前读取的版本号,并且必须将之前读取的所有值合并在一起(针对写入请求的响应可以像读取请求一样,返回所有当前值,这使得我们可以像购物车示例那样将多个写入串联起来)
* 当服务器接收到具有特定版本号的写入时,它可以覆盖该版本号或更低版本的所有值(因为它知道它们已经被合并到新的值中),但是它必须用更高的版本号来保存所有值(因为这些值与随后的写入是并发的)。 * 当服务器接收到具有特定版本号的写入时,它可以覆盖该版本号或更低版本的所有值(因为它知道它们已经被合并到新的值中),但是它必须用更高的版本号来保存所有值(因为这些值与随后的写入是并发的)。
当一个写入包含前一次读取的版本号时,它会告诉我们的写入是基于之前的哪一种状态。如果在不包含版本号的情况下进行写操作,则与所有其他写操作并发,因此它不会覆盖任何内容 —— 只会在随后的读取中作为其中一个值返回。 当一个写入包含前一次读取的版本号时,它会告诉我们的写入是基于之前的哪一种状态。如果在不包含版本号的情况下进行写操作,则与所有其他写操作并发,因此它不会覆盖任何内容 —— 只会在随后的读取中作为其中一个值返回。
@ -688,7 +688,7 @@ LWW实现了最终收敛的目标但以**持久性**为代价:如果同一
以购物车为例,一种合理的合并值的方法就是做并集。在[图5-14](img/fig5-14.png)中,最后的合并结果是[牛奶,面粉,鸡蛋,熏肉]和[鸡蛋,牛奶,火腿]。注意牛奶和鸡蛋同时出现在两个并发值里,即使他们每个只被写过一次。合并的值可以是[牛奶,面粉,鸡蛋,培根,火腿],他们没有重复。 以购物车为例,一种合理的合并值的方法就是做并集。在[图5-14](img/fig5-14.png)中,最后的合并结果是[牛奶,面粉,鸡蛋,熏肉]和[鸡蛋,牛奶,火腿]。注意牛奶和鸡蛋同时出现在两个并发值里,即使他们每个只被写过一次。合并的值可以是[牛奶,面粉,鸡蛋,培根,火腿],他们没有重复。
然而,如果你想让人们也可以从他们的购物车中**删除**东西而不是仅仅添加东西那么把并发值做并集可能不会产生正确的结果如果你合并了两个客户端的购物车并且只在其中一个客户端里面删掉了它那么被删除的项目会重新出现在这两个客户端的交集结果中【37】。为了防止这个问题一个项目在删除时不能简单地从数据库中删除相反系统必须留下一个具有适当版本号的标记在合并兄弟时表明该项目已被删除。这种删除标记被称为**墓碑tombstone**(我们之前在“[散列索引”](ch3.md#散列索引)中的日志压缩的上下文中看到了墓碑 然而,如果你想让人们也可以从他们的购物车中**删除**东西而不是仅仅添加东西那么把并发值做并集可能不会产生正确的结果如果你合并了两个客户端的购物车并且只在其中一个客户端里面删掉了它那么被删除的项目会重新出现在这两个客户端的交集结果中【37】。为了防止这个问题一个项目在删除时不能简单地从数据库中删除相反系统必须留下一个具有适当版本号的标记在合并兄弟时表明该项目已被删除。这种删除标记被称为**墓碑tombstone**(我们之前在“[散列索引”](ch3.md#散列索引)中的日志压缩的上下文中看到了墓碑)
因为在应用程序代码中做合并是复杂且易出错,所以有一些数据结构被设计出来用于自动执行这种合并,如“[自动冲突解决](#自动冲突解决)”中讨论的。例如Riak的数据类型支持使用称为CRDT的数据结构家族【38,39,55】可以以合理的方式自动合并包括保留删除。 因为在应用程序代码中做合并是复杂且易出错,所以有一些数据结构被设计出来用于自动执行这种合并,如“[自动冲突解决](#自动冲突解决)”中讨论的。例如Riak的数据类型支持使用称为CRDT的数据结构家族【38,39,55】可以以合理的方式自动合并包括保留删除。

8
ch7.md
View File

@ -79,7 +79,7 @@ ACID原子性的定义特征是**能够在错误时中止事务,丢弃该
ACID一致性的概念是**对数据的一组特定约束必须始终成立**。即**不变量invariants**。例如,在会计系统中,所有账户整体上必须借贷相抵。如果一个事务开始于一个满足这些不变量的有效数据库,且在事务处理期间的任何写入操作都保持这种有效性,那么可以确定,不变量总是满足的。 ACID一致性的概念是**对数据的一组特定约束必须始终成立**。即**不变量invariants**。例如,在会计系统中,所有账户整体上必须借贷相抵。如果一个事务开始于一个满足这些不变量的有效数据库,且在事务处理期间的任何写入操作都保持这种有效性,那么可以确定,不变量总是满足的。
但是,一致性的这种概念取决于应用程序对不变量的理解,应用程序负责正确定义它的事务,并保持一致性。这并不是数据库可以保证的事情:如果你写入违反不变量的脏数据,数据库也无法阻止你(一些特定类型的不变量可以由数据库检查,例如外键约束或唯一约束,但是一般来说,是应用程序来定义什么样的数据是有效的,什么样是无效的。—— 数据库只管存储 但是,一致性的这种概念取决于应用程序对不变量的理解,应用程序负责正确定义它的事务,并保持一致性。这并不是数据库可以保证的事情:如果你写入违反不变量的脏数据,数据库也无法阻止你(一些特定类型的不变量可以由数据库检查,例如外键约束或唯一约束,但是一般来说,是应用程序来定义什么样的数据是有效的,什么样是无效的。—— 数据库只管存储)
原子性隔离性和持久性是数据库的属性而一致性在ACID意义上是应用程序的属性。应用可能依赖数据库的原子性和隔离属性来实现一致性但这并不仅取决于数据库。因此字母C不属于ACID[^i]。 原子性隔离性和持久性是数据库的属性而一致性在ACID意义上是应用程序的属性。应用可能依赖数据库的原子性和隔离属性来实现一致性但这并不仅取决于数据库。因此字母C不属于ACID[^i]。
@ -454,7 +454,7 @@ UPDATE wiki_pages SET content = '新内容'
在复制数据库中(请参阅[第五章](ch5.md)),防止丢失的更新需要考虑另一个维度:由于在多个节点上存在数据副本,并且在不同节点上的数据可能被并发地修改,因此需要采取一些额外的步骤来防止丢失更新。 在复制数据库中(请参阅[第五章](ch5.md)),防止丢失的更新需要考虑另一个维度:由于在多个节点上存在数据副本,并且在不同节点上的数据可能被并发地修改,因此需要采取一些额外的步骤来防止丢失更新。
锁和CAS操作假定只有一个最新的数据副本。但是多主或无主复制的数据库通常允许多个写入并发执行并异步复制到副本上因此无法保证只有一个最新数据的副本。所以基于锁或CAS操作的技术不适用于这种情况(我们将在“[线性一致性](ch9.md#线性一致性)”中更详细地讨论这个问题 锁和CAS操作假定只有一个最新的数据副本。但是多主或无主复制的数据库通常允许多个写入并发执行并异步复制到副本上因此无法保证只有一个最新数据的副本。所以基于锁或CAS操作的技术不适用于这种情况我们将在“[线性一致性](ch9.md#线性一致性)”中更详细地讨论这个问题)
相反,如“[检测并发写入](ch5.md#检测并发写入)”一节所述,这种复制数据库中的一种常见方法是允许并发写入创建多个冲突版本的值(也称为兄弟),并使用应用代码或特殊数据结构在事实发生之后解决和合并这些版本。 相反,如“[检测并发写入](ch5.md#检测并发写入)”一节所述,这种复制数据库中的一种常见方法是允许并发写入创建多个冲突版本的值(也称为兄弟),并使用应用代码或特殊数据结构在事实发生之后解决和合并这些版本。
@ -677,8 +677,8 @@ VoltDB还使用存储过程进行复制但不是将事务的写入结果从
两阶段锁定类似,但是锁的要求更强得多。只要没有写入,就允许多个事务同时读取同一个对象。但对象只要有写入(修改或删除),就需要**独占访问exclusive access** 权限: 两阶段锁定类似,但是锁的要求更强得多。只要没有写入,就允许多个事务同时读取同一个对象。但对象只要有写入(修改或删除),就需要**独占访问exclusive access** 权限:
- 如果事务A读取了一个对象并且事务B想要写入该对象那么B必须等到A提交或中止才能继续这确保B不能在A底下意外地改变对象 - 如果事务A读取了一个对象并且事务B想要写入该对象那么B必须等到A提交或中止才能继续这确保B不能在A底下意外地改变对象
- 如果事务A写入了一个对象并且事务B想要读取该对象则B必须等到A提交或中止才能继续(像[图7-1](img/fig7-1.png)那样读取旧版本的对象在2PL下是不可接受的 - 如果事务A写入了一个对象并且事务B想要读取该对象则B必须等到A提交或中止才能继续像[图7-1](img/fig7-1.png)那样读取旧版本的对象在2PL下是不可接受的
在2PL中写入不仅会阻塞其他写入也会阻塞读反之亦然。快照隔离使得**读不阻塞写,写也不阻塞读**(请参阅“[实现快照隔离](#实现快照隔离)”这是2PL和快照隔离之间的关键区别。另一方面因为2PL提供了可串行化的性质它可以防止早先讨论的所有竞争条件包括丢失更新和写入偏差。 在2PL中写入不仅会阻塞其他写入也会阻塞读反之亦然。快照隔离使得**读不阻塞写,写也不阻塞读**(请参阅“[实现快照隔离](#实现快照隔离)”这是2PL和快照隔离之间的关键区别。另一方面因为2PL提供了可串行化的性质它可以防止早先讨论的所有竞争条件包括丢失更新和写入偏差。

4
ch8.md
View File

@ -71,7 +71,7 @@
* 在地理位置分散的部署中(保持数据在地理位置上接近用户以减少访问延迟),通信很可能通过互联网进行,与本地网络相比,通信速度缓慢且不可靠。超级计算机通常假设它们的所有节点都靠近在一起。 * 在地理位置分散的部署中(保持数据在地理位置上接近用户以减少访问延迟),通信很可能通过互联网进行,与本地网络相比,通信速度缓慢且不可靠。超级计算机通常假设它们的所有节点都靠近在一起。
如果要使分布式系统工作,就必须接受部分故障的可能性,并在软件中建立容错机制。换句话说,我们需要从不可靠的组件构建一个可靠的系统(正如“[可靠性](ch1.md#可靠性)”中所讨论的那样,没有完美的可靠性,所以我们需要理解我们可以实际承诺的极限 如果要使分布式系统工作,就必须接受部分故障的可能性,并在软件中建立容错机制。换句话说,我们需要从不可靠的组件构建一个可靠的系统(正如“[可靠性](ch1.md#可靠性)”中所讨论的那样,没有完美的可靠性,所以我们需要理解我们可以实际承诺的极限)
即使在只有少数节点的小型系统中,考虑部分故障也是很重要的。在一个小系统中,很可能大部分组件在大部分时间都正常工作。然而,迟早会有一部分系统出现故障,软件必须以某种方式处理。故障处理必须是软件设计的一部分,并且作为软件的运维,你需要知道在发生故障的情况下,软件可能会表现出怎样的行为。 即使在只有少数节点的小型系统中,考虑部分故障也是很重要的。在一个小系统中,很可能大部分组件在大部分时间都正常工作。然而,迟早会有一部分系统出现故障,软件必须以某种方式处理。故障处理必须是软件设计的一部分,并且作为软件的运维,你需要知道在发生故障的情况下,软件可能会表现出怎样的行为。
@ -602,7 +602,7 @@ Web应用程序确实需要预期受终端用户控制的客户端如Web浏
为了澄清这种情况,有必要区分两种不同的属性:**安全safety属性**和**活性liveness属性**。在刚刚给出的例子中,**唯一性**和**单调序列**是安全属性,而**可用性**是活性属性。 为了澄清这种情况,有必要区分两种不同的属性:**安全safety属性**和**活性liveness属性**。在刚刚给出的例子中,**唯一性**和**单调序列**是安全属性,而**可用性**是活性属性。
这两种性质有什么区别?一个试金石就是,活性属性通常在定义中通常包括“**最终**”一词是的你猜对了——最终一致性是一个活性属性【89】 这两种性质有什么区别?一个试金石就是,活性属性通常在定义中通常包括“**最终**”一词是的你猜对了——最终一致性是一个活性属性【89】
安全通常被非正式地定义为:**没有坏事发生**,而活性通常就类似:**最终好事发生**。但是最好不要过多地阅读那些非正式的定义因为好与坏的含义是主观的。安全和活性的实际定义是精确的和数学的【90】 安全通常被非正式地定义为:**没有坏事发生**,而活性通常就类似:**最终好事发生**。但是最好不要过多地阅读那些非正式的定义因为好与坏的含义是主观的。安全和活性的实际定义是精确的和数学的【90】

12
ch9.md
View File

@ -101,7 +101,7 @@
在一个线性一致的系统中,我们可以想象,在 `x` 的值从`0` 自动翻转到 `1` 的时候(在写操作的开始和结束之间)必定有一个时间点。因此,如果一个客户端的读取返回新的值 `1`,即使写操作尚未完成,所有后续读取也必须返回新值。 在一个线性一致的系统中,我们可以想象,在 `x` 的值从`0` 自动翻转到 `1` 的时候(在写操作的开始和结束之间)必定有一个时间点。因此,如果一个客户端的读取返回新的值 `1`,即使写操作尚未完成,所有后续读取也必须返回新值。
[图9-3](img/fig9-3.png)中的箭头说明了这个时序依赖关系。客户端A 是第一个读取新的值 `1` 的位置。在A 的读取返回之后B开始新的读取。由于B的读取严格在发生于A的读取之后因此即使C的写入仍在进行中也必须返回 `1`(与[图9-1](img/fig9-1.png)中的Alice和Bob的情况相同在Alice读取新值之后Bob也希望读取新的值 [图9-3](img/fig9-3.png)中的箭头说明了这个时序依赖关系。客户端A 是第一个读取新的值 `1` 的位置。在A 的读取返回之后B开始新的读取。由于B的读取严格在发生于A的读取之后因此即使C的写入仍在进行中也必须返回 `1`(与[图9-1](img/fig9-1.png)中的Alice和Bob的情况相同在Alice读取新值之后Bob也希望读取新的值
我们可以进一步细化这个时序图,展示每个操作是如何在特定时刻原子性生效的。[图9-4](img/fig9-4.png)显示了一个更复杂的例子【10】。 我们可以进一步细化这个时序图,展示每个操作是如何在特定时刻原子性生效的。[图9-4](img/fig9-4.png)显示了一个更复杂的例子【10】。
@ -435,7 +435,7 @@ CAP定理的正式定义仅限于很狭隘的范围【30】它只考虑了一
虽然兰伯特时间戳定义了一个与因果一致的全序,但它还不足以解决分布式系统中的许多常见问题。 虽然兰伯特时间戳定义了一个与因果一致的全序,但它还不足以解决分布式系统中的许多常见问题。
例如,考虑一个需要确保用户名能唯一标识用户帐户的系统。如果两个用户同时尝试使用相同的用户名创建帐户,则其中一个应该成功,另一个应该失败(我们之前在“[领导者和锁](ch8.md#领导者和锁)”中提到过这个问题 例如,考虑一个需要确保用户名能唯一标识用户帐户的系统。如果两个用户同时尝试使用相同的用户名创建帐户,则其中一个应该成功,另一个应该失败(我们之前在“[领导者和锁](ch8.md#领导者和锁)”中提到过这个问题)
乍看之下,似乎操作的全序关系足以解决这一问题(例如使用兰伯特时间戳):如果创建了两个具有相同用户名的帐户,选择时间戳较小的那个作为胜者(第一个抓到用户名的人),并让带有更大时间戳者失败。由于时间戳上有全序关系,所以这个比较总是可行的。 乍看之下,似乎操作的全序关系足以解决这一问题(例如使用兰伯特时间戳):如果创建了两个具有相同用户名的帐户,选择时间戳较小的那个作为胜者(第一个抓到用户名的人),并让带有更大时间戳者失败。由于时间戳上有全序关系,所以这个比较总是可行的。
@ -511,9 +511,9 @@ CAP定理的正式定义仅限于很狭隘的范围【30】它只考虑了一
尽管这一过程保证写入是线性一致的,但它并不保证读取也是线性一致的 —— 如果你从与日志异步更新的存储中读取数据,结果可能是陈旧的。 (精确地说,这里描述的过程提供了**顺序一致性sequential consistency**【47,64】有时也称为**时间线一致性timeline consistency**【65,66】比线性一致性稍微弱一些的保证。为了使读取也线性一致有几个选项 尽管这一过程保证写入是线性一致的,但它并不保证读取也是线性一致的 —— 如果你从与日志异步更新的存储中读取数据,结果可能是陈旧的。 (精确地说,这里描述的过程提供了**顺序一致性sequential consistency**【47,64】有时也称为**时间线一致性timeline consistency**【65,66】比线性一致性稍微弱一些的保证。为了使读取也线性一致有几个选项
* 你可以通过在日志中追加一条消息,然后读取日志,直到该消息被读回才执行实际的读取操作。消息在日志中的位置因此定义了读取发生的时间点etcd的法定人数读取有些类似这种情况【16】 * 你可以通过在日志中追加一条消息然后读取日志直到该消息被读回才执行实际的读取操作。消息在日志中的位置因此定义了读取发生的时间点etcd的法定人数读取有些类似这种情况【16】
* 如果日志允许以线性一致的方式获取最新日志消息的位置,则可以查询该位置,等待该位置前的所有消息都传达到你,然后执行读取。 这是Zookeeper `sync()` 操作背后的思想【15】 * 如果日志允许以线性一致的方式获取最新日志消息的位置,则可以查询该位置,等待该位置前的所有消息都传达到你,然后执行读取。 这是Zookeeper `sync()` 操作背后的思想【15】
* 你可以从同步更新的副本中进行读取,因此可以确保结果是最新的这种技术用于链式复制chain replication【63】请参阅“[关于复制的研究](ch5.md#关于复制的研究)” * 你可以从同步更新的副本中进行读取因此可以确保结果是最新的这种技术用于链式复制chain replication【63】请参阅“[关于复制的研究](ch5.md#关于复制的研究)”)
#### 使用线性一致性存储实现全序广播 #### 使用线性一致性存储实现全序广播
@ -625,7 +625,7 @@ CAP定理的正式定义仅限于很狭隘的范围【30】它只考虑了一
5. 当协调者收到所有准备请求的答复时,会就提交或中止事务作出明确的决定(只有在所有参与者投赞成票的情况下才会提交)。协调者必须把这个决定写到磁盘上的事务日志中,如果它随后就崩溃,恢复后也能知道自己所做的决定。这被称为**提交点commit point**。 5. 当协调者收到所有准备请求的答复时,会就提交或中止事务作出明确的决定(只有在所有参与者投赞成票的情况下才会提交)。协调者必须把这个决定写到磁盘上的事务日志中,如果它随后就崩溃,恢复后也能知道自己所做的决定。这被称为**提交点commit point**。
6. 一旦协调者的决定落盘,提交或放弃请求会发送给所有参与者。如果这个请求失败或超时,协调者必须永远保持重试,直到成功为止。没有回头路:如果已经做出决定,不管需要多少次重试它都必须被执行。如果参与者在此期间崩溃,事务将在其恢复后提交——由于参与者投了赞成,因此恢复后它不能拒绝提交。 6. 一旦协调者的决定落盘,提交或放弃请求会发送给所有参与者。如果这个请求失败或超时,协调者必须永远保持重试,直到成功为止。没有回头路:如果已经做出决定,不管需要多少次重试它都必须被执行。如果参与者在此期间崩溃,事务将在其恢复后提交——由于参与者投了赞成,因此恢复后它不能拒绝提交。
因此该协议包含两个关键的“不归路”点当参与者投票“是”时它承诺它稍后肯定能够提交尽管协调者可能仍然选择放弃以及一旦协调者做出决定这一决定是不可撤销的。这些承诺保证了2PC的原子性(单节点原子提交将这两个事件合为了一体:将提交记录写入事务日志 因此该协议包含两个关键的“不归路”点当参与者投票“是”时它承诺它稍后肯定能够提交尽管协调者可能仍然选择放弃以及一旦协调者做出决定这一决定是不可撤销的。这些承诺保证了2PC的原子性单节点原子提交将这两个事件合为了一体将提交记录写入事务日志
回到婚姻的比喻,在说“我是”之前,你和你的新娘/新郎有中止这个事务的自由,通过回复 “没门或者有类似效果的话。然而在说了“我愿意”之后你就不能撤回那个声明了。如果你说“我愿意”后晕倒了没有听到司仪说“你们现在是夫妻了”那也并不会改变事务已经提交的现实。当你稍后恢复意识时可以通过查询司仪的全局事务ID状态来确定你是否已经成婚或者你可以等待司仪重试下一次提交请求因为重试将在你无意识期间一直持续 回到婚姻的比喻,在说“我是”之前,你和你的新娘/新郎有中止这个事务的自由,通过回复 “没门或者有类似效果的话。然而在说了“我愿意”之后你就不能撤回那个声明了。如果你说“我愿意”后晕倒了没有听到司仪说“你们现在是夫妻了”那也并不会改变事务已经提交的现实。当你稍后恢复意识时可以通过查询司仪的全局事务ID状态来确定你是否已经成婚或者你可以等待司仪重试下一次提交请求因为重试将在你无意识期间一直持续
@ -755,7 +755,7 @@ XA事务解决了保持多个参与者数据系统相互一致的现实的
如果你不关心容错,那么满足前三个属性很容易:你可以将一个节点硬编码为“独裁者”,并让该节点做出所有的决定。但如果该节点失效,那么系统就无法再做出任何决定。事实上,这就是我们在两阶段提交的情况中所看到的:如果协调者失效,那么存疑的参与者就无法决定提交还是中止。 如果你不关心容错,那么满足前三个属性很容易:你可以将一个节点硬编码为“独裁者”,并让该节点做出所有的决定。但如果该节点失效,那么系统就无法再做出任何决定。事实上,这就是我们在两阶段提交的情况中所看到的:如果协调者失效,那么存疑的参与者就无法决定提交还是中止。
**终止**属性形式化了容错的思想。它实质上说的是,一个共识算法不能简单地永远闲坐着等死 —— 换句话说,它必须取得进展。即使部分节点出现故障,其他节点也必须达成一项决定**终止**是一种**活性属性**,而另外三种是**安全属性** —— 请参阅“[安全性和活性](ch8.md#安全性和活性)” **终止**属性形式化了容错的思想。它实质上说的是,一个共识算法不能简单地永远闲坐着等死 —— 换句话说,它必须取得进展。即使部分节点出现故障,其他节点也必须达成一项决定(**终止**是一种**活性属性**,而另外三种是**安全属性** —— 请参阅“[安全性和活性](ch8.md#安全性和活性)”)
共识的系统模型假设当一个节点“崩溃”时它会突然消失而且永远不会回来。不像软件崩溃想象一下地震包含你的节点的数据中心被山体滑坡所摧毁你必须假设节点被埋在30英尺以下的泥土中并且永远不会重新上线在这个系统模型中任何需要等待节点恢复的算法都不能满足**终止**属性。特别是2PC不符合终止属性的要求。 共识的系统模型假设当一个节点“崩溃”时它会突然消失而且永远不会回来。不像软件崩溃想象一下地震包含你的节点的数据中心被山体滑坡所摧毁你必须假设节点被埋在30英尺以下的泥土中并且永远不会重新上线在这个系统模型中任何需要等待节点恢复的算法都不能满足**终止**属性。特别是2PC不符合终止属性的要求。

View File

@ -128,7 +128,7 @@
* 將人們最容易犯錯的地方與可能導致失效的地方**解耦decouple**。特別是提供一個功能齊全的非生產環境**沙箱sandbox**,使人們可以在不影響真實使用者的情況下,使用真實資料安全地探索和實驗。 * 將人們最容易犯錯的地方與可能導致失效的地方**解耦decouple**。特別是提供一個功能齊全的非生產環境**沙箱sandbox**,使人們可以在不影響真實使用者的情況下,使用真實資料安全地探索和實驗。
* 在各個層次進行徹底的測試【3】從單元測試、全系統整合測試到手動測試。自動化測試易於理解已經被廣泛使用特別適合用來覆蓋正常情況中少見的**邊緣場景corner case**。 * 在各個層次進行徹底的測試【3】從單元測試、全系統整合測試到手動測試。自動化測試易於理解已經被廣泛使用特別適合用來覆蓋正常情況中少見的**邊緣場景corner case**。
* 允許從人為錯誤中簡單快速地恢復,以最大限度地減少失效情況帶來的影響。 例如,快速回滾配置變更,分批發布新程式碼(以便任何意外錯誤隻影響一小部分使用者),並提供資料重算工具(以備舊的計算出錯)。 * 允許從人為錯誤中簡單快速地恢復,以最大限度地減少失效情況帶來的影響。 例如,快速回滾配置變更,分批發布新程式碼(以便任何意外錯誤隻影響一小部分使用者),並提供資料重算工具(以備舊的計算出錯)。
* 配置詳細和明確的監控,比如效能指標和錯誤率。 在其他工程學科中這指的是**遙測telemetry**(一旦火箭離開了地面,遙測技術對於跟蹤發生的事情和理解失敗是至關重要的)監控可以向我們發出預警訊號,並允許我們檢查是否有任何地方違反了假設和約束。當出現問題時,指標資料對於問題診斷是非常寶貴的。 * 配置詳細和明確的監控,比如效能指標和錯誤率。 在其他工程學科中這指的是**遙測telemetry**(一旦火箭離開了地面,遙測技術對於跟蹤發生的事情和理解失敗是至關重要的)監控可以向我們發出預警訊號,並允許我們檢查是否有任何地方違反了假設和約束。當出現問題時,指標資料對於問題診斷是非常寶貴的。
* 良好的管理實踐與充分的培訓——一個複雜而重要的方面,但超出了本書的範圍。 * 良好的管理實踐與充分的培訓——一個複雜而重要的方面,但超出了本書的範圍。
### 可靠性有多重要? ### 可靠性有多重要?
@ -285,7 +285,7 @@
* 簡單性Simplicity * 簡單性Simplicity
從系統中消除儘可能多的**複雜度complexity**,使新工程師也能輕鬆理解系統(注意這和使用者介面的簡單性不一樣 從系統中消除儘可能多的**複雜度complexity**,使新工程師也能輕鬆理解系統(注意這和使用者介面的簡單性不一樣)
* 可演化性evolability * 可演化性evolability
@ -299,26 +299,26 @@
運維團隊對於保持軟體系統順利執行至關重要。一個優秀運維團隊的典型職責如下或者更多【29】 運維團隊對於保持軟體系統順利執行至關重要。一個優秀運維團隊的典型職責如下或者更多【29】
* 監控系統的執行狀況,並在服務狀態不佳時快速恢復服務 * 監控系統的執行狀況,並在服務狀態不佳時快速恢復服務
* 跟蹤問題的原因,例如系統故障或效能下降 * 跟蹤問題的原因,例如系統故障或效能下降
* 及時更新軟體和平臺,比如安全補丁 * 及時更新軟體和平臺,比如安全補丁
* 瞭解系統間的相互作用,以便在異常變更造成損失前進行規避。 * 瞭解系統間的相互作用,以便在異常變更造成損失前進行規避。
* 預測未來的問題,並在問題出現之前加以解決(例如,容量規劃) * 預測未來的問題,並在問題出現之前加以解決(例如,容量規劃)
* 建立部署,配置、管理方面的良好實踐,編寫相應工具 * 建立部署,配置、管理方面的良好實踐,編寫相應工具
* 執行復雜的維護任務,例如將應用程式從一個平臺遷移到另一個平臺 * 執行復雜的維護任務,例如將應用程式從一個平臺遷移到另一個平臺
* 當配置變更時,維持系統的安全性 * 當配置變更時,維持系統的安全性
* 定義工作流程,使運維操作可預測,並保持生產環境穩定。 * 定義工作流程,使運維操作可預測,並保持生產環境穩定。
* 鐵打的營盤流水的兵,維持組織對系統的瞭解。 * 鐵打的營盤流水的兵,維持組織對系統的瞭解。
良好的可操作性意味著更輕鬆的日常工作,進而運維團隊能專注於高價值的事情。資料系統可以透過各種方式使日常任務更輕鬆: 良好的可操作性意味著更輕鬆的日常工作,進而運維團隊能專注於高價值的事情。資料系統可以透過各種方式使日常任務更輕鬆:
* 透過良好的監控,提供對系統內部狀態和執行時行為的**可見性visibility** * 透過良好的監控,提供對系統內部狀態和執行時行為的**可見性visibility**
* 為自動化提供良好支援,將系統與標準化工具相整合 * 為自動化提供良好支援,將系統與標準化工具相整合
* 避免依賴單臺機器(在整個系統繼續不間斷執行的情況下允許機器停機維護) * 避免依賴單臺機器(在整個系統繼續不間斷執行的情況下允許機器停機維護)
* 提供良好的文件和易於理解的操作模型“如果做X會發生Y” * 提供良好的文件和易於理解的操作模型“如果做X會發生Y”
* 提供良好的預設行為,但需要時也允許管理員自由覆蓋預設值 * 提供良好的預設行為,但需要時也允許管理員自由覆蓋預設值
* 有條件時進行自我修復,但需要時也允許管理員手動控制系統狀態 * 有條件時進行自我修復,但需要時也允許管理員手動控制系統狀態
* 行為可預測,最大限度減少意外 * 行為可預測,最大限度減少意外
### 簡單性:管理複雜度 ### 簡單性:管理複雜度
@ -357,7 +357,7 @@
一個應用必須滿足各種需求才稱得上有用。有一些**功能需求**functional requirements即它應該做什麼比如允許以各種方式儲存檢索搜尋和處理資料以及一些**非功能性需求*nonfunctional即通用屬性例如安全性、可靠性、合規性、可伸縮性、相容性和可維護性。在本章詳細討論了可靠性可伸縮性和可維護性。 一個應用必須滿足各種需求才稱得上有用。有一些**功能需求**functional requirements即它應該做什麼比如允許以各種方式儲存檢索搜尋和處理資料以及一些**非功能性需求*nonfunctional即通用屬性例如安全性、可靠性、合規性、可伸縮性、相容性和可維護性。在本章詳細討論了可靠性可伸縮性和可維護性。
**可靠性Reliability** 意味著即使發生故障,系統也能正常工作。故障可能發生在硬體(通常是隨機的和不相關的)軟體通常是系統性的Bug很難處理和人類(不可避免地時不時出錯)。 **容錯技術** 可以對終端使用者隱藏某些型別的故障。 **可靠性Reliability** 意味著即使發生故障,系統也能正常工作。故障可能發生在硬體(通常是隨機的和不相關的)軟體通常是系統性的Bug很難處理和人類不可避免地時不時出錯**容錯技術** 可以對終端使用者隱藏某些型別的故障。
**可伸縮性Scalability** 意味著即使在負載增加的情況下也有保持效能的策略。為了討論可伸縮性,我們首先需要定量描述負載和效能的方法。我們簡要了解了推特主頁時間線的例子,介紹描述負載的方法,並將響應時間百分位點作為衡量效能的一種方式。在可伸縮的系統中可以新增 **處理容量processing capacity** 以在高負載下保持可靠。 **可伸縮性Scalability** 意味著即使在負載增加的情況下也有保持效能的策略。為了討論可伸縮性,我們首先需要定量描述負載和效能的方法。我們簡要了解了推特主頁時間線的例子,介紹描述負載的方法,並將響應時間百分位點作為衡量效能的一種方式。在可伸縮的系統中可以新增 **處理容量processing capacity** 以在高負載下保持可靠。

View File

@ -277,7 +277,7 @@ Unix和關係資料庫以非常不同的哲學來處理資訊管理問題。Unix
在這個典型的Web應用模型中資料庫充當一種可以透過網路同步訪問的可變共享變數。應用程式可以讀取和更新變數而資料庫負責維持它的永續性提供一些諸如併發控制和容錯的功能。 在這個典型的Web應用模型中資料庫充當一種可以透過網路同步訪問的可變共享變數。應用程式可以讀取和更新變數而資料庫負責維持它的永續性提供一些諸如併發控制和容錯的功能。
但是,在大多數程式語言中,你無法訂閱可變變數中的變更 —— 你只能定期讀取它。與電子表格不同,如果變數的值發生變化,變數的讀者不會收到通知(你可以在自己的程式碼中實現這樣的通知 —— 這被稱為**觀察者模式** —— 但大多數語言沒有將這種模式作為內建功能 但是,在大多數程式語言中,你無法訂閱可變變數中的變更 —— 你只能定期讀取它。與電子表格不同,如果變數的值發生變化,變數的讀者不會收到通知(你可以在自己的程式碼中實現這樣的通知 —— 這被稱為**觀察者模式** —— 但大多數語言沒有將這種模式作為內建功能)
資料庫繼承了這種可變資料的被動方法:如果你想知道資料庫的內容是否發生了變化,通常你唯一的選擇就是輪詢(即定期重複你的查詢)。 訂閱變更只是剛剛開始出現的功能(請參閱“[變更流的API支援](ch11.md#變更流的API支援)”)。 資料庫繼承了這種可變資料的被動方法:如果你想知道資料庫的內容是否發生了變化,通常你唯一的選擇就是輪詢(即定期重複你的查詢)。 訂閱變更只是剛剛開始出現的功能(請參閱“[變更流的API支援](ch11.md#變更流的API支援)”)。
@ -461,7 +461,7 @@ COMMIT;
即使我們可以抑制資料庫客戶端與伺服器之間的重複事務我們仍然需要擔心終端使用者裝置與應用伺服器之間的網路。例如如果終端使用者的客戶端是Web瀏覽器則它可能會使用HTTP POST請求向伺服器提交指令。也許使用者正處於一個訊號微弱的蜂窩資料網路連線中它們成功地傳送了POST但卻在能夠從伺服器接收響應之前沒了訊號。 即使我們可以抑制資料庫客戶端與伺服器之間的重複事務我們仍然需要擔心終端使用者裝置與應用伺服器之間的網路。例如如果終端使用者的客戶端是Web瀏覽器則它可能會使用HTTP POST請求向伺服器提交指令。也許使用者正處於一個訊號微弱的蜂窩資料網路連線中它們成功地傳送了POST但卻在能夠從伺服器接收響應之前沒了訊號。
在這種情況下,可能會向用戶顯示錯誤訊息,而他們可能會手動重試。 Web瀏覽器警告說“你確定要再次提交這個表單嗎” —— 使用者選“是”,因為他們希望操作發生Post/Redirect/Get模式【54】可以避免在正常操作中出現此警告訊息但POST請求超時就沒辦法了從Web伺服器的角度來看重試是一個獨立的請求從資料庫的角度來看這是一個獨立的事務。通常的除重機制無濟於事。 在這種情況下,可能會向用戶顯示錯誤訊息,而他們可能會手動重試。 Web瀏覽器警告說“你確定要再次提交這個表單嗎” —— 使用者選“是”因為他們希望操作發生Post/Redirect/Get模式【54】可以避免在正常操作中出現此警告訊息但POST請求超時就沒辦法了從Web伺服器的角度來看重試是一個獨立的請求從資料庫的角度來看這是一個獨立的事務。通常的除重機制無濟於事。
#### 操作識別符號 #### 操作識別符號
@ -490,7 +490,7 @@ COMMIT;
抑制重複事務的這種情況只是一個更普遍的原則的一個例子,這個原則被稱為**端到端原則end-to-end argument**它在1984年由SaltzerReed和Clark闡述【55】 抑制重複事務的這種情況只是一個更普遍的原則的一個例子,這個原則被稱為**端到端原則end-to-end argument**它在1984年由SaltzerReed和Clark闡述【55】
> 只有在通訊系統兩端應用的知識與幫助下,所討論的功能才能完全地正確地實現。因而將這種被質疑的功能作為通訊系統本身的功能是不可能的(有時,通訊系統可以提供這種功能的不完備版本,可能有助於提高效能 > 只有在通訊系統兩端應用的知識與幫助下,所討論的功能才能完全地正確地實現。因而將這種被質疑的功能作為通訊系統本身的功能是不可能的(有時,通訊系統可以提供這種功能的不完備版本,可能有助於提高效能)
> >
在我們的例子中**所討論的功能**是重複抑制。我們看到TCP在TCP連線層次抑制了重複的資料包一些流處理器在訊息處理層次提供了所謂的恰好一次語義但這些都無法阻止當一個請求超時時使用者親自提交重複的請求。TCP資料庫事務以及流處理器本身並不能完全排除這些重複。解決這個問題需要一個端到端的解決方案從終端使用者的客戶端一路傳遞到資料庫的事務識別符號。 在我們的例子中**所討論的功能**是重複抑制。我們看到TCP在TCP連線層次抑制了重複的資料包一些流處理器在訊息處理層次提供了所謂的恰好一次語義但這些都無法阻止當一個請求超時時使用者親自提交重複的請求。TCP資料庫事務以及流處理器本身並不能完全排除這些重複。解決這個問題需要一個端到端的解決方案從終端使用者的客戶端一路傳遞到資料庫的事務識別符號。

View File

@ -50,7 +50,7 @@
採用NoSQL資料庫的背後有幾個驅動因素其中包括 採用NoSQL資料庫的背後有幾個驅動因素其中包括
* 需要比關係資料庫更好的可伸縮性,包括非常大的資料集或非常高的寫入吞吐量 * 需要比關係資料庫更好的可伸縮性,包括非常大的資料集或非常高的寫入吞吐量
* 相比商業資料庫產品,免費和開源軟體更受偏愛 * 相比商業資料庫產品,免費和開源軟體更受偏愛
* 關係模型不能很好地支援一些特殊的查詢操作 * 關係模型不能很好地支援一些特殊的查詢操作
* 受挫於關係模型的限制性渴望一種更具多動態性與表現力的資料模型【5】 * 受挫於關係模型的限制性渴望一種更具多動態性與表現力的資料模型【5】
@ -208,7 +208,7 @@ CODASYL中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資
在關係資料庫中,查詢最佳化器自動決定查詢的哪些部分以哪個順序執行,以及使用哪些索引。這些選擇實際上是“訪問路徑”,但最大的區別在於它們是由查詢最佳化器自動生成的,而不是由程式設計師生成,所以我們很少需要考慮它們。 在關係資料庫中,查詢最佳化器自動決定查詢的哪些部分以哪個順序執行,以及使用哪些索引。這些選擇實際上是“訪問路徑”,但最大的區別在於它們是由查詢最佳化器自動生成的,而不是由程式設計師生成,所以我們很少需要考慮它們。
如果想按新的方式查詢資料,你可以宣告一個新的索引,查詢會自動使用最合適的那些索引。無需更改查詢來利用新的索引(請參閱“[資料查詢語言](#資料查詢語言)”)關係模型因此使新增應用程式新功能變得更加容易。 如果想按新的方式查詢資料,你可以宣告一個新的索引,查詢會自動使用最合適的那些索引。無需更改查詢來利用新的索引(請參閱“[資料查詢語言](#資料查詢語言)”)關係模型因此使新增應用程式新功能變得更加容易。
關係資料庫的查詢最佳化器是複雜的已耗費了多年的研究和開發精力【18】。關係模型的一個關鍵洞察是只需構建一次查詢最佳化器隨後使用該資料庫的所有應用程式都可以從中受益。如果你沒有查詢最佳化器的話那麼為特定查詢手動編寫訪問路徑比編寫通用最佳化器更容易——不過從長期看通用解決方案更好。 關係資料庫的查詢最佳化器是複雜的已耗費了多年的研究和開發精力【18】。關係模型的一個關鍵洞察是只需構建一次查詢最佳化器隨後使用該資料庫的所有應用程式都可以從中受益。如果你沒有查詢最佳化器的話那麼為特定查詢手動編寫訪問路徑比編寫通用最佳化器更容易——不過從長期看通用解決方案更好。
@ -581,7 +581,7 @@ CREATE INDEX edges_heads ON edges (head_vertex);
關於這個模型的一些重要方面是: 關於這個模型的一些重要方面是:
1. 任何頂點都可以有一條邊連線到任何其他頂點。沒有模式限制哪種事物可不可以關聯。 1. 任何頂點都可以有一條邊連線到任何其他頂點。沒有模式限制哪種事物可不可以關聯。
2. 給定任何頂點,可以高效地找到它的入邊和出邊,從而遍歷圖,即沿著一系列頂點的路徑前後移動(這就是為什麼[例2-2]()在`tail_vertex`和`head_vertex`列上都有索引的原因 2. 給定任何頂點,可以高效地找到它的入邊和出邊,從而遍歷圖,即沿著一系列頂點的路徑前後移動(這就是為什麼[例2-2]()在`tail_vertex`和`head_vertex`列上都有索引的原因)
3. 透過對不同型別的關係使用不同的標籤,可以在一個圖中儲存幾種不同的資訊,同時仍然保持一個清晰的資料模型。 3. 透過對不同型別的關係使用不同的標籤,可以在一個圖中儲存幾種不同的資訊,同時仍然保持一個清晰的資料模型。
這些特性為資料建模提供了很大的靈活性,如[圖2-5](../img/fig2-5.png)所示。圖中顯示了一些傳統關係模式難以表達的事情例如不同國家的不同地區結構法國有省和州美國有不同的州和州國中國的怪事先忽略主權國家和國家錯綜複雜的爛攤子不同的資料粒度Lucy現在的住所被指定為一個城市而她的出生地點只是在一個州的級別 這些特性為資料建模提供了很大的靈活性,如[圖2-5](../img/fig2-5.png)所示。圖中顯示了一些傳統關係模式難以表達的事情例如不同國家的不同地區結構法國有省和州美國有不同的州和州國中國的怪事先忽略主權國家和國家錯綜複雜的爛攤子不同的資料粒度Lucy現在的住所被指定為一個城市而她的出生地點只是在一個州的級別
@ -590,7 +590,7 @@ CREATE INDEX edges_heads ON edges (head_vertex);
### Cypher查詢語言 ### Cypher查詢語言
Cypher是屬性圖的宣告式查詢語言為Neo4j圖形資料庫而發明【37】它是以電影“駭客帝國”中的一個角色來命名的而與密碼術中的密碼無關【38】 Cypher是屬性圖的宣告式查詢語言為Neo4j圖形資料庫而發明【37】它是以電影“駭客帝國”中的一個角色來命名的而與密碼術中的密碼無關【38】
[例2-3]()顯示了將[圖2-5](../img/fig2-5.png)的左邊部分插入圖形資料庫的Cypher查詢。可以類似地新增圖的其餘部分為了便於閱讀而省略。每個頂點都有一個像`USA`或`Idaho`這樣的符號名稱,查詢的其他部分可以使用這些名稱在頂點之間建立邊,使用箭頭符號:`Idaho - [WITHIN] ->USA`建立一條標記為`WITHIN`的邊,`Idaho`為尾節點,`USA`為頭節點。 [例2-3]()顯示了將[圖2-5](../img/fig2-5.png)的左邊部分插入圖形資料庫的Cypher查詢。可以類似地新增圖的其餘部分為了便於閱讀而省略。每個頂點都有一個像`USA`或`Idaho`這樣的符號名稱,查詢的其他部分可以使用這些名稱在頂點之間建立邊,使用箭頭符號:`Idaho - [WITHIN] ->USA`建立一條標記為`WITHIN`的邊,`Idaho`為尾節點,`USA`為頭節點。
@ -790,7 +790,7 @@ RDF有一些奇怪之處因為它是為了在網際網路上交換資料而
### SPARQL查詢語言 ### SPARQL查詢語言
**SPARQL**是一種用於三元組儲存的面向RDF資料模型的查詢語言【43】它是SPARQL協議和RDF查詢語言的縮寫發音為“sparkle”SPARQL早於Cypher並且由於Cypher的模式匹配借鑑於SPARQL這使得它們看起來非常相似【37】。 **SPARQL**是一種用於三元組儲存的面向RDF資料模型的查詢語言【43】它是SPARQL協議和RDF查詢語言的縮寫發音為“sparkle”SPARQL早於Cypher並且由於Cypher的模式匹配借鑑於SPARQL這使得它們看起來非常相似【37】。
與之前相同的查詢 - 查詢從美國轉移到歐洲的人 - 使用SPARQL比使用Cypher甚至更為簡潔請參閱[例2-9]())。 與之前相同的查詢 - 查詢從美國轉移到歐洲的人 - 使用SPARQL比使用Cypher甚至更為簡潔請參閱[例2-9]())。

View File

@ -49,7 +49,7 @@ $ db_get 42
{"name":"San Francisco","attractions":["Golden Gate Bridge"]} {"name":"San Francisco","attractions":["Golden Gate Bridge"]}
``` ```
底層的儲存格式非常簡單一個文字檔案每行包含一條逗號分隔的鍵值對忽略轉義問題的話大致與CSV檔案類似。每次對 `db_set` 的呼叫都會向檔案末尾追加記錄,所以更新鍵的時候舊版本的值不會被覆蓋 —— 因而查詢最新值的時候,需要找到檔案中鍵最後一次出現的位置(因此 `db_get` 中使用了 `tail -n 1 ` ) 底層的儲存格式非常簡單一個文字檔案每行包含一條逗號分隔的鍵值對忽略轉義問題的話大致與CSV檔案類似。每次對 `db_set` 的呼叫都會向檔案末尾追加記錄,所以更新鍵的時候舊版本的值不會被覆蓋 —— 因而查詢最新值的時候,需要找到檔案中鍵最後一次出現的位置(因此 `db_get` 中使用了 `tail -n 1 ` )
```bash ```bash
$ db_set 42 '{"name":"San Francisco","attractions":["Exploratorium"]}' $ db_set 42 '{"name":"San Francisco","attractions":["Exploratorium"]}'
@ -236,7 +236,7 @@ Lucene是Elasticsearch和Solr使用的一種全文搜尋的索引引擎它使
[^ii]: 向B樹中插入一個新的鍵是相當符合直覺的但刪除一個鍵同時保持樹平衡就會牽扯很多其他東西了【2】。 [^ii]: 向B樹中插入一個新的鍵是相當符合直覺的但刪除一個鍵同時保持樹平衡就會牽扯很多其他東西了【2】。
這個演算法可以確保樹保持平衡具有n個鍵的B樹總是具有 $O(log n)$ 的深度。大多數資料庫可以放入一個三到四層的B樹所以你不需要追蹤多個頁面引用來找到你正在查詢的頁面分支因子為500的4KB頁面的四層樹可以儲存多達256TB的資料 這個演算法可以確保樹保持平衡具有n個鍵的B樹總是具有 $O(log n)$ 的深度。大多數資料庫可以放入一個三到四層的B樹所以你不需要追蹤多個頁面引用來找到你正在查詢的頁面分支因子為500的4KB頁面的四層樹可以儲存多達256TB的資料
#### 讓B樹更可靠 #### 讓B樹更可靠

View File

@ -248,7 +248,7 @@ Avro的關鍵思想是Writer模式和Reader模式不必是相同的 - 他們只
使用Avro向前相容性意味著你可以將新版本的模式作為Writer並將舊版本的模式作為Reader。相反向後相容意味著你可以有一個作為Reader的新版本模式和作為Writer的舊版本模式。 使用Avro向前相容性意味著你可以將新版本的模式作為Writer並將舊版本的模式作為Reader。相反向後相容意味著你可以有一個作為Reader的新版本模式和作為Writer的舊版本模式。
為了保持相容性,你只能新增或刪除具有預設值的欄位我們的Avro模式中的欄位`favoriteNumber`的預設值為`null`。例如假設你添加了一個有預設值的欄位這個新的欄位將存在於新模式而不是舊模式中。當使用新模式的Reader讀取使用舊模式寫入的記錄時將為缺少的欄位填充預設值。 為了保持相容性你只能新增或刪除具有預設值的欄位我們的Avro模式中的欄位`favoriteNumber`的預設值為`null`。例如假設你添加了一個有預設值的欄位這個新的欄位將存在於新模式而不是舊模式中。當使用新模式的Reader讀取使用舊模式寫入的記錄時將為缺少的欄位填充預設值。
如果你要新增一個沒有預設值的欄位新的Reader將無法讀取舊Writer寫的資料所以你會破壞向後相容性。如果你要刪除沒有預設值的欄位舊的Reader將無法讀取新Writer寫入的資料因此你會打破向前相容性。在一些程式語言中null是任何變數可以接受的預設值但在Avro中並不是這樣如果要允許一個欄位為`null`,則必須使用聯合型別。例如,`union {null, long, string} field;`表示field可以是數字或字串也可以是`null`。如果要將null作為預設值則它必須是union的分支之一[^iv]。這樣的寫法比預設情況下就允許任何變數是`null`顯得更加冗長,但是透過明確什麼可以和什麼不可以是`null`有助於防止出錯【22】。 如果你要新增一個沒有預設值的欄位新的Reader將無法讀取舊Writer寫的資料所以你會破壞向後相容性。如果你要刪除沒有預設值的欄位舊的Reader將無法讀取新Writer寫入的資料因此你會打破向前相容性。在一些程式語言中null是任何變數可以接受的預設值但在Avro中並不是這樣如果要允許一個欄位為`null`,則必須使用聯合型別。例如,`union {null, long, string} field;`表示field可以是數字或字串也可以是`null`。如果要將null作為預設值則它必須是union的分支之一[^iv]。這樣的寫法比預設情況下就允許任何變數是`null`顯得更加冗長,但是透過明確什麼可以和什麼不可以是`null`有助於防止出錯【22】。
@ -266,11 +266,11 @@ Avro的關鍵思想是Writer模式和Reader模式不必是相同的 - 他們只
* 有很多記錄的大檔案 * 有很多記錄的大檔案
Avro的一個常見用途 - 尤其是在Hadoop環境中 - 用於儲存包含數百萬條記錄的大檔案,所有記錄都使用相同的模式進行編碼(我們將在[第十章](ch10.md)討論這種情況在這種情況下該檔案的作者可以在檔案的開頭只包含一次Writer模式。 Avro指定了一個檔案格式物件容器檔案來做到這一點。 Avro的一個常見用途 - 尤其是在Hadoop環境中 - 用於儲存包含數百萬條記錄的大檔案,所有記錄都使用相同的模式進行編碼(我們將在[第十章](ch10.md)討論這種情況)在這種情況下該檔案的作者可以在檔案的開頭只包含一次Writer模式。 Avro指定了一個檔案格式物件容器檔案來做到這一點。
* 支援獨立寫入的記錄的資料庫 * 支援獨立寫入的記錄的資料庫
在一個數據庫中不同的記錄可能會在不同的時間點使用不同的Writer模式來寫入 - 你不能假定所有的記錄都有相同的模式。最簡單的解決方案是在每個編碼記錄的開始處包含一個版本號並在資料庫中保留一個模式版本列表。Reader可以獲取記錄提取版本號然後從資料庫中獲取該版本號的Writer模式。使用該Writer模式它可以解碼記錄的其餘部分例如Espresso 【23】就是這樣工作的 在一個數據庫中不同的記錄可能會在不同的時間點使用不同的Writer模式來寫入 - 你不能假定所有的記錄都有相同的模式。最簡單的解決方案是在每個編碼記錄的開始處包含一個版本號並在資料庫中保留一個模式版本列表。Reader可以獲取記錄提取版本號然後從資料庫中獲取該版本號的Writer模式。使用該Writer模式它可以解碼記錄的其餘部分例如Espresso 【23】就是這樣工作的
* 透過網路連線傳送記錄 * 透過網路連線傳送記錄
@ -286,7 +286,7 @@ Avro的關鍵思想是Writer模式和Reader模式不必是相同的 - 他們只
現在如果資料庫模式發生變化例如一個表中添加了一列刪除了一列則可以從更新的資料庫模式生成新的Avro模式並在新的Avro模式中匯出資料。資料匯出過程不需要注意模式的改變 - 每次執行時都可以簡單地進行模式轉換。任何讀取新資料檔案的人都會看到記錄的欄位已經改變但是由於欄位是透過名字來標識的所以更新的Writer模式仍然可以與舊的Reader模式匹配。 現在如果資料庫模式發生變化例如一個表中添加了一列刪除了一列則可以從更新的資料庫模式生成新的Avro模式並在新的Avro模式中匯出資料。資料匯出過程不需要注意模式的改變 - 每次執行時都可以簡單地進行模式轉換。任何讀取新資料檔案的人都會看到記錄的欄位已經改變但是由於欄位是透過名字來標識的所以更新的Writer模式仍然可以與舊的Reader模式匹配。
相比之下如果你為此使用Thrift或Protocol Buffers則欄位標籤可能必須手動分配每次資料庫模式更改時管理員都必須手動更新從資料庫列名到欄位標籤的對映(這可能會自動化,但模式生成器必須非常小心,不要分配以前使用的欄位標籤這種動態生成的模式根本不是Thrift或Protocol Buffers的設計目標而是Avro的。 相比之下如果你為此使用Thrift或Protocol Buffers則欄位標籤可能必須手動分配每次資料庫模式更改時管理員都必須手動更新從資料庫列名到欄位標籤的對映這可能會自動化但模式生成器必須非常小心不要分配以前使用的欄位標籤這種動態生成的模式根本不是Thrift或Protocol Buffers的設計目標而是Avro的。
#### 程式碼生成和動態型別的語言 #### 程式碼生成和動態型別的語言
@ -416,7 +416,7 @@ Web服務僅僅是透過網路進行API請求的一系列技術的最新版本
所有這些都是基於 **遠端過程呼叫RPC** 的思想該過程呼叫自20世紀70年代以來一直存在【42】。 RPC模型試圖向遠端網路服務發出請求看起來與在同一程序中呼叫程式語言中的函式或方法相同這種抽象稱為位置透明。儘管RPC起初看起來很方便但這種方法根本上是有缺陷的【43,44】。網路請求與本地函式呼叫非常不同 所有這些都是基於 **遠端過程呼叫RPC** 的思想該過程呼叫自20世紀70年代以來一直存在【42】。 RPC模型試圖向遠端網路服務發出請求看起來與在同一程序中呼叫程式語言中的函式或方法相同這種抽象稱為位置透明。儘管RPC起初看起來很方便但這種方法根本上是有缺陷的【43,44】。網路請求與本地函式呼叫非常不同
* 本地函式呼叫是可預測的,並且成功或失敗僅取決於受你控制的引數。網路請求是不可預知的:由於網路問題,請求或響應可能會丟失,或者遠端計算機可能很慢或不可用,這些問題完全不在你的控制範圍之內。網路問題是常見的,所以你必須預測他們,例如透過重試失敗的請求。 * 本地函式呼叫是可預測的,並且成功或失敗僅取決於受你控制的引數。網路請求是不可預知的:由於網路問題,請求或響應可能會丟失,或者遠端計算機可能很慢或不可用,這些問題完全不在你的控制範圍之內。網路問題是常見的,所以你必須預測他們,例如透過重試失敗的請求。
* 本地函式呼叫要麼返回結果,要麼丟擲異常,或者永遠不返回(因為進入無限迴圈或程序崩潰)。網路請求有另一個可能的結果:由於超時,它可能會返回沒有結果。在這種情況下,你根本不知道發生了什麼:如果你沒有得到來自遠端服務的響應,你無法知道請求是否透過(我們將在[第八章](ch8.md)更詳細地討論這個問題 * 本地函式呼叫要麼返回結果,要麼丟擲異常,或者永遠不返回(因為進入無限迴圈或程序崩潰)。網路請求有另一個可能的結果:由於超時,它可能會返回沒有結果。在這種情況下,你根本不知道發生了什麼:如果你沒有得到來自遠端服務的響應,你無法知道請求是否透過(我們將在[第八章](ch8.md)更詳細地討論這個問題)
* 如果你重試失敗的網路請求,可能會發生請求實際上正在透過,只有響應丟失。在這種情況下,重試將導致該操作被執行多次,除非你在協議中引入去重機制(**冪等**即idempotence。本地函式呼叫沒有這個問題。 (在[第十一章](ch11.md)更詳細地討論冪等性) * 如果你重試失敗的網路請求,可能會發生請求實際上正在透過,只有響應丟失。在這種情況下,重試將導致該操作被執行多次,除非你在協議中引入去重機制(**冪等**即idempotence。本地函式呼叫沒有這個問題。 (在[第十一章](ch11.md)更詳細地討論冪等性)
* 每次呼叫本地功能時,通常需要大致相同的時間來執行。網路請求比函式呼叫要慢得多,而且其延遲也是非常可變的:好的時候它可能會在不到一毫秒的時間內完成,但是當網路擁塞或者遠端服務超載時,可能需要幾秒鐘的時間完成一樣的東西。 * 每次呼叫本地功能時,通常需要大致相同的時間來執行。網路請求比函式呼叫要慢得多,而且其延遲也是非常可變的:好的時候它可能會在不到一毫秒的時間內完成,但是當網路擁塞或者遠端服務超載時,可能需要幾秒鐘的時間完成一樣的東西。
* 呼叫本地函式時,可以高效地將引用(指標)傳遞給本地記憶體中的物件。當你發出一個網路請求時,所有這些引數都需要被編碼成可以透過網路傳送的一系列位元組。如果引數是像數字或字串這樣的基本型別倒是沒關係,但是對於較大的物件很快就會變成問題。 * 呼叫本地函式時,可以高效地將引用(指標)傳遞給本地記憶體中的物件。當你發出一個網路請求時,所有這些引數都需要被編碼成可以透過網路傳送的一系列位元組。如果引數是像數字或字串這樣的基本型別倒是沒關係,但是對於較大的物件很快就會變成問題。

View File

@ -234,7 +234,7 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
從非同步從庫讀取第二個異常例子是,使用者可能會遇到 **時光倒流moving backward in time** 從非同步從庫讀取第二個異常例子是,使用者可能會遇到 **時光倒流moving backward in time**
如果使用者從不同從庫進行多次讀取,就可能發生這種情況。例如,[圖5-4](../img/fig5-4.png)顯示了使用者2345兩次進行相同的查詢首先查詢了一個延遲很小的從庫然後是一個延遲較大的從庫(如果使用者重新整理網頁,而每個請求被路由到一個隨機的伺服器,這種情況是很有可能的第一個查詢返回最近由使用者1234新增的評論但是第二個查詢不返回任何東西因為滯後的從庫還沒有拉取寫入內容。在效果上相比第一個查詢第二個查詢是在更早的時間點來觀察系統。如果第一個查詢沒有返回任何內容那問題並不大因為使用者2345可能不知道使用者1234最近添加了評論。但如果使用者2345先看見使用者1234的評論然後又看到它消失那麼對於使用者2345就很讓人頭大了。 如果使用者從不同從庫進行多次讀取,就可能發生這種情況。例如,[圖5-4](../img/fig5-4.png)顯示了使用者2345兩次進行相同的查詢首先查詢了一個延遲很小的從庫然後是一個延遲較大的從庫如果使用者重新整理網頁而每個請求被路由到一個隨機的伺服器這種情況是很有可能的第一個查詢返回最近由使用者1234新增的評論但是第二個查詢不返回任何東西因為滯後的從庫還沒有拉取寫入內容。在效果上相比第一個查詢第二個查詢是在更早的時間點來觀察系統。如果第一個查詢沒有返回任何內容那問題並不大因為使用者2345可能不知道使用者1234最近添加了評論。但如果使用者2345先看見使用者1234的評論然後又看到它消失那麼對於使用者2345就很讓人頭大了。
![](../img/fig5-4.png) ![](../img/fig5-4.png)
@ -578,7 +578,7 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
後者被認為是一個**寬鬆的法定人數sloppy quorum**【37】寫和讀仍然需要w和r成功的響應但這些響應可能來自不在指定的n個“主”節點中的其它節點。比方說如果你把自己鎖在房子外面你可能會敲開鄰居的門問你是否可以暫時呆在沙發上。 後者被認為是一個**寬鬆的法定人數sloppy quorum**【37】寫和讀仍然需要w和r成功的響應但這些響應可能來自不在指定的n個“主”節點中的其它節點。比方說如果你把自己鎖在房子外面你可能會敲開鄰居的門問你是否可以暫時呆在沙發上。
一旦網路中斷得到解決,代表另一個節點臨時接受的一個節點的任何寫入都被傳送到適當的“主”節點。這就是所謂的**提示移交hinted handoff**(一旦你再次找到你的房子的鑰匙,你的鄰居禮貌地要求你離開沙發回家 一旦網路中斷得到解決,代表另一個節點臨時接受的一個節點的任何寫入都被傳送到適當的“主”節點。這就是所謂的**提示移交hinted handoff**(一旦你再次找到你的房子的鑰匙,你的鄰居禮貌地要求你離開沙發回家)
寬鬆的法定人數對寫入可用性的提高特別有用只要有任何w節點可用資料庫就可以接受寫入。然而這意味著即使當$w + r> n$時也不能確定讀取某個鍵的最新值因為最新的值可能已經臨時寫入了n之外的某些節點【47】。 寬鬆的法定人數對寫入可用性的提高特別有用只要有任何w節點可用資料庫就可以接受寫入。然而這意味著即使當$w + r> n$時也不能確定讀取某個鍵的最新值因為最新的值可能已經臨時寫入了n之外的某些節點【47】。
@ -675,7 +675,7 @@ LWW實現了最終收斂的目標但以**永續性**為代價:如果同一
* 伺服器為每個鍵保留一個版本號,每次寫入鍵時都增加版本號,並將新版本號與寫入的值一起儲存。 * 伺服器為每個鍵保留一個版本號,每次寫入鍵時都增加版本號,並將新版本號與寫入的值一起儲存。
* 當客戶端讀取鍵時,伺服器將返回所有未覆蓋的值以及最新的版本號。客戶端在寫入前必須讀取。 * 當客戶端讀取鍵時,伺服器將返回所有未覆蓋的值以及最新的版本號。客戶端在寫入前必須讀取。
* 客戶端寫入鍵時,必須包含之前讀取的版本號,並且必須將之前讀取的所有值合併在一起(針對寫入請求的響應可以像讀取請求一樣,返回所有當前值,這使得我們可以像購物車示例那樣將多個寫入串聯起來 * 客戶端寫入鍵時,必須包含之前讀取的版本號,並且必須將之前讀取的所有值合併在一起(針對寫入請求的響應可以像讀取請求一樣,返回所有當前值,這使得我們可以像購物車示例那樣將多個寫入串聯起來)
* 當伺服器接收到具有特定版本號的寫入時,它可以覆蓋該版本號或更低版本的所有值(因為它知道它們已經被合併到新的值中),但是它必須用更高的版本號來儲存所有值(因為這些值與隨後的寫入是併發的)。 * 當伺服器接收到具有特定版本號的寫入時,它可以覆蓋該版本號或更低版本的所有值(因為它知道它們已經被合併到新的值中),但是它必須用更高的版本號來儲存所有值(因為這些值與隨後的寫入是併發的)。
當一個寫入包含前一次讀取的版本號時,它會告訴我們的寫入是基於之前的哪一種狀態。如果在不包含版本號的情況下進行寫操作,則與所有其他寫操作併發,因此它不會覆蓋任何內容 —— 只會在隨後的讀取中作為其中一個值返回。 當一個寫入包含前一次讀取的版本號時,它會告訴我們的寫入是基於之前的哪一種狀態。如果在不包含版本號的情況下進行寫操作,則與所有其他寫操作併發,因此它不會覆蓋任何內容 —— 只會在隨後的讀取中作為其中一個值返回。
@ -688,7 +688,7 @@ LWW實現了最終收斂的目標但以**永續性**為代價:如果同一
以購物車為例,一種合理的合併值的方法就是做並集。在[圖5-14](../img/fig5-14.png)中,最後的合併結果是[牛奶,麵粉,雞蛋,燻肉]和[雞蛋,牛奶,火腿]。注意牛奶和雞蛋同時出現在兩個併發值裡,即使他們每個只被寫過一次。合併的值可以是[牛奶,麵粉,雞蛋,培根,火腿],他們沒有重複。 以購物車為例,一種合理的合併值的方法就是做並集。在[圖5-14](../img/fig5-14.png)中,最後的合併結果是[牛奶,麵粉,雞蛋,燻肉]和[雞蛋,牛奶,火腿]。注意牛奶和雞蛋同時出現在兩個併發值裡,即使他們每個只被寫過一次。合併的值可以是[牛奶,麵粉,雞蛋,培根,火腿],他們沒有重複。
然而,如果你想讓人們也可以從他們的購物車中**刪除**東西而不是僅僅新增東西那麼把併發值做並集可能不會產生正確的結果如果你合併了兩個客戶端的購物車並且只在其中一個客戶端裡面刪掉了它那麼被刪除的專案會重新出現在這兩個客戶端的交集結果中【37】。為了防止這個問題一個專案在刪除時不能簡單地從資料庫中刪除相反系統必須留下一個具有適當版本號的標記在合併兄弟時表明該專案已被刪除。這種刪除標記被稱為**墓碑tombstone**(我們之前在“[雜湊索引”](ch3.md#雜湊索引)中的日誌壓縮的上下文中看到了墓碑 然而,如果你想讓人們也可以從他們的購物車中**刪除**東西而不是僅僅新增東西那麼把併發值做並集可能不會產生正確的結果如果你合併了兩個客戶端的購物車並且只在其中一個客戶端裡面刪掉了它那麼被刪除的專案會重新出現在這兩個客戶端的交集結果中【37】。為了防止這個問題一個專案在刪除時不能簡單地從資料庫中刪除相反系統必須留下一個具有適當版本號的標記在合併兄弟時表明該專案已被刪除。這種刪除標記被稱為**墓碑tombstone**(我們之前在“[雜湊索引”](ch3.md#雜湊索引)中的日誌壓縮的上下文中看到了墓碑)
因為在應用程式程式碼中做合併是複雜且易出錯,所以有一些資料結構被設計出來用於自動執行這種合併,如“[自動衝突解決](#自動衝突解決)”中討論的。例如Riak的資料型別支援使用稱為CRDT的資料結構家族【38,39,55】可以以合理的方式自動合併包括保留刪除。 因為在應用程式程式碼中做合併是複雜且易出錯,所以有一些資料結構被設計出來用於自動執行這種合併,如“[自動衝突解決](#自動衝突解決)”中討論的。例如Riak的資料型別支援使用稱為CRDT的資料結構家族【38,39,55】可以以合理的方式自動合併包括保留刪除。

View File

@ -79,7 +79,7 @@ ACID原子性的定義特徵是**能夠在錯誤時中止事務,丟棄該
ACID一致性的概念是**對資料的一組特定約束必須始終成立**。即**不變數invariants**。例如,在會計系統中,所有賬戶整體上必須借貸相抵。如果一個事務開始於一個滿足這些不變數的有效資料庫,且在事務處理期間的任何寫入操作都保持這種有效性,那麼可以確定,不變數總是滿足的。 ACID一致性的概念是**對資料的一組特定約束必須始終成立**。即**不變數invariants**。例如,在會計系統中,所有賬戶整體上必須借貸相抵。如果一個事務開始於一個滿足這些不變數的有效資料庫,且在事務處理期間的任何寫入操作都保持這種有效性,那麼可以確定,不變數總是滿足的。
但是,一致性的這種概念取決於應用程式對不變數的理解,應用程式負責正確定義它的事務,並保持一致性。這並不是資料庫可以保證的事情:如果你寫入違反不變數的髒資料,資料庫也無法阻止你(一些特定型別的不變數可以由資料庫檢查,例如外來鍵約束或唯一約束,但是一般來說,是應用程式來定義什麼樣的資料是有效的,什麼樣是無效的。—— 資料庫只管儲存 但是,一致性的這種概念取決於應用程式對不變數的理解,應用程式負責正確定義它的事務,並保持一致性。這並不是資料庫可以保證的事情:如果你寫入違反不變數的髒資料,資料庫也無法阻止你(一些特定型別的不變數可以由資料庫檢查,例如外來鍵約束或唯一約束,但是一般來說,是應用程式來定義什麼樣的資料是有效的,什麼樣是無效的。—— 資料庫只管儲存)
原子性隔離性和永續性是資料庫的屬性而一致性在ACID意義上是應用程式的屬性。應用可能依賴資料庫的原子性和隔離屬性來實現一致性但這並不僅取決於資料庫。因此字母C不屬於ACID[^i]。 原子性隔離性和永續性是資料庫的屬性而一致性在ACID意義上是應用程式的屬性。應用可能依賴資料庫的原子性和隔離屬性來實現一致性但這並不僅取決於資料庫。因此字母C不屬於ACID[^i]。
@ -454,7 +454,7 @@ UPDATE wiki_pages SET content = '新內容'
在複製資料庫中(請參閱[第五章](ch5.md)),防止丟失的更新需要考慮另一個維度:由於在多個節點上存在資料副本,並且在不同節點上的資料可能被併發地修改,因此需要採取一些額外的步驟來防止丟失更新。 在複製資料庫中(請參閱[第五章](ch5.md)),防止丟失的更新需要考慮另一個維度:由於在多個節點上存在資料副本,並且在不同節點上的資料可能被併發地修改,因此需要採取一些額外的步驟來防止丟失更新。
鎖和CAS操作假定只有一個最新的資料副本。但是多主或無主複製的資料庫通常允許多個寫入併發執行並非同步複製到副本上因此無法保證只有一個最新資料的副本。所以基於鎖或CAS操作的技術不適用於這種情況(我們將在“[線性一致性](ch9.md#線性一致性)”中更詳細地討論這個問題 鎖和CAS操作假定只有一個最新的資料副本。但是多主或無主複製的資料庫通常允許多個寫入併發執行並非同步複製到副本上因此無法保證只有一個最新資料的副本。所以基於鎖或CAS操作的技術不適用於這種情況我們將在“[線性一致性](ch9.md#線性一致性)”中更詳細地討論這個問題)
相反,如“[檢測併發寫入](ch5.md#檢測併發寫入)”一節所述,這種複製資料庫中的一種常見方法是允許併發寫入建立多個衝突版本的值(也稱為兄弟),並使用應用程式碼或特殊資料結構在事實發生之後解決和合並這些版本。 相反,如“[檢測併發寫入](ch5.md#檢測併發寫入)”一節所述,這種複製資料庫中的一種常見方法是允許併發寫入建立多個衝突版本的值(也稱為兄弟),並使用應用程式碼或特殊資料結構在事實發生之後解決和合並這些版本。
@ -677,8 +677,8 @@ VoltDB還使用儲存過程進行復制但不是將事務的寫入結果從
兩階段鎖定類似,但是鎖的要求更強得多。只要沒有寫入,就允許多個事務同時讀取同一個物件。但物件只要有寫入(修改或刪除),就需要**獨佔訪問exclusive access** 許可權: 兩階段鎖定類似,但是鎖的要求更強得多。只要沒有寫入,就允許多個事務同時讀取同一個物件。但物件只要有寫入(修改或刪除),就需要**獨佔訪問exclusive access** 許可權:
- 如果事務A讀取了一個物件並且事務B想要寫入該物件那麼B必須等到A提交或中止才能繼續這確保B不能在A底下意外地改變物件 - 如果事務A讀取了一個物件並且事務B想要寫入該物件那麼B必須等到A提交或中止才能繼續這確保B不能在A底下意外地改變物件
- 如果事務A寫入了一個物件並且事務B想要讀取該物件則B必須等到A提交或中止才能繼續(像[圖7-1](../img/fig7-1.png)那樣讀取舊版本的物件在2PL下是不可接受的 - 如果事務A寫入了一個物件並且事務B想要讀取該物件則B必須等到A提交或中止才能繼續像[圖7-1](../img/fig7-1.png)那樣讀取舊版本的物件在2PL下是不可接受的
在2PL中寫入不僅會阻塞其他寫入也會阻塞讀反之亦然。快照隔離使得**讀不阻塞寫,寫也不阻塞讀**(請參閱“[實現快照隔離](#實現快照隔離)”這是2PL和快照隔離之間的關鍵區別。另一方面因為2PL提供了可序列化的性質它可以防止早先討論的所有競爭條件包括丟失更新和寫入偏差。 在2PL中寫入不僅會阻塞其他寫入也會阻塞讀反之亦然。快照隔離使得**讀不阻塞寫,寫也不阻塞讀**(請參閱“[實現快照隔離](#實現快照隔離)”這是2PL和快照隔離之間的關鍵區別。另一方面因為2PL提供了可序列化的性質它可以防止早先討論的所有競爭條件包括丟失更新和寫入偏差。

View File

@ -71,7 +71,7 @@
* 在地理位置分散的部署中(保持資料在地理位置上接近使用者以減少訪問延遲),通訊很可能透過網際網路進行,與本地網路相比,通訊速度緩慢且不可靠。超級計算機通常假設它們的所有節點都靠近在一起。 * 在地理位置分散的部署中(保持資料在地理位置上接近使用者以減少訪問延遲),通訊很可能透過網際網路進行,與本地網路相比,通訊速度緩慢且不可靠。超級計算機通常假設它們的所有節點都靠近在一起。
如果要使分散式系統工作,就必須接受部分故障的可能性,並在軟體中建立容錯機制。換句話說,我們需要從不可靠的元件構建一個可靠的系統(正如“[可靠性](ch1.md#可靠性)”中所討論的那樣,沒有完美的可靠性,所以我們需要理解我們可以實際承諾的極限 如果要使分散式系統工作,就必須接受部分故障的可能性,並在軟體中建立容錯機制。換句話說,我們需要從不可靠的元件構建一個可靠的系統(正如“[可靠性](ch1.md#可靠性)”中所討論的那樣,沒有完美的可靠性,所以我們需要理解我們可以實際承諾的極限)
即使在只有少數節點的小型系統中,考慮部分故障也是很重要的。在一個小系統中,很可能大部分元件在大部分時間都正常工作。然而,遲早會有一部分系統出現故障,軟體必須以某種方式處理。故障處理必須是軟體設計的一部分,並且作為軟體的運維,你需要知道在發生故障的情況下,軟體可能會表現出怎樣的行為。 即使在只有少數節點的小型系統中,考慮部分故障也是很重要的。在一個小系統中,很可能大部分元件在大部分時間都正常工作。然而,遲早會有一部分系統出現故障,軟體必須以某種方式處理。故障處理必須是軟體設計的一部分,並且作為軟體的運維,你需要知道在發生故障的情況下,軟體可能會表現出怎樣的行為。
@ -602,7 +602,7 @@ Web應用程式確實需要預期受終端使用者控制的客戶端如Web
為了澄清這種情況,有必要區分兩種不同的屬性:**安全safety屬性**和**活性liveness屬性**。在剛剛給出的例子中,**唯一性**和**單調序列**是安全屬性,而**可用性**是活性屬性。 為了澄清這種情況,有必要區分兩種不同的屬性:**安全safety屬性**和**活性liveness屬性**。在剛剛給出的例子中,**唯一性**和**單調序列**是安全屬性,而**可用性**是活性屬性。
這兩種性質有什麼區別?一個試金石就是,活性屬性通常在定義中通常包括“**最終**”一詞是的你猜對了——最終一致性是一個活性屬性【89】 這兩種性質有什麼區別?一個試金石就是,活性屬性通常在定義中通常包括“**最終**”一詞是的你猜對了——最終一致性是一個活性屬性【89】
安全通常被非正式地定義為:**沒有壞事發生**,而活性通常就類似:**最終好事發生**。但是最好不要過多地閱讀那些非正式的定義因為好與壞的含義是主觀的。安全和活性的實際定義是精確的和數學的【90】 安全通常被非正式地定義為:**沒有壞事發生**,而活性通常就類似:**最終好事發生**。但是最好不要過多地閱讀那些非正式的定義因為好與壞的含義是主觀的。安全和活性的實際定義是精確的和數學的【90】

View File

@ -101,7 +101,7 @@
在一個線性一致的系統中,我們可以想象,在 `x` 的值從`0` 自動翻轉到 `1` 的時候(在寫操作的開始和結束之間)必定有一個時間點。因此,如果一個客戶端的讀取返回新的值 `1`,即使寫操作尚未完成,所有後續讀取也必須返回新值。 在一個線性一致的系統中,我們可以想象,在 `x` 的值從`0` 自動翻轉到 `1` 的時候(在寫操作的開始和結束之間)必定有一個時間點。因此,如果一個客戶端的讀取返回新的值 `1`,即使寫操作尚未完成,所有後續讀取也必須返回新值。
[圖9-3](../img/fig9-3.png)中的箭頭說明了這個時序依賴關係。客戶端A 是第一個讀取新的值 `1` 的位置。在A 的讀取返回之後B開始新的讀取。由於B的讀取嚴格在發生於A的讀取之後因此即使C的寫入仍在進行中也必須返回 `1`(與[圖9-1](../img/fig9-1.png)中的Alice和Bob的情況相同在Alice讀取新值之後Bob也希望讀取新的值 [圖9-3](../img/fig9-3.png)中的箭頭說明了這個時序依賴關係。客戶端A 是第一個讀取新的值 `1` 的位置。在A 的讀取返回之後B開始新的讀取。由於B的讀取嚴格在發生於A的讀取之後因此即使C的寫入仍在進行中也必須返回 `1`(與[圖9-1](../img/fig9-1.png)中的Alice和Bob的情況相同在Alice讀取新值之後Bob也希望讀取新的值
我們可以進一步細化這個時序圖,展示每個操作是如何在特定時刻原子性生效的。[圖9-4](../img/fig9-4.png)顯示了一個更復雜的例子【10】。 我們可以進一步細化這個時序圖,展示每個操作是如何在特定時刻原子性生效的。[圖9-4](../img/fig9-4.png)顯示了一個更復雜的例子【10】。
@ -435,7 +435,7 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】它只考慮了一
雖然蘭伯特時間戳定義了一個與因果一致的全序,但它還不足以解決分散式系統中的許多常見問題。 雖然蘭伯特時間戳定義了一個與因果一致的全序,但它還不足以解決分散式系統中的許多常見問題。
例如,考慮一個需要確保使用者名稱能唯一標識使用者帳戶的系統。如果兩個使用者同時嘗試使用相同的使用者名稱建立帳戶,則其中一個應該成功,另一個應該失敗(我們之前在“[領導者和鎖](ch8.md#領導者和鎖)”中提到過這個問題 例如,考慮一個需要確保使用者名稱能唯一標識使用者帳戶的系統。如果兩個使用者同時嘗試使用相同的使用者名稱建立帳戶,則其中一個應該成功,另一個應該失敗(我們之前在“[領導者和鎖](ch8.md#領導者和鎖)”中提到過這個問題)
乍看之下,似乎操作的全序關係足以解決這一問題(例如使用蘭伯特時間戳):如果建立了兩個具有相同使用者名稱的帳戶,選擇時間戳較小的那個作為勝者(第一個抓到使用者名稱的人),並讓帶有更大時間戳者失敗。由於時間戳上有全序關係,所以這個比較總是可行的。 乍看之下,似乎操作的全序關係足以解決這一問題(例如使用蘭伯特時間戳):如果建立了兩個具有相同使用者名稱的帳戶,選擇時間戳較小的那個作為勝者(第一個抓到使用者名稱的人),並讓帶有更大時間戳者失敗。由於時間戳上有全序關係,所以這個比較總是可行的。
@ -511,9 +511,9 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】它只考慮了一
儘管這一過程保證寫入是線性一致的,但它並不保證讀取也是線性一致的 —— 如果你從與日誌非同步更新的儲存中讀取資料,結果可能是陳舊的。 (精確地說,這裡描述的過程提供了**順序一致性sequential consistency**【47,64】有時也稱為**時間線一致性timeline consistency**【65,66】比線性一致性稍微弱一些的保證。為了使讀取也線性一致有幾個選項 儘管這一過程保證寫入是線性一致的,但它並不保證讀取也是線性一致的 —— 如果你從與日誌非同步更新的儲存中讀取資料,結果可能是陳舊的。 (精確地說,這裡描述的過程提供了**順序一致性sequential consistency**【47,64】有時也稱為**時間線一致性timeline consistency**【65,66】比線性一致性稍微弱一些的保證。為了使讀取也線性一致有幾個選項
* 你可以透過在日誌中追加一條訊息,然後讀取日誌,直到該訊息被讀回才執行實際的讀取操作。訊息在日誌中的位置因此定義了讀取發生的時間點etcd的法定人數讀取有些類似這種情況【16】 * 你可以透過在日誌中追加一條訊息然後讀取日誌直到該訊息被讀回才執行實際的讀取操作。訊息在日誌中的位置因此定義了讀取發生的時間點etcd的法定人數讀取有些類似這種情況【16】
* 如果日誌允許以線性一致的方式獲取最新日誌訊息的位置,則可以查詢該位置,等待該位置前的所有訊息都傳達到你,然後執行讀取。 這是Zookeeper `sync()` 操作背後的思想【15】 * 如果日誌允許以線性一致的方式獲取最新日誌訊息的位置,則可以查詢該位置,等待該位置前的所有訊息都傳達到你,然後執行讀取。 這是Zookeeper `sync()` 操作背後的思想【15】
* 你可以從同步更新的副本中進行讀取,因此可以確保結果是最新的這種技術用於鏈式複製chain replication【63】請參閱“[關於複製的研究](ch5.md#關於複製的研究)” * 你可以從同步更新的副本中進行讀取因此可以確保結果是最新的這種技術用於鏈式複製chain replication【63】請參閱“[關於複製的研究](ch5.md#關於複製的研究)”)
#### 使用線性一致性儲存實現全序廣播 #### 使用線性一致性儲存實現全序廣播
@ -625,7 +625,7 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】它只考慮了一
5. 當協調者收到所有準備請求的答覆時,會就提交或中止事務作出明確的決定(只有在所有參與者投贊成票的情況下才會提交)。協調者必須把這個決定寫到磁碟上的事務日誌中,如果它隨後就崩潰,恢復後也能知道自己所做的決定。這被稱為**提交點commit point**。 5. 當協調者收到所有準備請求的答覆時,會就提交或中止事務作出明確的決定(只有在所有參與者投贊成票的情況下才會提交)。協調者必須把這個決定寫到磁碟上的事務日誌中,如果它隨後就崩潰,恢復後也能知道自己所做的決定。這被稱為**提交點commit point**。
6. 一旦協調者的決定落盤,提交或放棄請求會發送給所有參與者。如果這個請求失敗或超時,協調者必須永遠保持重試,直到成功為止。沒有回頭路:如果已經做出決定,不管需要多少次重試它都必須被執行。如果參與者在此期間崩潰,事務將在其恢復後提交——由於參與者投了贊成,因此恢復後它不能拒絕提交。 6. 一旦協調者的決定落盤,提交或放棄請求會發送給所有參與者。如果這個請求失敗或超時,協調者必須永遠保持重試,直到成功為止。沒有回頭路:如果已經做出決定,不管需要多少次重試它都必須被執行。如果參與者在此期間崩潰,事務將在其恢復後提交——由於參與者投了贊成,因此恢復後它不能拒絕提交。
因此該協議包含兩個關鍵的“不歸路”點當參與者投票“是”時它承諾它稍後肯定能夠提交儘管協調者可能仍然選擇放棄以及一旦協調者做出決定這一決定是不可撤銷的。這些承諾保證了2PC的原子性(單節點原子提交將這兩個事件合為了一體:將提交記錄寫入事務日誌 因此該協議包含兩個關鍵的“不歸路”點當參與者投票“是”時它承諾它稍後肯定能夠提交儘管協調者可能仍然選擇放棄以及一旦協調者做出決定這一決定是不可撤銷的。這些承諾保證了2PC的原子性單節點原子提交將這兩個事件合為了一體將提交記錄寫入事務日誌
回到婚姻的比喻,在說“我是”之前,你和你的新娘/新郎有中止這個事務的自由,透過回覆 “沒門或者有類似效果的話。然而在說了“我願意”之後你就不能撤回那個聲明瞭。如果你說“我願意”後暈倒了沒有聽到司儀說“你們現在是夫妻了”那也並不會改變事務已經提交的現實。當你稍後恢復意識時可以透過查詢司儀的全域性事務ID狀態來確定你是否已經成婚或者你可以等待司儀重試下一次提交請求因為重試將在你無意識期間一直持續 回到婚姻的比喻,在說“我是”之前,你和你的新娘/新郎有中止這個事務的自由,透過回覆 “沒門或者有類似效果的話。然而在說了“我願意”之後你就不能撤回那個聲明瞭。如果你說“我願意”後暈倒了沒有聽到司儀說“你們現在是夫妻了”那也並不會改變事務已經提交的現實。當你稍後恢復意識時可以透過查詢司儀的全域性事務ID狀態來確定你是否已經成婚或者你可以等待司儀重試下一次提交請求因為重試將在你無意識期間一直持續
@ -755,7 +755,7 @@ XA事務解決了保持多個參與者資料系統相互一致的現實的
如果你不關心容錯,那麼滿足前三個屬性很容易:你可以將一個節點硬編碼為“獨裁者”,並讓該節點做出所有的決定。但如果該節點失效,那麼系統就無法再做出任何決定。事實上,這就是我們在兩階段提交的情況中所看到的:如果協調者失效,那麼存疑的參與者就無法決定提交還是中止。 如果你不關心容錯,那麼滿足前三個屬性很容易:你可以將一個節點硬編碼為“獨裁者”,並讓該節點做出所有的決定。但如果該節點失效,那麼系統就無法再做出任何決定。事實上,這就是我們在兩階段提交的情況中所看到的:如果協調者失效,那麼存疑的參與者就無法決定提交還是中止。
**終止**屬性形式化了容錯的思想。它實質上說的是,一個共識演算法不能簡單地永遠閒坐著等死 —— 換句話說,它必須取得進展。即使部分節點出現故障,其他節點也必須達成一項決定**終止**是一種**活性屬性**,而另外三種是**安全屬性** —— 請參閱“[安全性和活性](ch8.md#安全性和活性)” **終止**屬性形式化了容錯的思想。它實質上說的是,一個共識演算法不能簡單地永遠閒坐著等死 —— 換句話說,它必須取得進展。即使部分節點出現故障,其他節點也必須達成一項決定(**終止**是一種**活性屬性**,而另外三種是**安全屬性** —— 請參閱“[安全性和活性](ch8.md#安全性和活性)”)
共識的系統模型假設當一個節點“崩潰”時它會突然消失而且永遠不會回來。不像軟體崩潰想象一下地震包含你的節點的資料中心被山體滑坡所摧毀你必須假設節點被埋在30英尺以下的泥土中並且永遠不會重新上線在這個系統模型中任何需要等待節點恢復的演算法都不能滿足**終止**屬性。特別是2PC不符合終止屬性的要求。 共識的系統模型假設當一個節點“崩潰”時它會突然消失而且永遠不會回來。不像軟體崩潰想象一下地震包含你的節點的資料中心被山體滑坡所摧毀你必須假設節點被埋在30英尺以下的泥土中並且永遠不會重新上線在這個系統模型中任何需要等待節點恢復的演算法都不能滿足**終止**屬性。特別是2PC不符合終止屬性的要求。