update opencc to 1.1.9

This commit is contained in:
Gang Yin 2024-08-08 16:52:38 +08:00
parent c40c09ac8f
commit e3ca1c3141
14 changed files with 50 additions and 49 deletions

View File

@ -18,6 +18,7 @@ def convert(src_path, dst_path, cfg='s2twp.json'):
.replace('區域性性', '區域性') .replace('區域性性', '區域性')
.replace('下麵', '下面') .replace('下麵', '下面')
.replace('日志', '日誌') .replace('日志', '日誌')
.replace('真即時間', '真實時間')
for line in src)) for line in src))
print("convert %s to %s" % (src_path, dst_path)) print("convert %s to %s" % (src_path, dst_path))

2
ch7.md
View File

@ -293,7 +293,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
**图 7-6 读取偏差Alice 观察数据库处于不一致的状态** **图 7-6 读取偏差Alice 观察数据库处于不一致的状态**
Alice 在银行有 1000 美元的储蓄,分为两个账户,每个 500 美元。现在有一笔事务从她的一个账户转移了 100 美元到另一个账户。如果她非常不幸地在事务处理的过程中查看其账户余额列表,她可能会在收到付款之前先看到一个账户的余额(收款账户,余额仍为 500 美元),在发出转账之后再看到另一个账户的余额(付款账户,新余额为 400 美元)。对 Alice 来说,现在她的账户似乎总共只有 900 美元 —— 看起来有 100 美元已经凭空消失了。 Alice 在银行有 1000 美元的储蓄,分为两个账户,每个 500 美元。现在有一笔事务从她的一个账户转移了 100 美元到另一个账户。如果她非常不幸地在事务处理的过程中查看其账户余额列表,她可能会在收到付款之前先看到一个账户的余额(收款账户,余额仍为 500 美元),在发出转账之后再看到另一个账户的余额(付款账户,新余额为 400 美元)。对 Alice 来说,现在她的账户似乎总共只有 900 美元 —— 看起来有 100 美元已经凭空消失了。
这种异常被称为 **不可重复读nonrepeatable read****读取偏差read skew**:如果 Alice 在事务结束时再次读取账户 1 的余额她将看到与她之前的查询中看到的不同的值600 美元)。在读已提交的隔离条件下,**不可重复读** 被认为是可接受的Alice 看到的帐户余额确实在阅读时已经提交了。 这种异常被称为 **不可重复读nonrepeatable read****读取偏差read skew**:如果 Alice 在事务结束时再次读取账户 1 的余额她将看到与她之前的查询中看到的不同的值600 美元)。在读已提交的隔离条件下,**不可重复读** 被认为是可接受的Alice 看到的帐户余额确实在阅读时已经提交了。

View File

