Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix some broken anchor links #281

Merged
merged 1 commit into from
Jan 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ch10.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ MapReduce 与 Unix 命令管道的主要区别在于,MapReduce 可以在多台

计算的 Reduce 端也被分区。虽然 Map 任务的数量由输入文件块的数量决定,但 Reducer 的任务的数量是由作业作者配置的(它可以不同于 Map 任务的数量)。为了确保具有相同键的所有键值对最终落在相同的 Reducer 处,框架使用键的散列值来确定哪个 Reduce 任务应该接收到特定的键值对(请参阅 “[根据键的散列分区](ch6.md#根据键的散列分区)”)。

键值对必须进行排序,但数据集可能太大,无法在单台机器上使用常规排序算法进行排序。相反,分类是分阶段进行的。首先每个 Map 任务都按照 Reducer 对输出进行分区。每个分区都被写入 Mapper 程序的本地磁盘,使用的技术与我们在 “[SSTables 与 LSM 树](ch3.md#SSTables与LSM树)” 中讨论的类似。
键值对必须进行排序,但数据集可能太大,无法在单台机器上使用常规排序算法进行排序。相反,分类是分阶段进行的。首先每个 Map 任务都按照 Reducer 对输出进行分区。每个分区都被写入 Mapper 程序的本地磁盘,使用的技术与我们在 “[SSTables 与 LSM 树](ch3.md#SSTables和LSM树)” 中讨论的类似。

只要当 Mapper 读取完输入文件,并写完排序后的输出文件,MapReduce 调度器就会通知 Reducer 可以从该 Mapper 开始获取输出文件。Reducer 连接到每个 Mapper,并下载自己相应分区的有序键值对文件。按 Reducer 分区,排序,从 Mapper 向 Reducer 复制分区数据,这一整个过程被称为 **混洗(shuffle)**【26】(一个容易混淆的术语 —— 不像洗牌,在 MapReduce 中的混洗没有随机性)。

Expand Down Expand Up @@ -664,7 +664,7 @@ Spark、Flink 和 Tez 避免将中间状态写入 HDFS,因此它们采取了

自由运行任意代码,长期以来都是传统 MapReduce 批处理系统与 MPP 数据库的区别所在(请参阅 “[Hadoop 与分布式数据库的对比](#Hadoop与分布式数据库的对比)” 一节)。虽然数据库具有编写用户定义函数的功能,但是它们通常使用起来很麻烦,而且与大多数编程语言中广泛使用的程序包管理器和依赖管理系统兼容不佳(例如 Java 的 Maven、Javascript 的 npm 以及 Ruby 的 gems)。

然而数据流引擎已经发现,支持除连接之外的更多 **声明式特性** 还有其他的优势。例如,如果一个回调函数只包含一个简单的过滤条件,或者只是从一条记录中选择了一些字段,那么在为每条记录调用函数时会有相当大的额外 CPU 开销。如果以声明方式表示这些简单的过滤和映射操作,那么查询优化器可以利用列式存储布局(请参阅 “[列式存储](ch3.md#列式存储)”),只从磁盘读取所需的列。 Hive、Spark DataFrames 和 Impala 还使用了向量化执行(请参阅 “[内存带宽和向量处理](ch3.md#内存带宽和向量处理)”):在对 CPU 缓存友好的内部循环中迭代数据,避免函数调用。Spark 生成 JVM 字节码【79】,Impala 使用 LLVM 为这些内部循环生成本机代码【41】。
然而数据流引擎已经发现,支持除连接之外的更多 **声明式特性** 还有其他的优势。例如,如果一个回调函数只包含一个简单的过滤条件,或者只是从一条记录中选择了一些字段,那么在为每条记录调用函数时会有相当大的额外 CPU 开销。如果以声明方式表示这些简单的过滤和映射操作,那么查询优化器可以利用列式存储布局(请参阅 “[列式存储](ch3.md#列式存储)”),只从磁盘读取所需的列。 Hive、Spark DataFrames 和 Impala 还使用了向量化执行(请参阅 “[内存带宽和矢量化处理](ch3.md#内存带宽和矢量化处理)”):在对 CPU 缓存友好的内部循环中迭代数据,避免函数调用。Spark 生成 JVM 字节码【79】,Impala 使用 LLVM 为这些内部循环生成本机代码【41】。

通过在高级 API 中引入声明式的部分,并使查询优化器可以在执行期间利用这些来做优化,批处理框架看起来越来越像 MPP 数据库了(并且能实现可与之媲美的性能)。同时,通过拥有运行任意代码和以任意格式读取数据的可扩展性,它们保持了灵活性的优势。

Expand Down
4 changes: 2 additions & 2 deletions ch11.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ Apache Kafka 【17,18】、Amazon Kinesis Streams 【19】和 Twitter 的 Distri

除非有一些额外的并发检测机制,例如我们在 “[检测并发写入](ch5.md#检测并发写入)” 中讨论的版本向量,否则你甚至不会意识到发生了并发写入 —— 一个值将简单地以无提示方式覆盖另一个值。

双重写入的另一个问题是,其中一个写入可能会失败,而另一个成功。这是一个容错问题,而不是一个并发问题,但也会造成两个系统互相不一致的结果。确保它们要么都成功要么都失败,是原子提交问题的一个例子,解决这个问题的代价是昂贵的(请参阅 “[原子提交与两阶段提交](ch7.md#原子提交与两阶段提交)”)。
双重写入的另一个问题是,其中一个写入可能会失败,而另一个成功。这是一个容错问题,而不是一个并发问题,但也会造成两个系统互相不一致的结果。确保它们要么都成功要么都失败,是原子提交问题的一个例子,解决这个问题的代价是昂贵的(请参阅 “[原子提交与两阶段提交](ch9.md#原子提交与两阶段提交)”)。

如果你只有一个单领导者复制的数据库,那么这个领导者决定了写入顺序,而状态机复制方法可以在数据库副本上工作。然而,在 [图 11-4](img/fig11-4.png) 中,没有单个主库:数据库可能有一个领导者,搜索索引也可能有一个领导者,但是两者都不追随对方,所以可能会发生冲突(请参阅 “[多主复制](ch5.md#多主复制)“)。

Expand Down Expand Up @@ -326,7 +326,7 @@ Kafka Connect【41】致力于将广泛的数据库系统的变更数据捕获

事件溯源的哲学是仔细区分 **事件(event)** 和 **命令(command)**【48】。当来自用户的请求刚到达时,它一开始是一个命令:在这个时间点上它仍然可能可能失败,比如,因为违反了一些完整性条件。应用必须首先验证它是否可以执行该命令。如果验证成功并且命令被接受,则它变为一个持久化且不可变的事件。

例如,如果用户试图注册特定用户名,或预定飞机或剧院的座位,则应用需要检查用户名或座位是否已被占用。(先前在 “[容错共识](ch8.md#容错共识)” 中讨论过这个例子)当检查成功时,应用可以生成一个事件,指示特定的用户名是由特定的用户 ID 注册的,或者座位已经预留给特定的顾客。
例如,如果用户试图注册特定用户名,或预定飞机或剧院的座位,则应用需要检查用户名或座位是否已被占用。(先前在 “[容错共识](ch9.md#容错共识)” 中讨论过这个例子)当检查成功时,应用可以生成一个事件,指示特定的用户名是由特定的用户 ID 注册的,或者座位已经预留给特定的顾客。

在事件生成的时刻,它就成为了 **事实(fact)**。即使客户稍后决定更改或取消预订,他们之前曾预定了某个特定座位的事实仍然成立,而更改或取消是之后添加的单独的事件。

Expand Down
4 changes: 2 additions & 2 deletions ch4.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
1. 在内存中,数据保存在对象、结构体、列表、数组、散列表、树等中。 这些数据结构针对 CPU 的高效访问和操作进行了优化(通常使用指针)。
2. 如果要将数据写入文件,或通过网络发送,则必须将其 **编码(encode)** 为某种自包含的字节序列(例如,JSON 文档)。 由于每个进程都有自己独立的地址空间,一个进程中的指针对任何其他进程都没有意义,所以这个字节序列表示会与通常在内存中使用的数据结构完全不同 [^i]。

[^i]: 除一些特殊情况外,例如某些内存映射文件或直接在压缩数据上操作(如 “[列压缩](ch4.md#列压缩)” 中所述)。
[^i]: 除一些特殊情况外,例如某些内存映射文件或直接在压缩数据上操作(如 “[列压缩](ch3.md#列压缩)” 中所述)。

所以,需要在两种表示之间进行某种类型的翻译。 从内存中表示到字节序列的转换称为 **编码(Encoding)** (也称为 **序列化(serialization)** 或 **编组(marshalling)**),反过来称为 **解码(Decoding)**[^ii](**解析(Parsing)**,**反序列化(deserialization)**,**反编组 (unmarshalling)**)[^译i]。

Expand Down Expand Up @@ -356,7 +356,7 @@ Avro 为静态类型编程语言提供了可选的代码生成功能,但是它

因此,模式演变允许整个数据库看起来好像是用单个模式编码的,即使底层存储可能包含用各种历史版本的模式编码的记录。

[^v]: 除了 MySQL,即使并非真的必要,它也经常会重写整个表,正如 “[文档模型中的模式灵活性](ch3.md#文档模型中的模式灵活性)” 中所提到的。
[^v]: 除了 MySQL,即使并非真的必要,它也经常会重写整个表,正如 “[文档模型中的模式灵活性](ch2.md#文档模型中的模式灵活性)” 中所提到的。


#### 归档存储
Expand Down
2 changes: 1 addition & 1 deletion ch6.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

对于在单个分区上运行的查询,每个节点可以独立执行对自己的查询,因此可以通过添加更多的节点来扩大查询吞吐量。大型,复杂的查询可能会跨越多个节点并行处理,尽管这也带来了新的困难。

分区数据库在 20 世纪 80 年代由 Teradata 和 NonStop SQL【1】等产品率先推出,最近因为 NoSQL 数据库和基于 Hadoop 的数据仓库重新被关注。有些系统是为事务性工作设计的,有些系统则用于分析(请参阅 “[事务处理还是分析](ch3.md#事务处理还是分析)”):这种差异会影响系统的运作方式,但是分区的基本原理均适用于这两种工作方式。
分区数据库在 20 世纪 80 年代由 Teradata 和 NonStop SQL【1】等产品率先推出,最近因为 NoSQL 数据库和基于 Hadoop 的数据仓库重新被关注。有些系统是为事务性工作设计的,有些系统则用于分析(请参阅 “[事务处理还是分析](ch3.md#事务处理还是分析)”):这种差异会影响系统的运作方式,但是分区的基本原理均适用于这两种工作方式。

在本章中,我们将首先介绍分割大型数据集的不同方法,并观察索引如何与分区配合。然后我们将讨论 [分区再平衡(rebalancing)](#分区再平衡),如果想要添加或删除集群中的节点,则必须进行再平衡。最后,我们将概述数据库如何将请求路由到正确的分区并执行查询。

Expand Down
2 changes: 1 addition & 1 deletion ch8.md
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ Web 应用程序确实需要预期受终端用户控制的客户端(如 Web

例如,在崩溃 - 恢复(crash-recovery)模型中的算法通常假设稳定存储器中的数据在崩溃后可以幸存。但是,如果磁盘上的数据被破坏,或者由于硬件错误或错误配置导致数据被清除,会发生什么情况【91】?如果服务器存在固件错误并且在重新启动时无法识别其硬盘驱动器,即使驱动器已正确连接到服务器,那又会发生什么情况【92】?

法定人数算法(请参阅 “[读写法定人数](ch5.md#读写法定人数)”)依赖节点来记住它声称存储的数据。如果一个节点可能患有健忘症,忘记了以前存储的数据,这会打破法定条件,从而破坏算法的正确性。也许需要一个新的系统模型,在这个模型中,我们假设稳定的存储大多能在崩溃后幸存,但有时也可能会丢失。但是那个模型就变得更难以推理了。
法定人数算法(请参阅 “[读写法定人数](ch5.md#读写的法定人数)”)依赖节点来记住它声称存储的数据。如果一个节点可能患有健忘症,忘记了以前存储的数据,这会打破法定条件,从而破坏算法的正确性。也许需要一个新的系统模型,在这个模型中,我们假设稳定的存储大多能在崩溃后幸存,但有时也可能会丢失。但是那个模型就变得更难以推理了。

算法的理论描述可以简单宣称一些事是不会发生的 —— 在非拜占庭式系统中,我们确实需要对可能发生和不可能发生的故障做出假设。然而,真实世界的实现,仍然会包括处理 “假设上不可能” 情况的代码,即使代码可能就是 `printf("Sucks to be you")` 和 `exit(666)`,实际上也就是留给运维来擦屁股【93】。(这可以说是计算机科学和软件工程间的一个差异)。

Expand Down
4 changes: 2 additions & 2 deletions glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

* **因果关系(causality)**

事件之间的依赖关系,当一件事发生在另一件事情之前。例如,后面的事件是对早期事件的回应,或者依赖于更早的事件,或者应该根据先前的事件来理解。请参阅“[“此前发生”的关系和并发](ch5.md#“此前发生”的关系和并发)”和“[顺序与因果关系](ch5.md#顺序与因果关系)”。
事件之间的依赖关系,当一件事发生在另一件事情之前。例如,后面的事件是对早期事件的回应,或者依赖于更早的事件,或者应该根据先前的事件来理解。请参阅“[“此前发生”的关系和并发](ch5.md#“此前发生”的关系和并发)”和“[顺序与因果关系](ch9.md#顺序与因果关系)”。

* **共识(consensus)**

Expand Down Expand Up @@ -203,7 +203,7 @@

* **偏斜(skew)**

各分区负载不平衡,例如某些分区有大量请求或数据,而其他分区则少得多。也被称为热点。请参阅“[负载偏斜和热点消除](ch6.md#负载偏斜和热点消除)”和“[处理偏斜](ch10.md#处理偏斜)”。
各分区负载不平衡,例如某些分区有大量请求或数据,而其他分区则少得多。也被称为热点。请参阅“[负载偏斜和热点消除](ch6.md#负载偏斜与热点消除)”和“[处理偏斜](ch10.md#处理偏斜)”。

时间线异常导致事件以不期望的顺序出现。 请参阅“[快照隔离和可重复读](ch7.md#快照隔离和可重复读)”中的关于读取偏差的讨论,“[写入偏差与幻读](ch7.md#写入偏差与幻读)”中的写入偏差以及“[有序事件的时间戳](ch8.md#有序事件的时间戳)”中的时钟偏斜。

Expand Down
4 changes: 2 additions & 2 deletions zh-tw/ch10.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ MapReduce 與 Unix 命令管道的主要區別在於,MapReduce 可以在多臺

計算的 Reduce 端也被分割槽。雖然 Map 任務的數量由輸入檔案塊的數量決定,但 Reducer 的任務的數量是由作業作者配置的(它可以不同於 Map 任務的數量)。為了確保具有相同鍵的所有鍵值對最終落在相同的 Reducer 處,框架使用鍵的雜湊值來確定哪個 Reduce 任務應該接收到特定的鍵值對(請參閱 “[根據鍵的雜湊分割槽](ch6.md#根據鍵的雜湊分割槽)”)。

鍵值對必須進行排序,但資料集可能太大,無法在單臺機器上使用常規排序演算法進行排序。相反,分類是分階段進行的。首先每個 Map 任務都按照 Reducer 對輸出進行分割槽。每個分割槽都被寫入 Mapper 程式的本地磁碟,使用的技術與我們在 “[SSTables 與 LSM 樹](ch3.md#SSTables與LSM樹)” 中討論的類似。
鍵值對必須進行排序,但資料集可能太大,無法在單臺機器上使用常規排序演算法進行排序。相反,分類是分階段進行的。首先每個 Map 任務都按照 Reducer 對輸出進行分割槽。每個分割槽都被寫入 Mapper 程式的本地磁碟,使用的技術與我們在 “[SSTables 與 LSM 樹](ch3.md#SSTables和LSM樹)” 中討論的類似。

只要當 Mapper 讀取完輸入檔案,並寫完排序後的輸出檔案,MapReduce 排程器就會通知 Reducer 可以從該 Mapper 開始獲取輸出檔案。Reducer 連線到每個 Mapper,並下載自己相應分割槽的有序鍵值對檔案。按 Reducer 分割槽,排序,從 Mapper 向 Reducer 複製分割槽資料,這一整個過程被稱為 **混洗(shuffle)**【26】(一個容易混淆的術語 —— 不像洗牌,在 MapReduce 中的混洗沒有隨機性)。

Expand Down Expand Up @@ -664,7 +664,7 @@ Spark、Flink 和 Tez 避免將中間狀態寫入 HDFS,因此它們採取了

自由執行任意程式碼,長期以來都是傳統 MapReduce 批處理系統與 MPP 資料庫的區別所在(請參閱 “[Hadoop 與分散式資料庫的對比](#Hadoop與分散式資料庫的對比)” 一節)。雖然資料庫具有編寫使用者定義函式的功能,但是它們通常使用起來很麻煩,而且與大多數程式語言中廣泛使用的程式包管理器和依賴管理系統相容不佳(例如 Java 的 Maven、Javascript 的 npm 以及 Ruby 的 gems)。

然而資料流引擎已經發現,支援除連線之外的更多 **宣告式特性** 還有其他的優勢。例如,如果一個回撥函式只包含一個簡單的過濾條件,或者只是從一條記錄中選擇了一些欄位,那麼在為每條記錄呼叫函式時會有相當大的額外 CPU 開銷。如果以宣告方式表示這些簡單的過濾和對映操作,那麼查詢最佳化器可以利用列式儲存佈局(請參閱 “[列式儲存](ch3.md#列式儲存)”),只從磁碟讀取所需的列。 Hive、Spark DataFrames 和 Impala 還使用了向量化執行(請參閱 “[記憶體頻寬和向量處理](ch3.md#記憶體頻寬和向量處理)”):在對 CPU 快取友好的內部迴圈中迭代資料,避免函式呼叫。Spark 生成 JVM 位元組碼【79】,Impala 使用 LLVM 為這些內部迴圈生成本機程式碼【41】。
然而資料流引擎已經發現,支援除連線之外的更多 **宣告式特性** 還有其他的優勢。例如,如果一個回撥函式只包含一個簡單的過濾條件,或者只是從一條記錄中選擇了一些欄位,那麼在為每條記錄呼叫函式時會有相當大的額外 CPU 開銷。如果以宣告方式表示這些簡單的過濾和對映操作,那麼查詢最佳化器可以利用列式儲存佈局(請參閱 “[列式儲存](ch3.md#列式儲存)”),只從磁碟讀取所需的列。 Hive、Spark DataFrames 和 Impala 還使用了向量化執行(請參閱 “[記憶體頻寬和向量化處理](ch3.md#記憶體頻寬和向量化處理)”):在對 CPU 快取友好的內部迴圈中迭代資料,避免函式呼叫。Spark 生成 JVM 位元組碼【79】,Impala 使用 LLVM 為這些內部迴圈生成本機程式碼【41】。

透過在高階 API 中引入宣告式的部分,並使查詢最佳化器可以在執行期間利用這些來做最佳化,批處理框架看起來越來越像 MPP 資料庫了(並且能實現可與之媲美的效能)。同時,透過擁有執行任意程式碼和以任意格式讀取資料的可擴充套件性,它們保持了靈活性的優勢。

Expand Down
Loading