From 541848820a31c239fddeae8f2d2df9242fe031d8 Mon Sep 17 00:00:00 2001 From: Yin Gang Date: Wed, 4 Aug 2021 11:21:15 +0800 Subject: [PATCH] fix some dangling cross references --- ch1.md | 2 +- ch2.md | 2 +- ch3.md | 4 ++-- ch4.md | 6 +++--- ch5.md | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ch1.md b/ch1.md index 7e28b73..6bbbf61 100644 --- a/ch1.md +++ b/ch1.md @@ -264,7 +264,7 @@ ​ 人们经常讨论**纵向伸缩(scaling up)**(**垂直伸缩(vertical scaling)**,转向更强大的机器)和**横向伸缩(scaling out)** (**水平伸缩(horizontal scaling)**,将负载分布到多台小机器上)之间的对立。跨多台机器分配负载也称为“**无共享(shared-nothing)**”架构。可以在单台机器上运行的系统通常更简单,但高端机器可能非常贵,所以非常密集的负载通常无法避免地需要横向伸缩。现实世界中的优秀架构需要将这两种方法务实地结合,因为使用几台足够强大的机器可能比使用大量的小型虚拟机更简单也更便宜。 -​ 有些系统是 **弹性(elastic)** 的,这意味着可以在检测到负载增加时自动增加计算资源,而其他系统则是手动伸缩(人工分析容量并决定向系统添加更多的机器)。如果负载**极难预测(highly unpredictable)**,则弹性系统可能很有用,但手动伸缩系统更简单,并且意外操作可能会更少(参阅“[重新平衡分区](ch6.md#分区再平衡)”)。 +​ 有些系统是 **弹性(elastic)** 的,这意味着可以在检测到负载增加时自动增加计算资源,而其他系统则是手动伸缩(人工分析容量并决定向系统添加更多的机器)。如果负载**极难预测(highly unpredictable)**,则弹性系统可能很有用,但手动伸缩系统更简单,并且意外操作可能会更少(参阅“[分区再平衡](ch6.md#分区再平衡)”)。 ​ 跨多台机器部署 **无状态服务(stateless services)** 非常简单,但将带状态的数据系统从单节点变为分布式配置则可能引入许多额外复杂度。出于这个原因,常识告诉我们应该将数据库放在单个节点上(纵向伸缩),直到伸缩成本或可用性需求迫使其改为分布式。 diff --git a/ch2.md b/ch2.md index dcddfc8..bf2e793 100644 --- a/ch2.md +++ b/ch2.md @@ -208,7 +208,7 @@ CODASYL中的查询是通过利用遍历记录列和跟随访问路径表在数 在关系数据库中,查询优化器自动决定查询的哪些部分以哪个顺序执行,以及使用哪些索引。这些选择实际上是“访问路径”,但最大的区别在于它们是由查询优化器自动生成的,而不是由程序员生成,所以我们很少需要考虑它们。 -如果想按新的方式查询数据,你可以声明一个新的索引,查询会自动使用最合适的那些索引。无需更改查询来利用新的索引。(请参阅“[用于数据的查询语言](#用于数据的查询语言)”。)关系模型因此使添加应用程序新功能变得更加容易。 +如果想按新的方式查询数据,你可以声明一个新的索引,查询会自动使用最合适的那些索引。无需更改查询来利用新的索引。(请参阅“[数据查询语言](#数据查询语言)”。)关系模型因此使添加应用程序新功能变得更加容易。 关系数据库的查询优化器是复杂的,已耗费了多年的研究和开发精力【18】。关系模型的一个关键洞察是:只需构建一次查询优化器,随后使用该数据库的所有应用程序都可以从中受益。如果你没有查询优化器的话,那么为特定查询手动编写访问路径比编写通用优化器更容易——不过从长期看通用解决方案更好。 diff --git a/ch3.md b/ch3.md index eeab51b..715fda2 100644 --- a/ch3.md +++ b/ch3.md @@ -130,7 +130,7 @@ $ cat database 乍一看,只有追加日志看起来很浪费:为什么不更新文件,用新值覆盖旧值?但是只能追加设计的原因有几个: -* 追加和分段合并是顺序写入操作,通常比随机写入快得多,尤其是在磁盘旋转硬盘上。在某种程度上,顺序写入在基于闪存的 **固态硬盘(SSD)** 上也是优选的【4】。我们将在第83页的“[比较B-树和LSM-树](#比较B-树和LSM-树)”中进一步讨论这个问题。 +* 追加和分段合并是顺序写入操作,通常比随机写入快得多,尤其是在磁盘旋转硬盘上。在某种程度上,顺序写入在基于闪存的 **固态硬盘(SSD)** 上也是优选的【4】。我们将在第83页的“[比较B树和LSM树](#比较B树和LSM树)”中进一步讨论这个问题。 * 如果段文件是附加的或不可变的,并发和崩溃恢复就简单多了。例如,您不必担心在覆盖值时发生崩溃的情况,而将包含旧值和新值的一部分的文件保留在一起。 * 合并旧段可以避免数据文件随着时间的推移而分散的问题。 @@ -339,7 +339,7 @@ SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079 例如,全文搜索引擎通常允许搜索一个单词以扩展为包括该单词的同义词,忽略单词的语法变体,并且搜索在相同文档中彼此靠近的单词的出现,并且支持各种其他功能取决于文本的语言分析。为了处理文档或查询中的拼写错误,Lucene能够在一定的编辑距离内搜索文本(编辑距离1意味着添加,删除或替换了一个字母)【37】。 -正如“[在SSTables中创建LSM树](#在SSTables中创建LSM树)”中所提到的,Lucene为其词典使用了一个类似于SSTable的结构。这个结构需要一个小的内存索引,告诉查询需要在排序文件中哪个偏移量查找键。在LevelDB中,这个内存中的索引是一些键的稀疏集合,但在Lucene中,内存中的索引是键中字符的有限状态自动机,类似于trie 【38】。这个自动机可以转换成Levenshtein自动机,它支持在给定的编辑距离内有效地搜索单词【39】。 +正如“[用SSTables制作LSM树](#用SSTables制作LSM树)”中所提到的,Lucene为其词典使用了一个类似于SSTable的结构。这个结构需要一个小的内存索引,告诉查询需要在排序文件中哪个偏移量查找键。在LevelDB中,这个内存中的索引是一些键的稀疏集合,但在Lucene中,内存中的索引是键中字符的有限状态自动机,类似于trie 【38】。这个自动机可以转换成Levenshtein自动机,它支持在给定的编辑距离内有效地搜索单词【39】。 其他的模糊搜索技术正朝着文档分类和机器学习的方向发展。有关更多详细信息,请参阅信息检索教科书,例如【40】。 diff --git a/ch4.md b/ch4.md index 4a421cc..658b731 100644 --- a/ch4.md +++ b/ch4.md @@ -274,7 +274,7 @@ Avro的关键思想是Writer模式和Reader模式不必是相同的 - 他们只 * 通过网络连接发送记录 - 当两个进程通过双向网络连接进行通信时,他们可以在连接设置上协商模式版本,然后在连接的生命周期中使用该模式。 Avro RPC协议(参阅“[通过服务的数据流:REST和RPC](#通过服务的数据流:REST和RPC)”)就是这样工作的。 + 当两个进程通过双向网络连接进行通信时,他们可以在连接设置上协商模式版本,然后在连接的生命周期中使用该模式。 Avro RPC协议(参阅“[服务中的数据流:REST与RPC](#服务中的数据流:REST与RPC)”)就是这样工作的。 具有模式版本的数据库在任何情况下都是非常有用的,因为它充当文档并为您提供了检查模式兼容性的机会【24】。作为版本号,你可以使用一个简单的递增整数,或者你可以使用模式的散列。 @@ -326,7 +326,7 @@ Avro为静态类型编程语言提供了可选的代码生成功能,但是它 这是一个相当抽象的概念 - 数据可以通过多种方式从一个流程流向另一个流程。谁编码数据,谁解码?在本章的其余部分中,我们将探讨数据如何在流程之间流动的一些最常见的方式: * 通过数据库(参阅“[数据库中的数据流](#数据库中的数据流)”) -* 通过服务调用(参阅“[服务中的数据流:REST和RPC](#服务中的数据流:REST和RPC)”) +* 通过服务调用(参阅“[服务中的数据流:REST与RPC](#服务中的数据流:REST与RPC)”) * 通过异步消息传递(参阅“[消息传递中的数据流](#消息传递中的数据流)”) @@ -383,7 +383,7 @@ Web浏览器不是唯一的客户端类型。例如,在移动设备或桌面 此外,服务器本身可以是另一个服务的客户端(例如,典型的Web应用服务器充当数据库的客户端)。这种方法通常用于将大型应用程序按照功能区域分解为较小的服务,这样当一个服务需要来自另一个服务的某些功能或数据时,就会向另一个服务发出请求。这种构建应用程序的方式传统上被称为**面向服务的体系结构(service-oriented architecture,SOA)**,最近被改进和更名为**微服务架构**【31,32】。 -在某些方面,服务类似于数据库:它们通常允许客户端提交和查询数据。但是,虽然数据库允许使用我们在[第2章](./ch2.md)中讨论的查询语言进行任意查询,但是服务公开了一个特定于应用程序的API,它只允许由服务的业务逻辑(应用程序代码)预定的输入和输出【33】。这种限制提供了一定程度的封装:服务能够对客户可以做什么和不可以做什么施加细粒度的限制。 +在某些方面,服务类似于数据库:它们通常允许客户端提交和查询数据。但是,虽然数据库允许使用我们在[第2章](ch2.md)中讨论的查询语言进行任意查询,但是服务公开了一个特定于应用程序的API,它只允许由服务的业务逻辑(应用程序代码)预定的输入和输出【33】。这种限制提供了一定程度的封装:服务能够对客户可以做什么和不可以做什么施加细粒度的限制。 面向服务/微服务架构的一个关键设计目标是通过使服务独立部署和演化来使应用程序更易于更改和维护。例如,每个服务应该由一个团队拥有,并且该团队应该能够经常发布新版本的服务,而不必与其他团队协调。换句话说,我们应该期望服务器和客户端的旧版本和新版本同时运行,因此服务器和客户端使用的数据编码必须在不同版本的服务API之间兼容——这正是我们在本章所一直在谈论的。 diff --git a/ch5.md b/ch5.md index d4de746..f454a5b 100644 --- a/ch5.md +++ b/ch5.md @@ -10,7 +10,7 @@ [TOC] -​ 复制意味着在通过网络连接的多台机器上保留相同数据的副本。正如在[第二部分简介](part-ii.md)中所讨论的那样,我们希望能复制数据,可能出于各种各样的原因: +​ 复制意味着在通过网络连接的多台机器上保留相同数据的副本。正如在[第二部分](part-ii.md)的介绍中所讨论的那样,我们希望能复制数据,可能出于各种各样的原因: * 使得数据与用户在地理上接近(从而减少延迟) * 即使系统的一部分出现故障,系统也能继续工作(从而提高可用性) @@ -112,7 +112,7 @@ * 如果数据库需要和其他外部存储相协调,那么丢弃写入内容是极其危险的操作。例如在GitHub 【13】的一场事故中,一个过时的MySQL从库被提升为主库。数据库使用自增ID作为主键,因为新主库的计数器落后于老主库的计数器,所以新主库重新分配了一些已经被老主库分配掉的ID作为主键。这些主键也在Redis中使用,主键重用使得MySQL和Redis中数据产生不一致,最后导致一些私有数据泄漏到错误的用户手中。 -* 发生某些故障时(见[第8章](ch8.md))可能会出现两个节点都以为自己是主库的情况。这种情况称为 **脑裂(split brain)**,非常危险:如果两个主库都可以接受写操作,却没有冲突解决机制(参见“[多领导者复制](#多领导者复制)”),那么数据就可能丢失或损坏。一些系统采取了安全防范措施:当检测到两个主库节点同时存在时会关闭其中一个节点[^ii],但设计粗糙的机制可能最后会导致两个节点都被关闭【14】。 +* 发生某些故障时(见[第8章](ch8.md))可能会出现两个节点都以为自己是主库的情况。这种情况称为 **脑裂(split brain)**,非常危险:如果两个主库都可以接受写操作,却没有冲突解决机制(参见“[多主复制](#多主复制)”),那么数据就可能丢失或损坏。一些系统采取了安全防范措施:当检测到两个主库节点同时存在时会关闭其中一个节点[^ii],但设计粗糙的机制可能最后会导致两个节点都被关闭【14】。 [^ii]: 这种机制称为 **屏蔽(fencing)**,充满感情的术语是:**爆彼之头(Shoot The Other Node In The Head, STONITH)**。 @@ -391,7 +391,7 @@ 实现冲突合并解决有多种途径: -* 给每个写入一个唯一的ID(例如,一个时间戳,一个长的随机数,一个UUID或者一个键和值的哈希),挑选最高ID的写入作为胜利者,并丢弃其他写入。如果使用时间戳,这种技术被称为**最后写入胜利(LWW, last write wins)**。虽然这种方法很流行,但是很容易造成数据丢失【35】。我们将在[本章末尾](#检测并发写入)更详细地讨论LWW。 +* 给每个写入一个唯一的ID(例如,一个时间戳,一个长的随机数,一个UUID或者一个键和值的哈希),挑选最高ID的写入作为胜利者,并丢弃其他写入。如果使用时间戳,这种技术被称为**最后写入胜利(LWW, last write wins)**。虽然这种方法很流行,但是很容易造成数据丢失【35】。我们将在本章末尾的[检测并发写入](#检测并发写入)更详细地讨论LWW。 * 为每个副本分配一个唯一的ID,ID编号更高的写入具有更高的优先级。这种方法也意味着数据丢失。 * 以某种方式将这些值合并在一起 - 例如,按字母顺序排序,然后连接它们(在[图5-7](img/fig5-7.png)中,合并的标题可能类似于“B/C”)。 * 用一种可保留所有信息的显式数据结构来记录冲突,并编写解决冲突的应用程序代码(也许通过提示用户的方式)。