@ -24,9 +24,9 @@ Web 和越來越多的基於 HTTP/REST 的 API 使互動的請求 / 響應風格
一個批處理系統有大量的輸入資料,跑一個 **作業job** 來處理它,並生成一些輸出資料,這往往需要一段時間(從幾分鐘到幾天),所以通常不會有使用者等待作業完成。相反,批次作業通常會定期執行(例如,每天一次)。批處理作業的主要效能衡量標準通常是吞吐量(處理特定大小的輸入所需的時間)。本章中討論的就是批處理。 一個批處理系統有大量的輸入資料,跑一個 **作業job** 來處理它,並生成一些輸出資料,這往往需要一段時間(從幾分鐘到幾天),所以通常不會有使用者等待作業完成。相反,批次作業通常會定期執行(例如,每天一次)。批處理作業的主要效能衡量標準通常是吞吐量(處理特定大小的輸入所需的時間)。本章中討論的就是批處理。
* 流處理系統(準時系統) * 流處理系統(準時系統)
流處理介於線上和離線(批處理)之間,所以有時候被稱為 **準near-real-time** 或 **準線上nearline** 處理。像批處理系統一樣,流處理消費輸入併產生輸出(並不需要響應請求)。但是,流式作業在事件發生後不久就會對事件進行操作,而批處理作業則需等待固定的一組輸入資料。這種差異使流處理系統比起批處理系統具有更低的延遲。由於流處理基於批處理,我們將在 [第十一章](ch11.md) 討論它。 流處理介於線上和離線(批處理)之間,所以有時候被稱為 **準near-real-time** 或 **準線上nearline** 處理。像批處理系統一樣,流處理消費輸入併產生輸出(並不需要響應請求)。但是,流式作業在事件發生後不久就會對事件進行操作,而批處理作業則需等待固定的一組輸入資料。這種差異使流處理系統比起批處理系統具有更低的延遲。由於流處理基於批處理,我們將在 [第十一章](ch11.md) 討論它。
正如我們將在本章中看到的那樣批處理是構建可靠、可伸縮和可維護應用程式的重要組成部分。例如2004 年釋出的批處理演算法 Map-Reduce可能被過分熱情地被稱為 “造就 Google 大規模可伸縮性的演算法”【2】。隨後在各種開源資料系統中得到應用包括 Hadoop、CouchDB 和 MongoDB。 正如我們將在本章中看到的那樣批處理是構建可靠、可伸縮和可維護應用程式的重要組成部分。例如2004 年釋出的批處理演算法 Map-Reduce可能被過分熱情地被稱為 “造就 Google 大規模可伸縮性的演算法”【2】。隨後在各種開源資料系統中得到應用包括 Hadoop、CouchDB 和 MongoDB。
@ -120,7 +120,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
Ruby 指令碼在記憶體中儲存了一個 URL 的雜湊表,將每個 URL 對映到它出現的次數。Unix 管道沒有這樣的雜湊表,而是依賴於對 URL 列表的排序,在這個 URL 列表中,同一個 URL 的只是簡單地重複出現。 Ruby 指令碼在記憶體中儲存了一個 URL 的雜湊表,將每個 URL 對映到它出現的次數。Unix 管道沒有這樣的雜湊表,而是依賴於對 URL 列表的排序,在這個 URL 列表中,同一個 URL 的只是簡單地重複出現。
哪種方法更好?這取決於你有多少個不同的 URL。對於大多數中小型網站你可能可以為所有不同網址提供一個計數器假設我們使用 1GB 記憶體)。在此例中,作業的 **工作集**working set即作業需要隨機訪問的記憶體大小僅取決於不同 URL 的數量:如果日誌中只有單個 URL重複出現一百萬次則散列表所需的空間表就只有一個 URL 加上一個計數器的大小。當工作集足夠小時,記憶體散列表表現良好,甚至在效能較差的膝上型電腦上也可以正常工作。 哪種方法更好?這取決於你有多少個不同的 URL。對於大多數中小型網站你可能可以為所有不同網址提供一個計數器假設我們使用 1GB 記憶體)。在此例中,作業的 **工作集**working set即作業需要隨機訪問的記憶體大小僅取決於不同 URL 的數量:如果日誌中只有單個 URL重複出現一百萬次則散列表所需的空間表就只有一個 URL 加上一個計數器的大小。當工作集足夠小時,記憶體散列表表現良好,甚至在效能較差的筆記型電腦上也可以正常工作。
另一方面,如果作業的工作集大於可用記憶體,則排序方法的優點是可以高效地使用磁碟。這與我們在 “[SSTables 和 LSM 樹](ch3.md#SSTables和LSM樹)” 中討論過的原理是一樣的:資料塊可以在記憶體中排序並作為段檔案寫入磁碟,然後多個排序好的段可以合併為一個更大的排序檔案。歸併排序具有在磁碟上執行良好的順序訪問模式。(請記住,針對順序 I/O 進行最佳化是 [第三章](ch3.md) 中反覆出現的主題,相同的模式在此重現) 另一方面,如果作業的工作集大於可用記憶體,則排序方法的優點是可以高效地使用磁碟。這與我們在 “[SSTables 和 LSM 樹](ch3.md#SSTables和LSM樹)” 中討論過的原理是一樣的:資料塊可以在記憶體中排序並作為段檔案寫入磁碟,然後多個排序好的段可以合併為一個更大的排序檔案。歸併排序具有在磁碟上執行良好的順序訪問模式。(請記住,針對順序 I/O 進行最佳化是 [第三章](ch3.md) 中反覆出現的主題,相同的模式在此重現)
@ -278,7 +278,7 @@ Hadoop 的各種高階工具(如 Pig 【30】、Hive 【31】、Cascading 【3
我們在 [第二章](ch2.md) 中討論了資料模型和查詢語言的連線,但是我們還沒有深入探討連線是如何實現的。現在是我們再次撿起這條線索的時候了。 我們在 [第二章](ch2.md) 中討論了資料模型和查詢語言的連線,但是我們還沒有深入探討連線是如何實現的。現在是我們再次撿起這條線索的時候了。
在許多資料集中,一條記錄與另一條記錄存在關聯是很常見的:關係模型中的 **外鍵**,文件模型中的 **文件引用** 或圖模型中的 **邊**。當你需要同時訪問這一關聯的兩側(持有引用的記錄與被引用的記錄)時,連線就是必須的。正如 [第二章](ch2.md) 所討論的,非規範化可以減少對連線的需求,但通常無法將其完全移除 [^v]。 在許多資料集中,一條記錄與另一條記錄存在關聯是很常見的:關係模型中的 **外部索引鍵**,文件模型中的 **文件引用** 或圖模型中的 **邊**。當你需要同時訪問這一關聯的兩側(持有引用的記錄與被引用的記錄)時,連線就是必須的。正如 [第二章](ch2.md) 所討論的,非規範化可以減少對連線的需求,但通常無法將其完全移除 [^v]。
[^v]: 我們在本書中討論的連線通常是等值連線,即最常見的連線型別,其中記錄透過與其他記錄在特定欄位(例如 ID中具有 **相同值** 相關聯。有些資料庫支援更通用的連線型別,例如使用小於運算子而不是等號運算子,但是我們沒有地方來講這些東西。 [^v]: 我們在本書中討論的連線通常是等值連線,即最常見的連線型別,其中記錄透過與其他記錄在特定欄位(例如 ID中具有 **相同值** 相關聯。有些資料庫支援更通用的連線型別,例如使用小於運算子而不是等號運算子,但是我們沒有地方來講這些東西。
@ -426,7 +426,7 @@ Google 最初使用 MapReduce 是為其搜尋引擎建立索引,其實現為
#### 鍵值儲存作為批處理輸出 #### 鍵值儲存作為批處理輸出
搜尋索引只是批處理工作流可能輸出的一個例子。批處理的另一個常見用途是構建機器學習系統,例如分類器(比如垃圾郵件過濾器,異常檢測,影識別與推薦系統例如你可能認識的人你可能感興趣的產品或相關的搜尋【29】 搜尋索引只是批處理工作流可能輸出的一個例子。批處理的另一個常見用途是構建機器學習系統,例如分類器(比如垃圾郵件過濾器,異常檢測,影識別與推薦系統例如你可能認識的人你可能感興趣的產品或相關的搜尋【29】
這些批處理作業的輸出通常是某種資料庫:例如,可以透過給定使用者 ID 查詢該使用者推薦好友的資料庫,或者可以透過產品 ID 查詢相關產品的資料庫【45】。 這些批處理作業的輸出通常是某種資料庫:例如,可以透過給定使用者 ID 查詢該使用者推薦好友的資料庫,或者可以透過產品 ID 查詢相關產品的資料庫【45】。
@ -468,7 +468,7 @@ MapReduce 作業的輸出處理遵循同樣的原理。透過將輸入視為不
#### 儲存多樣性 #### 儲存多樣性
資料庫要求你根據特定的模型(例如關係或文件)來構造資料,而分散式檔案系統中的檔案只是位元組序列,可以使用任何資料模型和編碼來編寫。它們可能是資料庫記錄的集合,但同樣可以是文字、影、影片、感測器讀數、稀疏矩陣、特徵向量、基因組序列或任何其他型別的資料。 資料庫要求你根據特定的模型(例如關係或文件)來構造資料,而分散式檔案系統中的檔案只是位元組序列,可以使用任何資料模型和編碼來編寫。它們可能是資料庫記錄的集合,但同樣可以是文字、影、影片、感測器讀數、稀疏矩陣、特徵向量、基因組序列或任何其他型別的資料。
說白了Hadoop 開放了將資料不加區分地轉儲到 HDFS 的可能性允許後續再研究如何進一步處理【53】。相比之下在將資料匯入資料庫專有儲存格式之前MPP 資料庫通常需要對資料和查詢模式進行仔細的前期建模。 說白了Hadoop 開放了將資料不加區分地轉儲到 HDFS 的可能性允許後續再研究如何進一步處理【53】。相比之下在將資料匯入資料庫專有儲存格式之前MPP 資料庫通常需要對資料和查詢模式進行仔細的前期建模。
@ -484,7 +484,7 @@ MapReduce 作業的輸出處理遵循同樣的原理。透過將輸入視為不
MPP 資料庫是單體的緊密整合的軟體負責磁碟上的儲存佈局查詢計劃排程和執行。由於這些元件都可以針對資料庫的特定需求進行調整和最佳化因此整個系統可以在其設計針對的查詢型別上取得非常好的效能。而且SQL 查詢語言允許以優雅的語法表達查詢,而無需編寫程式碼,可以在業務分析師使用的視覺化工具(例如 Tableau中訪問到。 MPP 資料庫是單體的緊密整合的軟體負責磁碟上的儲存佈局查詢計劃排程和執行。由於這些元件都可以針對資料庫的特定需求進行調整和最佳化因此整個系統可以在其設計針對的查詢型別上取得非常好的效能。而且SQL 查詢語言允許以優雅的語法表達查詢,而無需編寫程式碼,可以在業務分析師使用的視覺化工具(例如 Tableau中訪問到。
另一方面,並非所有型別的處理都可以合理地表達為 SQL 查詢。例如,如果要構建機器學習和推薦系統,或者使用相關性排名模型的全文搜尋索引,或者執行影分析,則很可能需要更一般的資料處理模型。這些型別的處理通常是特別針對特定應用的(例如機器學習的特徵工程,機器翻譯的自然語言模型,欺詐預測的風險評估函式),因此它們不可避免地需要編寫程式碼,而不僅僅是查詢。 另一方面,並非所有型別的處理都可以合理地表達為 SQL 查詢。例如,如果要構建機器學習和推薦系統,或者使用相關性排名模型的全文搜尋索引,或者執行影分析,則很可能需要更一般的資料處理模型。這些型別的處理通常是特別針對特定應用的(例如機器學習的特徵工程,機器翻譯的自然語言模型,欺詐預測的風險評估函式),因此它們不可避免地需要編寫程式碼,而不僅僅是查詢。
MapReduce 使工程師能夠輕鬆地在大型資料集上執行自己的程式碼。如果你有 HDFS 和 MapReduce那麼你 **可以** 在它之上建立一個 SQL 查詢執行引擎,事實上這正是 Hive 專案所做的【31】。但是你也可以編寫許多其他形式的批處理這些批處理不必非要用 SQL 查詢表示。 MapReduce 使工程師能夠輕鬆地在大型資料集上執行自己的程式碼。如果你有 HDFS 和 MapReduce那麼你 **可以** 在它之上建立一個 SQL 查詢執行引擎,事實上這正是 Hive 專案所做的【31】。但是你也可以編寫許多其他形式的批處理這些批處理不必非要用 SQL 查詢表示。
@ -660,7 +660,7 @@ Spark、Flink 和 Tez 避免將中間狀態寫入 HDFS因此它們採取了
連線演算法的選擇可以對批處理作業的效能產生巨大影響,而無需理解和記住本章中討論的各種連線演算法。如果連線是以 **宣告式declarative** 的方式指定的,那這就這是可行的:應用只是簡單地說明哪些連線是必需的,查詢最佳化器決定如何最好地執行連線。我們以前在 “[資料查詢語言](ch2.md#資料查詢語言)” 中見過這個想法。 連線演算法的選擇可以對批處理作業的效能產生巨大影響,而無需理解和記住本章中討論的各種連線演算法。如果連線是以 **宣告式declarative** 的方式指定的,那這就這是可行的:應用只是簡單地說明哪些連線是必需的,查詢最佳化器決定如何最好地執行連線。我們以前在 “[資料查詢語言](ch2.md#資料查詢語言)” 中見過這個想法。
但 MapReduce 及其資料流後繼者在其他方面,與 SQL 的完全宣告式查詢模型有很大區別。MapReduce 是圍繞著回撥函式的概念建立的對於每條記錄或者一組記錄呼叫一個使用者定義的函式Mapper 或 Reducer並且該函式可以自由地呼叫任意程式碼來決定輸出什麼。這種方法的優點是可以基於大量已有庫的生態系統創作解析、自然語言分析、影分析以及執行數值或統計算法等。 但 MapReduce 及其資料流後繼者在其他方面,與 SQL 的完全宣告式查詢模型有很大區別。MapReduce 是圍繞著回撥函式的概念建立的對於每條記錄或者一組記錄呼叫一個使用者定義的函式Mapper 或 Reducer並且該函式可以自由地呼叫任意程式碼來決定輸出什麼。這種方法的優點是可以基於大量已有庫的生態系統創作解析、自然語言分析、影分析以及執行數值或統計算法等。
自由執行任意程式碼,長期以來都是傳統 MapReduce 批處理系統與 MPP 資料庫的區別所在(請參閱 “[Hadoop 與分散式資料庫的對比](#Hadoop與分散式資料庫的對比)” 一節)。雖然資料庫具有編寫使用者定義函式的功能,但是它們通常使用起來很麻煩,而且與大多數程式語言中廣泛使用的程式包管理器和依賴管理系統相容不佳(例如 Java 的 Maven、Javascript 的 npm 以及 Ruby 的 gems 自由執行任意程式碼,長期以來都是傳統 MapReduce 批處理系統與 MPP 資料庫的區別所在(請參閱 “[Hadoop 與分散式資料庫的對比](#Hadoop與分散式資料庫的對比)” 一節)。雖然資料庫具有編寫使用者定義函式的功能,但是它們通常使用起來很麻煩,而且與大多數程式語言中廣泛使用的程式包管理器和依賴管理系統相容不佳(例如 Java 的 Maven、Javascript 的 npm 以及 Ruby 的 gems

View File

@ -412,7 +412,7 @@ $$
剩下的就是討論一下你可以用流做什麼 —— 也就是說,你可以處理它。一般來說,有三種選項: 剩下的就是討論一下你可以用流做什麼 —— 也就是說,你可以處理它。一般來說,有三種選項:
1. 你可以將事件中的資料寫入資料庫、快取、搜尋索引或類似的儲存系統,然後能被其他客戶端查詢。如 [圖 11-5](../img/fig11-5.png) 所示,這是資料庫與系統其他部分所發生的變更保持同步的好方法 —— 特別是當流消費者是寫入資料庫的唯一客戶端時。如 “[批處理工作流的輸出](ch10.md#批處理工作流的輸出)” 中所討論的,它是寫入儲存系統的流等價物。 1. 你可以將事件中的資料寫入資料庫、快取、搜尋索引或類似的儲存系統,然後能被其他客戶端查詢。如 [圖 11-5](../img/fig11-5.png) 所示,這是資料庫與系統其他部分所發生的變更保持同步的好方法 —— 特別是當流消費者是寫入資料庫的唯一客戶端時。如 “[批處理工作流的輸出](ch10.md#批處理工作流的輸出)” 中所討論的,它是寫入儲存系統的流等價物。
2. 你能以某種方式將事件推送給使用者,例如傳送報警郵件或推送通知,或將事件流式傳輸到可時顯示的儀表板上。在這種情況下,人是流的最終消費者。 2. 你能以某種方式將事件推送給使用者,例如傳送報警郵件或推送通知,或將事件流式傳輸到可時顯示的儀表板上。在這種情況下,人是流的最終消費者。
3. 你可以處理一個或多個輸入流,併產生一個或多個輸出流。流可能會經過由幾個這樣的處理階段組成的流水線,最後再輸出(選項 1 或 2 3. 你可以處理一個或多個輸入流,併產生一個或多個輸出流。流可能會經過由幾個這樣的處理階段組成的流水線,最後再輸出(選項 1 或 2
在本章的剩餘部分中,我們將討論選項 3處理流以產生其他衍生流。處理這樣的流的程式碼片段被稱為 **運算元operator****作業job**。它與我們在 [第十章](ch10.md) 中討論過的 Unix 程序和 MapReduce 作業密切相關,資料流的模式是相似的:一個流處理器以只讀的方式使用輸入流,並將其輸出以僅追加的方式寫入一個不同的位置。 在本章的剩餘部分中,我們將討論選項 3處理流以產生其他衍生流。處理這樣的流的程式碼片段被稱為 **運算元operator****作業job**。它與我們在 [第十章](ch10.md) 中討論過的 Unix 程序和 MapReduce 作業密切相關,資料流的模式是相似的:一個流處理器以只讀的方式使用輸入流,並將其輸出以僅追加的方式寫入一個不同的位置。

View File

@ -375,7 +375,7 @@ Unix 和關係資料庫以非常不同的哲學來處理資訊管理問題。Uni
將這種程式設計模型擴充套件為:允許伺服器將狀態變更事件推送到客戶端的事件管道中,是非常自然的。因此,狀態變化可以透過 **端到端end-to-end** 的寫路徑流動:從一個裝置上的互動觸發狀態變更開始,經由事件日誌,並穿過幾個衍生資料系統與流處理器,一直到另一臺裝置上的使用者介面,而有人正在觀察使用者介面上的狀態變化。這些狀態變化能以相當低的延遲傳播 —— 比如說,在一秒內從一端到另一端。 將這種程式設計模型擴充套件為:允許伺服器將狀態變更事件推送到客戶端的事件管道中,是非常自然的。因此,狀態變化可以透過 **端到端end-to-end** 的寫路徑流動:從一個裝置上的互動觸發狀態變更開始,經由事件日誌,並穿過幾個衍生資料系統與流處理器,一直到另一臺裝置上的使用者介面,而有人正在觀察使用者介面上的狀態變化。這些狀態變化能以相當低的延遲傳播 —— 比如說,在一秒內從一端到另一端。
一些應用(如即時訊息傳遞與線上遊戲)已經具有這種 “時” 架構(在低延遲互動的意義上,不是在 “[響應時間保證](ch8.md#響應時間保證)” 中的意義上)。但我們為什麼不用這種方式構建所有的應用? 一些應用(如即時訊息傳遞與線上遊戲)已經具有這種 “時” 架構(在低延遲互動的意義上,不是在 “[響應時間保證](ch8.md#響應時間保證)” 中的意義上)。但我們為什麼不用這種方式構建所有的應用?
挑戰在於,關於無狀態客戶端和請求 / 響應互動的假設已經根深蒂固地植入在我們的資料庫、庫、框架以及協議之中。許多資料儲存支援讀取與寫入操作,為請求返回一個響應,但只有極少數提供訂閱變更的能力 —— 請求返回一個隨時間推移的響應流(請參閱 “[變更流的 API 支援](ch11.md#變更流的API支援)” )。 挑戰在於,關於無狀態客戶端和請求 / 響應互動的假設已經根深蒂固地植入在我們的資料庫、庫、框架以及協議之中。許多資料儲存支援讀取與寫入操作,為請求返回一個響應,但只有極少數提供訂閱變更的能力 —— 請求返回一個隨時間推移的響應流(請參閱 “[變更流的 API 支援](ch11.md#變更流的API支援)” )。
@ -661,7 +661,7 @@ ACID 事務通常既提供及時性(例如線性一致性)也提供完整性
儘管在仔細設計,測試,以及審查上做出很多努力,但 Bug 仍然會在不知不覺中產生。儘管它們很少,而且最終會被發現並被修復,但總會有那麼一段時間,這些 Bug 可能會損壞資料。 儘管在仔細設計,測試,以及審查上做出很多努力,但 Bug 仍然會在不知不覺中產生。儘管它們很少,而且最終會被發現並被修復,但總會有那麼一段時間,這些 Bug 可能會損壞資料。
而對於應用程式碼,我們不得不假設會有更多的錯誤,因為絕大多數應用的程式碼經受的評審與測試遠遠無法與資料庫的程式碼相比。許多應用甚至沒有正確使用資料庫提供的用於維持完整性的功能,例如外鍵或唯一性約束【36】。 而對於應用程式碼,我們不得不假設會有更多的錯誤,因為絕大多數應用的程式碼經受的評審與測試遠遠無法與資料庫的程式碼相比。許多應用甚至沒有正確使用資料庫提供的用於維持完整性的功能,例如外部索引鍵或唯一性約束【36】。
ACID 意義下的一致性(請參閱 “[一致性](ch7.md#一致性)”)基於這樣一種想法:資料庫以一致的狀態啟動,而事務將其從一個一致狀態轉換至另一個一致的狀態。因此,我們期望資料庫始終處於一致狀態。然而,只有當你假設事務沒有 Bug 時,這種想法才有意義。如果應用以某種錯誤的方式使用資料庫,例如,不安全地使用弱隔離等級,資料庫的完整性就無法得到保證。 ACID 意義下的一致性(請參閱 “[一致性](ch7.md#一致性)”)基於這樣一種想法:資料庫以一致的狀態啟動,而事務將其從一個一致狀態轉換至另一個一致的狀態。因此,我們期望資料庫始終處於一致狀態。然而,只有當你假設事務沒有 Bug 時,這種想法才有意義。如果應用以某種錯誤的方式使用資料庫,例如,不安全地使用弱隔離等級,資料庫的完整性就無法得到保證。
@ -782,7 +782,7 @@ ACID 意義下的一致性(請參閱 “[一致性](ch7.md#一致性)”)基
#### 監視 #### 監視
讓我們做一個思想實驗,嘗試用 **監視surveillance** 一詞替換 **資料data**再看看常見的短語是不是聽起來還那麼漂亮【93】。比如“在我們的監視驅動的組織中我們收集時監視流並將它們儲存在我們的監視倉庫中。我們的監視科學家使用高階分析和監視處理來獲得新的見解。” 讓我們做一個思想實驗,嘗試用 **監視surveillance** 一詞替換 **資料data**再看看常見的短語是不是聽起來還那麼漂亮【93】。比如“在我們的監視驅動的組織中我們收集時監視流並將它們儲存在我們的監視倉庫中。我們的監視科學家使用高階分析和監視處理來獲得新的見解。”
對於本書《設計監控密集型應用》而言,這個思想實驗是罕見的爭議性內容,但我認為需要激烈的言辭來強調這一點。在我們嘗試製造軟體 “吞噬世界” 的過程中【94】我們已經建立了世界上迄今為止所見過的最偉大的大規模監視基礎設施。我們正朝著萬物互聯邁進我們正在迅速走近這樣一個世界每個有人居住的空間至少包含一個帶網際網路連線的麥克風以智慧手機、智慧電視、語音控制助理裝置、嬰兒監視器甚至兒童玩具的形式存在並使用基於雲的語音識別。這些裝置中的很多都有著可怕的安全記錄【95】。 對於本書《設計監控密集型應用》而言,這個思想實驗是罕見的爭議性內容,但我認為需要激烈的言辭來強調這一點。在我們嘗試製造軟體 “吞噬世界” 的過程中【94】我們已經建立了世界上迄今為止所見過的最偉大的大規模監視基礎設施。我們正朝著萬物互聯邁進我們正在迅速走近這樣一個世界每個有人居住的空間至少包含一個帶網際網路連線的麥克風以智慧手機、智慧電視、語音控制助理裝置、嬰兒監視器甚至兒童玩具的形式存在並使用基於雲的語音識別。這些裝置中的很多都有著可怕的安全記錄【95】。

View File

@ -70,7 +70,7 @@
例如,[圖 2-1](../img/fig2-1.png) 展示瞭如何在關係模式中表示簡歷(一個 LinkedIn 簡介)。整個簡介可以透過一個唯一的識別符號 `user_id` 來標識。像 `first_name``last_name` 這樣的欄位每個使用者只出現一次,所以可以在 User 表上將其建模為列。但是,大多數人在職業生涯中擁有多於一份的工作,人們可能有不同樣的教育階段和任意數量的聯絡資訊。從使用者到這些專案之間存在一對多的關係,可以用多種方式來表示: 例如,[圖 2-1](../img/fig2-1.png) 展示瞭如何在關係模式中表示簡歷(一個 LinkedIn 簡介)。整個簡介可以透過一個唯一的識別符號 `user_id` 來標識。像 `first_name``last_name` 這樣的欄位每個使用者只出現一次,所以可以在 User 表上將其建模為列。但是,大多數人在職業生涯中擁有多於一份的工作,人們可能有不同樣的教育階段和任意數量的聯絡資訊。從使用者到這些專案之間存在一對多的關係,可以用多種方式來表示:
* 傳統 SQL 模型SQL1999 之前)中,最常見的規範化表示形式是將職位,教育和聯絡資訊放在單獨的表中,對 User 表提供外鍵引用,如 [圖 2-1](../img/fig2-1.png) 所示。 * 傳統 SQL 模型SQL1999 之前)中,最常見的規範化表示形式是將職位,教育和聯絡資訊放在單獨的表中,對 User 表提供外部索引鍵引用,如 [圖 2-1](../img/fig2-1.png) 所示。
* 後續的 SQL 標準增加了對結構化資料型別和 XML 資料的支援;這允許將多值資料儲存在單行內,並支援在這些文件內查詢和索引。這些功能在 OracleIBM DB2MS SQL Server 和 PostgreSQL 中都有不同程度的支援【6,7】。JSON 資料型別也得到多個數據庫的支援,包括 IBM DB2MySQL 和 PostgreSQL 【8】。 * 後續的 SQL 標準增加了對結構化資料型別和 XML 資料的支援;這允許將多值資料儲存在單行內,並支援在這些文件內查詢和索引。這些功能在 OracleIBM DB2MS SQL Server 和 PostgreSQL 中都有不同程度的支援【6,7】。JSON 資料型別也得到多個數據庫的支援,包括 IBM DB2MySQL 和 PostgreSQL 【8】。
* 第三種選擇是將職業,教育和聯絡資訊編碼為 JSON 或 XML 文件,將其儲存在資料庫的文字列中,並讓應用程式解析其結構和內容。這種配置下,通常不能使用資料庫來查詢該編碼列中的值。 * 第三種選擇是將職業,教育和聯絡資訊編碼為 JSON 或 XML 文件,將其儲存在資料庫的文字列中,並讓應用程式解析其結構和內容。這種配置下,通常不能使用資料庫來查詢該編碼列中的值。
@ -192,7 +192,7 @@ IMS 的設計中使用了一個相當簡單的資料模型,稱為 **層次模
CODASYL 模型是層次模型的推廣。在層次模型的樹結構中每條記錄只有一個父節點在網路模式中每條記錄可能有多個父節點。例如“Greater Seattle Area” 地區可能是一條記錄,每個居住在該地區的使用者都可以與之相關聯。這允許對多對一和多對多的關係進行建模。 CODASYL 模型是層次模型的推廣。在層次模型的樹結構中每條記錄只有一個父節點在網路模式中每條記錄可能有多個父節點。例如“Greater Seattle Area” 地區可能是一條記錄,每個居住在該地區的使用者都可以與之相關聯。這允許對多對一和多對多的關係進行建模。
網狀模型中記錄之間的連結不是外鍵,而更像程式語言中的指標(同時仍然儲存在磁碟上)。訪問記錄的唯一方法是跟隨從根記錄起沿這些鏈路所形成的路徑。這被稱為 **訪問路徑access path** 網狀模型中記錄之間的連結不是外部索引鍵,而更像程式語言中的指標(同時仍然儲存在磁碟上)。訪問記錄的唯一方法是跟隨從根記錄起沿這些鏈路所形成的路徑。這被稱為 **訪問路徑access path**
最簡單的情況下,訪問路徑類似遍歷連結串列:從列表頭開始,每次檢視一條記錄,直到找到所需的記錄。但在多對多關係的情況中,數條不同的路徑可以到達相同的記錄,網狀模型的程式設計師必須跟蹤這些不同的訪問路徑。 最簡單的情況下,訪問路徑類似遍歷連結串列:從列表頭開始,每次檢視一條記錄,直到找到所需的記錄。但在多對多關係的情況中,數條不同的路徑可以到達相同的記錄,網狀模型的程式設計師必須跟蹤這些不同的訪問路徑。
@ -202,9 +202,9 @@ CODASYL 中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資
#### 關係模型 #### 關係模型
相比之下,關係模型做的就是將所有的資料放在光天化日之下:一個 **關係(表)** 只是一個 **元組(行)** 的集合,僅此而已。如果你想讀取資料,它沒有迷宮似的巢狀結構,也沒有複雜的訪問路徑。你可以選中符合任意條件的行,讀取表中的任何或所有行。你可以透過指定某些列作為匹配關鍵字來讀取特定行。你可以在任何表中插入一個新的行,而不必擔心與其他表的外鍵關係 [^iv]。 相比之下,關係模型做的就是將所有的資料放在光天化日之下:一個 **關係(表)** 只是一個 **元組(行)** 的集合,僅此而已。如果你想讀取資料,它沒有迷宮似的巢狀結構,也沒有複雜的訪問路徑。你可以選中符合任意條件的行,讀取表中的任何或所有行。你可以透過指定某些列作為匹配關鍵字來讀取特定行。你可以在任何表中插入一個新的行,而不必擔心與其他表的外部索引鍵關係 [^iv]。
[^iv]: 外鍵約束允許對修改進行限制,但對於關係模型這並不是必選項。即使有約束,外鍵連線在查詢時執行,而在 CODASYL 中,連線在插入時高效完成。 [^iv]: 外部索引鍵約束允許對修改進行限制,但對於關係模型這並不是必選項。即使有約束,外部索引鍵連線在查詢時執行,而在 CODASYL 中,連線在插入時高效完成。
在關係資料庫中,查詢最佳化器自動決定查詢的哪些部分以哪個順序執行,以及使用哪些索引。這些選擇實際上是 “訪問路徑”,但最大的區別在於它們是由查詢最佳化器自動生成的,而不是由程式設計師生成,所以我們很少需要考慮它們。 在關係資料庫中,查詢最佳化器自動決定查詢的哪些部分以哪個順序執行,以及使用哪些索引。這些選擇實際上是 “訪問路徑”,但最大的區別在於它們是由查詢最佳化器自動生成的,而不是由程式設計師生成,所以我們很少需要考慮它們。
@ -216,7 +216,7 @@ CODASYL 中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資
在一個方面,文件資料庫還原為層次模型:在其父記錄中儲存巢狀記錄([圖 2-1](../img/fig2-1.png) 中的一對多關係,如 `positions``education` 和 `contact_info`),而不是在單獨的表中。 在一個方面,文件資料庫還原為層次模型:在其父記錄中儲存巢狀記錄([圖 2-1](../img/fig2-1.png) 中的一對多關係,如 `positions``education` 和 `contact_info`),而不是在單獨的表中。
但是,在表示多對一和多對多的關係時,關係資料庫和文件資料庫並沒有根本的不同:在這兩種情況下,相關專案都被一個唯一的識別符號引用,這個識別符號在關係模型中被稱為 **外鍵**,在文件模型中稱為 **文件引用**【9】。該識別符號在讀取時透過連線或後續查詢來解析。迄今為止文件資料庫沒有走 CODASYL 的老路。 但是,在表示多對一和多對多的關係時,關係資料庫和文件資料庫並沒有根本的不同:在這兩種情況下,相關專案都被一個唯一的識別符號引用,這個識別符號在關係模型中被稱為 **外部索引鍵**,在文件模型中稱為 **文件引用**【9】。該識別符號在讀取時透過連線或後續查詢來解析。迄今為止文件資料庫沒有走 CODASYL 的老路。
### 關係型資料庫與文件資料庫在今日的對比 ### 關係型資料庫與文件資料庫在今日的對比

View File

@ -432,15 +432,15 @@ Teradata、Vertica、SAP HANA 和 ParAccel 等資料倉庫供應商通常使用
通常情況下,事實被視為單獨的事件,因為這樣可以在以後分析中獲得最大的靈活性。但是,這意味著事實表可以變得非常大。像蘋果、沃爾瑪或 eBay 這樣的大企業在其資料倉庫中可能有幾十 PB 的交易歷史其中大部分儲存在事實表中【56】。 通常情況下,事實被視為單獨的事件,因為這樣可以在以後分析中獲得最大的靈活性。但是,這意味著事實表可以變得非常大。像蘋果、沃爾瑪或 eBay 這樣的大企業在其資料倉庫中可能有幾十 PB 的交易歷史其中大部分儲存在事實表中【56】。
事實表中的一些列是屬性,例如產品銷售的價格和從供應商那裡購買的成本(可以用來計算利潤率)。事實表中的其他列是對其他表(稱為維度表)的外鍵引用。由於事實表中的每一行都表示一個事件,因此這些維度代表事件發生的物件、內容、地點、時間、方式和原因。 事實表中的一些列是屬性,例如產品銷售的價格和從供應商那裡購買的成本(可以用來計算利潤率)。事實表中的其他列是對其他表(稱為維度表)的外部索引鍵引用。由於事實表中的每一行都表示一個事件,因此這些維度代表事件發生的物件、內容、地點、時間、方式和原因。
例如,在 [圖 3-9](../img/fig3-9.png) 中,其中一個維度是已售出的產品。`dim_product` 表中的每一行代表一種待售產品包括庫存單位SKU、產品描述、品牌名稱、類別、脂肪含量、包裝尺寸等。`fact_sales` 表中的每一行都使用外鍵表明在特定交易中銷售了什麼產品。(簡單起見,如果客戶一次購買了幾種不同的產品,則它們在事實表中被表示為單獨的行)。 例如,在 [圖 3-9](../img/fig3-9.png) 中,其中一個維度是已售出的產品。`dim_product` 表中的每一行代表一種待售產品包括庫存單位SKU、產品描述、品牌名稱、類別、脂肪含量、包裝尺寸等。`fact_sales` 表中的每一行都使用外部索引鍵表明在特定交易中銷售了什麼產品。(簡單起見,如果客戶一次購買了幾種不同的產品,則它們在事實表中被表示為單獨的行)。
甚至日期和時間也通常使用維度表來表示,因為這允許對日期的附加資訊(諸如公共假期)進行編碼,從而允許區分假期和非假期的銷售查詢。 甚至日期和時間也通常使用維度表來表示,因為這允許對日期的附加資訊(諸如公共假期)進行編碼,從而允許區分假期和非假期的銷售查詢。
“星型模式” 這個名字來源於這樣一個事實,即當我們對錶之間的關係進行視覺化時,事實表在中間,被維度表包圍;與這些表的連線就像星星的光芒。 “星型模式” 這個名字來源於這樣一個事實,即當我們對錶之間的關係進行視覺化時,事實表在中間,被維度表包圍;與這些表的連線就像星星的光芒。
這個模板的變體被稱為雪花模式,其中維度被進一步分解為子維度。例如,品牌和產品類別可能有單獨的表格,並且 `dim_product` 表格中的每一行都可以將品牌和類別作為外鍵引用,而不是將它們作為字串儲存在 `dim_product` 表格中。雪花模式比星形模式更規範化但是星形模式通常是首選因為分析師使用它更簡單【55】。 這個模板的變體被稱為雪花模式,其中維度被進一步分解為子維度。例如,品牌和產品類別可能有單獨的表格,並且 `dim_product` 表格中的每一行都可以將品牌和類別作為外部索引鍵引用,而不是將它們作為字串儲存在 `dim_product` 表格中。雪花模式比星形模式更規範化但是星形模式通常是首選因為分析師使用它更簡單【55】。
在典型的資料倉庫中,表格通常非常寬:事實表通常有 100 列以上有時甚至有數百列【51】。維度表也可以是非常寬的因為它們包括了所有可能與分析相關的元資料 —— 例如,`dim_store` 表可以包括在每個商店提供哪些服務的細節、它是否具有店內麵包房、店面面積、商店第一次開張的日期、最近一次改造的時間、離最近的高速公路的距離等等。 在典型的資料倉庫中,表格通常非常寬:事實表通常有 100 列以上有時甚至有數百列【51】。維度表也可以是非常寬的因為它們包括了所有可能與分析相關的元資料 —— 例如,`dim_store` 表可以包括在每個商店提供哪些服務的細節、它是否具有店內麵包房、店面面積、商店第一次開張的日期、最近一次改造的時間、離最近的高速公路的距離等等。
@ -573,7 +573,7 @@ WHERE product_sk = 31 AND store_sk = 3
**圖 3-12 資料立方的兩個維度,透過求和聚合** **圖 3-12 資料立方的兩個維度,透過求和聚合**
想象一下,現在每個事實都只有兩個維度表的外鍵 —— 在 [圖 3-12](../img/fig-3-12.png) 中分別是日期和產品。你現在可以繪製一個二維表格,一個軸線上是日期,另一個軸線上是產品。每個單元格包含具有該日期 - 產品組合的所有事實的屬性(例如 `net_price`)的聚合(例如 `SUM`)。然後,你可以沿著每行或每列應用相同的彙總,並獲得減少了一個維度的彙總(按產品的銷售額,無論日期,或者按日期的銷售額,無論產品)。 想象一下,現在每個事實都只有兩個維度表的外部索引鍵 —— 在 [圖 3-12](../img/fig-3-12.png) 中分別是日期和產品。你現在可以繪製一個二維表格,一個軸線上是日期,另一個軸線上是產品。每個單元格包含具有該日期 - 產品組合的所有事實的屬性(例如 `net_price`)的聚合(例如 `SUM`)。然後,你可以沿著每行或每列應用相同的彙總,並獲得減少了一個維度的彙總(按產品的銷售額,無論日期,或者按日期的銷售額,無論產品)。
一般來說,事實往往有兩個以上的維度。在圖 3-9 中有五個維度:日期、產品、商店、促銷和客戶。要想象一個五維超立方體是什麼樣子是很困難的,但是原理是一樣的:每個單元格都包含特定日期 - 產品 - 商店 - 促銷 - 客戶組合的銷售額。這些值可以在每個維度上求和彙總。 一般來說,事實往往有兩個以上的維度。在圖 3-9 中有五個維度:日期、產品、商店、促銷和客戶。要想象一個五維超立方體是什麼樣子是很困難的,但是原理是一樣的:每個單元格都包含特定日期 - 產品 - 商店 - 促銷 - 客戶組合的銷售額。這些值可以在每個維度上求和彙總。

View File

@ -372,7 +372,7 @@ Avro 為靜態型別程式語言提供了可選的程式碼生成功能,但是
當你需要透過網路進行程序間的通訊時,安排該通訊的方式有幾種。最常見的安排是有兩個角色:客戶端和伺服器。伺服器透過網路公開 API並且客戶端可以連線到伺服器以向該 API 發出請求。伺服器公開的 API 被稱為服務。 當你需要透過網路進行程序間的通訊時,安排該通訊的方式有幾種。最常見的安排是有兩個角色:客戶端和伺服器。伺服器透過網路公開 API並且客戶端可以連線到伺服器以向該 API 發出請求。伺服器公開的 API 被稱為服務。
Web 以這種方式工作客戶Web 瀏覽器)向 Web 伺服器發出請求,透過 GET 請求下載 HTML、CSS、JavaScript、影等,並透過 POST 請求提交資料到伺服器。API 包含一組標準的協議和資料格式HTTP、URL、SSL/TLS、HTML 等)。由於網路瀏覽器、網路伺服器和網站作者大多同意這些標準,你可以使用任何網路瀏覽器訪問任何網站(至少在理論上!)。 Web 以這種方式工作客戶Web 瀏覽器)向 Web 伺服器發出請求,透過 GET 請求下載 HTML、CSS、JavaScript、影等,並透過 POST 請求提交資料到伺服器。API 包含一組標準的協議和資料格式HTTP、URL、SSL/TLS、HTML 等)。由於網路瀏覽器、網路伺服器和網站作者大多同意這些標準,你可以使用任何網路瀏覽器訪問任何網站(至少在理論上!)。
Web 瀏覽器不是唯一的客戶端型別。例如,在移動裝置或桌面計算機上執行的本地應用程式也可以向伺服器發出網路請求,並且在 Web 瀏覽器內執行的客戶端 JavaScript 應用程式可以使用 XMLHttpRequest 成為 HTTP 客戶端(該技術被稱為 Ajax 【30】。在這種情況下伺服器的響應通常不是用於顯示給人的 HTML而是便於客戶端應用程式程式碼進一步處理的編碼資料如 JSON。儘管 HTTP 可能被用作傳輸協議,但頂層實現的 API 是特定於應用程式的,客戶端和伺服器需要就該 API 的細節達成一致。 Web 瀏覽器不是唯一的客戶端型別。例如,在移動裝置或桌面計算機上執行的本地應用程式也可以向伺服器發出網路請求,並且在 Web 瀏覽器內執行的客戶端 JavaScript 應用程式可以使用 XMLHttpRequest 成為 HTTP 客戶端(該技術被稱為 Ajax 【30】。在這種情況下伺服器的響應通常不是用於顯示給人的 HTML而是便於客戶端應用程式程式碼進一步處理的編碼資料如 JSON。儘管 HTTP 可能被用作傳輸協議,但頂層實現的 API 是特定於應用程式的,客戶端和伺服器需要就該 API 的細節達成一致。

View File

@ -336,7 +336,7 @@
多主複製的另一種適用場景是:應用程式在斷網之後仍然需要繼續工作。 多主複製的另一種適用場景是:應用程式在斷網之後仍然需要繼續工作。
例如,考慮手機,膝上型電腦和其他裝置上的日曆應用。無論裝置目前是否有網際網路連線,你需要能隨時檢視你的會議(發出讀取請求),輸入新的會議(發出寫入請求)。如果在離線狀態下進行任何更改,則裝置下次上線時,需要與伺服器和其他裝置同步。 例如,考慮手機,筆記型電腦和其他裝置上的日曆應用。無論裝置目前是否有網際網路連線,你需要能隨時檢視你的會議(發出讀取請求),輸入新的會議(發出寫入請求)。如果在離線狀態下進行任何更改,則裝置下次上線時,需要與伺服器和其他裝置同步。
在這種情況下,每個裝置都有一個充當主庫的本地資料庫(它接受寫請求),並且在所有裝置上的日曆副本之間同步時,存在非同步的多主複製過程。複製延遲可能是幾小時甚至幾天,具體取決於何時可以訪問網際網路。 在這種情況下,每個裝置都有一個充當主庫的本地資料庫(它接受寫請求),並且在所有裝置上的日曆副本之間同步時,存在非同步的多主複製過程。複製延遲可能是幾小時甚至幾天,具體取決於何時可以訪問網際網路。
@ -346,7 +346,7 @@
#### 協同編輯 #### 協同編輯
時協作編輯應用程式允許多個人同時編輯文件。例如Etherpad 【30】和 Google Docs 【31】允許多人同時編輯文字文件或電子表格該演算法在 “[自動衝突解決](#自動衝突解決)” 中簡要討論。我們通常不會將協作式編輯視為資料庫複製問題但它與前面提到的離線編輯用例有許多相似之處。當一個使用者編輯文件時所做的更改將立即應用到其本地副本Web 瀏覽器或客戶端應用程式中的文件狀態),並非同步複製到伺服器和編輯同一文件的任何其他使用者。 時協作編輯應用程式允許多個人同時編輯文件。例如Etherpad 【30】和 Google Docs 【31】允許多人同時編輯文字文件或電子表格該演算法在 “[自動衝突解決](#自動衝突解決)” 中簡要討論。我們通常不會將協作式編輯視為資料庫複製問題但它與前面提到的離線編輯用例有許多相似之處。當一個使用者編輯文件時所做的更改將立即應用到其本地副本Web 瀏覽器或客戶端應用程式中的文件狀態),並非同步複製到伺服器和編輯同一文件的任何其他使用者。
如果要保證不會發生編輯衝突,則應用程式必須先取得文件的鎖定,然後使用者才能對其進行編輯。如果另一個使用者想要編輯同一個文件,他們首先必須等到第一個使用者提交修改並釋放鎖定。這種協作模式相當於主從複製模型下在主節點上執行事務操作。 如果要保證不會發生編輯衝突,則應用程式必須先取得文件的鎖定,然後使用者才能對其進行編輯。如果另一個使用者想要編輯同一個文件,他們首先必須等到第一個使用者提交修改並釋放鎖定。這種協作模式相當於主從複製模型下在主節點上執行事務操作。

View File

@ -79,7 +79,7 @@ ACID 原子性的定義特徵是:**能夠在錯誤時中止事務,丟棄該
ACID 一致性的概念是,**對資料的一組特定約束必須始終成立**,即 **不變式invariants**。例如,在會計系統中,所有賬戶整體上必須借貸相抵。如果一個事務開始於一個滿足這些不變式的有效資料庫,且在事務處理期間的任何寫入操作都保持這種有效性,那麼可以確定,不變式總是滿足的。 ACID 一致性的概念是,**對資料的一組特定約束必須始終成立**,即 **不變式invariants**。例如,在會計系統中,所有賬戶整體上必須借貸相抵。如果一個事務開始於一個滿足這些不變式的有效資料庫,且在事務處理期間的任何寫入操作都保持這種有效性,那麼可以確定,不變式總是滿足的。
但是,一致性的這種概念取決於應用程式對不變式的理解,應用程式負責正確定義它的事務,並保持一致性。這並不是資料庫可以保證的事情:如果你寫入違反不變式的髒資料,資料庫也無法阻止你(一些特定型別的不變式可以由資料庫檢查,例如外鍵約束或唯一約束,但是一般來說,是應用程式來定義什麼樣的資料是有效的,什麼樣是無效的。—— 資料庫只管儲存)。 但是,一致性的這種概念取決於應用程式對不變式的理解,應用程式負責正確定義它的事務,並保持一致性。這並不是資料庫可以保證的事情:如果你寫入違反不變式的髒資料,資料庫也無法阻止你(一些特定型別的不變式可以由資料庫檢查,例如外部索引鍵約束或唯一約束,但是一般來說,是應用程式來定義什麼樣的資料是有效的,什麼樣是無效的。—— 資料庫只管儲存)。
原子性、隔離性和永續性是資料庫的屬性,而一致性(在 ACID 意義上)是應用程式的屬性。應用可能依賴資料庫的原子性和隔離性來實現一致性,但這並不僅取決於資料庫。因此,字母 C 不屬於 ACID [^i]。 原子性、隔離性和永續性是資料庫的屬性,而一致性(在 ACID 意義上)是應用程式的屬性。應用可能依賴資料庫的原子性和隔離性來實現一致性,但這並不僅取決於資料庫。因此,字母 C 不屬於 ACID [^i]。
@ -188,7 +188,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
有一些場景中,單物件插入,更新和刪除是足夠的。但是許多其他場景需要協調寫入幾個不同的物件: 有一些場景中,單物件插入,更新和刪除是足夠的。但是許多其他場景需要協調寫入幾個不同的物件:
* 在關係資料模型中,一個表中的行通常具有對另一個表中的行的外鍵引用。(類似的是,在一個圖資料模型中,一個頂點有著到其他頂點的邊)。多物件事務使你確保這些引用始終有效:當插入幾個相互引用的記錄時,外鍵必須是正確的和最新的,不然資料就沒有意義。 * 在關係資料模型中,一個表中的行通常具有對另一個表中的行的外部索引鍵引用。(類似的是,在一個圖資料模型中,一個頂點有著到其他頂點的邊)。多物件事務使你確保這些引用始終有效:當插入幾個相互引用的記錄時,外部索引鍵必須是正確的和最新的,不然資料就沒有意義。
* 在文件資料模型中,需要一起更新的欄位通常在同一個文件中,這被視為單個物件 —— 更新單個文件時不需要多物件事務。但是,缺乏連線功能的文件資料庫會鼓勵非規範化(請參閱 “[關係型資料庫與文件資料庫在今日的對比](ch2.md#關係型資料庫與文件資料庫在今日的對比)”)。當需要更新非規範化的資訊時,如 [圖 7-2](../img/fig7-2.png) 所示,需要一次更新多個文件。事務在這種情況下非常有用,可以防止非規範化的資料不同步。 * 在文件資料模型中,需要一起更新的欄位通常在同一個文件中,這被視為單個物件 —— 更新單個文件時不需要多物件事務。但是,缺乏連線功能的文件資料庫會鼓勵非規範化(請參閱 “[關係型資料庫與文件資料庫在今日的對比](ch2.md#關係型資料庫與文件資料庫在今日的對比)”)。當需要更新非規範化的資訊時,如 [圖 7-2](../img/fig7-2.png) 所示,需要一次更新多個文件。事務在這種情況下非常有用,可以防止非規範化的資料不同步。
* 在具有次級索引的資料庫中(除了純粹的鍵值儲存以外幾乎都有),每次更改值時都需要更新索引。從事務角度來看,這些索引是不同的資料庫物件:例如,如果沒有事務隔離性,記錄可能出現在一個索引中,但沒有出現在另一個索引中,因為第二個索引的更新還沒有發生。 * 在具有次級索引的資料庫中(除了純粹的鍵值儲存以外幾乎都有),每次更改值時都需要更新索引。從事務角度來看,這些索引是不同的資料庫物件:例如,如果沒有事務隔離性,記錄可能出現在一個索引中,但沒有出現在另一個索引中,因為第二個索引的更新還沒有發生。
@ -293,7 +293,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
**圖 7-6 讀取偏差Alice 觀察資料庫處於不一致的狀態** **圖 7-6 讀取偏差Alice 觀察資料庫處於不一致的狀態**
Alice 在銀行有 1000 美元的儲蓄,分為兩個賬戶,每個 500 美元。現在有一筆事務從她的一個賬戶轉移了 100 美元到另一個賬戶。如果她非常不幸地在事務處理的過程中檢視其賬戶餘額列表,她可能會在收到付款之前先看到一個賬戶的餘額(收款賬戶,餘額仍為 500 美元),在發出轉賬之後再看到另一個賬戶的餘額(付款賬戶,新餘額為 400 美元)。對 Alice 來說,現在她的賬戶似乎總共只有 900 美元 —— 看起來有 100 美元已經憑空消失了。 Alice 在銀行有 1000 美元的儲蓄,分為兩個賬戶,每個 500 美元。現在有一筆事務從她的一個賬戶轉移了 100 美元到另一個賬戶。如果她非常不幸地在事務處理的過程中檢視其賬戶餘額列表,她可能會在收到付款之前先看到一個賬戶的餘額(收款賬戶,餘額仍為 500 美元),在發出轉賬之後再看到另一個賬戶的餘額(付款賬戶,新餘額為 400 美元)。對 Alice 來說,現在她的賬戶似乎總共只有 900 美元 —— 看起來有 100 美元已經憑空消失了。
這種異常被稱為 **不可重複讀nonrepeatable read****讀取偏差read skew**:如果 Alice 在事務結束時再次讀取賬戶 1 的餘額她將看到與她之前的查詢中看到的不同的值600 美元)。在讀已提交的隔離條件下,**不可重複讀** 被認為是可接受的Alice 看到的帳戶餘額確實在閱讀時已經提交了。 這種異常被稱為 **不可重複讀nonrepeatable read****讀取偏差read skew**:如果 Alice 在事務結束時再次讀取賬戶 1 的餘額她將看到與她之前的查詢中看到的不同的值600 美元)。在讀已提交的隔離條件下,**不可重複讀** 被認為是可接受的Alice 看到的帳戶餘額確實在閱讀時已經提交了。
@ -488,7 +488,7 @@ UPDATE wiki_pages SET content = '新內容'
* 由於涉及多個物件,單物件的原子操作不起作用。 * 由於涉及多個物件,單物件的原子操作不起作用。
* 不幸的是,在一些快照隔離的實現中,自動檢測丟失更新對此並沒有幫助。在 PostgreSQL 的可重複讀MySQL/InnoDB 的可重複讀Oracle 可序列化或 SQL Server 的快照隔離級別中都不會自動檢測寫入偏差【23】。自動防止寫入偏差需要真正的可序列化隔離請參閱 “[可序列化](#可序列化)”)。 * 不幸的是,在一些快照隔離的實現中,自動檢測丟失更新對此並沒有幫助。在 PostgreSQL 的可重複讀MySQL/InnoDB 的可重複讀Oracle 可序列化或 SQL Server 的快照隔離級別中都不會自動檢測寫入偏差【23】。自動防止寫入偏差需要真正的可序列化隔離請參閱 “[可序列化](#可序列化)”)。
* 某些資料庫允許配置約束,然後由資料庫強制執行(例如,唯一性,外鍵約束或特定值限制。但是為了指定至少有一名醫生必須線上需要一個涉及多個物件的約束。大多數資料庫沒有內建對這種約束的支援但是你可以使用觸發器或者物化檢視來實現它們這取決於不同的資料庫【42】。 * 某些資料庫允許配置約束,然後由資料庫強制執行(例如,唯一性,外部索引鍵約束或特定值限制。但是為了指定至少有一名醫生必須線上需要一個涉及多個物件的約束。大多數資料庫沒有內建對這種約束的支援但是你可以使用觸發器或者物化檢視來實現它們這取決於不同的資料庫【42】。
* 如果無法使用可序列化的隔離級別,則此情況下的次優選項可能是顯式鎖定事務所依賴的行。在例子中,你可以寫下如下的程式碼: * 如果無法使用可序列化的隔離級別,則此情況下的次優選項可能是顯式鎖定事務所依賴的行。在例子中,你可以寫下如下的程式碼:
```sql ```sql

View File

@ -264,7 +264,7 @@
日曆時鐘是你直觀地瞭解時鐘的依據:它根據某個日曆(也稱為 **掛鐘時間**,即 wall-clock time返回當前日期和時間。例如Linux 上的 `clock_gettime(CLOCK_REALTIME)`[^v] 和 Java 中的 `System.currentTimeMillis()` 返回自 epochUTC 時間 1970 年 1 月 1 日午夜以來的秒數或毫秒根據公曆Gregorian日曆不包括閏秒。有些系統使用其他日期作為參考點。 日曆時鐘是你直觀地瞭解時鐘的依據:它根據某個日曆(也稱為 **掛鐘時間**,即 wall-clock time返回當前日期和時間。例如Linux 上的 `clock_gettime(CLOCK_REALTIME)`[^v] 和 Java 中的 `System.currentTimeMillis()` 返回自 epochUTC 時間 1970 年 1 月 1 日午夜以來的秒數或毫秒根據公曆Gregorian日曆不包括閏秒。有些系統使用其他日期作為參考點。
[^v]: 雖然該時鐘被稱為實時時鐘,但它與實時作業系統無關,如 “[響應時間保證](#響應時間保證)” 中所述。 [^v]: 雖然該時鐘被稱為即時時鐘,但它與即時作業系統無關,如 “[響應時間保證](#響應時間保證)” 中所述。
日曆時鐘通常與 NTP 同步,這意味著來自一臺機器的時間戳(理想情況下)與另一臺機器上的時間戳相同。但是如下節所述,日曆時鐘也具有各種各樣的奇特之處。特別是,如果本地時鐘在 NTP 伺服器之前太遠則它可能會被強制重置看上去好像跳回了先前的時間點。這些跳躍以及他們經常忽略閏秒的事實使日曆時鐘不能用於測量經過時間elapsed time【38】。 日曆時鐘通常與 NTP 同步,這意味著來自一臺機器的時間戳(理想情況下)與另一臺機器上的時間戳相同。但是如下節所述,日曆時鐘也具有各種各樣的奇特之處。特別是,如果本地時鐘在 NTP 伺服器之前太遠則它可能會被強制重置看上去好像跳回了先前的時間點。這些跳躍以及他們經常忽略閏秒的事實使日曆時鐘不能用於測量經過時間elapsed time【38】。
@ -398,8 +398,8 @@ while (true) {
假設一個執行緒可能會暫停很長時間,這是瘋了嗎?不幸的是,這種情況發生的原因有很多種: 假設一個執行緒可能會暫停很長時間,這是瘋了嗎?不幸的是,這種情況發生的原因有很多種:
* 許多程式語言執行時(如 Java 虛擬機器都有一個垃圾收集器GC偶爾需要停止所有正在執行的執行緒。這些 “**停止所有處理stop-the-world**”GC 暫停有時會持續幾分鐘【64】甚至像 HotSpot JVM 的 CMS 這樣的所謂的 “並行” 垃圾收集器也不能完全與應用程式程式碼並行執行它需要不時地停止所有處理【65】。儘管通常可以透過改變分配模式或調整 GC 設定來減少暫停【66】但是如果我們想要提供健壯的保證就必須假設最壞的情況發生。 * 許多程式語言執行時(如 Java 虛擬機器都有一個垃圾收集器GC偶爾需要停止所有正在執行的執行緒。這些 “**停止所有處理stop-the-world**”GC 暫停有時會持續幾分鐘【64】甚至像 HotSpot JVM 的 CMS 這樣的所謂的 “並行” 垃圾收集器也不能完全與應用程式程式碼並行執行它需要不時地停止所有處理【65】。儘管通常可以透過改變分配模式或調整 GC 設定來減少暫停【66】但是如果我們想要提供健壯的保證就必須假設最壞的情況發生。
* 在虛擬化環境中,可以 **掛起suspend** 虛擬機器(暫停執行所有程序並將記憶體內容儲存到磁碟)並恢復(恢復記憶體內容並繼續執行)。這個暫停可以在程序執行的任何時候發生,並且可以持續任意長的時間。這個功能有時用於虛擬機器從一個主機到另一個主機的時遷移而不需要重新啟動在這種情況下暫停的長度取決於程序寫入記憶體的速率【67】。 * 在虛擬化環境中,可以 **掛起suspend** 虛擬機器(暫停執行所有程序並將記憶體內容儲存到磁碟)並恢復(恢復記憶體內容並繼續執行)。這個暫停可以在程序執行的任何時候發生,並且可以持續任意長的時間。這個功能有時用於虛擬機器從一個主機到另一個主機的時遷移而不需要重新啟動在這種情況下暫停的長度取決於程序寫入記憶體的速率【67】。
* 在終端使用者的裝置(如膝上型電腦)上,執行也可能被暫停並隨意恢復,例如當用戶關閉膝上型電腦的蓋子時。 * 在終端使用者的裝置(如筆記型電腦)上,執行也可能被暫停並隨意恢復,例如當用戶關閉筆記型電腦的蓋子時。
* 當作業系統上下文切換到另一個執行緒時,或者當管理程式切換到另一個虛擬機器時(在虛擬機器中執行時),當前正在執行的執行緒可能在程式碼中的任意點處暫停。在虛擬機器的情況下,在其他虛擬機器中花費的 CPU 時間被稱為 **竊取時間steal time**。如果機器處於沉重的負載下(即,如果等待執行的執行緒佇列很長),暫停的執行緒再次執行可能需要一些時間。 * 當作業系統上下文切換到另一個執行緒時,或者當管理程式切換到另一個虛擬機器時(在虛擬機器中執行時),當前正在執行的執行緒可能在程式碼中的任意點處暫停。在虛擬機器的情況下,在其他虛擬機器中花費的 CPU 時間被稱為 **竊取時間steal time**。如果機器處於沉重的負載下(即,如果等待執行的執行緒佇列很長),暫停的執行緒再次執行可能需要一些時間。
* 如果應用程式執行同步磁碟訪問,則執行緒可能暫停,等待緩慢的磁碟 I/O 操作完成【68】。在許多語言中即使程式碼沒有包含檔案訪問磁碟訪問也可能出乎意料地發生 —— 例如Java 類載入器在第一次使用時惰性載入類檔案這可能在程式執行過程中隨時發生。I/O 暫停和 GC 暫停甚至可能合謀組合它們的延遲【69】。如果磁碟實際上是一個網路檔案系統或網路塊裝置如亞馬遜的 EBSI/O 延遲進一步受到網路延遲變化的影響【29】。 * 如果應用程式執行同步磁碟訪問,則執行緒可能暫停,等待緩慢的磁碟 I/O 操作完成【68】。在許多語言中即使程式碼沒有包含檔案訪問磁碟訪問也可能出乎意料地發生 —— 例如Java 類載入器在第一次使用時惰性載入類檔案這可能在程式執行過程中隨時發生。I/O 暫停和 GC 暫停甚至可能合謀組合它們的延遲【69】。如果磁碟實際上是一個網路檔案系統或網路塊裝置如亞馬遜的 EBSI/O 延遲進一步受到網路延遲變化的影響【29】。
* 如果作業系統配置為允許交換到磁碟(頁面交換),則簡單的記憶體訪問可能導致 **頁面錯誤page fault**,要求將磁碟中的頁面裝入記憶體。當這個緩慢的 I/O 操作發生時,執行緒暫停。如果記憶體壓力很高,則可能需要將另一個頁面換出到磁碟。在極端情況下,作業系統可能花費大部分時間將頁面交換到記憶體中,而實際上完成的工作很少(這被稱為 **抖動**,即 thrashing。為了避免這個問題通常在伺服器機器上停用頁面排程如果你寧願幹掉一個程序來釋放記憶體也不願意冒抖動風險 * 如果作業系統配置為允許交換到磁碟(頁面交換),則簡單的記憶體訪問可能導致 **頁面錯誤page fault**,要求將磁碟中的頁面裝入記憶體。當這個緩慢的 I/O 操作發生時,執行緒暫停。如果記憶體壓力很高,則可能需要將另一個頁面換出到磁碟。在極端情況下,作業系統可能花費大部分時間將頁面交換到記憶體中,而實際上完成的工作很少(這被稱為 **抖動**,即 thrashing。為了避免這個問題通常在伺服器機器上停用頁面排程如果你寧願幹掉一個程序來釋放記憶體也不願意冒抖動風險
@ -415,23 +415,23 @@ while (true) {
在許多程式語言和作業系統中,執行緒和程序可能暫停一段無限制的時間,正如討論的那樣。如果你足夠努力,導致暫停的原因是 **可以** 消除的。 在許多程式語言和作業系統中,執行緒和程序可能暫停一段無限制的時間,正如討論的那樣。如果你足夠努力,導致暫停的原因是 **可以** 消除的。
某些軟體的執行環境要求很高,不能在特定時間內響應可能會導致嚴重的損失:控制飛機、火箭、機器人、汽車和其他物體的計算機必須對其感測器輸入做出快速而可預測的響應。在這些系統中,軟體必須有一個特定的 **截止時間deadline**,如果截止時間不滿足,可能會導致整個系統的故障。這就是所謂的 **硬hard real-time** 系統。 某些軟體的執行環境要求很高,不能在特定時間內響應可能會導致嚴重的損失:控制飛機、火箭、機器人、汽車和其他物體的計算機必須對其感測器輸入做出快速而可預測的響應。在這些系統中,軟體必須有一個特定的 **截止時間deadline**,如果截止時間不滿足,可能會導致整個系統的故障。這就是所謂的 **硬hard real-time** 系統。
> #### 時是真的嗎? > #### 時是真的嗎?
> >
> 在嵌入式系統中,時是指系統經過精心設計和測試,以滿足所有情況下的特定時間保證。這個含義與 Web 上對時術語的模糊使用相反,後者描述了伺服器將資料推送到客戶端以及沒有嚴格的響應時間限制的流處理(見 [第十一章](ch11.md))。 > 在嵌入式系統中,時是指系統經過精心設計和測試,以滿足所有情況下的特定時間保證。這個含義與 Web 上對時術語的模糊使用相反,後者描述了伺服器將資料推送到客戶端以及沒有嚴格的響應時間限制的流處理(見 [第十一章](ch11.md))。
例如,如果車載感測器檢測到當前正在經歷碰撞,你肯定不希望安全氣囊釋放系統因為 GC 暫停而延遲彈出。 例如,如果車載感測器檢測到當前正在經歷碰撞,你肯定不希望安全氣囊釋放系統因為 GC 暫停而延遲彈出。
在系統中提供 **實時保證** 需要各級軟體棧的支援:一個實時作業系統RTOS允許在指定的時間間隔內保證 CPU 時間的分配。庫函式必須申明最壞情況下的執行時間;動態記憶體分配可能受到限制或完全不允許(時垃圾收集器存在,但是應用程式仍然必須確保它不會給 GC 太多的負擔);必須進行大量的測試和測量,以確保達到保證。 在系統中提供 **即時保證** 需要各級軟體棧的支援:一個即時作業系統RTOS允許在指定的時間間隔內保證 CPU 時間的分配。庫函式必須申明最壞情況下的執行時間;動態記憶體分配可能受到限制或完全不允許(時垃圾收集器存在,但是應用程式仍然必須確保它不會給 GC 太多的負擔);必須進行大量的測試和測量,以確保達到保證。
所有這些都需要大量額外的工作,嚴重限制了可以使用的程式語言、庫和工具的範圍(因為大多數語言和工具不提供實時保證)。由於這些原因,開發實時系統非常昂貴,並且它們通常用於安全關鍵的嵌入式裝置。而且,“**實時**” 與 “**高效能**” 不一樣 —— 事實上,實時系統可能具有較低的吞吐量,因為他們必須讓及時響應的優先順序高於一切(另請參閱 “[延遲和資源利用](#延遲和資源利用)”)。 所有這些都需要大量額外的工作,嚴重限制了可以使用的程式語言、庫和工具的範圍(因為大多數語言和工具不提供即時保證)。由於這些原因,開發即時系統非常昂貴,並且它們通常用於安全關鍵的嵌入式裝置。而且,“**即時**” 與 “**高效能**” 不一樣 —— 事實上,即時系統可能具有較低的吞吐量,因為他們必須讓及時響應的優先順序高於一切(另請參閱 “[延遲和資源利用](#延遲和資源利用)”)。
對於大多數伺服器端資料處理系統來說,實時保證是不經濟或不合適的。因此,這些系統必須承受在非實時環境中執行的暫停和時鐘不穩定性。 對於大多數伺服器端資料處理系統來說,即時保證是不經濟或不合適的。因此,這些系統必須承受在非即時環境中執行的暫停和時鐘不穩定性。
#### 限制垃圾收集的影響 #### 限制垃圾收集的影響
程序暫停的負面影響可以在不訴諸昂貴的時排程保證的情況下得到緩解。語言執行時在計劃垃圾回收時具有一定的靈活性,因為它們可以跟蹤物件分配的速度和隨著時間的推移剩餘的空閒記憶體。 程序暫停的負面影響可以在不訴諸昂貴的時排程保證的情況下得到緩解。語言執行時在計劃垃圾回收時具有一定的靈活性,因為它們可以跟蹤物件分配的速度和隨著時間的推移剩餘的空閒記憶體。
一個新興的想法是將 GC 暫停視為一個節點的短暫計劃中斷,並在這個節點收集其垃圾的同時,讓其他節點處理來自客戶端的請求。如果執行時可以警告應用程式一個節點很快需要 GC 暫停,那麼應用程式可以停止向該節點發送新的請求,等待它完成處理未完成的請求,然後在沒有請求正在進行時執行 GC。這個技巧向客戶端隱藏了 GC 暫停並降低了響應時間的高百分比【70,71】。一些對延遲敏感的金融交易系統【72】使用這種方法。 一個新興的想法是將 GC 暫停視為一個節點的短暫計劃中斷,並在這個節點收集其垃圾的同時,讓其他節點處理來自客戶端的請求。如果執行時可以警告應用程式一個節點很快需要 GC 暫停,那麼應用程式可以停止向該節點發送新的請求,等待它完成處理未完成的請求,然後在沒有請求正在進行時執行 GC。這個技巧向客戶端隱藏了 GC 暫停並降低了響應時間的高百分比【70,71】。一些對延遲敏感的金融交易系統【72】使用這種方法。
@ -646,7 +646,7 @@ Web 應用程式確實需要預期受終端使用者控制的客戶端(如 Web
但是,正如在 [第二部分](part-ii.md) 的介紹中所討論的那樣,可伸縮性並不是使用分散式系統的唯一原因。容錯和低延遲(透過將資料放置在距離使用者較近的地方)是同等重要的目標,而這些不能用單個節點實現。 但是,正如在 [第二部分](part-ii.md) 的介紹中所討論的那樣,可伸縮性並不是使用分散式系統的唯一原因。容錯和低延遲(透過將資料放置在距離使用者較近的地方)是同等重要的目標,而這些不能用單個節點實現。
在本章中,我們也轉換了幾次話題,探討了網路、時鐘和程序的不可靠性是否是不可避免的自然規律。我們看到這並不是:有可能給網路提供硬時的響應保證和有限的延遲,但是這樣做非常昂貴,且導致硬體資源的利用率降低。大多數非安全關鍵系統會選擇 **便宜而不可靠**,而不是 **昂貴和可靠** 在本章中,我們也轉換了幾次話題,探討了網路、時鐘和程序的不可靠性是否是不可避免的自然規律。我們看到這並不是:有可能給網路提供硬時的響應保證和有限的延遲,但是這樣做非常昂貴,且導致硬體資源的利用率降低。大多數非安全關鍵系統會選擇 **便宜而不可靠**,而不是 **昂貴和可靠**
我們還談到了超級計算機,它們採用可靠的元件,因此當元件發生故障時必須完全停止並重新啟動。相比之下,分散式系統可以永久執行而不會在服務層面中斷,因為所有的錯誤和維護都可以在節點級別進行處理 —— 至少在理論上是如此。(實際上,如果一個錯誤的配置變更被應用到所有的節點,仍然會使分散式系統癱瘓)。 我們還談到了超級計算機,它們採用可靠的元件,因此當元件發生故障時必須完全停止並重新啟動。相比之下,分散式系統可以永久執行而不會在服務層面中斷,因為所有的錯誤和維護都可以在節點級別進行處理 —— 至少在理論上是如此。(實際上,如果一個錯誤的配置變更被應用到所有的節點,仍然會使分散式系統癱瘓)。

View File

@ -172,7 +172,7 @@
在實際應用中,寬鬆地處理這些限制有時是可以接受的(例如,如果航班超額預訂,你可以將客戶轉移到不同的航班併為其提供補償)。在這種情況下,可能不需要線性一致性,我們將在 “[及時性與完整性](ch12.md#及時性與完整性)” 中討論這種寬鬆的約束。 在實際應用中,寬鬆地處理這些限制有時是可以接受的(例如,如果航班超額預訂,你可以將客戶轉移到不同的航班併為其提供補償)。在這種情況下,可能不需要線性一致性,我們將在 “[及時性與完整性](ch12.md#及時性與完整性)” 中討論這種寬鬆的約束。
然而,一個硬性的唯一性約束(關係型資料庫中常見的那種)需要線性一致性。其他型別的約束,如外鍵或屬性約束可以不需要線性一致性【19】。 然而,一個硬性的唯一性約束(關係型資料庫中常見的那種)需要線性一致性。其他型別的約束,如外部索引鍵或屬性約束可以不需要線性一致性【19】。
#### 跨通道的時序依賴 #### 跨通道的時序依賴
@ -180,13 +180,13 @@
計算機系統也會出現類似的情況。例如,假設有一個網站,使用者可以上傳照片,一個後臺程序會調整照片大小,降低解析度以加快下載速度(縮圖)。該系統的架構和資料流如 [圖 9-5](../img/fig9-5.png) 所示。 計算機系統也會出現類似的情況。例如,假設有一個網站,使用者可以上傳照片,一個後臺程序會調整照片大小,降低解析度以加快下載速度(縮圖)。該系統的架構和資料流如 [圖 9-5](../img/fig9-5.png) 所示。
縮放器需要明確的指令來執行尺寸縮放作業,指令是 Web 伺服器透過訊息佇列傳送的(請參閱 [第十一章](ch11.md)。Web 伺服器不會將整個照片放在佇列中,因為大多數訊息代理都是針對較短的訊息而設計的,而一張照片的空間佔用可能達到幾兆位元組。取而代之的是,首先將照片寫入檔案儲存服務,寫入完成後再將給縮放器的指令放入訊息佇列。 縮放器需要明確的指令來執行尺寸縮放作業,指令是 Web 伺服器透過訊息佇列傳送的(請參閱 [第十一章](ch11.md)。Web 伺服器不會將整個照片放在佇列中,因為大多數訊息代理都是針對較短的訊息而設計的,而一張照片的空間佔用可能達到幾兆位元組。取而代之的是,首先將照片寫入檔案儲存服務,寫入完成後再將給縮放器的指令放入訊息佇列。
![](../img/fig9-5.png) ![](../img/fig9-5.png)
**圖 9-5 Web 伺服器和影縮放器透過檔案儲存和訊息佇列進行通訊,開啟競爭條件的可能性。** **圖 9-5 Web 伺服器和影縮放器透過檔案儲存和訊息佇列進行通訊,開啟競爭條件的可能性。**
如果檔案儲存服務是線性一致的,那麼這個系統應該可以正常工作。如果它不是線性一致的,則存在競爭條件的風險:訊息佇列([圖 9-5](../img/fig9-5.png) 中的步驟 3 和 4可能比儲存服務內部的複製replication更快。在這種情況下當縮放器讀取影象(步驟 5可能會看到影象的舊版本或者什麼都沒有。如果它處理的是舊版本的影象,則檔案儲存中的全尺寸圖和縮圖就產生了永久性的不一致。 如果檔案儲存服務是線性一致的,那麼這個系統應該可以正常工作。如果它不是線性一致的,則存在競爭條件的風險:訊息佇列([圖 9-5](../img/fig9-5.png) 中的步驟 3 和 4可能比儲存服務內部的複製replication更快。在這種情況下當縮放器讀取影像(步驟 5可能會看到影像的舊版本或者什麼都沒有。如果它處理的是舊版本的影像,則檔案儲存中的全尺寸圖和縮圖就產生了永久性的不一致。
出現這個問題是因為 Web 伺服器和縮放器之間存在兩個不同的通道:檔案儲存與訊息佇列。沒有線性一致性的新鮮性保證,這兩個通道之間的競爭條件是可能的。這種情況類似於 [圖 9-1](../img/fig9-1.png),資料庫複製與 Alice 的嘴到 Bob 耳朵之間的真人音訊通道之間也存在競爭條件。 出現這個問題是因為 Web 伺服器和縮放器之間存在兩個不同的通道:檔案儲存與訊息佇列。沒有線性一致性的新鮮性保證,這兩個通道之間的競爭條件是可能的。這種情況類似於 [圖 9-1](../img/fig9-1.png),資料庫複製與 Alice 的嘴到 Bob 耳朵之間的真人音訊通道之間也存在競爭條件。
@ -322,7 +322,7 @@ CAP 定理的正式定義僅限於很狹隘的範圍【30】它只考慮了
* 在 “[檢測併發寫入](ch5.md#檢測併發寫入)” 中我們觀察到,如果有兩個操作 A 和 B則存在三種可能性A 發生在 B 之前,或 B 發生在 A 之前,或者 A 和 B**併發**。這種 **此前發生happened before** 關係是因果關係的另一種表述:如果 A 在 B 前發生,那麼意味著 B 可能已經知道了 A或者建立在 A 的基礎上,或者依賴於 A。如果 A 和 B 是 **併發** 的,那麼它們之間並沒有因果聯絡;換句話說,我們確信 A 和 B 不知道彼此。 * 在 “[檢測併發寫入](ch5.md#檢測併發寫入)” 中我們觀察到,如果有兩個操作 A 和 B則存在三種可能性A 發生在 B 之前,或 B 發生在 A 之前,或者 A 和 B**併發**。這種 **此前發生happened before** 關係是因果關係的另一種表述:如果 A 在 B 前發生,那麼意味著 B 可能已經知道了 A或者建立在 A 的基礎上,或者依賴於 A。如果 A 和 B 是 **併發** 的,那麼它們之間並沒有因果聯絡;換句話說,我們確信 A 和 B 不知道彼此。
* 在事務快照隔離的上下文中(“[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)”),我們說事務是從一致性快照中讀取的。但此語境中 “一致” 到底又是什麼意思?這意味著 **與因果關係保持一致consistent with causality**如果快照包含答案它也必須包含被回答的問題【48】。在某個時間點觀察整個資料庫與因果關係保持一致意味著因果上在該時間點之前發生的所有操作其影響都是可見的但因果上在該時間點之後發生的操作其影響對觀察者不可見。**讀偏差read skew** 意味著讀取的資料處於違反因果關係的狀態(不可重複讀,如 [圖 7-6](../img/fig7-6.png) 所示)。 * 在事務快照隔離的上下文中(“[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)”),我們說事務是從一致性快照中讀取的。但此語境中 “一致” 到底又是什麼意思?這意味著 **與因果關係保持一致consistent with causality**如果快照包含答案它也必須包含被回答的問題【48】。在某個時間點觀察整個資料庫與因果關係保持一致意味著因果上在該時間點之前發生的所有操作其影響都是可見的但因果上在該時間點之後發生的操作其影響對觀察者不可見。**讀偏差read skew** 意味著讀取的資料處於違反因果關係的狀態(不可重複讀,如 [圖 7-6](../img/fig7-6.png) 所示)。
* 事務之間 **寫偏差write skew** 的例子(請參閱 “[寫入偏差與幻讀](ch7.md#寫入偏差與幻讀)”)也說明了因果依賴:在 [圖 7-8](../img/fig7-8.png) 中,愛麗絲被允許離班,因為事務認為鮑勃仍在值班,反之亦然。在這種情況下,離班的動作因果依賴於對當前值班情況的觀察。[可序列化快照隔離](ch7.md#可序列化快照隔離) 透過跟蹤事務之間的因果依賴來檢測寫偏差。 * 事務之間 **寫偏差write skew** 的例子(請參閱 “[寫入偏差與幻讀](ch7.md#寫入偏差與幻讀)”)也說明了因果依賴:在 [圖 7-8](../img/fig7-8.png) 中,愛麗絲被允許離班,因為事務認為鮑勃仍在值班,反之亦然。在這種情況下,離班的動作因果依賴於對當前值班情況的觀察。[可序列化快照隔離](ch7.md#可序列化快照隔離) 透過跟蹤事務之間的因果依賴來檢測寫偏差。
* 在愛麗絲和鮑勃看球的例子中([圖 9-1](../img/fig9-1.png)),在聽到愛麗絲驚呼比賽結果後,鮑勃從伺服器得到陳舊結果的事實違背了因果關係:愛麗絲的驚呼因果依賴於得分宣告,所以鮑勃應該也能在聽到愛麗斯驚呼後查詢到比分。相同的模式在 “[跨通道的時序依賴](#跨通道的時序依賴)” 一節中,以 “影大小調整服務” 的偽裝再次出現。 * 在愛麗絲和鮑勃看球的例子中([圖 9-1](../img/fig9-1.png)),在聽到愛麗絲驚呼比賽結果後,鮑勃從伺服器得到陳舊結果的事實違背了因果關係:愛麗絲的驚呼因果依賴於得分宣告,所以鮑勃應該也能在聽到愛麗斯驚呼後查詢到比分。相同的模式在 “[跨通道的時序依賴](#跨通道的時序依賴)” 一節中,以 “影大小調整服務” 的偽裝再次出現。
因果關係對事件施加了一種 **順序**:因在果之前;訊息傳送在訊息收取之前。而且就像現實生活中一樣,一件事會導致另一件事:某個節點讀取了一些資料然後寫入一些結果,另一個節點讀取其寫入的內容,並依次寫入一些其他內容,等等。這些因果依賴的操作鏈定義了系統中的因果順序,即,什麼在什麼之前發生。 因果關係對事件施加了一種 **順序**:因在果之前;訊息傳送在訊息收取之前。而且就像現實生活中一樣,一件事會導致另一件事:某個節點讀取了一些資料然後寫入一些結果,另一個節點讀取其寫入的內容,並依次寫入一些其他內容,等等。這些因果依賴的操作鏈定義了系統中的因果順序,即,什麼在什麼之前發生。
@ -442,7 +442,7 @@ CAP 定理的正式定義僅限於很狹隘的範圍【30】它只考慮了
乍看之下,似乎操作的全序關係足以解決這一問題(例如使用蘭伯特時間戳):如果建立了兩個具有相同使用者名稱的帳戶,選擇時間戳較小的那個作為勝者(第一個抓到使用者名稱的人),並讓帶有更大時間戳者失敗。由於時間戳上有全序關係,所以這個比較總是可行的。 乍看之下,似乎操作的全序關係足以解決這一問題(例如使用蘭伯特時間戳):如果建立了兩個具有相同使用者名稱的帳戶,選擇時間戳較小的那個作為勝者(第一個抓到使用者名稱的人),並讓帶有更大時間戳者失敗。由於時間戳上有全序關係,所以這個比較總是可行的。
這種方法適用於事後確定勝利者:一旦你收集了系統中的所有使用者名稱建立操作,就可以比較它們的時間戳。然而當某個節點需要時處理使用者建立使用者名稱的請求時,這樣的方法就無法滿足了。節點需要 **馬上right now** 決定這個請求是成功還是失敗。在那個時刻,節點並不知道是否存在其他節點正在併發執行建立同樣使用者名稱的操作,罔論其它節點可能分配給那個操作的時間戳。 這種方法適用於事後確定勝利者:一旦你收集了系統中的所有使用者名稱建立操作,就可以比較它們的時間戳。然而當某個節點需要時處理使用者建立使用者名稱的請求時,這樣的方法就無法滿足了。節點需要 **馬上right now** 決定這個請求是成功還是失敗。在那個時刻,節點並不知道是否存在其他節點正在併發執行建立同樣使用者名稱的操作,罔論其它節點可能分配給那個操作的時間戳。
為了確保沒有其他節點正在使用相同的使用者名稱和較小的時間戳併發建立同名賬戶你必須檢查其它每個節點看看它在做什麼【56】。如果其中一個節點由於網路問題出現故障或不可達則整個系統可能被拖至停機。這不是我們需要的那種容錯系統。 為了確保沒有其他節點正在使用相同的使用者名稱和較小的時間戳併發建立同名賬戶你必須檢查其它每個節點看看它在做什麼【56】。如果其中一個節點由於網路問題出現故障或不可達則整個系統可能被拖至停機。這不是我們需要的那種容錯系統。
@ -462,7 +462,7 @@ CAP 定理的正式定義僅限於很狹隘的範圍【30】它只考慮了
> #### 順序保證的範圍 > #### 順序保證的範圍
> >
> 每個分割槽各有一個主庫的分割槽資料庫,通常只在每個分割槽內維持順序,這意味著它們不能提供跨分割槽的一致性保證(例如,一致性快照,外鍵引用。跨所有分割槽的全序是可能的但需要額外的協調【59】。 > 每個分割槽各有一個主庫的分割槽資料庫,通常只在每個分割槽內維持順序,這意味著它們不能提供跨分割槽的一致性保證(例如,一致性快照,外部索引鍵引用。跨所有分割槽的全序是可能的但需要額外的協調【59】。
全序廣播通常被描述為在節點間交換訊息的協議。非正式地講,它要滿足兩個安全屬性: 全序廣播通常被描述為在節點間交換訊息的協議。非正式地講,它要滿足兩個安全屬性:

View File

@ -119,7 +119,7 @@
* **連線join** * **連線join**
彙集有共同點的記錄。在一個記錄與另一個記錄有關(外鍵,文件參考,圖中的邊)的情況下最常用,查詢需要獲取參考所指向的記錄。請參閱“[多對一和多對多的關係](ch2.md#多對一和多對多的關係)”和“[Reduce側連線與分組](ch10.md#Reduce側連線與分組)”。 彙集有共同點的記錄。在一個記錄與另一個記錄有關(外部索引鍵,文件參考,圖中的邊)的情況下最常用,查詢需要獲取參考所指向的記錄。請參閱“[多對一和多對多的關係](ch2.md#多對一和多對多的關係)”和“[Reduce側連線與分組](ch10.md#Reduce側連線與分組)”。
* **領導者leader** * **領導者leader**

View File

@ -1,6 +1,6 @@
# 序言 # 序言
如果近幾年從業於軟體工程,特別是伺服器端和後端系統開發,那麼你很有可能已經被大量關於資料儲存和處理的時髦詞彙轟炸過了: NoSQL大資料Web-Scale分片最終一致性ACIDCAP 定理雲服務MapReduce時! 如果近幾年從業於軟體工程,特別是伺服器端和後端系統開發,那麼你很有可能已經被大量關於資料儲存和處理的時髦詞彙轟炸過了: NoSQL大資料Web-Scale分片最終一致性ACIDCAP 定理雲服務MapReduce時!
在最近十年中,我們看到了很多有趣的進展,關於資料庫,分散式系統,以及在此基礎上構建應用程式的方式。這些進展有著各種各樣的驅動力: 在最近十年中,我們看到了很多有趣的進展,關於資料庫,分散式系統,以及在此基礎上構建應用程式的方式。這些進展有著各種各樣的驅動力: