diff --git a/README.md b/README.md index 0e33709..33c83b1 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,15 @@ - 作者: [Martin Kleppmann](https://martin.kleppmann.com) - 原名:[《Designing Data-Intensive Applications》](http://shop.oreilly.com/product/0636920032175.do) -- 译者:[冯若航]( https://vonng.com) (rh@vonng.com ) -- 使用 [Typora](https://www.typora.io)、[Gitbook](https://vonng.gitbooks.io/ddia-cn/content/),[Github Pages](https://vonng.github.io/ddia)以获取最佳阅读体验。 -- 繁体:[繁體中文版本](zh-tw/README.md) -- 本地:若无合适的Markdown编辑器,您可在项目根目录中执行`make`,并通过浏览器阅读( Powered by [Docsify](https://docsify.js.org/#/zh-cn/) )。 +- 译者:[冯若航](https://vonng.com) ([@Vonng](https://vonng.com/en/)) +- 校订: [@yingang](https://github.com/yingang) +- 繁体:[繁體中文版本](zh-tw/README.md) by [@afunTW](https://github.com/afunTW) +> 使用 [Typora](https://www.typora.io)、[Gitbook](https://vonng.gitbooks.io/ddia-cn/content/),[Github Pages](https://vonng.github.io/ddia)以获取最佳阅读体验。 +> +> 本地:您可在项目根目录中执行`make`,并通过浏览器阅读([在线预览](http://ddia.vonng.com/#/))。 + ## 译序 > 不懂数据库的全栈工程师不是好架构师 @@ -133,23 +136,23 @@ 本译文只供学习研究参考之用,不得公开传播发行或用于商业用途。有能力阅读英文书籍者请购买正版支持。 - ## 贡献 +0. 全文校订 by [@yingang](https://github.com/yingang) 1. [序言初翻修正](https://github.com/Vonng/ddia/commit/afb5edab55c62ed23474149f229677e3b42dfc2c) by [@seagullbird](https://github.com/Vonng/ddia/commits?author=seagullbird) 2. [第一章语法标点校正](https://github.com/Vonng/ddia/commit/973b12cd8f8fcdf4852f1eb1649ddd9d187e3644) by [@nevertiree](https://github.com/Vonng/ddia/commits?author=nevertiree) 3. [第六章部分校正](https://github.com/Vonng/ddia/commit/d4eb0852c0ec1e93c8aacc496c80b915bb1e6d48) 与[第10章的初翻](https://github.com/Vonng/ddia/commit/9de8dbd1bfe6fbb03b3bf6c1a1aa2291aed2490e) by @[MuAlex](https://github.com/Vonng/ddia/commits?author=MuAlex) 4. [第一部分](part-i.md)前言,[ch2](ch2.md)校正 by [@jiajiadebug](https://github.com/Vonng/ddia/commits?author=jiajiadebug) 5. [词汇表](glossary.md)、[后记]()关于野猪的部分 by @[Chowss](https://github.com/Vonng/ddia/commits?author=Chowss) 6. [繁體中文](https://github.com/Vonng/ddia/pulls)版本与转换脚本 by [@afunTW](https://github.com/afunTW) -7. [对各章进行大量翻译更正润色](https://github.com/Vonng/ddia/pull/118) by [@yingang](https://github.com/yingang) -8. 感谢所有作出贡献,提出意见的朋友们: +7. 感谢所有作出贡献,提出意见的朋友们:
Pull Requests & Issues | ISSUE & Pull Requests | USER | Title | | ----------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| [123](https://github.com/Vonng/ddia/pull/123) | [@yingang](https://github.com/yingang) | translation updates (chapter 9, TOC in readme, glossary, etc.) | | [121](https://github.com/Vonng/ddia/pull/121) | [@yingang](https://github.com/yingang) | translation updates (chapter 5 to chapter 8) | | [120](https://github.com/Vonng/ddia/pull/120) | [@jiong-han](https://github.com/jiong-han) | Typo fix: 呲之以鼻 -> 嗤之以鼻 | | [119](https://github.com/Vonng/ddia/pull/119) | [@cclauss](https://github.com/cclauss) | Streamline file operations in convert() | diff --git a/_coverpage.md b/_coverpage.md deleted file mode 100644 index 591fa3b..0000000 --- a/_coverpage.md +++ /dev/null @@ -1,6 +0,0 @@ - - -# 设计数据密集型应用 - -> Designing Data Intensive Applications - diff --git a/index.html b/index.html index 613776c..af1944b 100644 --- a/index.html +++ b/index.html @@ -27,7 +27,7 @@ name: '设计数据密集型应用', repo: 'Vonng/ddia', auto2top: true, - coverpage: true, + coverpage: false, executeScript: true, loadSidebar: true, loadNavbar: true, diff --git a/zh-tw/README.md b/zh-tw/README.md index dc76e4c..04d4dc7 100644 --- a/zh-tw/README.md +++ b/zh-tw/README.md @@ -90,29 +90,111 @@ -* [第一章:可靠性、可伸縮性、可維護性](ch1.md) +* [第一章:可靠性、可伸縮性、可維護性](ch1.md) + + * [關於資料系統的思考](ch1.md#關於資料系統的思考) + + * [可靠性](ch1.md#可靠性) + + * [可伸縮性](ch1.md#可伸縮性) + + * [可維護性](ch1.md#可維護性) + + * [本章小結](ch1.md#本章小結) * [第二章:資料模型與查詢語言](ch2.md) -* [第三章:儲存與檢索](ch3.md) + * [關係模型與文件模型](ch2.md#關係模型與文件模型) + + * [資料查詢語言](ch2.md#資料查詢語言) + + * [圖資料模型](ch2.md#圖資料模型) + + * [本章小結](ch2.md#本章小結) + +* [第三章:儲存與檢索](ch3.md) + + * [驅動資料庫的資料結構](ch3.md#驅動資料庫的資料結構) + + * [事務處理還是分析?](ch3.md#事務處理還是分析?) + + * [列儲存](ch3.md#列儲存) + + * [本章小結](ch3.md#本章小結) * [第四章:編碼與演化](ch4.md) + * [編碼資料的格式](ch4.md#編碼資料的格式) + + * [資料流的型別](ch4.md#資料流的型別) + + * [本章小結](ch4.md#本章小結) + ### [第二部分:分散式資料](part-ii.md) -* [第五章:複製](ch5.md) +* [第五章:複製](ch5.md) -* [第六章:分割槽](ch6.md) + * [領導者與追隨者](ch5.md#領導者與追隨者) -* [第七章:事務](ch7.md) + * [複製延遲問題](ch5.md#複製延遲問題) -* [第八章:分散式系統的麻煩](ch8.md) + * [多主複製](ch5.md#多主複製) -* [第九章:一致性與共識](ch9.md) + * [無主複製](ch5.md#無主複製) + + * [本章小結](ch5.md#本章小結) + +* [第六章:分割槽](ch6.md) + + * [分割槽與複製](ch6.md#分割槽與複製) + + * [鍵值資料的分割槽](ch6.md#鍵值資料的分割槽) + + * [分割槽與次級索引](ch6.md#分割槽與次級索引) + + * [分割槽再平衡](ch6.md#分割槽再平衡) + + * [請求路由](ch6.md#請求路由) + + * [本章小結](ch6.md#本章小結) + +* [第七章:事務](ch7.md) + + * [事務的棘手概念](ch7.md#事務的棘手概念) + + * [弱隔離級別](ch7.md#弱隔離級別) + + * [可序列化](ch7.md#可序列化) + + * [本章小結](ch7.md#本章小結) + +* [第八章:分散式系統的麻煩](ch8.md) + + * [故障與部分失效](ch8.md#故障與部分失效) + + * [不可靠的網路](ch8.md#不可靠的網路) + + * [不可靠的時鐘](ch8.md#不可靠的時鐘) + + * [知識、真相與謊言](ch8.md#知識、真相與謊言) + + * [本章小結](ch8.md#本章小結) + +* [第九章:一致性與共識](ch9.md) + + * [一致性保證](ch9.md#一致性保證) + + * [線性一致性](ch9.md#線性一致性) + + * [順序保證](ch9.md#順序保證) + + * [分散式事務與共識](ch9.md#分散式事務與共識) + + * [本章小結](ch9.md#本章小結) @@ -120,11 +202,37 @@ -* [第十章:批處理](ch10.md) +* [第十章:批處理](ch10.md) -* [第十一章:流處理](ch11.md) + * [使用Unix工具的批處理](ch10.md#使用Unix工具的批處理) -* [第十二章:資料系統的未來](ch12.md) + * [MapReduce和分散式檔案系統](ch10.md#MapReduce和分散式檔案系統) + + * [MapReduce之後](ch10.md#MapReduce之後) + + * [本章小結](ch10.md#本章小結) + +* [第十一章:流處理](ch11.md) + + * [傳遞事件流](ch11.md#傳遞事件流) + + * [流與資料庫](ch11.md#流與資料庫) + + * [流處理](ch11.md#流處理) + + * [本章小結](ch11.md#本章小結) + +* [第十二章:資料系統的未來](ch12.md) + + * [資料整合](ch12.md#資料整合) + + * [分拆資料庫](ch12.md#分拆資料庫) + + * [將事情做正確](ch12.md#將事情做正確) + + * [做正確的事情](ch12.md#做正確的事情) + + * [本章小結](ch12.md#本章小結) @@ -178,7 +286,7 @@ 6. [繁體中文](https://github.com/Vonng/ddia/pulls)版本與轉換指令碼 by [@afunTW](https://github.com/afunTW) -7. [對第各章進行大量翻譯更正潤色](https://github.com/Vonng/ddia/pull/118) by [@yingang](https://github.com/yingang) +7. [對各章進行大量翻譯更正潤色](https://github.com/Vonng/ddia/pull/118) by [@yingang](https://github.com/yingang) 8. 感謝所有作出貢獻,提出意見的朋友們: @@ -200,7 +308,7 @@ | [119](https://github.com/Vonng/ddia/pull/119) | [@cclauss](https://github.com/cclauss) | Streamline file operations in convert() | -| [118](https://github.com/Vonng/ddia/pull/118) | [@yingang](https://github.com/yingang) | translation updates (chapter 2 and 3) | +| [118](https://github.com/Vonng/ddia/pull/118) | [@yingang](https://github.com/yingang) | translation updates (chapter 2 and 4) | | [117](https://github.com/Vonng/ddia/pull/117) | [@feeeei](https://github.com/feeeei) | 統一每章的標題格式 | diff --git a/zh-tw/ch1.md b/zh-tw/ch1.md index 437732e..0b30cd3 100644 --- a/zh-tw/ch1.md +++ b/zh-tw/ch1.md @@ -2,7 +2,7 @@ -![](../img/ch1.png) +![](img/ch1.png) @@ -82,11 +82,11 @@ -​ 例如,如果將快取(應用管理的快取層,Memcached或同類產品)和全文搜尋(全文搜尋伺服器,例如Elasticsearch或Solr)功能從主資料庫剝離出來,那麼使快取/索引與主資料庫保持同步通常是應用程式碼的責任。[圖1-1](../img/fig1-1.png) 給出了這種架構可能的樣子(細節將在後面的章節中詳細介紹)。 +​ 例如,如果將快取(應用管理的快取層,Memcached或同類產品)和全文搜尋(全文搜尋伺服器,例如Elasticsearch或Solr)功能從主資料庫剝離出來,那麼使快取/索引與主資料庫保持同步通常是應用程式碼的責任。[圖1-1](img/fig1-1.png) 給出了這種架構可能的樣子(細節將在後面的章節中詳細介紹)。 -![](../img/fig1-1.png) +![](img/fig1-1.png) @@ -350,7 +350,7 @@ -1. 釋出推文時,只需將新推文插入全域性推文集合即可。當一個使用者請求自己的主頁時間線時,首先查詢他關注的所有人,查詢這些被關注使用者釋出的推文並按時間順序合併。在如[圖1-2](../img/fig1-2.png)所示的關係型資料庫中,可以編寫這樣的查詢: +1. 釋出推文時,只需將新推文插入全域性推文集合即可。當一個使用者請求自己的主頁時間線時,首先查詢他關注的所有人,查詢這些被關注使用者釋出的推文並按時間順序合併。在如[圖1-2](img/fig1-2.png)所示的關係型資料庫中,可以編寫這樣的查詢: @@ -368,7 +368,7 @@ ``` - ![](../img/fig1-2.png) + ![](img/fig1-2.png) @@ -376,11 +376,11 @@ -2. 為每個使用者的主頁時間線維護一個快取,就像每個使用者的推文收件箱([圖1-3](../img/fig1-3.png))。 當一個使用者釋出推文時,查詢所有關注該使用者的人,並將新的推文插入到每個主頁時間線快取中。 因此讀取主頁時間線的請求開銷很小,因為結果已經提前計算好了。 +2. 為每個使用者的主頁時間線維護一個快取,就像每個使用者的推文收件箱([圖1-3](img/fig1-3.png))。 當一個使用者釋出推文時,查詢所有關注該使用者的人,並將新的推文插入到每個主頁時間線快取中。 因此讀取主頁時間線的請求開銷很小,因為結果已經提前計算好了。 - ![](../img/fig1-3.png) + ![](img/fig1-3.png) @@ -442,11 +442,11 @@ -​ 在[圖1-4](../img/fig1-4.png)中,每個灰條代表一次對服務的請求,其高度表示請求花費了多長時間。大多數請求是相當快的,但偶爾會出現需要更長的時間的異常值。這也許是因為緩慢的請求實質上開銷更大,例如它們可能會處理更多的資料。但即使(你認為)所有請求都花費相同時間的情況下,隨機的附加延遲也會導致結果變化,例如:上下文切換到後臺程序,網路資料包丟失與TCP重傳,垃圾收集暫停,強制從磁碟讀取的頁面錯誤,伺服器機架中的震動【18】,還有很多其他原因。 +​ 在[圖1-4](img/fig1-4.png)中,每個灰條代表一次對服務的請求,其高度表示請求花費了多長時間。大多數請求是相當快的,但偶爾會出現需要更長的時間的異常值。這也許是因為緩慢的請求實質上開銷更大,例如它們可能會處理更多的資料。但即使(你認為)所有請求都花費相同時間的情況下,隨機的附加延遲也會導致結果變化,例如:上下文切換到後臺程序,網路資料包丟失與TCP重傳,垃圾收集暫停,強制從磁碟讀取的頁面錯誤,伺服器機架中的震動【18】,還有很多其他原因。 -![](../img/fig1-4.png) +![](img/fig1-4.png) @@ -466,7 +466,7 @@ -​ 為了弄清異常值有多糟糕,可以看看更高的百分位點,例如第95、99和99.9百分位點(縮寫為p95,p99和p999)。它們意味著95%,99%或99.9%的請求響應時間要比該閾值快,例如:如果第95百分位點響應時間是1.5秒,則意味著100個請求中的95個響應時間快於1.5秒,而100個請求中的5個響應時間超過1.5秒。如[圖1-4](../img/fig1-4.png)所示。 +​ 為了弄清異常值有多糟糕,可以看看更高的百分位點,例如第95、99和99.9百分位點(縮寫為p95,p99和p999)。它們意味著95%,99%或99.9%的請求響應時間要比該閾值快,例如:如果第95百分位點響應時間是1.5秒,則意味著100個請求中的95個響應時間快於1.5秒,而100個請求中的5個響應時間超過1.5秒。如[圖1-4](img/fig1-4.png)所示。 @@ -494,7 +494,7 @@ > -> ​ 在多重呼叫的後端服務裡,高百分位數變得特別重要。即使並行呼叫,終端使用者請求仍然需要等待最慢的並行呼叫完成。如[圖1-5](../img/fig1-5.png)所示,只需要一個緩慢的呼叫就可以使整個終端使用者請求變慢。即使只有一小部分後端呼叫速度較慢,如果終端使用者請求需要多個後端呼叫,則獲得較慢呼叫的機會也會增加,因此較高比例的終端使用者請求速度會變慢(效果稱為尾部延遲放大【24】)。 +> ​ 在多重呼叫的後端服務裡,高百分位數變得特別重要。即使並行呼叫,終端使用者請求仍然需要等待最慢的並行呼叫完成。如[圖1-5](img/fig1-5.png)所示,只需要一個緩慢的呼叫就可以使整個終端使用者請求變慢。即使只有一小部分後端呼叫速度較慢,如果終端使用者請求需要多個後端呼叫,則獲得較慢呼叫的機會也會增加,因此較高比例的終端使用者請求速度會變慢(效果稱為尾部延遲放大【24】)。 > @@ -506,7 +506,7 @@ -![](../img/fig1-5.png) +![](img/fig1-5.png) @@ -754,7 +754,7 @@ -​ 在本書後面的[第三部分](part-iii.md)中,我們將看到一種模式:幾個元件協同工作以構成一個完整的系統(如[圖1-1](../img/fig1-1.png)中的例子) +​ 在本書後面的[第三部分](part-iii.md)中,我們將看到一種模式:幾個元件協同工作以構成一個完整的系統(如[圖1-1](img/fig1-1.png)中的例子) diff --git a/zh-tw/ch10.md b/zh-tw/ch10.md index 59d6d74..6e7dd64 100644 --- a/zh-tw/ch10.md +++ b/zh-tw/ch10.md @@ -2,7 +2,7 @@ -![](../img/ch10.png) +![](img/ch10.png) @@ -496,7 +496,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5 -​ [圖10-1]()顯示了Hadoop MapReduce作業中的資料流。其並行化基於分割槽(參見[第6章](ch6.md)):作業的輸入通常是HDFS中的一個目錄,輸入目錄中的每個檔案或檔案塊都被認為是一個單獨的分割槽,可以單獨處理map任務([圖10-1](../img/fig10-1.png)中的m1,m2和m3標記)。 +​ [圖10-1]()顯示了Hadoop MapReduce作業中的資料流。其並行化基於分割槽(參見[第6章](ch6.md)):作業的輸入通常是HDFS中的一個目錄,輸入目錄中的每個檔案或檔案塊都被認為是一個單獨的分割槽,可以單獨處理map任務([圖10-1](img/fig10-1.png)中的m1,m2和m3標記)。 @@ -504,7 +504,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5 -![](../img/fig10-1.png) +![](img/fig10-1.png) @@ -596,11 +596,11 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5 -​ [圖10-2](../img/fig10-2.png)給出了一個批處理作業中連線的典型例子。左側是事件日誌,描述登入使用者在網站上做的事情(稱為**活動事件(activity events)**或**點選流資料(clickstream data)**),右側是使用者資料庫。 你可以將此示例看作是星型模式的一部分(參閱“[星型和雪花型:分析的模式](ch3.md#星型和雪花型:分析的模式)”):事件日誌是事實表,使用者資料庫是其中的一個維度。 +​ [圖10-2](img/fig10-2.png)給出了一個批處理作業中連線的典型例子。左側是事件日誌,描述登入使用者在網站上做的事情(稱為**活動事件(activity events)**或**點選流資料(clickstream data)**),右側是使用者資料庫。 你可以將此示例看作是星型模式的一部分(參閱“[星型和雪花型:分析的模式](ch3.md#星型和雪花型:分析的模式)”):事件日誌是事實表,使用者資料庫是其中的一個維度。 -![](../img/fig10-2.png) +![](img/fig10-2.png) @@ -628,11 +628,11 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5 -​ 回想一下,Mapper的目的是從每個輸入記錄中提取一對鍵值。在[圖10-2](../img/fig10-2.png)的情況下,這個鍵就是使用者ID:一組Mapper會掃過活動事件(提取使用者ID作為鍵,活動事件作為值),而另一組Mapper將會掃過使用者資料庫(提取使用者ID作為鍵,使用者的出生日期作為值)。這個過程如[圖10-3](../img/fig10-3.png)所示。 +​ 回想一下,Mapper的目的是從每個輸入記錄中提取一對鍵值。在[圖10-2](img/fig10-2.png)的情況下,這個鍵就是使用者ID:一組Mapper會掃過活動事件(提取使用者ID作為鍵,活動事件作為值),而另一組Mapper將會掃過使用者資料庫(提取使用者ID作為鍵,使用者的出生日期作為值)。這個過程如[圖10-3](img/fig10-3.png)所示。 -![](../img/fig10-3.png) +![](img/fig10-3.png) @@ -696,7 +696,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5 -#### 處理傾斜 +#### 處理偏斜 @@ -752,7 +752,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5 -​ 例如,假設在[圖10-2](../img/fig10-2.png)的情況下,使用者資料庫小到足以放進記憶體中。在這種情況下,當Mapper啟動時,它可以首先將使用者資料庫從分散式檔案系統讀取到記憶體中的雜湊中。完成此操作後,Map程式可以掃描使用者活動事件,並簡單地在散列表中查詢每個事件的使用者ID[^vi]。 +​ 例如,假設在[圖10-2](img/fig10-2.png)的情況下,使用者資料庫小到足以放進記憶體中。在這種情況下,當Mapper啟動時,它可以首先將使用者資料庫從分散式檔案系統讀取到記憶體中的雜湊中。完成此操作後,Map程式可以掃描使用者活動事件,並簡單地在散列表中查詢每個事件的使用者ID[^vi]。 @@ -760,7 +760,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5 -​ 參與連線的較大輸入的每個檔案塊各有一個Mapper(在[圖10-2](../img/fig10-2.png)的例子中活動事件是較大的輸入)。每個Mapper都會將較小輸入整個載入到記憶體中。 +​ 參與連線的較大輸入的每個檔案塊各有一個Mapper(在[圖10-2](img/fig10-2.png)的例子中活動事件是較大的輸入)。每個Mapper都會將較小輸入整個載入到記憶體中。 @@ -776,7 +776,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5 -​ 如果Map端連線的輸入以相同的方式進行分割槽,則雜湊連線方法可以獨立應用於每個分割槽。在[圖10-2](../img/fig10-2.png)的情況中,你可以根據使用者ID的最後一位十進位制數字來對活動事件和使用者資料庫進行分割槽(因此連線兩側各有10個分割槽)。例如,Mapper3首先將所有具有以3結尾的ID的使用者載入到散列表中,然後掃描ID為3的每個使用者的所有活動事件。 +​ 如果Map端連線的輸入以相同的方式進行分割槽,則雜湊連線方法可以獨立應用於每個分割槽。在[圖10-2](img/fig10-2.png)的情況中,你可以根據使用者ID的最後一位十進位制數字來對活動事件和使用者資料庫進行分割槽(因此連線兩側各有10個分割槽)。例如,Mapper3首先將所有具有以3結尾的ID的使用者載入到散列表中,然後掃描ID為3的每個使用者的所有活動事件。 @@ -1228,7 +1228,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5 -​ 許多圖演算法是透過一次遍歷一條邊來表示的,將一個頂點與近鄰的頂點連線起來,以傳播一些資訊,並不斷重複,直到滿足一些條件為止 —— 例如,直到沒有更多的邊要跟進,或直到一些指標收斂。我們在[圖2-6](../img/fig2-6.png)中看到一個例子,它透過重複跟進標明地點歸屬關係的邊,生成了資料庫中北美包含的所有地點列表(這種演算法被稱為**閉包傳遞(transitive closure)**)。 +​ 許多圖演算法是透過一次遍歷一條邊來表示的,將一個頂點與近鄰的頂點連線起來,以傳播一些資訊,並不斷重複,直到滿足一些條件為止 —— 例如,直到沒有更多的邊要跟進,或直到一些指標收斂。我們在[圖2-6](img/fig2-6.png)中看到一個例子,它透過重複跟進標明地點歸屬關係的邊,生成了資料庫中北美包含的所有地點列表(這種演算法被稱為**閉包傳遞(transitive closure)**)。 diff --git a/zh-tw/ch11.md b/zh-tw/ch11.md index 43f6396..9af38d2 100644 --- a/zh-tw/ch11.md +++ b/zh-tw/ch11.md @@ -2,7 +2,7 @@ -![](../img/ch11.png) +![](img/ch11.png) @@ -190,7 +190,7 @@ -當多個消費者從同一主題中讀取訊息時,有使用兩種主要的訊息傳遞模式,如[圖11-1](../img/fig11-1.png)所示: +當多個消費者從同一主題中讀取訊息時,有使用兩種主要的訊息傳遞模式,如[圖11-1](img/fig11-1.png)所示: @@ -210,7 +210,7 @@ -![](../img/fig11-1.png) +![](img/fig11-1.png) @@ -234,11 +234,11 @@ -​ 當與負載均衡相結合時,這種重傳行為對訊息的順序有種有趣的影響。在[圖11-2](../img/fig11-2.png)中,消費者通常按照生產者傳送的順序處理訊息。然而消費者2在處理訊息m3時崩潰,與此同時消費者1正在處理訊息m4。未確認的訊息m3隨後被重新發送給消費者1,結果消費者1按照m4,m3,m5的順序處理訊息。因此m3和m4的交付順序與以生產者1的傳送順序不同。 +​ 當與負載均衡相結合時,這種重傳行為對訊息的順序有種有趣的影響。在[圖11-2](img/fig11-2.png)中,消費者通常按照生產者傳送的順序處理訊息。然而消費者2在處理訊息m3時崩潰,與此同時消費者1正在處理訊息m4。未確認的訊息m3隨後被重新發送給消費者1,結果消費者1按照m4,m3,m5的順序處理訊息。因此m3和m4的交付順序與以生產者1的傳送順序不同。 -![](../img/fig11-2.png) +![](img/fig11-2.png) @@ -286,15 +286,15 @@ -​ 為了伸縮超出單個磁碟所能提供的更高吞吐量,可以對日誌進行**分割槽**(在[第6章](ch6.md)的意義上)。不同的分割槽可以託管在不同的機器上,且每個分割槽都拆分出一份能獨立於其他分割槽進行讀寫的日誌。一個主題可以定義為一組攜帶相同型別訊息的分割槽。這種方法如[圖11-3](../img/fig11-3.png)所示。 +​ 為了伸縮超出單個磁碟所能提供的更高吞吐量,可以對日誌進行**分割槽**(在[第6章](ch6.md)的意義上)。不同的分割槽可以託管在不同的機器上,且每個分割槽都拆分出一份能獨立於其他分割槽進行讀寫的日誌。一個主題可以定義為一組攜帶相同型別訊息的分割槽。這種方法如[圖11-3](img/fig11-3.png)所示。 -​ 在每個分割槽內,代理為每個訊息分配一個單調遞增的序列號或**偏移量(offset)**(在[圖11-3](../img/fig11-3.png)中,框中的數字是訊息偏移量)。這種序列號是有意義的,因為分割槽是僅追加寫入的,所以分割槽內的訊息是完全有序的。沒有跨不同分割槽的順序保證。 +​ 在每個分割槽內,代理為每個訊息分配一個單調遞增的序列號或**偏移量(offset)**(在[圖11-3](img/fig11-3.png)中,框中的數字是訊息偏移量)。這種序列號是有意義的,因為分割槽是僅追加寫入的,所以分割槽內的訊息是完全有序的。沒有跨不同分割槽的順序保證。 -![](../img/fig11-3.png) +![](img/fig11-3.png) @@ -448,11 +448,11 @@ -​ 但是,雙寫有一些嚴重的問題,其中一個是競爭條件,如[圖11-4](../img/fig11-4.png)所示。在這個例子中,兩個客戶端同時想要更新一個專案X:客戶端1想要將值設定為A,客戶端2想要將其設定為B。兩個客戶端首先將新值寫入資料庫,然後將其寫入到搜尋索引。因為運氣不好,這些請求的時序是交錯的:資料庫首先看到來自客戶端1的寫入將值設定為A,然後來自客戶端2的寫入將值設定為B,因此資料庫中的最終值為B。搜尋索引首先看到來自客戶端2的寫入,然後是客戶端1的寫入,所以搜尋索引中的最終值是A。即使沒發生錯誤,這兩個系統現在也永久地不一致了。 +​ 但是,雙寫有一些嚴重的問題,其中一個是競爭條件,如[圖11-4](img/fig11-4.png)所示。在這個例子中,兩個客戶端同時想要更新一個專案X:客戶端1想要將值設定為A,客戶端2想要將其設定為B。兩個客戶端首先將新值寫入資料庫,然後將其寫入到搜尋索引。因為運氣不好,這些請求的時序是交錯的:資料庫首先看到來自客戶端1的寫入將值設定為A,然後來自客戶端2的寫入將值設定為B,因此資料庫中的最終值為B。搜尋索引首先看到來自客戶端2的寫入,然後是客戶端1的寫入,所以搜尋索引中的最終值是A。即使沒發生錯誤,這兩個系統現在也永久地不一致了。 -![](../img/fig11-4.png) +![](img/fig11-4.png) @@ -468,7 +468,7 @@ -​ 如果你只有一個單領導者複製的資料庫,那麼這個領導者決定了寫入順序,而狀態機複製方法可以在資料庫副本上工作。然而,在[圖11-4](../img/fig11-4.png)中,沒有單個主庫:資料庫可能有一個領導者,搜尋索引也可能有一個領導者,但是兩者都不追隨對方,所以可能會發生衝突(參見“[多領導者複製](ch5.md#多領導者複製)“)。 +​ 如果你只有一個單領導者複製的資料庫,那麼這個領導者決定了寫入順序,而狀態機複製方法可以在資料庫副本上工作。然而,在[圖11-4](img/fig11-4.png)中,沒有單個主庫:資料庫可能有一個領導者,搜尋索引也可能有一個領導者,但是兩者都不追隨對方,所以可能會發生衝突(參見“[多領導者複製](ch5.md#多領導者複製)“)。 @@ -492,11 +492,11 @@ -​ 例如,你可以捕獲資料庫中的變更,並不斷將相同的變更應用至搜尋索引。如果變更日誌以相同的順序應用,則可以預期搜尋索引中的資料與資料庫中的資料是匹配的。搜尋索引和任何其他衍生資料系統只是變更流的消費者,如[圖11-5](../img/fig11-5.png)所示。 +​ 例如,你可以捕獲資料庫中的變更,並不斷將相同的變更應用至搜尋索引。如果變更日誌以相同的順序應用,則可以預期搜尋索引中的資料與資料庫中的資料是匹配的。搜尋索引和任何其他衍生資料系統只是變更流的消費者,如[圖11-5](img/fig11-5.png)所示。 -![](../img/fig11-5.png) +![](img/fig11-5.png) @@ -512,7 +512,7 @@ -​ 從本質上說,變更資料捕獲使得一個數據庫成為領導者(被捕獲變化的資料庫),並將其他元件變為追隨者。基於日誌的訊息代理非常適合從源資料庫傳輸變更事件,因為它保留了訊息的順序(避免了[圖11-2](../img/fig11-2.png)的重新排序問題)。 +​ 從本質上說,變更資料捕獲使得一個數據庫成為領導者(被捕獲變化的資料庫),並將其他元件變為追隨者。基於日誌的訊息代理非常適合從源資料庫傳輸變更事件,因為它保留了訊息的順序(避免了[圖11-2](img/fig11-2.png)的重新排序問題)。 @@ -552,7 +552,7 @@ -​ 我們之前在日誌結構儲存引擎的上下文中討論了“[Hash索引](ch3.md#Hash索引)”中的日誌壓縮(參見[圖3-2](../img/fig3-2.png)的示例)。原理很簡單:儲存引擎定期在日誌中查詢具有相同鍵的記錄,丟掉所有重複的內容,並只保留每個鍵的最新更新。這個壓縮與合併過程在後臺執行。 +​ 我們之前在日誌結構儲存引擎的上下文中討論了“[Hash索引](ch3.md#Hash索引)”中的日誌壓縮(參見[圖3-2](img/fig3-2.png)的示例)。原理很簡單:儲存引擎定期在日誌中查詢具有相同鍵的記錄,丟掉所有重複的內容,並只保留每個鍵的最新更新。這個壓縮與合併過程在後臺執行。 @@ -600,7 +600,7 @@ -* 在變更資料捕獲中,應用以**可變方式(mutable way)** 使用資料庫,任意更新和刪除記錄。變更日誌是從資料庫的底層提取的(例如,透過解析複製日誌),從而確保從資料庫中提取的寫入順序與實際寫入的順序相匹配,從而避免[圖11-4](../img/fig11-4.png)中的競態條件。寫入資料庫的應用不需要知道CDC的存在。 +* 在變更資料捕獲中,應用以**可變方式(mutable way)** 使用資料庫,任意更新和刪除記錄。變更日誌是從資料庫的底層提取的(例如,透過解析複製日誌),從而確保從資料庫中提取的寫入順序與實際寫入的順序相匹配,從而避免[圖11-4](img/fig11-4.png)中的競態條件。寫入資料庫的應用不需要知道CDC的存在。 * 在事件溯源中,應用邏輯顯式構建在寫入事件日誌的不可變事件之上。在這種情況下,事件儲存是僅追加寫入的,更新與刪除是不鼓勵的或禁止的。事件被設計為旨在反映應用層面發生的事情,而不是底層的狀態變更。 @@ -668,11 +668,11 @@ -​ 或者,預訂座位的使用者請求可以拆分為兩個事件:第一個是暫時預約,第二個是驗證預約後的獨立的確認事件(如“[使用全序廣播實現線性一致儲存](ch9.md#使用全序廣播實現線性一致儲存)”中所述) 。這種分割方式允許驗證發生在一個非同步的過程中。 +​ 或者,預訂座位的使用者請求可以拆分為兩個事件:第一個是暫時預約,第二個是驗證預約後的獨立的確認事件(如“[使用全序廣播實現線性一致的儲存](ch9.md#使用全序廣播實現線性一致的儲存)”中所述) 。這種分割方式允許驗證發生在一個非同步的過程中。 -#### 狀態,流和不變性 +### 狀態,流和不變性 @@ -692,7 +692,7 @@ -​ 如果你傾向於數學表示,那麼你可能會說,應用狀態是事件流對時間求積分得到的結果,而變更流是狀態對時間求微分的結果,如[圖11-6](../img/fig11-6.png)所示【49,50,51】。這個比喻有一些侷限性(例如,狀態的二階導似乎沒有意義),但這是考慮資料的一個實用出發點。 +​ 如果你傾向於數學表示,那麼你可能會說,應用狀態是事件流對時間求積分得到的結果,而變更流是狀態對時間求微分的結果,如[圖11-6](img/fig11-6.png)所示【49,50,51】。這個比喻有一些侷限性(例如,狀態的二階導似乎沒有意義),但這是考慮資料的一個實用出發點。 $$ @@ -702,7 +702,7 @@ stream(t) = \frac{d\ state(t)}{dt} $$ -![](../img/fig11-6.png) +![](img/fig11-6.png) @@ -746,7 +746,7 @@ $$ -​ 此外,透過從不變的事件日誌中分離出可變的狀態,你可以針對不同的讀取方式,從相同的事件日誌中衍生出幾種不同的表現形式。效果就像一個流的多個消費者一樣([圖11-5](../img/fig11-5.png)):例如,分析型資料庫Druid使用這種方式直接從Kafka攝取資料【55】,Pistachio是一個分散式的鍵值儲存,使用Kafka作為提交日誌【56】,Kafka Connect能將來自Kafka的資料匯出到各種不同的資料庫與索引【41】。這對於許多其他儲存和索引系統(如搜尋伺服器)來說是很有意義的,當系統要從分散式日誌中獲取輸入時亦然(參閱“[保持系統同步](#保持系統同步)”)。 +​ 此外,透過從不變的事件日誌中分離出可變的狀態,你可以針對不同的讀取方式,從相同的事件日誌中衍生出幾種不同的表現形式。效果就像一個流的多個消費者一樣([圖11-5](img/fig11-5.png)):例如,分析型資料庫Druid使用這種方式直接從Kafka攝取資料【55】,Pistachio是一個分散式的鍵值儲存,使用Kafka作為提交日誌【56】,Kafka Connect能將來自Kafka的資料匯出到各種不同的資料庫與索引【41】。這對於許多其他儲存和索引系統(如搜尋伺服器)來說是很有意義的,當系統要從分散式日誌中獲取輸入時亦然(參閱“[保持系統同步](#保持系統同步)”)。 @@ -774,7 +774,7 @@ $$ -​ 一種解決方案是將事件附加到日誌時同步執行讀取檢視的更新。而將這些寫入操作合併為一個原子單元需要**事務**,所以要麼將事件日誌和讀取檢視儲存在同一個儲存系統中,要麼就需要跨不同系統進行分散式事務。或者,你也可以使用在“[使用全序廣播實現線性化儲存](ch9.md#使用全序廣播實現線性化儲存)”中討論的方法。 +​ 一種解決方案是將事件附加到日誌時同步執行讀取檢視的更新。而將這些寫入操作合併為一個原子單元需要**事務**,所以要麼將事件日誌和讀取檢視儲存在同一個儲存系統中,要麼就需要跨不同系統進行分散式事務。或者,你也可以使用在“[使用全序廣播實現線性一致的儲存](ch9.md#使用全序廣播實現線性一致的儲存)”中討論的方法。 @@ -826,7 +826,7 @@ $$ -1. 你可以將事件中的資料寫入資料庫,快取,搜尋索引或類似的儲存系統,然後能被其他客戶端查詢。如[圖11-5](../img/fig11-5.png)所示,這是資料庫與系統其他部分發生變更保持同步的好方法 —— 特別是當流消費者是寫入資料庫的唯一客戶端時。如“[批處理工作流的輸出](ch10.md#批處理工作流的輸出)”中所討論的,它是寫入儲存系統的流等價物。 +1. 你可以將事件中的資料寫入資料庫,快取,搜尋索引或類似的儲存系統,然後能被其他客戶端查詢。如[圖11-5](img/fig11-5.png)所示,這是資料庫與系統其他部分發生變更保持同步的好方法 —— 特別是當流消費者是寫入資料庫的唯一客戶端時。如“[批處理工作流的輸出](ch10.md#批處理工作流的輸出)”中所討論的,它是寫入儲存系統的流等價物。 2. 你能以某種方式將事件推送給使用者,例如傳送報警郵件或推送通知,或將事件流式傳輸到可實時顯示的儀表板上。在這種情況下,人是流的最終消費者。 @@ -1016,11 +1016,11 @@ $$ -​ 將事件時間和處理時間搞混會導致錯誤的資料。例如,假設你有一個流處理器用於測量請求速率(計算每秒請求數)。如果你重新部署流處理器,它可能會停止一分鐘,並在恢復之後處理積壓的事件。如果你按處理時間來衡量速率,那麼在處理積壓日誌時,請求速率看上去就像有一個異常的突發尖峰,而實際上請求速率是穩定的([圖11-7](../img/fig11-7.png))。 +​ 將事件時間和處理時間搞混會導致錯誤的資料。例如,假設你有一個流處理器用於測量請求速率(計算每秒請求數)。如果你重新部署流處理器,它可能會停止一分鐘,並在恢復之後處理積壓的事件。如果你按處理時間來衡量速率,那麼在處理積壓日誌時,請求速率看上去就像有一個異常的突發尖峰,而實際上請求速率是穩定的([圖11-7](img/fig11-7.png))。 -![](../img/fig11-7.png) +![](img/fig11-7.png) @@ -1162,7 +1162,7 @@ $$ -​ 在“[示例:使用者活動事件分析](ch10.md#示例:使用者活動事件分析)”([圖10-2](../img/fig10-2.png))中,我們看到了連線兩個資料集的批處理作業示例:一組使用者活動事件和一個使用者檔案資料庫。將使用者活動事件視為流,並在流處理器中連續執行相同的連線是很自然的想法:輸入是包含使用者ID的活動事件流,而輸出還是活動事件流,但其中使用者ID已經被擴充套件為使用者的檔案資訊。這個過程有時被稱為 使用資料庫的資訊來**擴充(enriching)** 活動事件。 +​ 在“[示例:使用者活動事件分析](ch10.md#示例:使用者活動事件分析)”([圖10-2](img/fig10-2.png))中,我們看到了連線兩個資料集的批處理作業示例:一組使用者活動事件和一個使用者檔案資料庫。將使用者活動事件視為流,並在流處理器中連續執行相同的連線是很自然的想法:輸入是包含使用者ID的活動事件流,而輸出還是活動事件流,但其中使用者ID已經被擴充套件為使用者的檔案資訊。這個過程有時被稱為 使用資料庫的資訊來**擴充(enriching)** 活動事件。 @@ -1232,7 +1232,7 @@ GROUP BY follows.follower_id -[^iii]: 如果你將流視作表的衍生物,如[圖11-6](../img/fig11-6.png)所示,而把一個連線看作是兩個表的乘法u·v,那麼會發生一些有趣的事情:物化連線的變化流遵循乘積法則:(u·v)'= u'v + uv'(u·v)'= u'v + uv'。 換句話說,任何推文的變化量都與當前的關注聯絡在一起,任何關注的變化量都與當前的推文相連線【49,50】。 +[^iii]: 如果你將流視作表的衍生物,如[圖11-6](img/fig11-6.png)所示,而把一個連線看作是兩個表的乘法u·v,那麼會發生一些有趣的事情:物化連線的變化流遵循乘積法則:(u·v)'= u'v + uv'(u·v)'= u'v + uv'。 換句話說,任何推文的變化量都與當前的關注聯絡在一起,任何關注的變化量都與當前的推文相連線【49,50】。 diff --git a/zh-tw/ch12.md b/zh-tw/ch12.md index 0a45441..e916ef9 100644 --- a/zh-tw/ch12.md +++ b/zh-tw/ch12.md @@ -2,7 +2,7 @@ -![](../img/ch12.png) +![](img/ch12.png) @@ -86,7 +86,7 @@ -​ 允許應用程式直接寫入搜尋索引和資料庫引入瞭如[圖11-4](../img/fig11-4.png)所示的問題,其中兩個客戶端同時傳送衝突的寫入,且兩個儲存系統按不同順序處理它們。在這種情況下,既不是資料庫說了算,也不是搜尋索引說了算,所以它們做出了相反的決定,進入彼此間永續性的不一致狀態。 +​ 允許應用程式直接寫入搜尋索引和資料庫引入瞭如[圖11-4](img/fig11-4.png)所示的問題,其中兩個客戶端同時傳送衝突的寫入,且兩個儲存系統按不同順序處理它們。在這種情況下,既不是資料庫說了算,也不是搜尋索引說了算,所以它們做出了相反的決定,進入彼此間永續性的不一致狀態。 @@ -658,11 +658,11 @@ -​ 在抽象層面,上一節討論的資料流系統提供了建立衍生資料集(例如搜尋索引,物化檢視和預測模型)並使其保持更新的過程。我們將這個過程稱為**寫路徑(write path)**:只要某些資訊被寫入系統,它可能會經歷批處理與流處理的多個階段,而最終每個衍生資料集都會被更新,以適配寫入的資料。[圖12-1](../img/fig12-1.png)顯示了一個更新搜尋索引的例子。 +​ 在抽象層面,上一節討論的資料流系統提供了建立衍生資料集(例如搜尋索引,物化檢視和預測模型)並使其保持更新的過程。我們將這個過程稱為**寫路徑(write path)**:只要某些資訊被寫入系統,它可能會經歷批處理與流處理的多個階段,而最終每個衍生資料集都會被更新,以適配寫入的資料。[圖12-1](img/fig12-1.png)顯示了一個更新搜尋索引的例子。 -![](../img/fig12-1.png) +![](img/fig12-1.png) @@ -678,7 +678,7 @@ -​ 如[圖12-1](../img/fig12-1.png)所示,衍生資料集是寫路徑和讀路徑相遇的地方。它代表了在寫入時需要完成的工作量與在讀取時需要完成的工作量之間的權衡。 +​ 如[圖12-1](img/fig12-1.png)所示,衍生資料集是寫路徑和讀路徑相遇的地方。它代表了在寫入時需要完成的工作量與在讀取時需要完成的工作量之間的權衡。 @@ -910,7 +910,7 @@ -​ 但是,這種重複抑制僅適用於單條TCP連線的場景中。假設TCP連線是一個客戶端與資料庫的連線,並且它正在執行[例12-1]()中的事務。在許多資料庫中,事務是繫結在客戶端連線上的(如果客戶端傳送了多個查詢,資料庫就知道它們屬於同一個事務,因為它們是在同一個TCP連線上傳送的)。如果客戶端在傳送`COMMIT`之後但在從資料庫伺服器收到響應之前遇到網路中斷與連線超時,客戶端是不知道事務是否已經被提交的([圖8-1](../img/fig8-1.png))。 +​ 但是,這種重複抑制僅適用於單條TCP連線的場景中。假設TCP連線是一個客戶端與資料庫的連線,並且它正在執行[例12-1]()中的事務。在許多資料庫中,事務是繫結在客戶端連線上的(如果客戶端傳送了多個查詢,資料庫就知道它們屬於同一個事務,因為它們是在同一個TCP連線上傳送的)。如果客戶端在傳送`COMMIT`之後但在從資料庫伺服器收到響應之前遇到網路中斷與連線超時,客戶端是不知道事務是否已經被提交的([圖8-1](img/fig8-1.png))。 @@ -984,7 +984,7 @@ COMMIT; -​ [例12-2]()依賴於`request_id`列上的唯一約束。如果一個事務嘗試插入一個已經存在的ID,那麼`INSERT`失敗,事務被中止,使其無法生效兩次。即使在較弱的隔離級別下,關係資料庫也能正確地維護唯一性約束(而在“[寫入偏差與幻讀](ch7.md#寫入偏差與幻讀)”中討論過,應用級別的**檢查-然後-插入**可能會在不可序列化的隔離下失敗)。 +​ [例12-2]()依賴於`request_id`列上的唯一約束。如果一個事務嘗試插入一個已經存在的ID,那麼`INSERT`失敗,事務被中止,使其無法生效兩次。即使在較弱的隔離級別下,關係資料庫也能正確地維護唯一性約束(而在“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”中討論過,應用級別的**檢查-然後-插入**可能會在不可序列化的隔離下失敗)。 @@ -1108,7 +1108,7 @@ COMMIT; -​ 該方法不僅適用於唯一性約束,而且適用於許多其他型別的約束。其基本原理是,任何可能衝突的寫入都會路由到相同的分割槽並按順序處理。正如“[什麼是衝突?](ch5.md#什麼是衝突?)”與“[寫入偏差與幻讀](ch7.md#寫入偏差與幻讀)”中所述,衝突的定義可能取決於應用,但流處理器可以使用任意邏輯來驗證請求。這個想法與Bayou在90年代開創的方法類似【58】。 +​ 該方法不僅適用於唯一性約束,而且適用於許多其他型別的約束。其基本原理是,任何可能衝突的寫入都會路由到相同的分割槽並按順序處理。正如“[什麼是衝突?](ch5.md#什麼是衝突?)”與“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”中所述,衝突的定義可能取決於應用,但流處理器可以使用任意邏輯來驗證請求。這個想法與Bayou在90年代開創的方法類似【58】。 diff --git a/zh-tw/ch2.md b/zh-tw/ch2.md index baa7a5f..803a05b 100644 --- a/zh-tw/ch2.md +++ b/zh-tw/ch2.md @@ -2,7 +2,7 @@ -![](../img/ch2.png) +![](img/ch2.png) @@ -132,7 +132,7 @@ -![](../img/fig2-1.png) +![](img/fig2-1.png) @@ -140,11 +140,11 @@ -例如,[圖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模型(SQL:1999之前)中,最常見的規範化表示形式是將職位,教育和聯絡資訊放在單獨的表中,對User表提供外來鍵引用,如[圖2-1](../img/fig2-1.png)所示。 +* 傳統SQL模型(SQL:1999之前)中,最常見的規範化表示形式是將職位,教育和聯絡資訊放在單獨的表中,對User表提供外來鍵引用,如[圖2-1](img/fig2-1.png)所示。 * 後續的SQL標準增加了對結構化資料型別和XML資料的支援;這允許將多值資料儲存在單行內,並支援在這些文件內查詢和索引。這些功能在Oracle,IBM DB2,MS SQL Server和PostgreSQL中都有不同程度的支援【6,7】。JSON資料型別也得到多個數據庫的支援,包括IBM DB2,MySQL和PostgreSQL 【8】。 @@ -240,15 +240,15 @@ -JSON表示比[圖2-1](../img/fig2-1.png)中的多表模式具有更好的**區域性性(locality)**。如果在前面的關係型示例中獲取簡介,那需要執行多個查詢(透過`user_id`查詢每個表),或者在User表與其下屬表之間混亂地執行多路連線。而在JSON表示中,所有相關資訊都在同一個地方,一個查詢就足夠了。 +JSON表示比[圖2-1](img/fig2-1.png)中的多表模式具有更好的**區域性性(locality)**。如果在前面的關係型示例中獲取簡介,那需要執行多個查詢(透過`user_id`查詢每個表),或者在User表與其下屬表之間混亂地執行多路連線。而在JSON表示中,所有相關資訊都在同一個地方,一個查詢就足夠了。 -從使用者簡介檔案到使用者職位,教育歷史和聯絡資訊,這種一對多關係隱含了資料中的一個樹狀結構,而JSON表示使得這個樹狀結構變得明確(見[圖2-2](../img/fig2-2.png))。 +從使用者簡介檔案到使用者職位,教育歷史和聯絡資訊,這種一對多關係隱含了資料中的一個樹狀結構,而JSON表示使得這個樹狀結構變得明確(見[圖2-2](img/fig2-2.png))。 -![](../img/fig2-2.png) +![](img/fig2-2.png) @@ -316,7 +316,7 @@ JSON表示比[圖2-1](../img/fig2-1.png)中的多表模式具有更好的**區 -在前面的描述中,`organization`(使用者工作的公司)和`school_name`(他們學習的地方)只是字串。也許他們應該是對實體的引用呢?然後,每個組織,學校或大學都可以擁有自己的網頁(標識,新聞提要等)。每個簡歷可以連結到它所提到的組織和學校,並且包括他們的圖示和其他資訊(參見[圖2-3](../img/fig2-3.png),來自LinkedIn的一個例子)。 +在前面的描述中,`organization`(使用者工作的公司)和`school_name`(他們學習的地方)只是字串。也許他們應該是對實體的引用呢?然後,每個組織,學校或大學都可以擁有自己的網頁(標識,新聞提要等)。每個簡歷可以連結到它所提到的組織和學校,並且包括他們的圖示和其他資訊(參見[圖2-3](img/fig2-3.png),來自LinkedIn的一個例子)。 @@ -326,7 +326,7 @@ JSON表示比[圖2-1](../img/fig2-1.png)中的多表模式具有更好的**區 假設你想新增一個新的功能:一個使用者可以為另一個使用者寫一個推薦。在使用者的簡歷上顯示推薦,並附上推薦使用者的姓名和照片。如果推薦人更新他們的照片,那他們寫的任何推薦都需要顯示新的照片。因此,推薦應該擁有作者個人簡介的引用。 -![](../img/fig2-3.png) +![](img/fig2-3.png) @@ -334,11 +334,11 @@ JSON表示比[圖2-1](../img/fig2-1.png)中的多表模式具有更好的**區 -[圖2-4](../img/fig2-4.png)闡明瞭這些新功能需要如何使用多對多關係。每個虛線矩形內的資料可以分組成一個文件,但是對單位,學校和其他使用者的引用需要表示成引用,並且在查詢時需要連線。 +[圖2-4](img/fig2-4.png)闡明瞭這些新功能需要如何使用多對多關係。每個虛線矩形內的資料可以分組成一個文件,但是對單位,學校和其他使用者的引用需要表示成引用,並且在查詢時需要連線。 -![](../img/fig2-4.png) +![](img/fig2-4.png) @@ -358,7 +358,7 @@ JSON表示比[圖2-1](../img/fig2-1.png)中的多表模式具有更好的**區 -IMS的設計中使用了一個相當簡單的資料模型,稱為**層次模型(hierarchical model)**,它與文件資料庫使用的JSON模型有一些驚人的相似之處【2】。它將所有資料表示為巢狀在記錄中的記錄樹,這很像[圖2-2](../img/fig2-2.png)的JSON結構。 +IMS的設計中使用了一個相當簡單的資料模型,稱為**層次模型(hierarchical model)**,它與文件資料庫使用的JSON模型有一些驚人的相似之處【2】。它將所有資料表示為巢狀在記錄中的記錄樹,這很像[圖2-2](img/fig2-2.png)的JSON結構。 @@ -430,7 +430,7 @@ CODASYL中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資 -在一個方面,文件資料庫還原為層次模型:在其父記錄中儲存巢狀記錄([圖2-1](../img/fig2-1.png)中的一對多關係,如`positions`,`education`和`contact_info`),而不是在單獨的表中。 +在一個方面,文件資料庫還原為層次模型:在其父記錄中儲存巢狀記錄([圖2-1](img/fig2-1.png)中的一對多關係,如`positions`,`education`和`contact_info`),而不是在單獨的表中。 @@ -454,7 +454,7 @@ CODASYL中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資 -如果應用程式中的資料具有類似文件的結構(即,一對多關係樹,通常一次性載入整個樹),那麼使用文件模型可能是一個好主意。將類似文件的結構分解成多個表(如[圖2-1](../img/fig2-1.png)中的`positions`,`education`和`contact_info`)的關係技術可能導致繁瑣的模式和不必要的複雜的應用程式程式碼。 +如果應用程式中的資料具有類似文件的結構(即,一對多關係樹,通常一次性載入整個樹),那麼使用文件模型可能是一個好主意。將類似文件的結構分解成多個表(如[圖2-1](img/fig2-1.png)中的`positions`,`education`和`contact_info`)的關係技術可能導致繁瑣的模式和不必要的複雜的應用程式程式碼。 @@ -550,7 +550,7 @@ UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL -文件通常以單個連續字串形式進行儲存,編碼為JSON,XML或其二進位制變體(如MongoDB的BSON)。如果應用程式經常需要訪問整個文件(例如,將其渲染至網頁),那麼儲存區域性性會帶來效能優勢。如果將資料分割到多個表中(如[圖2-1](../img/fig2-1.png)所示),則需要進行多次索引查詢才能將其全部檢索出來,這可能需要更多的磁碟查詢並花費更多的時間。 +文件通常以單個連續字串形式進行儲存,編碼為JSON,XML或其二進位制變體(如MongoDB的BSON)。如果應用程式經常需要訪問整個文件(例如,將其渲染至網頁),那麼儲存區域性性會帶來效能優勢。如果將資料分割到多個表中(如[圖2-1](img/fig2-1.png)所示),則需要進行多次索引查詢才能將其全部檢索出來,這可能需要更多的磁碟查詢並花費更多的時間。 @@ -1068,11 +1068,11 @@ db.observations.aggregate([ -在本節中,我們將使用[圖2-5](../img/fig2-5.png)所示的示例。它可以從社交網路或系譜資料庫中獲得:它顯示了兩個人,來自愛達荷州的Lucy和來自法國Beaune的Alain。他們已婚,住在倫敦。 +在本節中,我們將使用[圖2-5](img/fig2-5.png)所示的示例。它可以從社交網路或系譜資料庫中獲得:它顯示了兩個人,來自愛達荷州的Lucy和來自法國Beaune的Alain。他們已婚,住在倫敦。 -![](../img/fig2-5.png) +![](img/fig2-5.png) @@ -1174,7 +1174,7 @@ CREATE INDEX edges_heads ON edges (head_vertex); -這些特性為資料建模提供了很大的靈活性,如[圖2-5](../img/fig2-5.png)所示。圖中顯示了一些傳統關係模式難以表達的事情,例如不同國家的不同地區結構(法國有省和州,美國有不同的州和州),國中國的怪事(先忽略主權國家和國家錯綜複雜的爛攤子),不同的資料粒度(Lucy現在的住所被指定為一個城市,而她的出生地點只是在一個州的級別)。 +這些特性為資料建模提供了很大的靈活性,如[圖2-5](img/fig2-5.png)所示。圖中顯示了一些傳統關係模式難以表達的事情,例如不同國家的不同地區結構(法國有省和州,美國有不同的州和州),國中國的怪事(先忽略主權國家和國家錯綜複雜的爛攤子),不同的資料粒度(Lucy現在的住所被指定為一個城市,而她的出生地點只是在一個州的級別)。 @@ -1190,7 +1190,7 @@ Cypher是屬性圖的宣告式查詢語言,為Neo4j圖形資料庫而發明【 -[例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`為頭節點。 @@ -1218,7 +1218,7 @@ CREATE -當[圖2-5](../img/fig2-5.png)的所有頂點和邊被新增到資料庫後,讓我們提些有趣的問題:例如,找到所有從美國移民到歐洲的人的名字。更確切地說,這裡我們想要找到符合下麵條件的所有頂點,並且返回這些頂點的`name`屬性:該頂點擁有一條連到美國任一位置的`BORN_IN`邊,和一條連到歐洲的任一位置的`LIVING_IN`邊。 +當[圖2-5](img/fig2-5.png)的所有頂點和邊被新增到資料庫後,讓我們提些有趣的問題:例如,找到所有從美國移民到歐洲的人的名字。更確切地說,這裡我們想要找到符合下麵條件的所有頂點,並且返回這些頂點的`name`屬性:該頂點擁有一條連到美國任一位置的`BORN_IN`邊,和一條連到歐洲的任一位置的`LIVING_IN`邊。 @@ -1794,11 +1794,11 @@ Cypher和SPARQL使用SELECT立即跳轉,但是Datalog一次只進行一小步 -透過重複應用規則1和2,`within_recursive`謂語可以告訴我們在資料庫中包含北美(或任何其他位置名稱)的所有位置。這個過程如[圖2-6](../img/fig2-6.png)所示。 +透過重複應用規則1和2,`within_recursive`謂語可以告訴我們在資料庫中包含北美(或任何其他位置名稱)的所有位置。這個過程如[圖2-6](img/fig2-6.png)所示。 -![](../img/fig2-6.png) +![](img/fig2-6.png) diff --git a/zh-tw/ch3.md b/zh-tw/ch3.md index 301edd4..aeccaf1 100644 --- a/zh-tw/ch3.md +++ b/zh-tw/ch3.md @@ -2,7 +2,7 @@ -![](../img/ch3.png) +![](img/ch3.png) @@ -168,11 +168,11 @@ $ cat database -假設我們的資料儲存只是一個追加寫入的檔案,就像前面的例子一樣。那麼最簡單的索引策略就是:保留一個記憶體中的雜湊對映,其中每個鍵都對映到一個數據檔案中的位元組偏移量,指明瞭可以找到對應值的位置,如[圖3-1](../img/fig3-1.png)所示。當你將新的鍵值對追加寫入檔案中時,還要更新雜湊對映,以反映剛剛寫入的資料的偏移量(這同時適用於插入新鍵與更新現有鍵)。當你想查詢一個值時,使用雜湊對映來查詢資料檔案中的偏移量,**尋找(seek)** 該位置並讀取該值。 +假設我們的資料儲存只是一個追加寫入的檔案,就像前面的例子一樣。那麼最簡單的索引策略就是:保留一個記憶體中的雜湊對映,其中每個鍵都對映到一個數據檔案中的位元組偏移量,指明瞭可以找到對應值的位置,如[圖3-1](img/fig3-1.png)所示。當你將新的鍵值對追加寫入檔案中時,還要更新雜湊對映,以反映剛剛寫入的資料的偏移量(這同時適用於插入新鍵與更新現有鍵)。當你想查詢一個值時,使用雜湊對映來查詢資料檔案中的偏移量,**尋找(seek)** 該位置並讀取該值。 -![](../img/fig3-1.png) +![](img/fig3-1.png) @@ -188,11 +188,11 @@ $ cat database -直到現在,我們只是追加寫入一個檔案 —— 所以如何避免最終用完磁碟空間?一種好的解決方案是,將日誌分為特定大小的段,當日志增長到特定尺寸時關閉當前段檔案,並開始寫入一個新的段檔案。然後,我們就可以對這些段進行**壓縮(compaction)**,如[圖3-2](../img/fig3-2.png)所示。壓縮意味著在日誌中丟棄重複的鍵,只保留每個鍵的最近更新。 +直到現在,我們只是追加寫入一個檔案 —— 所以如何避免最終用完磁碟空間?一種好的解決方案是,將日誌分為特定大小的段,當日志增長到特定尺寸時關閉當前段檔案,並開始寫入一個新的段檔案。然後,我們就可以對這些段進行**壓縮(compaction)**,如[圖3-2](img/fig3-2.png)所示。壓縮意味著在日誌中丟棄重複的鍵,只保留每個鍵的最近更新。 -![](../img/fig3-2.png) +![](img/fig3-2.png) @@ -200,11 +200,11 @@ $ cat database -而且,由於壓縮經常會使得段變得很小(假設在一個段內鍵被平均重寫了好幾次),我們也可以在執行壓縮的同時將多個段合併在一起,如[圖3-3](../img/fig3-3.png)所示。段被寫入後永遠不會被修改,所以合併的段被寫入一個新的檔案。凍結段的合併和壓縮可以在後臺執行緒中完成,在進行時,我們仍然可以繼續使用舊的段檔案來正常提供讀寫請求。合併過程完成後,我們將讀取請求轉換為使用新的合併段而不是舊段 —— 然後可以簡單地刪除舊的段檔案。 +而且,由於壓縮經常會使得段變得很小(假設在一個段內鍵被平均重寫了好幾次),我們也可以在執行壓縮的同時將多個段合併在一起,如[圖3-3](img/fig3-3.png)所示。段被寫入後永遠不會被修改,所以合併的段被寫入一個新的檔案。凍結段的合併和壓縮可以在後臺執行緒中完成,在進行時,我們仍然可以繼續使用舊的段檔案來正常提供讀寫請求。合併過程完成後,我們將讀取請求轉換為使用新的合併段而不是舊段 —— 然後可以簡單地刪除舊的段檔案。 -![](../img/fig3-3.png) +![](img/fig3-3.png) @@ -298,7 +298,7 @@ $ cat database -在[圖3-3](../img/fig3-3.png)中,每個日誌結構儲存段都是一系列鍵值對。這些對按照它們寫入的順序出現,日誌中稍後的值優先於日誌中較早的相同鍵的值。除此之外,檔案中鍵值對的順序並不重要。 +在[圖3-3](img/fig3-3.png)中,每個日誌結構儲存段都是一系列鍵值對。這些對按照它們寫入的順序出現,日誌中稍後的值優先於日誌中較早的相同鍵的值。除此之外,檔案中鍵值對的順序並不重要。 @@ -310,15 +310,15 @@ $ cat database -1. 合併段是簡單而高效的,即使檔案大於可用記憶體。這種方法就像歸併排序演算法中使用的方法一樣,如[圖3-4](../img/fig3-4.png)所示:您開始並排讀取輸入檔案,檢視每個檔案中的第一個鍵,複製最低鍵(根據排序順序)到輸出檔案,並重復。這產生一個新的合併段檔案,也按鍵排序。 +1. 合併段是簡單而高效的,即使檔案大於可用記憶體。這種方法就像歸併排序演算法中使用的方法一樣,如[圖3-4](img/fig3-4.png)所示:您開始並排讀取輸入檔案,檢視每個檔案中的第一個鍵,複製最低鍵(根據排序順序)到輸出檔案,並重復。這產生一個新的合併段檔案,也按鍵排序。 - ![](../img/fig3-4.png) + ![](img/fig3-4.png) - ##### 圖3-4 合併幾個SSTable段,只保留每個鍵的最新值 + **圖3-4 合併幾個SSTable段,只保留每個鍵的最新值** @@ -326,11 +326,11 @@ $ cat database -2. 為了在檔案中找到一個特定的鍵,你不再需要儲存記憶體中所有鍵的索引。以[圖3-5](../img/fig3-5.png)為例:假設你正在記憶體中尋找鍵 `handiwork`,但是你不知道段檔案中該關鍵字的確切偏移量。然而,你知道 `handbag` 和 `handsome` 的偏移,而且由於排序特性,你知道 `handiwork` 必須出現在這兩者之間。這意味著您可以跳到 `handbag` 的偏移位置並從那裡掃描,直到您找到 `handiwork`(或沒找到,如果該檔案中沒有該鍵)。 +2. 為了在檔案中找到一個特定的鍵,你不再需要儲存記憶體中所有鍵的索引。以[圖3-5](img/fig3-5.png)為例:假設你正在記憶體中尋找鍵 `handiwork`,但是你不知道段檔案中該關鍵字的確切偏移量。然而,你知道 `handbag` 和 `handsome` 的偏移,而且由於排序特性,你知道 `handiwork` 必須出現在這兩者之間。這意味著您可以跳到 `handbag` 的偏移位置並從那裡掃描,直到您找到 `handiwork`(或沒找到,如果該檔案中沒有該鍵)。 - ![](../img/fig3-5.png) + ![](img/fig3-5.png) @@ -344,7 +344,7 @@ $ cat database -3. 由於讀取請求無論如何都需要掃描所請求範圍內的多個鍵值對,因此可以將這些記錄分組到塊中,並在將其寫入磁碟之前對其進行壓縮(如[圖3-5](../img/fig3-5.png)中的陰影區域所示) 。稀疏記憶體中索引的每個條目都指向壓縮塊的開始處。除了節省磁碟空間之外,壓縮還可以減少IO頻寬的使用。 +3. 由於讀取請求無論如何都需要掃描所請求範圍內的多個鍵值對,因此可以將這些記錄分組到塊中,並在將其寫入磁碟之前對其進行壓縮(如[圖3-5](img/fig3-5.png)中的陰影區域所示) 。稀疏記憶體中索引的每個條目都指向壓縮塊的開始處。除了節省磁碟空間之外,壓縮還可以減少IO頻寬的使用。 @@ -436,11 +436,11 @@ Lucene是Elasticsearch和Solr使用的一種全文搜尋的索引引擎,它使 -每個頁面都可以使用地址或位置來標識,這允許一個頁面引用另一個頁面 —— 類似於指標,但在磁碟而不是在記憶體中。我們可以使用這些頁面引用來構建一個頁面樹,如[圖3-6](../img/fig3-6.png)所示。 +每個頁面都可以使用地址或位置來標識,這允許一個頁面引用另一個頁面 —— 類似於指標,但在磁碟而不是在記憶體中。我們可以使用這些頁面引用來構建一個頁面樹,如[圖3-6](img/fig3-6.png)所示。 -![](../img/fig3-6.png) +![](img/fig3-6.png) @@ -452,7 +452,7 @@ Lucene是Elasticsearch和Solr使用的一種全文搜尋的索引引擎,它使 -在[圖3-6](../img/fig3-6.png)的例子中,我們正在尋找關鍵字 251 ,所以我們知道我們需要遵循邊界 200 和 300 之間的頁面引用。這將我們帶到一個類似的頁面,進一步打破了200 - 300到子範圍。 +在[圖3-6](img/fig3-6.png)的例子中,我們正在尋找關鍵字 251 ,所以我們知道我們需要遵循邊界 200 和 300 之間的頁面引用。這將我們帶到一個類似的頁面,進一步打破了200 - 300到子範圍。 @@ -460,11 +460,11 @@ Lucene是Elasticsearch和Solr使用的一種全文搜尋的索引引擎,它使 -在B樹的一個頁面中對子頁面的引用的數量稱為分支因子。例如,在[圖3-6](../img/fig3-6.png)中,分支因子是 6 。在實踐中,分支因子取決於儲存頁面參考和範圍邊界所需的空間量,但通常是幾百個。 +在B樹的一個頁面中對子頁面的引用的數量稱為分支因子。例如,在[圖3-6](img/fig3-6.png)中,分支因子是 6 。在實踐中,分支因子取決於儲存頁面參考和範圍邊界所需的空間量,但通常是幾百個。 -如果要更新B樹中現有鍵的值,則搜尋包含該鍵的葉頁,更改該頁中的值,並將該頁寫回到磁碟(對該頁的任何引用保持有效) 。如果你想新增一個新的鍵,你需要找到其範圍包含新鍵的頁面,並將其新增到該頁面。如果頁面中沒有足夠的可用空間容納新鍵,則將其分成兩個半滿頁面,並更新父頁面以解釋鍵範圍的新分割槽,如[圖3-7](../img/fig3-7.png)所示[^ii]。 +如果要更新B樹中現有鍵的值,則搜尋包含該鍵的葉頁,更改該頁中的值,並將該頁寫回到磁碟(對該頁的任何引用保持有效) 。如果你想新增一個新的鍵,你需要找到其範圍包含新鍵的頁面,並將其新增到該頁面。如果頁面中沒有足夠的可用空間容納新鍵,則將其分成兩個半滿頁面,並更新父頁面以解釋鍵範圍的新分割槽,如[圖3-7](img/fig3-7.png)所示[^ii]。 @@ -472,7 +472,7 @@ Lucene是Elasticsearch和Solr使用的一種全文搜尋的索引引擎,它使 -![](../img/fig3-7.png) +![](img/fig3-7.png) @@ -600,7 +600,7 @@ B樹在資料庫體系結構中是非常根深蒂固的,為許多工作負載 -有二級索引也很常見。在關係資料庫中,您可以使用 `CREATE INDEX` 命令在同一個表上建立多個二級索引,而且這些索引通常對於有效地執行聯接而言至關重要。例如,在[第2章](ch2.md)中的[圖2-1](../img/fig2-1.png)中,很可能在 `user_id` 列上有一個二級索引,以便您可以在每個表中找到屬於同一使用者的所有行。 +有二級索引也很常見。在關係資料庫中,您可以使用 `CREATE INDEX` 命令在同一個表上建立多個二級索引,而且這些索引通常對於有效地執行聯接而言至關重要。例如,在[第2章](ch2.md)中的[圖2-1](img/fig2-1.png)中,很可能在 `user_id` 列上有一個二級索引,以便您可以在每個表中找到屬於同一使用者的所有行。 @@ -800,11 +800,11 @@ SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079 -相比之下,資料倉庫是一個獨立的資料庫,分析人員可以查詢他們想要的內容而不影響OLTP操作【48】。資料倉庫包含公司各種OLTP系統中所有的只讀資料副本。從OLTP資料庫中提取資料(使用定期的資料轉儲或連續的更新流),轉換成適合分析的模式,清理並載入到資料倉庫中。將資料存入倉庫的過程稱為“**抽取-轉換-載入(ETL)**”,如[圖3-8](../img/fig3-8)所示。 +相比之下,資料倉庫是一個獨立的資料庫,分析人員可以查詢他們想要的內容而不影響OLTP操作【48】。資料倉庫包含公司各種OLTP系統中所有的只讀資料副本。從OLTP資料庫中提取資料(使用定期的資料轉儲或連續的更新流),轉換成適合分析的模式,清理並載入到資料倉庫中。將資料存入倉庫的過程稱為“**抽取-轉換-載入(ETL)**”,如[圖3-8](img/fig3-8.png)所示。 -![](../img/fig3-8.png) +![](img/fig3-8.png) @@ -852,7 +852,7 @@ Teradata,Vertica,SAP HANA和ParAccel等資料倉庫供應商通常使用昂 -![](../img/fig3-9.png) +![](img/fig3-9.png) @@ -868,7 +868,7 @@ Teradata,Vertica,SAP HANA和ParAccel等資料倉庫供應商通常使用昂 -例如,在[圖3-9](../img/fig3-9.md)中,其中一個維度是已售出的產品。 `dim_product` 表中的每一行代表一種待售產品,包括**庫存單位(SKU)**,說明,品牌名稱,類別,脂肪含量,包裝尺寸等。`fact_sales` 表中的每一行都使用外部表明在特定交易中銷售了哪些產品。 (為了簡單起見,如果客戶一次購買幾種不同的產品,則它們在事實表中被表示為單獨的行)。 +例如,在[圖3-9](img/fig3-9.md)中,其中一個維度是已售出的產品。 `dim_product` 表中的每一行代表一種待售產品,包括**庫存單位(SKU)**,說明,品牌名稱,類別,脂肪含量,包裝尺寸等。`fact_sales` 表中的每一行都使用外部表明在特定交易中銷售了哪些產品。 (為了簡單起見,如果客戶一次購買幾種不同的產品,則它們在事實表中被表示為單獨的行)。 @@ -942,7 +942,7 @@ GROUP BY -在大多數OLTP資料庫中,儲存都是以面向行的方式進行佈局的:表格的一行中的所有值都相鄰儲存。文件資料庫是相似的:整個文件通常儲存為一個連續的位元組序列。你可以在[圖3-1](../img/fig3-1.png)的CSV例子中看到這個。 +在大多數OLTP資料庫中,儲存都是以面向行的方式進行佈局的:表格的一行中的所有值都相鄰儲存。文件資料庫是相似的:整個文件通常儲存為一個連續的位元組序列。你可以在[圖3-1](img/fig3-1.png)的CSV例子中看到這個。 @@ -950,11 +950,11 @@ GROUP BY -面向列的儲存背後的想法很簡單:不要將所有來自一行的值儲存在一起,而是將來自每一列的所有值儲存在一起。如果每個列儲存在一個單獨的檔案中,查詢只需要讀取和解析查詢中使用的那些列,這可以節省大量的工作。這個原理如[圖3-10](../img/fig3-10.png)所示。 +面向列的儲存背後的想法很簡單:不要將所有來自一行的值儲存在一起,而是將來自每一列的所有值儲存在一起。如果每個列儲存在一個單獨的檔案中,查詢只需要讀取和解析查詢中使用的那些列,這可以節省大量的工作。這個原理如[圖3-10](img/fig3-10.png)所示。 -![](../img/fig3-10.png) +![](img/fig3-10.png) @@ -982,11 +982,11 @@ GROUP BY -看看[圖3-10](../img/fig3-10.png)中每一列的值序列:它們通常看起來是相當重複的,這是壓縮的好兆頭。根據列中的資料,可以使用不同的壓縮技術。在資料倉庫中特別有效的一種技術是點陣圖編碼,如[圖3-11](../img/fig3-11.png)所示。 +看看[圖3-10](img/fig3-10.png)中每一列的值序列:它們通常看起來是相當重複的,這是壓縮的好兆頭。根據列中的資料,可以使用不同的壓縮技術。在資料倉庫中特別有效的一種技術是點陣圖編碼,如[圖3-11](img/fig3-11.png)所示。 -![](../img/fig3-11.png) +![](img/fig3-11.png) @@ -1076,11 +1076,11 @@ WHERE product_sk = 31 AND store_sk = 3 -第二列可以確定第一列中具有相同值的任何行的排序順序。例如,如果 `date_key` 是[圖3-10](../img/fig3-10.png)中的第一個排序關鍵字,那麼 `product_sk` 可能是第二個排序關鍵字,因此同一天的同一產品的所有銷售都將在儲存中組合在一起。這將有助於需要在特定日期範圍內按產品對銷售進行分組或過濾的查詢。 +第二列可以確定第一列中具有相同值的任何行的排序順序。例如,如果 `date_key` 是[圖3-10](img/fig3-10.png)中的第一個排序關鍵字,那麼 `product_sk` 可能是第二個排序關鍵字,因此同一天的同一產品的所有銷售都將在儲存中組合在一起。這將有助於需要在特定日期範圍內按產品對銷售進行分組或過濾的查詢。 -排序順序的另一個好處是它可以幫助壓縮列。如果主要排序列沒有多個不同的值,那麼在排序之後,它將具有很長的序列,其中相同的值連續重複多次。一個簡單的遊程編碼(就像我們用於[圖3-11](../img/fig3-11.png)中的點陣圖一樣)可以將該列壓縮到幾千位元組 —— 即使表中有數十億行。 +排序順序的另一個好處是它可以幫助壓縮列。如果主要排序列沒有多個不同的值,那麼在排序之後,它將具有很長的序列,其中相同的值連續重複多次。一個簡單的遊程編碼(就像我們用於[圖3-11](img/fig3-11.png)中的點陣圖一樣)可以將該列壓縮到幾千位元組 —— 即使表中有數十億行。 @@ -1140,11 +1140,11 @@ WHERE product_sk = 31 AND store_sk = 3 -物化檢視的常見特例稱為資料立方體或OLAP立方【64】。它是按不同維度分組的聚合網格。[圖3-12](../img/fig3-12.png)顯示了一個例子。 +物化檢視的常見特例稱為資料立方體或OLAP立方【64】。它是按不同維度分組的聚合網格。[圖3-12](img/fig3-12.png)顯示了一個例子。 -![](../img/fig3-12.png) +![](img/fig3-12.png) @@ -1152,7 +1152,7 @@ WHERE product_sk = 31 AND store_sk = 3 -想象一下,現在每個事實都只有兩個維度表的外來鍵——在[圖3-12](../img/fig-3-12.png)中,這些是日期和產品。您現在可以繪製一個二維表格,一個軸線上是日期,另一個軸線上是產品。每個單元包含具有該日期 - 產品組合的所有事實的屬性(例如,`net_price`)的聚集(例如,`SUM`)。然後,您可以沿著每行或每列應用相同的彙總,並獲得一個維度減少的彙總(按產品的銷售額,無論日期,還是按日期銷售,無論產品如何)。 +想象一下,現在每個事實都只有兩個維度表的外來鍵——在[圖3-12](img/fig-3-12.png)中,這些是日期和產品。您現在可以繪製一個二維表格,一個軸線上是日期,另一個軸線上是產品。每個單元包含具有該日期 - 產品組合的所有事實的屬性(例如,`net_price`)的聚集(例如,`SUM`)。然後,您可以沿著每行或每列應用相同的彙總,並獲得一個維度減少的彙總(按產品的銷售額,無論日期,還是按日期銷售,無論產品如何)。 diff --git a/zh-tw/ch4.md b/zh-tw/ch4.md index c690028..422391f 100644 --- a/zh-tw/ch4.md +++ b/zh-tw/ch4.md @@ -2,7 +2,7 @@ -![](../img/ch4.png) +![](img/ch4.png) @@ -230,7 +230,7 @@ JSON比XML簡潔,但與二進位制格式相比還是太佔空間。這一事 -![](../img/fig4-1.png) +![](img/fig4-1.png) @@ -286,11 +286,11 @@ message Person { Thrift和Protocol Buffers每一個都帶有一個程式碼生成工具,它採用了類似於這裡所示的模式定義,並且生成了以各種程式語言實現模式的類【18】。您的應用程式程式碼可以呼叫此生成的程式碼來對模式的記錄進行編碼或解碼。 -用這個模式編碼的資料是什麼樣的?令人困惑的是,Thrift有兩種不同的二進位制編碼格式[^iii],分別稱為BinaryProtocol和CompactProtocol。先來看看BinaryProtocol。使用這種格式的編碼來編碼[例4-1]()中的訊息只需要59個位元組,如[圖4-2](../img/fig4-2.png)所示【19】。 +用這個模式編碼的資料是什麼樣的?令人困惑的是,Thrift有兩種不同的二進位制編碼格式[^iii],分別稱為BinaryProtocol和CompactProtocol。先來看看BinaryProtocol。使用這種格式的編碼來編碼[例4-1]()中的訊息只需要59個位元組,如[圖4-2](img/fig4-2.png)所示【19】。 -![](../img/fig4-2.png) +![](img/fig4-2.png) @@ -306,15 +306,15 @@ Thrift和Protocol Buffers每一個都帶有一個程式碼生成工具,它採 -與[圖4-1](../img/fig4-1.png)相比,最大的區別是沒有欄位名`(userName, favoriteNumber, interest)`。相反,編碼資料包含欄位標籤,它們是數字`(1, 2和3)`。這些是模式定義中出現的數字。欄位標記就像欄位的別名 - 它們是說我們正在談論的欄位的一種緊湊的方式,而不必拼出欄位名稱。 +與[圖4-1](img/fig4-1.png)相比,最大的區別是沒有欄位名`(userName, favoriteNumber, interest)`。相反,編碼資料包含欄位標籤,它們是數字`(1, 2和3)`。這些是模式定義中出現的數字。欄位標記就像欄位的別名 - 它們是說我們正在談論的欄位的一種緊湊的方式,而不必拼出欄位名稱。 -Thrift CompactProtocol編碼在語義上等同於BinaryProtocol,但是如[圖4-3](../img/fig4-3.png)所示,它只將相同的資訊打包成只有34個位元組。它透過將欄位型別和標籤號打包到單個位元組中,並使用可變長度整數來實現。數字1337不是使用全部八個位元組,而是用兩個位元組編碼,每個位元組的最高位用來指示是否還有更多的位元組來。這意味著-64到63之間的數字被編碼為一個位元組,-8192和8191之間的數字以兩個位元組編碼,等等。較大的數字使用更多的位元組。 +Thrift CompactProtocol編碼在語義上等同於BinaryProtocol,但是如[圖4-3](img/fig4-3.png)所示,它只將相同的資訊打包成只有34個位元組。它透過將欄位型別和標籤號打包到單個位元組中,並使用可變長度整數來實現。數字1337不是使用全部八個位元組,而是用兩個位元組編碼,每個位元組的最高位用來指示是否還有更多的位元組來。這意味著-64到63之間的數字被編碼為一個位元組,-8192和8191之間的數字以兩個位元組編碼,等等。較大的數字使用更多的位元組。 -![](../img/fig4-3.png) +![](img/fig4-3.png) @@ -322,11 +322,11 @@ Thrift CompactProtocol編碼在語義上等同於BinaryProtocol,但是如[圖4 -最後,Protocol Buffers(只有一種二進位制編碼格式)對相同的資料進行編碼,如[圖4-4](../img/fig4-4.png)所示。 它的打包方式稍有不同,但與Thrift的CompactProtocol非常相似。 Protobuf將同樣的記錄塞進了33個位元組中。 +最後,Protocol Buffers(只有一種二進位制編碼格式)對相同的資料進行編碼,如[圖4-4](img/fig4-4.png)所示。 它的打包方式稍有不同,但與Thrift的CompactProtocol非常相似。 Protobuf將同樣的記錄塞進了33個位元組中。 -![](../img/fig4-4.png) +![](img/fig4-4.png) @@ -370,7 +370,7 @@ Thrift CompactProtocol編碼在語義上等同於BinaryProtocol,但是如[圖4 -Protobuf的一個奇怪的細節是,它沒有列表或陣列資料型別,而是有一個欄位的重複標記(這是除必需和可選之外的第三個選項)。如[圖4-4](../img/fig4-4.png)所示,重複欄位的編碼正如它所說的那樣:同一個欄位標記只是簡單地出現在記錄中。這具有很好的效果,可以將可選(單值)欄位更改為重複(多值)欄位。讀取舊資料的新程式碼會看到一個包含零個或一個元素的列表(取決於該欄位是否存在)。讀取新資料的舊程式碼只能看到列表的最後一個元素。 +Protobuf的一個奇怪的細節是,它沒有列表或陣列資料型別,而是有一個欄位的重複標記(這是除必需和可選之外的第三個選項)。如[圖4-4](img/fig4-4.png)所示,重複欄位的編碼正如它所說的那樣:同一個欄位標記只是簡單地出現在記錄中。這具有很好的效果,可以將可選(單值)欄位更改為重複(多值)欄位。讀取舊資料的新程式碼會看到一個包含零個或一個元素的列表(取決於該欄位是否存在)。讀取新資料的舊程式碼只能看到列表的最後一個元素。 @@ -438,7 +438,7 @@ record Person { -首先,請注意模式中沒有標籤號碼。 如果我們使用這個模式編碼我們的例子記錄([例4-1]()),Avro二進位制編碼只有32個位元組長,這是我們所見過的所有編碼中最緊湊的。 編碼位元組序列的分解如[圖4-5](../img/fig4-5.png)所示。 +首先,請注意模式中沒有標籤號碼。 如果我們使用這個模式編碼我們的例子記錄([例4-1]()),Avro二進位制編碼只有32個位元組長,這是我們所見過的所有編碼中最緊湊的。 編碼位元組序列的分解如[圖4-5](img/fig4-5.png)所示。 @@ -446,7 +446,7 @@ record Person { -![](../img/fig4-5.png) +![](img/fig4-5.png) @@ -474,7 +474,7 @@ record Person { -Avro的關鍵思想是Writer模式和Reader模式不必是相同的 - 他們只需要相容。當資料解碼(讀取)時,Avro庫透過並排檢視Writer模式和Reader模式並將資料從Writer模式轉換到Reader模式來解決差異。 Avro規範【20】確切地定義了這種解析的工作原理,如[圖4-6](../img/fig4-6.png)所示。 +Avro的關鍵思想是Writer模式和Reader模式不必是相同的 - 他們只需要相容。當資料解碼(讀取)時,Avro庫透過並排檢視Writer模式和Reader模式並將資料從Writer模式轉換到Reader模式來解決差異。 Avro規範【20】確切地定義了這種解析的工作原理,如[圖4-6](img/fig4-6.png)所示。 @@ -482,7 +482,7 @@ Avro的關鍵思想是Writer模式和Reader模式不必是相同的 - 他們只 -![](../img/fig4-6.png) +![](img/fig4-6.png) @@ -518,7 +518,7 @@ Avro的關鍵思想是Writer模式和Reader模式不必是相同的 - 他們只 -##### 但Writer模式到底是什麼? +#### 但Writer模式到底是什麼? @@ -692,7 +692,7 @@ Avro為靜態型別程式語言提供了可選的程式碼生成功能,但是 -![](../img/fig4-7.png) +![](img/fig4-7.png) @@ -964,7 +964,7 @@ RPC方案的前後向相容性屬性從它使用的編碼方式中繼承: -如果消費者重新發布訊息到另一個主題,則可能需要小心保留未知欄位,以防止前面在資料庫環境中描述的問題([圖4-7](../img/fig4-7.png))。 +如果消費者重新發布訊息到另一個主題,則可能需要小心保留未知欄位,以防止前面在資料庫環境中描述的問題([圖4-7](img/fig4-7.png))。 diff --git a/zh-tw/ch5.md b/zh-tw/ch5.md index 165a61c..59ce482 100644 --- a/zh-tw/ch5.md +++ b/zh-tw/ch5.md @@ -2,7 +2,7 @@ -![](../img/ch5.png) +![](img/ch5.png) @@ -74,7 +74,7 @@ -![](../img/fig5-1.png) +![](img/fig5-1.png) **圖5-1 基於領導者(主-從)的複製** @@ -96,17 +96,17 @@ -[圖5-2](../img/fig5-2.png)顯示了系統各個元件之間的通訊:使用者客戶端,主庫和兩個從庫。時間從左到右流動。請求或響應訊息用粗箭頭表示。 +[圖5-2](img/fig5-2.png)顯示了系統各個元件之間的通訊:使用者客戶端,主庫和兩個從庫。時間從左到右流動。請求或響應訊息用粗箭頭表示。 -![](../img/fig5-2.png) +![](img/fig5-2.png) **圖5-2 基於領導者的複製:一個同步從庫和一個非同步從庫** -​ 在[圖5-2]()的示例中,從庫1的複製是同步的:在向用戶報告寫入成功,並使結果對其他使用者可見之前,主庫需要等待從庫1的確認,確保從庫1已經收到寫入操作。以及在使寫入對其他客戶端可見之前接收到寫入。跟隨者2的複製是非同步的:主庫傳送訊息,但不等待從庫的響應。 +​ 在[圖5-2](img/fig5-2.png)的示例中,從庫1的複製是同步的:在向用戶報告寫入成功,並使結果對其他使用者可見之前,主庫需要等待從庫1的確認,確保從庫1已經收到寫入操作。以及在使寫入對其他客戶端可見之前接收到寫入。跟隨者2的複製是非同步的:主庫傳送訊息,但不等待從庫的響應。 @@ -412,7 +412,7 @@ -![](../img/fig5-3.png) +![](img/fig5-3.png) @@ -474,11 +474,11 @@ -​ 如果使用者從不同從庫進行多次讀取,就可能發生這種情況。例如,[圖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) @@ -522,7 +522,7 @@ -​ 現在,想象第三個人正在透過從庫來聽這個對話。 Cake夫人說的內容是從一個延遲很低的從庫讀取的,但Poons先生所說的內容,從庫的延遲要大的多(見[圖5-5](../img/fig5-5.png))。 於是,這個觀察者會聽到以下內容: +​ 現在,想象第三個人正在透過從庫來聽這個對話。 Cake夫人說的內容是從一個延遲很低的從庫讀取的,但Poons先生所說的內容,從庫的延遲要大的多(見[圖5-5](img/fig5-5.png))。 於是,這個觀察者會聽到以下內容: @@ -544,7 +544,7 @@ -![](../img/fig5-5.png) +![](img/fig5-5.png) @@ -556,7 +556,7 @@ -​ 這是**分割槽(partitioned)**(**分片(sharded)**)資料庫中的一個特殊問題,將在第6章中討論。如果資料庫總是以相同的順序應用寫入,則讀取總是會看到一致的字首,所以這種異常不會發生。但是在許多分散式資料庫中,不同的分割槽獨立執行,因此不存在**全域性寫入順序**:當用戶從資料庫中讀取資料時,可能會看到資料庫的某些部分處於較舊的狀態,而某些處於較新的狀態。 +​ 這是**分割槽(partitioned)**(**分片(sharded)**)資料庫中的一個特殊問題,將在[第6章](ch6.md)中討論。如果資料庫總是以相同的順序應用寫入,則讀取總是會看到一致的字首,所以這種異常不會發生。但是在許多分散式資料庫中,不同的分割槽獨立執行,因此不存在**全域性寫入順序**:當用戶從資料庫中讀取資料時,可能會看到資料庫的某些部分處於較舊的狀態,而某些處於較新的狀態。 @@ -624,11 +624,11 @@ -​ 多領導者配置中可以在每個資料中心都有主庫。 [圖5-6](../img/fig5-6.png)展示了這個架構的樣子。 在每個資料中心內使用常規的主從複製;在資料中心之間,每個資料中心的主庫都會將其更改複製到其他資料中心的主庫中。 +​ 多領導者配置中可以在每個資料中心都有主庫。 [圖5-6](img/fig5-6.png)展示了這個架構的樣子。 在每個資料中心內使用常規的主從複製;在資料中心之間,每個資料中心的主庫都會將其更改複製到其他資料中心的主庫中。 -![](../img/fig5-6.png) +![](img/fig5-6.png) @@ -668,7 +668,7 @@ -​ 儘管多主複製有這些優勢,但也有一個很大的缺點:兩個不同的資料中心可能會同時修改相同的資料,寫衝突是必須解決的(如[圖5-6](../img/fig5-6.png)中“衝突解決(conflict resolution)”)。本書將在“[處理寫入衝突](#處理寫入衝突)”中詳細討論這個問題。 +​ 儘管多主複製有這些優勢,但也有一個很大的缺點:兩個不同的資料中心可能會同時修改相同的資料,寫衝突是必須解決的(如[圖5-6](img/fig5-6.png)中“衝突解決(conflict resolution)”)。本書將在“[處理寫入衝突](#處理寫入衝突)”中詳細討論這個問題。 @@ -724,11 +724,11 @@ -​ 例如,考慮一個由兩個使用者同時編輯的維基頁面,如[圖5-7](../img/fig5-7.png)所示。使用者1將頁面的標題從A更改為B,並且使用者2同時將標題從A更改為C。每個使用者的更改已成功應用到其本地主庫。但當非同步複製時,會發現衝突【33】。單主資料庫中不會出現此問題。 +​ 例如,考慮一個由兩個使用者同時編輯的維基頁面,如[圖5-7](img/fig5-7.png)所示。使用者1將頁面的標題從A更改為B,並且使用者2同時將標題從A更改為C。每個使用者的更改已成功應用到其本地主庫。但當非同步複製時,會發現衝突【33】。單主資料庫中不會出現此問題。 -![](../img/fig5-7.png) +![](img/fig5-7.png) @@ -772,7 +772,7 @@ -​ 在多主配置中,沒有明確的寫入順序,所以最終值應該是什麼並不清楚。在[圖5-7](../img/fig5-7.png)中,在主庫1中標題首先更新為B而後更新為C;在主庫2中,首先更新為C,然後更新為B。兩個順序都不是“更正確”的。 +​ 在多主配置中,沒有明確的寫入順序,所以最終值應該是什麼並不清楚。在[圖5-7](img/fig5-7.png)中,在主庫1中標題首先更新為B而後更新為C;在主庫2中,首先更新為C,然後更新為B。兩個順序都不是“更正確”的。 @@ -788,7 +788,7 @@ * 為每個副本分配一個唯一的ID,ID編號更高的寫入具有更高的優先順序。這種方法也意味著資料丟失。 -* 以某種方式將這些值合併在一起 - 例如,按字母順序排序,然後連線它們(在[圖5-7](../img/fig5-7.png)中,合併的標題可能類似於“B/C”)。 +* 以某種方式將這些值合併在一起 - 例如,按字母順序排序,然後連線它們(在[圖5-7](img/fig5-7.png)中,合併的標題可能類似於“B/C”)。 * 用一種可保留所有資訊的顯式資料結構來記錄衝突,並編寫解決衝突的應用程式程式碼(也許透過提示使用者的方式)。 @@ -864,7 +864,7 @@ -​ 有些衝突是顯而易見的。在[圖5-7](../img/fig5-7.png)的例子中,兩個寫操作併發地修改了同一條記錄中的同一個欄位,並將其設定為兩個不同的值。毫無疑問這是一個衝突。 +​ 有些衝突是顯而易見的。在[圖5-7](img/fig5-7.png)的例子中,兩個寫操作併發地修改了同一條記錄中的同一個欄位,並將其設定為兩個不同的值。毫無疑問這是一個衝突。 @@ -884,11 +884,11 @@ -​ **複製拓撲**(replication topology)描述寫入從一個節點傳播到另一個節點的通訊路徑。如果你有兩個領導者,如[圖5-7](../img/fig5-7.png)所示,只有一個合理的拓撲結構:領導者1必須把他所有的寫到領導者2,反之亦然。當有兩個以上的領導,各種不同的拓撲是可能的。[圖5-8](../img/fig5-8.png)舉例說明了一些例子。 +​ **複製拓撲**(replication topology)描述寫入從一個節點傳播到另一個節點的通訊路徑。如果你有兩個領導者,如[圖5-7](img/fig5-7.png)所示,只有一個合理的拓撲結構:領導者1必須把他所有的寫到領導者2,反之亦然。當有兩個以上的領導,各種不同的拓撲是可能的。[圖5-8](img/fig5-8.png)舉例說明了一些例子。 -![](../img/fig5-8.png) +![](img/fig5-8.png) @@ -896,7 +896,7 @@ -​ 最普遍的拓撲是全部到全部([圖5-8 (c)](../img/fig5-8.png)),其中每個領導者將其寫入每個其他領導。但是,也會使用更多受限制的拓撲:例如,預設情況下,MySQL僅支援**環形拓撲(circular topology)**【34】,其中每個節點接收來自一個節點的寫入,並將這些寫入(加上自己的任何寫入)轉發給另一個節點。另一種流行的拓撲結構具有星形的形狀[^v]。一個指定的根節點將寫入轉發給所有其他節點。星型拓撲可以推廣到樹。 +​ 最普遍的拓撲是全部到全部([圖5-8 (c)](img/fig5-8.png)),其中每個領導者將其寫入每個其他領導。但是,也會使用更多受限制的拓撲:例如,預設情況下,MySQL僅支援**環形拓撲(circular topology)**【34】,其中每個節點接收來自一個節點的寫入,並將這些寫入(加上自己的任何寫入)轉發給另一個節點。另一種流行的拓撲結構具有星形的形狀[^v]。一個指定的根節點將寫入轉發給所有其他節點。星型拓撲可以推廣到樹。 @@ -912,11 +912,11 @@ -​ 另一方面,全部到全部的拓撲也可能有問題。特別是,一些網路連結可能比其他網路連結更快(例如,由於網路擁塞),結果是一些複製訊息可能“超過”其他複製訊息,如[圖5-9](../img/fig5-9.png)所示。 +​ 另一方面,全部到全部的拓撲也可能有問題。特別是,一些網路連結可能比其他網路連結更快(例如,由於網路擁塞),結果是一些複製訊息可能“超過”其他複製訊息,如[圖5-9](img/fig5-9.png)所示。 -![](../img/fig5-9.png) +![](img/fig5-9.png) @@ -924,7 +924,7 @@ -​ 在[圖5-9](../img/fig5-9.png)中,客戶端A向主庫1的表中插入一行,客戶端B在主庫3上更新該行。然而,主庫2可以以不同的順序接收寫入:它可以首先接收更新(從它的角度來看,是對資料庫中不存在的行的更新),並且僅在稍後接收到相應的插入(其應該在更新之前)。 +​ 在[圖5-9](img/fig5-9.png)中,客戶端A向主庫1的表中插入一行,客戶端B在主庫3上更新該行。然而,主庫2可以以不同的順序接收寫入:它可以首先接收更新(從它的角度來看,是對資料庫中不存在的行的更新),並且僅在稍後接收到相應的插入(其應該在更新之前)。 @@ -972,11 +972,11 @@ -​ 另一方面,在無領導配置中,故障切換不存在。[圖5-10](../img/fig5-10.png)顯示了發生了什麼事情:客戶端(使用者1234)並行傳送寫入到所有三個副本,並且兩個可用副本接受寫入,但是不可用副本錯過了它。假設三個副本中的兩個承認寫入是足夠的:在使用者1234已經收到兩個確定的響應之後,我們認為寫入成功。客戶簡單地忽略了其中一個副本錯過了寫入的事實。 +​ 另一方面,在無領導配置中,故障切換不存在。[圖5-10](img/fig5-10.png)顯示了發生了什麼事情:客戶端(使用者1234)並行傳送寫入到所有三個副本,並且兩個可用副本接受寫入,但是不可用副本錯過了它。假設三個副本中的兩個承認寫入是足夠的:在使用者1234已經收到兩個確定的響應之後,我們認為寫入成功。客戶簡單地忽略了其中一個副本錯過了寫入的事實。 -![](../img/fig5-10.png) +![](img/fig5-10.png) @@ -1008,7 +1008,7 @@ -​ 當客戶端並行讀取多個節點時,它可以檢測到任何陳舊的響應。例如,在[圖5-10](../img/fig5-10.png)中,使用者2345獲得了來自副本3的版本6值和來自副本1和2的版本7值。客戶端發現副本3具有陳舊值,並將新值寫回到該副本。這種方法適用於讀頻繁的值。 +​ 當客戶端並行讀取多個節點時,它可以檢測到任何陳舊的響應。例如,在[圖5-10](img/fig5-10.png)中,使用者2345獲得了來自副本3的版本6值和來自副本1和2的版本7值。客戶端發現副本3具有陳舊值,並將新值寫回到該副本。這種方法適用於讀頻繁的值。 @@ -1028,7 +1028,7 @@ -​ 在[圖5-10](../img/fig5-10.png)的示例中,我們認為即使僅在三個副本中的兩個上進行處理,寫入仍然是成功的。如果三個副本中只有一個接受了寫入,會怎樣?我們能推多遠呢? +​ 在[圖5-10](img/fig5-10.png)的示例中,我們認為即使僅在三個副本中的兩個上進行處理,寫入仍然是成功的。如果三個副本中只有一個接受了寫入,會怎樣?我們能推多遠呢? @@ -1064,13 +1064,13 @@ * 對於$n = 3,w = 2,r = 2$,我們可以容忍一個不可用的節點。 -* 對於$n = 5,w = 3,r = 3$,我們可以容忍兩個不可用的節點。 這個案例如[圖5-11](../img/fig5-11.png)所示。 +* 對於$n = 5,w = 3,r = 3$,我們可以容忍兩個不可用的節點。 這個案例如[圖5-11](img/fig5-11.png)所示。 * 通常,讀取和寫入操作始終並行傳送到所有n個副本。 引數w和r決定我們等待多少個節點,即在我們認為讀或寫成功之前,有多少個節點需要報告成功。 -![](../img/fig5-11.png) +![](img/fig5-11.png) @@ -1090,7 +1090,7 @@ -​ 如果你有n個副本,並且你選擇w和r,使得$w + r> n$,你通常可以期望每個鍵的讀取都能返回最近寫入的值。情況就是這樣,因為你寫入的節點集合和你讀取的節點集合必須重疊。也就是說,您讀取的節點中必須至少有一個具有最新值的節點(如[圖5-11](../img/fig5-11.png)所示)。 +​ 如果你有n個副本,並且你選擇w和r,使得$w + r> n$,你通常可以期望每個鍵的讀取都能返回最近寫入的值。情況就是這樣,因為你寫入的節點集合和你讀取的節點集合必須重疊。也就是說,您讀取的節點中必須至少有一個具有最新值的節點(如[圖5-11](img/fig5-11.png)所示)。 @@ -1218,7 +1218,7 @@ -​ 問題在於,由於可變的網路延遲和部分故障,事件可能在不同的節點以不同的順序到達。例如,[圖5-12](../img/fig5-12.png)顯示了兩個客戶機A和B同時寫入三節點資料儲存區中的鍵X: +​ 問題在於,由於可變的網路延遲和部分故障,事件可能在不同的節點以不同的順序到達。例如,[圖5-12](img/fig5-12.png)顯示了兩個客戶機A和B同時寫入三節點資料儲存區中的鍵X: @@ -1230,7 +1230,7 @@ -![](../img/fig5-12.png) +![](img/fig5-12.png) @@ -1238,7 +1238,7 @@ -​ 如果每個節點只要接收到來自客戶端的寫入請求就簡單地覆蓋了某個鍵的值,那麼節點就會永久地不一致,如[圖5-12](../img/fig5-12.png)中的最終獲取請求所示:節點2認為 X 的最終值是 B,而其他節點認為值是 A 。 +​ 如果每個節點只要接收到來自客戶端的寫入請求就簡單地覆蓋了某個鍵的值,那麼節點就會永久地不一致,如[圖5-12](img/fig5-12.png)中的最終獲取請求所示:節點2認為 X 的最終值是 B,而其他節點認為值是 A 。 @@ -1258,7 +1258,7 @@ -​ 正如 **“最近”** 的引號所表明的,這個想法其實頗具誤導性。在[圖5-12](../img/fig5-12.png)的例子中,當客戶端向資料庫節點發送寫入請求時,客戶端都不知道另一個客戶端,因此不清楚哪一個先發生了。事實上,說“發生”是沒有意義的:我們說寫入是 **併發(concurrent)** 的,所以它們的順序是不確定的。 +​ 正如 **“最近”** 的引號所表明的,這個想法其實頗具誤導性。在[圖5-12](img/fig5-12.png)的例子中,當客戶端向資料庫節點發送寫入請求時,客戶端都不知道另一個客戶端,因此不清楚哪一個先發生了。事實上,說“發生”是沒有意義的:我們說寫入是 **併發(concurrent)** 的,所以它們的順序是不確定的。 @@ -1332,7 +1332,7 @@ -[圖5-13](../img/fig5-13.png)顯示了兩個客戶端同時向同一購物車新增專案。 (如果這樣的例子讓你覺得太麻煩了,那麼可以想象,兩個空中交通管制員同時把飛機新增到他們正在跟蹤的區域)最初,購物車是空的。在它們之間,客戶端向資料庫發出五次寫入: +[圖5-13](img/fig5-13.png)顯示了兩個客戶端同時向同一購物車新增專案。 (如果這樣的例子讓你覺得太麻煩了,那麼可以想象,兩個空中交通管制員同時把飛機新增到他們正在跟蹤的區域)最初,購物車是空的。在它們之間,客戶端向資料庫發出五次寫入: @@ -1348,7 +1348,7 @@ -![](../img/fig5-13.png) +![](img/fig5-13.png) @@ -1356,11 +1356,11 @@ -​ [圖5-13](../img/fig5-13.png)中的操作之間的資料流如[圖5-14](../img/fig5-14.png)所示。 箭頭表示哪個操作發生在其他操作之前,意味著後面的操作知道或依賴於較早的操作。 在這個例子中,客戶端永遠不會完全掌握伺服器上的資料,因為總是有另一個操作同時進行。 但是,舊版本的值最終會被覆蓋,並且不會丟失任何寫入。 +​ [圖5-13](img/fig5-13.png)中的操作之間的資料流如[圖5-14](img/fig5-14.png)所示。 箭頭表示哪個操作發生在其他操作之前,意味著後面的操作知道或依賴於較早的操作。 在這個例子中,客戶端永遠不會完全掌握伺服器上的資料,因為總是有另一個操作同時進行。 但是,舊版本的值最終會被覆蓋,並且不會丟失任何寫入。 -![](../img/fig5-14.png) +![](img/fig5-14.png) @@ -1398,7 +1398,7 @@ -​ 以購物車為例,一種合理的合併兄弟方法就是集合求並集。在[圖5-14](../img/fig5-14.png)中,最後的兩個兄弟是[牛奶,麵粉,雞蛋,燻肉]和[雞蛋,牛奶,火腿]。注意牛奶和雞蛋同時出現在兩個兄弟裡,即使他們每個只被寫過一次。合併的值可以是[牛奶,麵粉,雞蛋,培根,火腿],沒有重複。 +​ 以購物車為例,一種合理的合併兄弟方法就是集合求並集。在[圖5-14](img/fig5-14.png)中,最後的兩個兄弟是[牛奶,麵粉,雞蛋,燻肉]和[雞蛋,牛奶,火腿]。注意牛奶和雞蛋同時出現在兩個兄弟裡,即使他們每個只被寫過一次。合併的值可以是[牛奶,麵粉,雞蛋,培根,火腿],沒有重複。 @@ -1414,11 +1414,11 @@ -​ [圖5-13](../img/fig5-13.png)中的示例只使用一個副本。當有多個副本但沒有領導者時,演算法如何修改? +​ [圖5-13](img/fig5-13.png)中的示例只使用一個副本。當有多個副本但沒有領導者時,演算法如何修改? -​ [圖5-13](../img/fig5-13.png)使用單個版本號來捕獲操作之間的依賴關係,但是當多個副本併發接受寫入時,這是不夠的。相反,除了對每個鍵使用版本號之外,還需要在**每個副本**中使用版本號。每個副本在處理寫入時增加自己的版本號,並且跟蹤從其他副本中看到的版本號。這個資訊指出了要覆蓋哪些值,以及保留哪些值作為兄弟。 +​ [圖5-13](img/fig5-13.png)使用單個版本號來捕獲操作之間的依賴關係,但是當多個副本併發接受寫入時,這是不夠的。相反,除了對每個鍵使用版本號之外,還需要在**每個副本**中使用版本號。每個副本在處理寫入時增加自己的版本號,並且跟蹤從其他副本中看到的版本號。這個資訊指出了要覆蓋哪些值,以及保留哪些值作為兄弟。 @@ -1426,7 +1426,7 @@ -​ 與[圖5-13](../img/fig5-13.png)中的版本號一樣,當讀取值時,版本向量會從資料庫副本傳送到客戶端,並且隨後寫入值時需要將其傳送回資料庫。(Riak將版本向量編碼為一個字串,它稱為**因果上下文(causal context)**)。版本向量允許資料庫區分覆蓋寫入和併發寫入。 +​ 與[圖5-13](img/fig5-13.png)中的版本號一樣,當讀取值時,版本向量會從資料庫副本傳送到客戶端,並且隨後寫入值時需要將其傳送回資料庫。(Riak將版本向量編碼為一個字串,它稱為**因果上下文(causal context)**)。版本向量允許資料庫區分覆蓋寫入和併發寫入。 diff --git a/zh-tw/ch6.md b/zh-tw/ch6.md index 8c7e923..2e80417 100644 --- a/zh-tw/ch6.md +++ b/zh-tw/ch6.md @@ -2,7 +2,7 @@ -![](../img/ch6.png) +![](img/ch6.png) @@ -32,7 +32,7 @@ -> ##### 術語澄清 +> #### 術語澄清 > @@ -70,13 +70,13 @@ -​ 一個節點可能儲存多個分割槽。 如果使用主從複製模型,則分割槽和複製的組合如[圖6-1](../img/fig6-1.png)所示。 每個分割槽領導者(主)被分配給一個節點,追隨者(從)被分配給其他節點。 每個節點可能是某些分割槽的領導者,同時是其他分割槽的追隨者。 +​ 一個節點可能儲存多個分割槽。 如果使用主從複製模型,則分割槽和複製的組合如[圖6-1](img/fig6-1.png)所示。 每個分割槽領導者(主)被分配給一個節點,追隨者(從)被分配給其他節點。 每個節點可能是某些分割槽的領導者,同時是其他分割槽的追隨者。 我們在[第5章](ch5.md)討論的關於資料庫複製的所有內容同樣適用於分割槽的複製。 大多數情況下,分割槽方案的選擇與複製方案的選擇是獨立的,為簡單起見,本章中將忽略複製。 -![](../img/fig6-1.png) +![](img/fig6-1.png) @@ -112,11 +112,11 @@ -​ 一種分割槽的方法是為每個分割槽指定一塊連續的鍵範圍(從最小值到最大值),如紙質百科全書的卷([圖6-2]())。如果知道範圍之間的邊界,則可以輕鬆確定哪個分割槽包含某個值。如果您還知道分割槽所在的節點,那麼可以直接向相應的節點發出請求(對於百科全書而言,就像從書架上選取正確的書籍)。 +​ 一種分割槽的方法是為每個分割槽指定一塊連續的鍵範圍(從最小值到最大值),如紙質百科全書的卷([圖6-2](img/fig6-2.png))。如果知道範圍之間的邊界,則可以輕鬆確定哪個分割槽包含某個值。如果您還知道分割槽所在的節點,那麼可以直接向相應的節點發出請求(對於百科全書而言,就像從書架上選取正確的書籍)。 -![](../img/fig6-2.png) +![](img/fig6-2.png) @@ -124,7 +124,7 @@ -​ 鍵的範圍不一定均勻分佈,因為資料也很可能不均勻分佈。例如在[圖6-2](../img/fig6-2.png)中,第1捲包含以A和B開頭的單詞,但第12卷則包含以T,U,V,X,Y和Z開頭的單詞。只是簡單的規定每個捲包含兩個字母會導致一些卷比其他卷大。為了均勻分配資料,分割槽邊界需要依據資料調整。 +​ 鍵的範圍不一定均勻分佈,因為資料也很可能不均勻分佈。例如在[圖6-2](img/fig6-2.png)中,第1捲包含以A和B開頭的單詞,但第12卷則包含以T,U,V,X,Y和Z開頭的單詞。只是簡單的規定每個捲包含兩個字母會導致一些卷比其他卷大。為了均勻分配資料,分割槽邊界需要依據資料調整。 @@ -160,11 +160,11 @@ -​ 一旦你有一個合適的鍵雜湊函式,你可以為每個分割槽分配一個雜湊範圍(而不是鍵的範圍),每個透過雜湊雜湊落在分割槽範圍內的鍵將被儲存在該分割槽中。如[圖6-3](../img/fig6-3.png)所示。 +​ 一旦你有一個合適的鍵雜湊函式,你可以為每個分割槽分配一個雜湊範圍(而不是鍵的範圍),每個透過雜湊雜湊落在分割槽範圍內的鍵將被儲存在該分割槽中。如[圖6-3](img/fig6-3.png)所示。 -![](../img/fig6-3.png) +![](img/fig6-3.png) @@ -252,7 +252,7 @@ -​ 假設你正在經營一個銷售二手車的網站(如[圖6-4](../img/fig6-4.png)所示)。 每個列表都有一個唯一的ID——稱之為文件ID——並且用文件ID對資料庫進行分割槽(例如,分割槽0中的ID 0到499,分割槽1中的ID 500到999等)。 +​ 假設你正在經營一個銷售二手車的網站(如[圖6-4](img/fig6-4.png)所示)。 每個列表都有一個唯一的ID——稱之為文件ID——並且用文件ID對資料庫進行分割槽(例如,分割槽0中的ID 0到499,分割槽1中的ID 500到999等)。 @@ -264,7 +264,7 @@ -![](../img/fig6-4.png) +![](img/fig6-4.png) @@ -276,7 +276,7 @@ -​ 但是,從文件分割槽索引中讀取需要注意:除非您對文件ID做了特別的處理,否則沒有理由將所有具有特定顏色或特定品牌的汽車放在同一個分割槽中。在[圖6-4](../img/fig6-4.png)中,紅色汽車出現在分割槽0和分割槽1中。因此,如果要搜尋紅色汽車,則需要將查詢傳送到所有分割槽,併合並所有返回的結果。 +​ 但是,從文件分割槽索引中讀取需要注意:除非您對文件ID做了特別的處理,否則沒有理由將所有具有特定顏色或特定品牌的汽車放在同一個分割槽中。在[圖6-4](img/fig6-4.png)中,紅色汽車出現在分割槽0和分割槽1中。因此,如果要搜尋紅色汽車,則需要將查詢傳送到所有分割槽,併合並所有返回的結果。 @@ -296,11 +296,11 @@ -​ [圖6-5](../img/fig6-5.png)描述了這可能是什麼樣子:來自所有分割槽的紅色汽車在紅色索引中,並且索引是分割槽的,首字母從`a`到`r`的顏色在分割槽0中,`s`到`z`的在分割槽1。汽車製造商的索引也與之類似(分割槽邊界在`f`和`h`之間)。 +​ [圖6-5](img/fig6-5.png)描述了這可能是什麼樣子:來自所有分割槽的紅色汽車在紅色索引中,並且索引是分割槽的,首字母從`a`到`r`的顏色在分割槽0中,`s`到`z`的在分割槽1。汽車製造商的索引也與之類似(分割槽邊界在`f`和`h`之間)。 -![](../img/fig6-5.png) +![](img/fig6-5.png) @@ -378,7 +378,7 @@ -​ 我們在前面說過([圖6-3](../img/fig6-3.png)),最好將可能的雜湊分成不同的範圍,並將每個範圍分配給一個分割槽(例如,如果$0≤hash(key) -> **線性一致性(Linearizability)**是讀取和寫入暫存器(單個物件)的**新鮮度保證**。它不會將操作組合為事務,因此它也不會阻止寫偏差等問題(參閱“[寫偏差和幻讀](ch7.md#寫偏差和幻讀)”),除非採取其他措施(例如[物化衝突](ch7.md#物化衝突))。 +> **線性一致性(Linearizability)**是讀取和寫入暫存器(單個物件)的**新鮮度保證**。它不會將操作組合為事務,因此它也不會阻止寫入偏差等問題(參閱“[寫入偏差和幻讀](ch7.md#寫入偏斜與幻讀)”),除非採取其他措施(例如[物化衝突](ch7.md#物化衝突))。 > -> 一個數據庫可以提供可序列化和線性一致性,這種組合被稱為嚴格的可序列化或強的**強單副本可序列化(strong-1SR)**【4,13】。基於兩階段鎖定的可序列化實現(參見“[兩階段鎖定(2PL)](#兩階段鎖定(2PL))”一節)或**實際序列執行**(參見第“[實際序列執行](ch7.md#實際序列執行)”)通常是線性一致性的。 +> 一個數據庫可以提供可序列化和線性一致性,這種組合被稱為嚴格的可序列化或**強的單副本可序列化(strong-1SR)**【4,13】。基於兩階段鎖定的可序列化實現(參見“[兩階段鎖定(2PL)](ch7.md#兩階段鎖定(2PL))”一節)或**真的序列執行**(參見第“[真的序列執行](ch7.md#真的序列執行)”)通常是線性一致性的。 > -> 但是,可序列化的快照隔離(參見“[可序列化的快照隔離(SSI)](ch7.md#可序列化的快照隔離(SSI))”)不是線性一致性的:按照設計,它從一致的快照中進行讀取,以避免讀者和寫者之間的鎖競爭。一致性快照的要點就在於**它不會包括該快照之後的寫入**,因此從快照讀取不是線性一致性的。 - - +> 但是,可序列化的快照隔離(參見“[可序列化快照隔離(SSI)](ch7.md#可序列化快照隔離(SSI))”)不是線性一致性的:按照設計,它從一致的快照中進行讀取,以避免讀者和寫者之間的鎖競爭。一致性快照的要點就在於**它不會包括該快照之後的寫入**,因此從快照讀取不是線性一致性的。 @@ -314,7 +306,7 @@ -​ 線性一致性在什麼情況下有用?觀看體育比賽的最後得分可能是一個輕率的例子:過了幾秒鐘的結果不可能在這種情況下造成任何真正的傷害。然而對於少數領域,線性一致性是系統正確工作的一個重要條件。 +​ 線性一致性在什麼情況下有用?觀看體育比賽的最後得分可能是一個輕率的例子:滯後了幾秒鐘的結果不太可能在這種情況下造成任何真正的傷害。然而對於少數領域,線性一致性是系統正確工作的一個重要條件。 @@ -326,11 +318,11 @@ -​ 諸如Apache ZooKeeper 【15】和etcd 【16】之類的協調服務通常用於實現分散式鎖和領導者選舉。它們使用一致性演算法,以容錯的方式實現線性一致的操作(在本章後面的“[容錯共識](#容錯共識)”中討論此類演算法)[^iii]。還有許多微妙的細節來正確地實現鎖和領導者選舉(例如,參閱“[領導者和鎖](#領導者和鎖)”中的防護問題),而像Apache Curator 【17】這樣的庫則透過在ZooKeeper之上提供更高級別的配方來提供幫助。但是,線性一致性儲存服務是這些協調任務的基礎。 +​ 諸如Apache ZooKeeper 【15】和etcd 【16】之類的協調服務通常用於實現分散式鎖和領導者選舉。它們使用一致性演算法,以容錯的方式實現線性一致的操作(在本章後面的“[容錯共識](#容錯共識)”中討論此類演算法)[^iii]。還有許多微妙的細節來正確地實現鎖和領導者選舉(例如,參閱“[領導者和鎖](ch8.md#領導者和鎖)”中的防護問題),而像Apache Curator 【17】這樣的庫則透過在ZooKeeper之上提供更高級別的配方來提供幫助。但是,線性一致性儲存服務是這些協調任務的基礎。 -[^iii]: 嚴格地說,ZooKeeper和etcd提供線性一致性的寫操作,但讀取可能是陳舊的,因為預設情況下,它們可以由任何一個副本服務。你可以選擇請求線性一致性讀取:etcd呼叫這個法定讀取【16】,而在ZooKeeper中,你需要在讀取【15】之前呼叫`sync()`。參閱“[使用全域性順序廣播實現線性儲存](#使用全域性順序廣播實現線性儲存)”。 +[^iii]: 嚴格地說,ZooKeeper和etcd提供線性一致性的寫操作,但讀取可能是陳舊的,因為預設情況下,它們可以由任何一個副本提供服務。你可以選擇請求線性一致性讀取:etcd稱之為**法定人數讀取(quorum read)**【16】,而在ZooKeeper中,你需要在讀取之前呼叫`sync()`【15】。參閱“[使用全序廣播實現線性一致的儲存](#使用全序廣播實現線性一致的儲存)”。 @@ -346,7 +338,7 @@ -​ 這種情況實際上類似於一個鎖:當一個使用者註冊你的服務時,可以認為他們獲得了所選使用者名稱的“鎖定”。該操作與原子性的比較與設定非常相似:將使用者名稱賦予宣告它的使用者,前提是使用者名稱尚未被使用。 +​ 這種情況實際上類似於一個鎖:當一個使用者註冊你的服務時,可以認為他們獲得了所選使用者名稱的“鎖定”。該操作與原子性的比較與設定(CAS)非常相似:將使用者名稱賦予宣告它的使用者,前提是使用者名稱尚未被使用。 @@ -366,31 +358,31 @@ -​ 注意[圖9-1](../img/fig9-1.png) 中的一個細節:如果Alice沒有驚呼得分,Bob就不會知道他的查詢結果是陳舊的。他會在幾秒鐘之後再次重新整理頁面,並最終看到最後的分數。由於系統中存在額外的通道(Alice的聲音傳到了Bob的耳朵中),線性一致性的違背才被注意到。 +​ 注意[圖9-1](img/fig9-1.png) 中的一個細節:如果Alice沒有驚呼得分,Bob就不會知道他的查詢結果是陳舊的。他會在幾秒鐘之後再次重新整理頁面,並最終看到最後的分數。由於系統中存在額外的通道(Alice的聲音傳到了Bob的耳朵中),線性一致性的違背才被注意到。 -​ 計算機系統也會出現類似的情況。例如,假設有一個網站,使用者可以上傳照片,一個後臺程序會調整照片大小,降低解析度以加快下載速度(縮圖)。該系統的架構和資料流如[圖9-5](../img/fig9-5.png)所示。 +​ 計算機系統也會出現類似的情況。例如,假設有一個網站,使用者可以上傳照片,一個後臺程序會調整照片大小,降低解析度以加快下載速度(縮圖)。該系統的架構和資料流如[圖9-5](img/fig9-5.png)所示。 -​ 影象縮放器需要明確的指令來執行尺寸縮放作業,指令是Web伺服器透過訊息佇列傳送的(參閱[第11章](ch11.md))。 Web伺服器不會將整個照片放在佇列中,因為大多數訊息代理都是針對較短的訊息而設計的,而一張照片的空間佔用可能達到幾兆位元組。取而代之的是,首先將照片寫入檔案儲存服務,寫入完成後再將縮放器的指令放入訊息佇列。 +​ 影象縮放器需要明確的指令來執行尺寸縮放作業,指令是Web伺服器透過訊息佇列傳送的(參閱[第11章](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)可能比儲存服務內部的複製更快。在這種情況下,當縮放器讀取影象(步驟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耳朵之間的真人音訊通道之間也存在競爭條件。 -​ 線性一致性並不是避免這種競爭條件的唯一方法,但它是最容易理解的。如果你可以控制額外通道(例如訊息佇列的例子,而不是在Alice和Bob的例子),則可以使用在“[讀己之寫](ch5.md#讀己之寫)”討論過的備選方法,不過會有額外的複雜度代價。 +​ 線性一致性並不是避免這種競爭條件的唯一方法,但它是最容易理解的。如果你可以控制額外通道(例如訊息佇列的例子,而不是在Alice和Bob的例子),則可以使用在“[讀己之寫](ch5.md#讀己之寫)”討論過的類似方法,不過會有額外的複雜度代價。 @@ -414,15 +406,15 @@ -​ 在具有單主複製功能的系統中(參見“[領導者與追隨者](ch5.md#領導者與追隨者)”),主庫具有用於寫入的資料的主副本,而追隨者在其他節點上保留資料的備份副本。如果從主庫或同步更新的從庫讀取資料,它們**可能(protential)**是線性一致性的[^iv]。然而,並不是每個單主資料庫都是實際線性一致性的,無論是透過設計(例如,因為使用快照隔離)還是併發錯誤【10】。 +​ 在具有單主複製功能的系統中(參見“[領導者與追隨者](ch5.md#領導者與追隨者)”),主庫具有用於寫入的資料的主副本,而追隨者在其他節點上保留資料的備份副本。如果從主庫或同步更新的從庫讀取資料,它們**可能(potential)**是線性一致性的[^iv]。然而,實際上並不是每個單主資料庫都是線性一致性的,無論是因為設計的原因(例如,因為使用了快照隔離)還是因為在併發處理上存在錯誤【10】。 -[^iv]: 對單領域資料庫進行分割槽(分片),以便每個分割槽有一個單獨的領導者,不會影響線性一致性,因為線性一致性只是對單一物件的保證。 交叉分割槽事務是一個不同的問題(參閱“[分散式事務和共識](#分散式事務和共識)”)。 +[^iv]: 對單主資料庫進行分割槽(分片),使得每個分割槽有一個單獨的領導者,不會影響線性一致性,因為線性一致性只是對單一物件的保證。 交叉分割槽事務是一個不同的問題(參閱“[分散式事務與共識](#分散式事務與共識)”)。 -​ 從主庫讀取依賴一個假設,你確定領導是誰。正如在“[真理在多數人手中](ch8.md#真理被多數人定義)”中所討論的那樣,一個節點很可能會認為它是領導者,而事實上並非如此——如果具有錯覺的領導者繼續為請求提供服務,可能違反線性一致性【20】。使用非同步複製,故障切換時甚至可能會丟失已提交的寫入(參閱“[處理節點宕機](ch5.md#處理節點宕機)”),這同時違反了永續性和線性一致性。 +​ 從主庫讀取依賴一個假設,你確定地知道領導者是誰。正如在“[真相由多數所定義](ch8.md#真相由多數所定義)”中所討論的那樣,一個節點很可能會認為它是領導者,而事實上並非如此——如果具有錯覺的領導者繼續為請求提供服務,可能違反線性一致性【20】。使用非同步複製,故障切換時甚至可能會丟失已提交的寫入(參閱“[處理節點宕機](ch5.md#處理節點宕機)”),這同時違反了永續性和線性一致性。 @@ -438,7 +430,7 @@ -​ 具有多主程式複製的系統通常不是線性一致的,因為它們同時在多個節點上處理寫入,並將其非同步複製到其他節點。因此,它們可能會產生衝突的寫入,需要解析(參閱“[處理寫入衝突](ch5.md#處理寫入衝突)”)。這種衝突是因為缺少單一資料副本人為產生的。 +​ 具有多主程式複製的系統通常不是線性一致的,因為它們同時在多個節點上處理寫入,並將其非同步複製到其他節點。因此,它們可能會產生需要被解決的寫入衝突(參閱“[處理寫入衝突](ch5.md#處理寫入衝突)”)。這種衝突是因為缺少單一資料副本所導致的。 @@ -450,11 +442,7 @@ - - - - -​ 基於時鐘(例如,在Cassandra中;參見“[依賴同步時鐘](ch8.md#依賴同步時鐘)”)的“最後寫入勝利”衝突解決方法幾乎可以確定是非線性的,由於時鐘偏差,不能保證時鐘的時間戳與實際事件順序一致。[寬鬆的法定人數](ch5.md#寬鬆的法定人數與提示移交)也破壞了線性一致的可能性。即使使用嚴格的法定人數,非線性一致的行為也是可能的,如下節所示。 +​ 基於日曆時鐘(例如,在Cassandra中;參見“[依賴同步時鐘](ch8.md#依賴同步時鐘)”)的“最後寫入勝利”衝突解決方法幾乎可以確定是非線性一致的,由於時鐘偏差,不能保證時鐘的時間戳與實際事件順序一致。寬鬆的法定人數(參見“[寬鬆的法定人數與提示移交](ch5.md#寬鬆的法定人數與提示移交)”)也破壞了線性一致的可能性。即使使用嚴格的法定人數,非線性一致的行為也只是可能的,如下節所示。 @@ -462,11 +450,11 @@ -​ 直覺上在Dynamo風格的模型中,嚴格的法定人數讀寫應該是線性一致性的。但是當我們有可變的網路延遲時,就可能存在競爭條件,如[圖9-6](../img/fig9-6.png)所示。 +​ 直覺上在Dynamo風格的模型中,嚴格的法定人數讀寫應該是線性一致性的。但是當我們有可變的網路延遲時,就可能存在競爭條件,如[圖9-6](img/fig9-6.png)所示。 -![](../img/fig9-6.png) +![](img/fig9-6.png) @@ -474,19 +462,19 @@ -​ 在[圖9-6](../img/fig9-6.png)中,$x$ 的初始值為0,寫入客戶端透過向所有三個副本( $n = 3, w = 3$ )傳送寫入將 $x$ 更新為 `1`。客戶端A併發地從兩個節點組成的法定人群( $r = 2$ )中讀取資料,並在其中一個節點上看到新值 `1` 。客戶端B也併發地從兩個不同的節點組成的法定人數中讀取,並從兩個節點中取回了舊值 `0` 。 +​ 在[圖9-6](img/fig9-6.png)中,$x$ 的初始值為0,寫入客戶端透過向所有三個副本( $n = 3, w = 3$ )傳送寫入將 $x$ 更新為 `1`。客戶端A併發地從兩個節點組成的法定人群( $r = 2$ )中讀取資料,並在其中一個節點上看到新值 `1` 。客戶端B也併發地從兩個不同的節點組成的法定人數中讀取,並從兩個節點中取回了舊值 `0` 。 -​ 法定人數條件滿足( $w + r> n$ ),但是這個執行是非線性一致的:B的請求在A的請求完成後開始,但是B返回舊值,而A返回新值。 (又一次,如同Alice和Bob的例子 [圖9-1](../img/fig9-1.png)) +​ 法定人數條件滿足( $w + r> n$ ),但是這個執行是非線性一致的:B的請求在A的請求完成後開始,但是B返回舊值,而A返回新值。 (又一次,如同Alice和Bob的例子 [圖9-1](img/fig9-1.png)) -​ 有趣的是,透過犧牲效能,可以使Dynamo風格的法定人數線性化:讀取者必須在將結果返回給應用之前,同步執行讀修復(參閱“[讀時修復與反熵過程](ch5.md#讀時修復與反熵過程)”) ,並且寫入者必須在傳送寫入之前,讀取法定數量節點的最新狀態【24,25】。然而,由於效能損失,Riak不執行同步讀修復【26】。 Cassandra在進行法定人數讀取時,**確實**在等待讀修復完成【27】;但是由於使用了最後寫入勝利的衝突解決方案,當同一個鍵有多個併發寫入時,將不能保證線性一致性。 +​ 有趣的是,透過犧牲效能,可以使Dynamo風格的法定人數線性化:讀取者必須在將結果返回給應用之前,同步執行讀修復(參閱“[讀修復和反熵](ch5.md#讀修復和反熵)”) ,並且寫入者必須在傳送寫入之前,讀取法定數量節點的最新狀態【24,25】。然而,由於效能損失,Riak不執行同步讀修復【26】。 Cassandra在進行法定人數讀取時,**確實**在等待讀修復完成【27】;但是由於使用了最後寫入勝利的衝突解決方案,當同一個鍵有多個併發寫入時,將不能保證線性一致性。 -​ 而且,這種方式只能實現線性一致的讀寫;不能實現線性一致的比較和設定操作,因為它需要一個共識演算法【28】。 +​ 而且,這種方式只能實現線性一致的讀寫;不能實現線性一致的比較和設定(CAS)操作,因為它需要一個共識演算法【28】。 @@ -506,11 +494,11 @@ -​ 我們已經在[第五章](ch5.md)中討論了不同複製方法的一些用例。例如對多資料中心的複製而言,多主複製通常是理想的選擇(參閱“[運維多個數據中心](ch5.md#運維多個數據中心)”)。[圖9-7](../img/fig9-7.png)說明了這種部署的一個例子。 +​ 我們已經在[第五章](ch5.md)中討論了不同複製方法的一些用例。例如對多資料中心的複製而言,多主複製通常是理想的選擇(參閱“[運維多個數據中心](ch5.md#運維多個數據中心)”)。[圖9-7](img/fig9-7.png)說明了這種部署的一個例子。 -![](../img/fig9-7.png) +![](img/fig9-7.png) @@ -556,7 +544,7 @@ -因此不需要線性一致性的應用對網路問題有更強的容錯能力。這種見解通常被稱為CAP定理【29,30,31,32】,由Eric Brewer於2000年命名,儘管70年代的分散式資料庫設計者早就知道了這種權衡【33,34,35,36】。 +因此,不需要線性一致性的應用對網路問題有更強的容錯能力。這種見解通常被稱為CAP定理【29,30,31,32】,由Eric Brewer於2000年命名,儘管70年代的分散式資料庫設計者早就知道了這種權衡【33,34,35,36】。 @@ -568,7 +556,7 @@ > -> CAP有時以這種面目出現:一致性,可用性和分割槽容錯性:三者只能擇其二。不幸的是這種說法很有誤導性【32】,因為網路分割槽是一種錯誤,所以它並不是一個選項:不管你喜不喜歡它都會發生【38】。 +> CAP有時以這種面目出現:一致性,可用性和分割槽容錯性:三者只能擇其二。不幸的是這種說法很有誤導性【32】,因為網路分割槽是一種故障型別,所以它並不是一個選項:不管你喜不喜歡它都會發生【38】。 > @@ -588,7 +576,7 @@ -[^vi]: 正如“[真實世界的網路故障](ch8.md#真實世界的網路故障)”中所討論的,本書使用**分割槽(partition)**指代將大資料集細分為小資料集的操作(分片;參見[第6章](ch6.md))。與之對應的是,**網路分割槽(network partition)**是一種特定型別的網路故障,我們通常不會將其與其他型別的故障分開考慮。但是,由於它是CAP的P,所以這種情況下不能將其混為一談。 +[^vi]: 正如“[真實世界的網路故障](ch8.md#真實世界的網路故障)”中所討論的,本書使用**分割槽(partition)**指代將大資料集細分為小資料集的操作(分片;參見[第6章](ch6.md))。與之對應的是,**網路分割槽(network partition)**是一種特定型別的網路故障,我們通常不會將其與其他型別的故障分開考慮。但是,由於它是CAP的P,所以這種情況下我們無法避免混亂。 @@ -620,31 +608,31 @@ -## 順序保證 +## 順序保證 -​ 之前說過,線性一致暫存器的行為就好像只有單個數據副本一樣,且每個操作似乎都是在某個時間點以原子性的方式生效的。這個定義意味著操作是按照某種良好定義的順序執行的。我們透過操作(似乎)執行完畢的順序來連線操作,以此說明[圖9-4](../img/fig9-4.png)中的順序。 +​ 之前說過,線性一致暫存器的行為就好像只有單個數據副本一樣,且每個操作似乎都是在某個時間點以原子性的方式生效的。這個定義意味著操作是按照某種良好定義的順序執行的。我們將操作以看上去被執行的順序連線起來,以此說明了[圖9-4](img/fig9-4.png)中的順序。 -**順序(ordering)**這一主題在本書中反覆出現,這表明它可能是一個重要的基礎性概念。讓我們簡要回顧一下其它**順序**曾經出現過的上下文: +**順序(ordering)**這一主題在本書中反覆出現,這表明它可能是一個重要的基礎性概念。讓我們簡要回顧一下其它曾經出現過**順序**的上下文: * 在[第5章](ch5.md)中我們看到,領導者在單主複製中的主要目的就是,在複製日誌中確定**寫入順序(order of write)**——也就是從庫應用這些寫入的順序。如果不存在一個領導者,則併發操作可能導致衝突(參閱“[處理寫入衝突](ch5.md#處理寫入衝突)”)。 -* 在[第7章](ch7.md)中討論的**可序列化**,是關於事務表現的像按**某種序列順序(some sequential order)**執行的保證。它可以透過字面意義上地**序列順序(serial order)**執行事務來實現,或者透過允許並行執行,同時防止序列化衝突來實現(透過鎖或中止事務)。 +* 在[第7章](ch7.md)中討論的**可序列化**,是關於事務表現的像按**某種先後順序(some sequential order)**執行的保證。它可以字面意義上地以**序列順序(serial order)**執行事務來實現,或者允許並行執行,但同時防止序列化衝突來實現(透過鎖或中止事務)。 -* 在[第8章](ch8.md)討論過的在分散式系統中使用時間戳和時鐘(參閱“[依賴於同步時鐘](ch8.md#依賴於同步時鐘)”)是另一種將順序引入無序世界的嘗試,例如,確定兩個寫入操作哪一個更晚發生。 +* 在[第8章](ch8.md)討論過的在分散式系統中使用時間戳和時鐘(參閱“[依賴同步時鐘](ch8.md#依賴同步時鐘)”)是另一種將順序引入無序世界的嘗試,例如,確定兩個寫入操作哪一個更晚發生。 -事實證明,順序,線性一致性和共識之間有著深刻的聯絡。儘管這個概念比本書其他部分更加理論化和抽象,但對於明確系統的能力範圍(可以做什麼和不可以做什麼)而言是非常有幫助的。我們將在接下來的幾節中探討這個話題。 +事實證明,順序、線性一致性和共識之間有著深刻的聯絡。儘管這個概念比本書其他部分更加理論化和抽象,但對於明確系統的能力範圍(可以做什麼和不可以做什麼)而言是非常有幫助的。我們將在接下來的幾節中探討這個話題。 -### 順序與因果 +### 順序與因果關係 @@ -652,17 +640,17 @@ -* 在“[一致字首讀](ch5.md#一致字首讀)”([圖5-5](../img/fig5-5.png))中,我們看到一個例子:一個對話的觀察者首先看到問題的答案,然後才看到被回答的問題。這是令人困惑的,因為它違背了我們對**因(cause)**與**果(effect)**的直覺:如果一個問題被回答,顯然問題本身得先在那裡,因為給出答案的人必須看到這個問題(假如他們並沒有預見未來的超能力)。我們認為在問題和答案之間存在**因果依賴(causal dependency)**。 +* 在“[一致字首讀](ch5.md#一致字首讀)”([圖5-5](img/fig5-5.png))中,我們看到一個例子:一個對話的觀察者首先看到問題的答案,然後才看到被回答的問題。這是令人困惑的,因為它違背了我們對**因(cause)**與**果(effect)**的直覺:如果一個問題被回答,顯然問題本身得先在那裡,因為給出答案的人必須先看到這個問題(假如他們並沒有預見未來的超能力)。我們認為在問題和答案之間存在**因果依賴(causal dependency)**。 -* [圖5-9](../img/fig5-9.png)中出現了類似的模式,我們看到三位領導者之間的複製,並注意到由於網路延遲,一些寫入可能會“壓倒”其他寫入。從其中一個副本的角度來看,好像有一個對尚不存在的記錄的更新操作。這裡的因果意味著,一條記錄必須先被建立,然後才能被更新。 +* [圖5-9](img/fig5-9.png)中出現了類似的模式,我們看到三位領導者之間的複製,並注意到由於網路延遲,一些寫入可能會“壓倒”其他寫入。從其中一個副本的角度來看,好像有一個對尚不存在的記錄的更新操作。這裡的因果意味著,一條記錄必須先被建立,然後才能被更新。 * 在“[檢測併發寫入](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)所示)。 +* 在事務快照隔離的上下文中(“[快照隔離和可重複讀](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#可序列化的快照隔離(SSI))透過跟蹤事務之間的因果依賴來檢測寫偏差。 +* 事務之間**寫偏差(write skew)**的例子(參見“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”)也說明了因果依賴:在[圖7-8](img/fig7-8.png)中,愛麗絲被允許離班,因為事務認為鮑勃仍在值班,反之亦然。在這種情況下,離班的動作因果依賴於對當前值班情況的觀察。[可序列化快照隔離(SSI)](ch7.md#可序列化快照隔離(SSI))透過跟蹤事務之間的因果依賴來檢測寫偏差。 -* 在愛麗絲和鮑勃看球的例子中([圖9-1](../img/fig9-1.png)),在聽到愛麗絲驚呼比賽結果後,鮑勃從伺服器得到陳舊結果的事實違背了因果關係:愛麗絲的驚呼因果依賴於得分宣告,所以鮑勃應該也能在聽到愛麗斯驚呼後查詢到比分。相同的模式在“[跨通道的時序依賴](#跨通道的時序依賴)”一節中,以“影象大小調整服務”的偽裝再次出現。 +* 在愛麗絲和鮑勃看球的例子中([圖9-1](img/fig9-1.png)),在聽到愛麗絲驚呼比賽結果後,鮑勃從伺服器得到陳舊結果的事實違背了因果關係:愛麗絲的驚呼因果依賴於得分宣告,所以鮑勃應該也能在聽到愛麗斯驚呼後查詢到比分。相同的模式在“[跨通道的時序依賴](#跨通道的時序依賴)”一節中,以“影象大小調整服務”的偽裝再次出現。 @@ -702,7 +690,7 @@ -​ 線上性一致的系統中,操作是全序的:如果系統表現的就好像只有一個數據副本,並且所有操作都是原子性的,這意味著對任何兩個操作,我們總是能判定哪個操作先發生。這個全序[圖9-4](../img/fig9-4.png)中以時間線表示。 +​ 線上性一致的系統中,操作是全序的:如果系統表現的就好像只有一個數據副本,並且所有操作都是原子性的,這意味著對任何兩個操作,我們總是能判定哪個操作先發生。這個全序在[圖9-4](img/fig9-4.png)中以時間線表示。 @@ -718,7 +706,7 @@ -​ 併發意味著時間線會分岔然後合併 —— 在這種情況下,不同分支上的操作是無法比較的(即併發操作)。在[第五章](ch5.md)中我們看到了這種現象:例如,[圖5-14](../img/fig5-14.md) 並不是一條直線的全序關係,而是一堆不同的操作併發進行。圖中的箭頭指明瞭因果依賴 —— 操作的偏序。 +​ 併發意味著時間線會分岔然後合併 —— 在這種情況下,不同分支上的操作是無法比較的(即併發操作)。在[第五章](ch5.md)中我們看到了這種現象:例如,[圖5-14](img/fig5-14.png) 並不是一條直線的全序關係,而是一堆不同的操作併發進行。圖中的箭頭指明瞭因果依賴 —— 操作的偏序。 @@ -730,7 +718,7 @@ -​ 那麼因果順序和線性一致性之間的關係是什麼?答案是線性一致性**隱含著(implies)**因果關係:任何線性一致的系統都能正確保持因果性【7】。特別是,如果系統中有多個通訊通道(如[圖9-5](../img/fig9-5.png) 中的訊息佇列和檔案儲存服務),線性一致性可以自動保證因果性,系統無需任何特殊操作(如在不同元件間傳遞時間戳)。 +​ 那麼因果順序和線性一致性之間的關係是什麼?答案是線性一致性**隱含著(implies)**因果關係:任何線性一致的系統都能正確保持因果性【7】。特別是,如果系統中有多個通訊通道(如[圖9-5](img/fig9-5.png) 中的訊息佇列和檔案儲存服務),線性一致性可以自動保證因果性,系統無需任何特殊操作(如在不同元件間傳遞時間戳)。 @@ -770,7 +758,7 @@ -​ 為了確定因果順序,資料庫需要知道應用讀取了哪個版本的資料。這就是為什麼在 [圖5-13 ](../img/fig5-13.png)中,來自先前操作的版本號在寫入時被傳回到資料庫的原因。在SSI 的衝突檢測中會出現類似的想法,如“[可序列化的快照隔離(SSI)]()”中所述:當事務要提交時,資料庫將檢查它所讀取的資料版本是否仍然是最新的。為此,資料庫跟蹤哪些資料被哪些事務所讀取。 +​ 為了確定因果順序,資料庫需要知道應用讀取了哪個版本的資料。這就是為什麼在 [圖5-13 ](img/fig5-13.png)中,來自先前操作的版本號在寫入時被傳回到資料庫的原因。在SSI 的衝突檢測中會出現類似的想法,如“[可序列化快照隔離(SSI)](ch7.md#可序列化快照隔離(SSI))”中所述:當事務要提交時,資料庫將檢查它所讀取的資料版本是否仍然是最新的。為此,資料庫跟蹤哪些資料被哪些事務所讀取。 @@ -786,17 +774,15 @@ -​ 但還有一個更好的方法:我們可以使用**序列號(sequence nunber)**或**時間戳(timestamp)**來排序事件。時間戳不一定來自時鐘(或物理時鐘,存在許多問題,如 “[不可靠時鐘](ch8.md#不可靠的時鐘)” 中所述)。它可以來自一個**邏輯時鐘(logical clock)**,這是一個用來生成標識操作的數字序列的演算法,典型實現是使用一個每次操作自增的計數器。 +​ 但還有一個更好的方法:我們可以使用**序列號(sequence nunber)**或**時間戳(timestamp)**來排序事件。時間戳不一定來自日曆時鐘(或物理時鐘,它們存在許多問題,如 “[不可靠的時鐘](ch8.md#不可靠的時鐘)” 中所述)。它可以來自一個**邏輯時鐘(logical clock)**,這是一個用來生成標識操作的數字序列的演算法,典型實現是使用一個每次操作自增的計數器。 - - -​ 這樣的序列號或時間戳是緊湊的(只有幾個位元組大小),它提供了一個全序關係:也就是說每操作都有一個唯一的序列號,而且總是可以比較兩個序列號,確定哪一個更大(即哪些操作後發生)。 +​ 這樣的序列號或時間戳是緊湊的(只有幾個位元組大小),它提供了一個全序關係:也就是說每個操作都有一個唯一的序列號,而且總是可以比較兩個序列號,確定哪一個更大(即哪些操作後發生)。 -​ 特別是,我們可以使用**與因果一致(consistent with causality)**的全序來生成序列號[^vii]:我們保證,如果操作 A 因果後繼於操作 B,那麼在這個全序中 A 在 B 前( A 具有比 B 更小的序列號)。並行操作之間可以任意排序。這樣一個全序關係捕獲了所有關於因果的資訊,但也施加了一個比因果性要求更為嚴格的順序。 +​ 特別是,我們可以使用**與因果一致(consistent with causality)**的全序來生成序列號[^vii]:我們保證,如果操作 A 因果地發生在操作 B 前,那麼在這個全序中 A 在 B 前( A 具有比 B 更小的序列號)。並行操作之間可以任意排序。這樣一個全序關係捕獲了所有關於因果的資訊,但也施加了一個比因果性要求更為嚴格的順序。 @@ -818,7 +804,7 @@ * 每個節點都可以生成自己獨立的一組序列號。例如有兩個節點,一個節點只能生成奇數,而另一個節點只能生成偶數。通常,可以在序列號的二進位制表示中預留一些位,用於唯一的節點識別符號,這樣可以確保兩個不同的節點永遠不會生成相同的序列號。 -* 可以將時鐘(物理時鐘)時間戳附加到每個操作上【55】。這種時間戳並不連續,但是如果它具有足夠高的解析度,那也許足以提供一個操作的全序關係。這一事實應用於 *最後寫入勝利* 的衝突解決方法中(參閱“[有序事件的時間戳](ch8.md#有序事件的時間戳)”)。 +* 可以將日曆時鐘(物理時鐘)的時間戳附加到每個操作上【55】。這種時間戳並不連續,但是如果它具有足夠高的解析度,那也許足以提供一個操作的全序關係。這一事實應用於 *最後寫入勝利* 的衝突解決方法中(參閱“[有序事件的時間戳](ch8.md#有序事件的時間戳)”)。 * 可以預先分配序列號區塊。例如,節點 A 可能要求從序列號1到1,000區塊的所有權,而節點 B 可能要求序列號1,001到2,000區塊的所有權。然後每個節點可以獨立分配所屬區塊中的序列號,並在序列號告急時請求分配一個新的區塊。 @@ -836,11 +822,11 @@ -* 來自物理時鐘的時間戳會受到時鐘偏移的影響,這可能會使其與因果不一致。例如[圖8-3](../img/fig8-3.png) 展示了一個例子,其中因果上晚發生的操作,卻被分配了一個更早的時間戳。[^vii] +* 來自物理時鐘的時間戳會受到時鐘偏移的影響,這可能會使其與因果不一致。例如[圖8-3](img/fig8-3.png) 展示了一個例子,其中因果上晚發生的操作,卻被分配了一個更早的時間戳。[^vii] - [^viii]: 可以使物理時鐘時間戳與因果關係保持一致:在“[用於全域性快照的同步時鐘](#用於全域性快照的同步時鐘)”中,我們討論了Google的Spanner,它可以估計預期的時鐘偏差,並在提交寫入之前等待不確定性間隔。 這中方法確保了實際上靠後的事務會有更大的時間戳。 但是大多數時鐘不能提供這種所需的不確定性度量。 + [^viii]: 可以使物理時鐘時間戳與因果關係保持一致:在“[全域性快照的同步時鐘](#全域性快照的同步時鐘)”中,我們討論了Google的Spanner,它可以估計預期的時鐘偏差,並在提交寫入之前等待不確定性間隔。這種方法確保了實際上靠後的事務會有更大的時間戳。但是大多數時鐘不能提供這種所需的不確定性度量。 @@ -860,21 +846,21 @@ -​ [圖9-8](../img/fig9-8.png) 說明了蘭伯特時間戳的應用。每個節點都有一個唯一識別符號,和一個儲存自己執行運算元量的計數器。 蘭伯特時間戳就是兩者的簡單組合:(計數器,節點ID)$(counter, node ID)$。兩個節點有時可能具有相同的計數器值,但透過在時間戳中包含節點ID,每個時間戳都是唯一的。 +​ [圖9-8](img/fig9-8.png) 說明了蘭伯特時間戳的應用。每個節點都有一個唯一識別符號,和一個儲存自己執行運算元量的計數器。 蘭伯特時間戳就是兩者的簡單組合:(計數器,節點ID)$(counter, node ID)$。兩個節點有時可能具有相同的計數器值,但透過在時間戳中包含節點ID,每個時間戳都是唯一的。 -![](../img/fig9-8.png) +![](img/fig9-8.png) -**圖9-8 Lamport時間戳提供了與因果關係一致的總排序。** +**圖9-8 Lamport時間戳提供了與因果關係一致的全序。** -​ 蘭伯特時間戳與物理時間時鐘沒有任何關係,但是它提供了一個全序:如果你有兩個時間戳,則**計數器**值大者是更大的時間戳。如果計數器值相同,則節點ID越大的,時間戳越大。 +​ 蘭伯特時間戳與物理的日曆時鐘沒有任何關係,但是它提供了一個全序:如果你有兩個時間戳,則**計數器**值大者是更大的時間戳。如果計數器值相同,則節點ID越大的,時間戳越大。 @@ -882,7 +868,7 @@ -​ 這如 [圖9-8](../img/fig9-8.png) 所示,其中客戶端 A 從節點2 接收計數器值 `5` ,然後將最大值 `5` 傳送到節點1 。此時,節點1 的計數器僅為 `1` ,但是它立即前移至 `5` ,所以下一個操作的計數器的值為 `6` 。 +​ 這如 [圖9-8](img/fig9-8.png) 所示,其中客戶端 A 從節點2 接收計數器值 `5` ,然後將最大值 `5` 傳送到節點1 。此時,節點1 的計數器僅為 `1` ,但是它立即前移至 `5` ,所以下一個操作的計數器的值為 `6` 。 @@ -902,7 +888,7 @@ -​ 例如,考慮一個需要確保使用者名稱能唯一標識使用者帳戶的系統。如果兩個使用者同時嘗試使用相同的使用者名稱建立帳戶,則其中一個應該成功,另一個應該失敗。 (我們之前在“[領導者與鎖定](ch8.md#領導者與鎖定)”中提到過這個問題。) +​ 例如,考慮一個需要確保使用者名稱能唯一標識使用者帳戶的系統。如果兩個使用者同時嘗試使用相同的使用者名稱建立帳戶,則其中一個應該成功,另一個應該失敗。 (我們之前在“[領導者和鎖](ch8.md#領導者和鎖)”中提到過這個問題。) @@ -910,7 +896,7 @@ -​ 這種方法適用於事後確定勝利者:一旦你收集了系統中的所有使用者名稱建立操作,就可以比較它們的時間戳。然而當某個節點需要實時處理使用者建立使用者名稱的請求時,這樣的方法就無法滿足了。節點需要**馬上(right now)**決定這個請求是成功還是失敗。在那個時刻,節點並不知道是否存其他節點正在併發執行建立同樣使用者名稱的操作,罔論其它節點可能分配給那個操作的時間戳。 +​ 這種方法適用於事後確定勝利者:一旦你收集了系統中的所有使用者名稱建立操作,就可以比較它們的時間戳。然而當某個節點需要實時處理使用者建立使用者名稱的請求時,這樣的方法就無法滿足了。節點需要**馬上(right now)**決定這個請求是成功還是失敗。在那個時刻,節點並不知道是否存在其他節點正在併發執行建立同樣使用者名稱的操作,罔論其它節點可能分配給那個操作的時間戳。 @@ -922,7 +908,7 @@ -​ 總之:為了實諸如如使用者名稱上的唯一約束這種東西,僅有操作的全序是不夠的,你還需要知道這個全序何時會塵埃落定。如果你有一個建立使用者名稱的操作,並且確定在全序中,沒有任何其他節點可以在你的操作之前插入對同一使用者名稱的聲稱,那麼你就可以安全地宣告操作執行成功。 +​ 總之:為了實現諸如使用者名稱上的唯一約束這種東西,僅有操作的全序是不夠的,你還需要知道這個全序何時會塵埃落定。如果你有一個建立使用者名稱的操作,並且確定在全序中沒有任何其他節點可以在你的操作之前插入對同一使用者名稱的聲稱,那麼你就可以安全地宣告操作執行成功。 @@ -938,11 +924,11 @@ -​ 如前所述,單主複製透過選擇一個節點作為主庫來確定操作的全序,並在主庫的單個CPU核上對所有操作進行排序。接下來的挑戰是,如果吞吐量超出單個主庫的處理能力,這種情況下如何擴充套件系統;以及,如果主庫失效(“[處理節點宕機](#處理節點宕機)”),如何處理故障切換。在分散式系統文獻中,這個問題被稱為**全序廣播(total order broadcast)**或**原子廣播(atomic broadcast)**[^ix]【25,57,58】。 +​ 如前所述,單主複製透過選擇一個節點作為主庫來確定操作的全序,並在主庫的單個CPU核上對所有操作進行排序。接下來的挑戰是,如果吞吐量超出單個主庫的處理能力,這種情況下如何擴充套件系統;以及,如果主庫失效(“[處理節點宕機](ch5.md#處理節點宕機)”),如何處理故障切換。在分散式系統文獻中,這個問題被稱為**全序廣播(total order broadcast)**或**原子廣播(atomic broadcast)**[^ix]【25,57,58】。 -[^ix]: “原子廣播”是一個傳統的術語,非常混亂,而且與“原子”一詞的其他用法不一致:它與ACID事務中的原子性沒有任何關係,只是與原子操作(在多執行緒程式設計的意義上 )或原子暫存器(線性一致儲存)有間接的聯絡。全序廣播是另一個同義詞。 +[^ix]: “原子廣播”是一個傳統的術語,非常混亂,而且與“原子”一詞的其他用法不一致:它與ACID事務中的原子性沒有任何關係,只是與原子操作(在多執行緒程式設計的意義上 )或原子暫存器(線性一致儲存)有間接的聯絡。全序組播(total order multicast)是另一個同義詞。 @@ -994,11 +980,11 @@ -​ 全序廣播的一個重要表現是,順序在訊息送達時被固化:如果後續的訊息已經送達,節點就不允許追溯地將(先前)訊息插入順序中的較早位置。這個事實使得全序廣播比時間戳命令更強。 +​ 全序廣播的一個重要表現是,順序在訊息送達時被固化:如果後續的訊息已經送達,節點就不允許追溯地將(先前)訊息插入順序中的較早位置。這個事實使得全序廣播比時間戳排序更強。 -​ 考量全序廣播的另一種方式是,這是一種建立日誌的方式(如在複製日誌,事務日誌或預寫式日誌中):傳遞訊息就像附加寫入日誌。由於所有節點必須以相同的順序傳遞相同的訊息,因此所有節點都可以讀取日誌,並看到相同的訊息序列。 +​ 考量全序廣播的另一種方式是,這是一種建立日誌的方式(如在複製日誌、事務日誌或預寫式日誌中):傳遞訊息就像追加寫入日誌。由於所有節點必須以相同的順序傳遞相同的訊息,因此所有節點都可以讀取日誌,並看到相同的訊息序列。 @@ -1010,7 +996,7 @@ -​ 如 [圖9-4](../img/fig9-4.png) 所示,線上性一致的系統中,存在操作的全序。這是否意味著線性一致與全序廣播一樣?不盡然,但兩者之間有者密切的聯絡[^x]。 +​ 如 [圖9-4](img/fig9-4.png) 所示,線上性一致的系統中,存在操作的全序。這是否意味著線性一致與全序廣播一樣?不盡然,但兩者之間有著密切的聯絡[^x]。 @@ -1026,7 +1012,7 @@ -​ 設想對於每一個可能的使用者名稱,你都可以有一個帶有CAS原子操作的線性一致暫存器。每個暫存器最初的值為空值(表示不使用使用者名稱)。當用戶想要建立一個使用者名稱時,對該使用者名稱的暫存器執行CAS操作,在先前暫存器值為空的條件,將其值設定為使用者的賬號ID。如果多個使用者試圖同時獲取相同的使用者名稱,則只有一個CAS操作會成功,因為其他使用者會看到非空的值(由於線性一致性)。 +​ 設想對於每一個可能的使用者名稱,你都可以有一個帶有CAS原子操作的線性一致暫存器。每個暫存器最初的值為空值(表示未使用該使用者名稱)。當用戶想要建立一個使用者名稱時,對該使用者名稱的暫存器執行CAS操作,在先前暫存器值為空的條件,將其值設定為使用者的賬號ID。如果多個使用者試圖同時獲取相同的使用者名稱,則只有一個CAS操作會成功,因為其他使用者會看到非空的值(由於線性一致性)。 @@ -1036,9 +1022,9 @@ 1. 在日誌中追加一條訊息,試探性地指明你要宣告的使用者名稱。 -2. 讀日誌,並等待你所附加的資訊被回送。[^xi] +2. 讀日誌,並等待你剛才追加的訊息被讀回。[^xi] -3. 檢查是否有任何訊息聲稱目標使用者名稱的所有權。如果這些訊息中的第一條就你自己的訊息,那麼你就成功了:你可以提交聲稱的使用者名稱(也許是透過向日志追加另一條訊息)並向客戶端確認。如果所需使用者名稱的第一條訊息來自其他使用者,則中止操作。 +4. 檢查是否有任何訊息聲稱目標使用者名稱的所有權。如果這些訊息中的第一條就是你自己的訊息,那麼你就成功了:你可以提交聲稱的使用者名稱(也許是透過向日志追加另一條訊息)並向客戶端確認。如果所需使用者名稱的第一條訊息來自其他使用者,則中止操作。 @@ -1054,11 +1040,11 @@ -* 你可以透過追加一條訊息,當訊息回送時讀取日誌,執行實際的讀取。訊息在日誌中的位置因此定義了讀取發生的時間點。 (etcd的法定人數讀取有些類似這種情況【16】。) +* 你可以透過在日誌中追加一條訊息,然後讀取日誌,直到該訊息被讀回才執行實際的讀取操作。訊息在日誌中的位置因此定義了讀取發生的時間點。 (etcd的法定人數讀取有些類似這種情況【16】。) -* 如果日誌允許以線性一致的方式獲取最新日誌訊息的位置,則可以查詢該位置,等待直到該位置前的所有訊息都傳達到你,然後執行讀取。 (這是Zookeeper `sync()` 操作背後的思想【15】)。 +* 如果日誌允許以線性一致的方式獲取最新日誌訊息的位置,則可以查詢該位置,等待該位置前的所有訊息都傳達到你,然後執行讀取。 (這是Zookeeper `sync()` 操作背後的思想【15】)。 -* 你可以從同步更新的副本中進行讀取,因此可以確保結果是最新的。 (這種技術用於鏈式複製【63】;參閱“[複製研究](ch5.md#設定新從庫)”。) +* 你可以從同步更新的副本中進行讀取,因此可以確保結果是最新的。 (這種技術用於鏈式複製(chain replication)【63】;參閱“[關於複製的研究](ch5.md#關於複製的研究)”。) @@ -1074,7 +1060,7 @@ -​ 該演算法很簡單:每個要透過全序廣播發送的訊息首先對線性一致暫存器執行**自增並返回**操作。然後將從暫存器獲得的值作為序列號附加到訊息中。然後你可以將訊息傳送到所有節點(重新發送任何丟失的訊息),而收件人將按序列號連續傳送訊息。 +​ 該演算法很簡單:每個要透過全序廣播發送的訊息首先對線性一致暫存器執行**自增並返回**操作。然後將從暫存器獲得的值作為序列號附加到訊息中。然後你可以將訊息傳送到所有節點(重新發送任何丟失的訊息),而收件人將按序列號依序傳遞(deliver)訊息。 @@ -1082,7 +1068,7 @@ -​ 實現一個帶有原子性**自增並返回**操作的線性一致暫存器有多困難?像往常一樣,如果事情從來不出差錯,那很容易:你可以簡單地把它儲存在單個節點內的變數中。問題在於處理當該節點的網路連線中斷時的情況,並在該節點失效時能恢復這個值【59】。一般來說,如果你對線性一致性的序列號生成器進行深入過足夠深入的思考,你不可避免地會得出一個共識演算法。 +​ 實現一個帶有原子性**自增並返回**操作的線性一致暫存器有多困難?像往常一樣,如果事情從來不出差錯,那很容易:你可以簡單地把它儲存在單個節點內的變數中。問題在於處理當該節點的網路連線中斷時的情況,並在該節點失效時能恢復這個值【59】。一般來說,如果你對線性一致性的序列號生成器進行過足夠深入的思考,你不可避免地會得出一個共識演算法。 @@ -1106,7 +1092,7 @@ -​ 儘管共識非常重要,但關於它的內容出現在本書的後半部分,因為這個主題非常微妙,欣賞細微之處需要一些必要的知識。即使在學術界,對共識的理解也是在幾十年的過程中逐漸沉澱而來,一路上也有著許多誤解。現在我們已經討論了複製([第5章](ch5.md)),事務([第7章](ch7.md)),系統模型([第8章](ch8.md)),線性一致以及全序廣播([本章](ch9.md)),我們終於準備好解決共識問題了。 +​ 儘管共識非常重要,但關於它的內容出現在本書的後半部分,因為這個主題非常微妙,欣賞細微之處需要一些必要的知識。即使在學術界,對共識的理解也是在幾十年的過程中逐漸沉澱而來,一路上也有著許多誤解。現在我們已經討論了複製([第5章](ch5.md)),事務([第7章](ch7.md)),系統模型([第8章](ch8.md)),線性一致以及全序廣播(本章),我們終於準備好解決共識問題了。 @@ -1126,7 +1112,7 @@ -​ 在支援跨多節點或跨多分割槽事務的資料庫中,一個事務可能在某些節點上失敗,但在其他節點上成功。如果我們想要維護事務的原子性(就ACID而言,請參“[原子性](ch7.md#原子性)”),我們必須讓所有節點對事務的結果達成一致:要麼全部中止/回滾(如果出現任何錯誤),要麼它們全部提交(如果沒有出錯)。這個共識的例子被稱為**原子提交(atomic commit)**問題[^xii]。 +​ 在支援跨多節點或跨多分割槽事務的資料庫中,一個事務可能在某些節點上失敗,但在其他節點上成功。如果我們想要維護事務的原子性(就ACID而言,請參閱“[原子性(Atomicity)](ch7.md#原子性(Atomicity))”),我們必須讓所有節點對事務的結果達成一致:要麼全部中止/回滾(如果出現任何錯誤),要麼它們全部提交(如果沒有出錯)。這個共識的例子被稱為**原子提交(atomic commit)**問題[^xii]。 @@ -1140,19 +1126,19 @@ > -> 你可能已經聽說過作者Fischer,Lynch和Paterson之後的FLP結果【68】,它證明,如果存在節點可能崩潰的風險,則不存在**總是**能夠達成共識的演算法。在分散式系統中,我們必須假設節點可能會崩潰,所以可靠的共識是不可能的。然而這裡我們正在討論達成共識的演算法,到底是怎麼回事? +> 你可能已經聽說過以作者Fischer,Lynch和Paterson命名的FLP結果【68】,它證明,如果存在節點可能崩潰的風險,則不存在**總是**能夠達成共識的演算法。在分散式系統中,我們必須假設節點可能會崩潰,所以可靠的共識是不可能的。然而這裡我們正在討論達成共識的演算法,到底是怎麼回事? > -> 答案是FLP結果在**非同步系統模型**中得到了證明(參閱“[系統模型與現實](ch8.md#系統模型與現實)”),這是一種限制性很強的模型,它假定確定性演算法不能使用任何時鐘或超時。如果允許演算法使用**超時**或其他方法來識別可疑的崩潰節點(即使懷疑有時是錯誤的),則共識變為一個可解的問題【67】。即使僅僅允許演算法使用隨機數,也足以繞過這個不可能的結果【69】。 +> 答案是FLP結果是在**非同步系統模型**中被證明的(參閱“[系統模型與現實](ch8.md#系統模型與現實)”),而這是一種限制性很強的模型,它假定確定性演算法不能使用任何時鐘或超時。如果允許演算法使用**超時**或其他方法來識別可疑的崩潰節點(即使懷疑有時是錯誤的),則共識變為一個可解的問題【67】。即使僅僅允許演算法使用隨機數,也足以繞過這個不可能的結果【69】。 > -> 因此,FLP是關於共識不可能性的重要理論結果,但現實中的分散式系統通常是可以達成共識的。 +> 因此,雖然FLP是關於共識不可能性的重要理論結果,但現實中的分散式系統通常是可以達成共識的。 -​ 在本節中,我們將首先更詳細地研究**原子提交**問題。具體來說,我們將討論**兩階段提交(2PC, two-phase commit)**演算法,這是解決原子提交問題最常見的辦法,並在各種資料庫、訊息佇列和應用伺服器中實現。事實證明2PC是一種共識演算法,但不是一個非常好的演算法【70,71】。 +​ 在本節中,我們將首先更詳細地研究**原子提交**問題。具體來說,我們將討論**兩階段提交(2PC, two-phase commit)**演算法,這是解決原子提交問題最常見的辦法,並在各種資料庫、訊息佇列和應用伺服器中被實現。事實證明2PC是一種共識演算法,但不是一個非常好的共識演算法【70,71】。 @@ -1172,7 +1158,7 @@ -​ 原子性可以防止失敗的事務攪亂資料庫,避免資料庫陷入半成品結果和半更新狀態。這對於多物件事務(參閱“[單物件和多物件操作](ch7.md#單物件和多物件操作)”)和維護次級索引的資料庫尤其重要。每個輔助索引都是與主資料相分離的資料結構—— 因此,如果你修改了一些資料,則還需要在輔助索引中進行相應的更改。原子性確保二級索引與主資料保持一致(如果索引與主資料不一致,就沒什麼用了)。 +​ 原子性可以防止失敗的事務攪亂資料庫,避免資料庫陷入半成品結果和半更新狀態。這對於多物件事務(參閱“[單物件和多物件操作](ch7.md#單物件和多物件操作)”)和維護次級索引的資料庫尤其重要。每個次級索引都是與主資料相分離的資料結構—— 因此,如果你修改了一些資料,則還需要在次級索引中進行相應的更改。原子性確保次級索引與主資料保持一致(如果索引與主資料不一致,就沒什麼用了)。 @@ -1180,15 +1166,15 @@ -​ 對於在單個數據庫節點執行的事務,原子性通常由儲存引擎實現。當客戶端請求資料庫節點提交事務時,資料庫將使事務的寫入持久化(通常在預寫式日誌中:參閱“[使B樹可靠](ch3.md#使B樹可靠)”),然後將提交記錄追加到磁碟中的日誌裡。如果資料庫在這個過程中間崩潰,當節點重啟時,事務會從日誌中恢復:如果提交記錄在崩潰之前成功地寫入磁碟,則認為事務被提交;否則來自該事務的任何寫入都被回滾。 +​ 對於在單個數據庫節點執行的事務,原子性通常由儲存引擎實現。當客戶端請求資料庫節點提交事務時,資料庫將使事務的寫入持久化(通常在預寫式日誌中,參閱“[讓B樹更可靠](ch3.md#讓B樹更可靠)”),然後將提交記錄追加到磁碟中的日誌裡。如果資料庫在這個過程中間崩潰,當節點重啟時,事務會從日誌中恢復:如果提交記錄在崩潰之前成功地寫入磁碟,則認為事務被提交;否則來自該事務的任何寫入都被回滾。 -​ 因此,在單個節點上,事務的提交主要取決於資料持久化落盤的**順序**:首先是資料,然後是提交記錄【72】。事務提交或終止的關鍵決定時刻是磁碟完成寫入提交記錄的時刻:在此之前,仍有可能中止(由於崩潰),但在此之後,事務已經提交(即使資料庫崩潰)。因此,是單一的裝置(連線到單個磁碟驅動的控制器,且掛載在單臺機器上)使得提交具有原子性。 +​ 因此,在單個節點上,事務的提交主要取決於資料持久化落盤的**順序**:首先是資料,然後是提交記錄【72】。事務提交或終止的關鍵決定時刻是磁碟完成寫入提交記錄的時刻:在此之前,仍有可能中止(由於崩潰),但在此之後,事務已經提交(即使資料庫崩潰)。因此,是單一的裝置(連線到單個磁碟的控制器,且掛載在單臺機器上)使得提交具有原子性。 -​ 但是,如果一個事務中涉及多個節點呢?例如,你也許在分割槽資料庫中會有一個多物件事務,或者是一個按關鍵詞分割槽的二級索引(其中索引條目可能位於與主資料不同的節點上;參閱“[分割槽和二級索引](ch6.md#分割槽和二級索引)”)。大多數“NoSQL”分散式資料儲存不支援這種分散式事務,但是很多關係型資料庫叢集支援(參見“[實踐中的分散式事務](#實踐中的分散式事務)”)。 +​ 但是,如果一個事務中涉及多個節點呢?例如,你也許在分割槽資料庫中會有一個多物件事務,或者是一個按關鍵詞分割槽的二級索引(其中索引條目可能位於與主資料不同的節點上;參閱“[分割槽與次級索引](ch6.md#分割槽與次級索引)”)。大多數“NoSQL”分散式資料儲存不支援這種分散式事務,但是很多關係型資料庫叢集支援(參見“[實踐中的分散式事務](#實踐中的分散式事務)”)。 @@ -1196,7 +1182,7 @@ -* 某些節點可能會檢測到約束衝突或衝突,因此需要中止,而其他節點則可以成功進行提交。 +* 某些節點可能會檢測到違反約束或衝突,因此需要中止,而其他節點則可以成功進行提交。 * 某些提交請求可能在網路中丟失,最終由於超時而中止,而其他提交請求則透過。 @@ -1204,7 +1190,7 @@ -如果某些節點提交了事務,但其他節點卻放棄了這些事務,那麼這些節點就會彼此不一致(如 [圖7-3](../img/fig7-3.png) 所示)。而且一旦在某個節點上提交了一個事務,如果事後發現它在其它節點上被中止了,它是無法撤回的。出於這個原因,一旦確定事務中的所有其他節點也將提交,節點就必須進行提交。 +如果某些節點提交了事務,但其他節點卻放棄了這些事務,那麼這些節點就會彼此不一致(如 [圖7-3](img/fig7-3.png) 所示)。而且一旦在某個節點上提交了一個事務,如果事後發現它在其它節點上被中止了,它是無法撤回的。出於這個原因,一旦確定事務中的所有其他節點也將提交,節點就必須進行提交。 @@ -1212,7 +1198,7 @@ -​ (提交事務的結果有可能透過事後執行另一個補償事務來取消【73,74】,但從資料庫的角度來看,這是一個單獨的事務,因此任何關於跨事務正確性的保證都是應用自己的問題。) +​ (提交事務的結果有可能透過事後執行另一個補償事務(compensating transaction)來取消【73,74】,但從資料庫的角度來看,這是一個單獨的事務,因此任何關於跨事務正確性的保證都是應用自己的問題。) @@ -1224,11 +1210,11 @@ -[ 圖9-9](../img/fig9-9)說明了2PC的基本流程。2PC中的提交/中止過程分為兩個階段(因此而得名),而不是單節點事務中的單個提交請求。 +[圖9-9](img/fig9-9.png)說明了2PC的基本流程。2PC中的提交/中止過程分為兩個階段(因此而得名),而不是單節點事務中的單個提交請求。 -![](../img/fig9-9.png) +![](img/fig9-9.png) @@ -1244,7 +1230,7 @@ -​ 2PC使用一個通常不會出現在單節點事務中的新元件:**協調者(coordinator)**(也稱為**事務管理器(transaction manager)**)。協調者通常在請求事務的相同應用程序中以庫的形式實現(例如,嵌入在Java EE容器中),但也可以是單獨的程序或服務。這種協調者的例子包括Narayana,JOTM,BTM或MSDTC。 +​ 2PC使用一個通常不會出現在單節點事務中的新元件:**協調者(coordinator)**(也稱為**事務管理器(transaction manager)**)。協調者通常在請求事務的相同應用程序中以庫的形式實現(例如,嵌入在Java EE容器中),但也可以是單獨的程序或服務。這種協調者的例子包括Narayana、JOTM、BTM或MSDTC。 @@ -1288,7 +1274,7 @@ -因此,該協議包含兩個關鍵的“不歸路”點:當參與者投票“是”時,它承諾它稍後肯定能夠提交(儘管協調者可能仍然選擇放棄)。一旦協調者做出決定,這一決定是不可撤銷的。這些承諾保證了2PC的原子性。 (單節點原子提交將這兩個事件混為一談:將提交記錄寫入事務日誌。) +因此,該協議包含兩個關鍵的“不歸路”點:當參與者投票“是”時,它承諾它稍後肯定能夠提交(儘管協調者可能仍然選擇放棄);以及一旦協調者做出決定,這一決定是不可撤銷的。這些承諾保證了2PC的原子性。 (單節點原子提交將這兩個事件合為了一體:將提交記錄寫入事務日誌。) @@ -1308,11 +1294,11 @@ -​ 情況如[圖9-10](../img/fig9-10) 所示。在這個特定的例子中,協調者實際上決定提交,資料庫2 收到提交請求。但是,協調者在將提交請求傳送到資料庫1 之前發生崩潰,因此資料庫1 不知道是否提交或中止。即使**超時**在這裡也沒有幫助:如果資料庫1 在超時後單方面中止,它將最終與執行提交的資料庫2 不一致。同樣,單方面提交也是不安全的,因為另一個參與者可能已經中止了。 +​ 情況如[圖9-10](img/fig9-10.png) 所示。在這個特定的例子中,協調者實際上決定提交,資料庫2 收到提交請求。但是,協調者在將提交請求傳送到資料庫1 之前發生崩潰,因此資料庫1 不知道是否提交或中止。即使**超時**在這裡也沒有幫助:如果資料庫1 在超時後單方面中止,它將最終與執行提交的資料庫2 不一致。同樣,單方面提交也是不安全的,因為另一個參與者可能已經中止了。 -![](../img/fig9-10.png) +![](img/fig9-10.png)  **圖9-10 參與者投贊成票後,協調者崩潰。資料庫1不知道是否提交或中止** @@ -1374,7 +1360,7 @@ -​ 在**異構(heterogeneous)**事務中,參與者是兩種或以上不同技術:例如來自不同供應商的兩個資料庫,甚至是非資料庫系統(如訊息代理)。跨系統的分散式事務必須確保原子提交,儘管系統可能完全不同。 +​ 在**異構(heterogeneous)**事務中,參與者是由兩種或兩種以上的不同技術組成的:例如來自不同供應商的兩個資料庫,甚至是非資料庫系統(如訊息代理)。跨系統的分散式事務必須確保原子提交,儘管系統可能完全不同。 @@ -1398,7 +1384,7 @@ -​ 在[第11章](ch11.md)中將再次回到”恰好一次“訊息處理的主題。讓我們先來看看允許這種異構分散式事務的原子提交協議。 +​ 在[第11章](ch11.md)中將再次回到“恰好一次”訊息處理的主題。讓我們先來看看允許這種異構分散式事務的原子提交協議。 @@ -1414,11 +1400,11 @@ -​ XA假定你的應用使用網路驅動或客戶端庫來與**參與者**進行通訊(資料庫或訊息服務)。如果驅動支援XA,則意味著它會呼叫XA API 以查明操作是否為分散式事務的一部分 —— 如果是,則將必要的資訊發往資料庫伺服器。驅動還會向協調者暴露回撥介面,協調者可以透過回撥來要求參與者準備,提交或中止。 +​ XA假定你的應用使用網路驅動或客戶端庫來與**參與者**(資料庫或訊息服務)進行通訊。如果驅動支援XA,則意味著它會呼叫XA API 以查明操作是否為分散式事務的一部分 —— 如果是,則將必要的資訊發往資料庫伺服器。驅動還會向協調者暴露回撥介面,協調者可以透過回撥來要求參與者準備、提交或中止。 -​ 事務協調者需要實現XA API。標準沒有指明應該如何實現,但實際上協調者通常只是一個庫,被載入到發起事務的應用的同一個程序中(而不是單獨的服務)。它在事務中個跟蹤所有的參與者,並在要求它們**準備**之後收集參與者的響應(透過驅動回撥),並使用本地磁碟上的日誌記錄每次事務的決定(提交/中止)。 +​ 事務協調者需要實現XA API。標準沒有指明應該如何實現,但實際上協調者通常只是一個庫,被載入到發起事務的應用的同一個程序中(而不是單獨的服務)。它在事務中跟蹤所有的參與者,並在要求它們**準備**之後收集參與者的響應(透過驅動回撥),並使用本地磁碟上的日誌記錄每次事務的決定(提交/中止)。 @@ -1438,7 +1424,7 @@ -​ 在事務提交或中止之前,資料庫不能釋放這些鎖(如[圖9-9](../img/fig9-9.png)中的陰影區域所示)。因此,在使用兩階段提交時,事務必須在整個存疑期間持有這些鎖。如果協調者已經崩潰,需要20分鐘才能重啟,那麼這些鎖將會被持有20分鐘。如果協調者的日誌由於某種原因徹底丟失,這些鎖將被永久持有 —— 或至少在管理員手動解決該情況之前。 +​ 在事務提交或中止之前,資料庫不能釋放這些鎖(如[圖9-9](img/fig9-9.png)中的陰影區域所示)。因此,在使用兩階段提交時,事務必須在整個存疑期間持有這些鎖。如果協調者已經崩潰,需要20分鐘才能重啟,那麼這些鎖將會被持有20分鐘。如果協調者的日誌由於某種原因徹底丟失,這些鎖將被永久持有 —— 或至少在管理員手動解決該情況之前。 @@ -1454,7 +1440,7 @@ -​ 即使重啟資料庫伺服器也無法解決這個問題,因為在2PC的正確實現中,即使重啟也必須保留存疑事務的鎖(否則就會冒有違反原子性保證的風險)。這是一種棘手的情況。 +​ 即使重啟資料庫伺服器也無法解決這個問題,因為在2PC的正確實現中,即使重啟也必須保留存疑事務的鎖(否則就會冒違反原子性保證的風險)。這是一種棘手的情況。 @@ -1462,7 +1448,7 @@ -​ 許多XA的實現都有一個叫做**啟發式決策(heuristic decistions)**的緊急逃生艙口:允許參與者單方面決定放棄或提交一個存疑事務,而無需協調者做出最終決定【76,77,91】。要清楚的是,這裡**啟發式**是**可能破壞原子性(probably breaking atomicity)**的委婉說法,因為它違背了兩階段提交的系統承諾。因此,啟發式決策只是為了逃出災難性的情況而準備的,而不是為了日常使用的。 +​ 許多XA的實現都有一個叫做**啟發式決策(heuristic decisions)**的緊急逃生艙口:允許參與者單方面決定放棄或提交一個存疑事務,而無需協調者做出最終決定【76,77,91】。要清楚的是,這裡**啟發式**是**可能破壞原子性(probably breaking atomicity)**的委婉說法,因為它違背了兩階段提交的系統承諾。因此,啟發式決策只是為了逃出災難性的情況而準備的,而不是為了日常使用的。 @@ -1470,7 +1456,7 @@ -​ XA事務解決了保持多個參與者(資料系統)相互一致的現實的重要問題,但正如我們所看到的那樣,它也引入了嚴重的運維問題。特別來講,這裡的核心認識是:事務協調者本身就是一種資料庫(儲存了事務的結果),因此需要像其他重要資料庫一樣小心地打交道: +​ XA事務解決了保持多個參與者(資料系統)相互一致的現實的和重要的問題,但正如我們所看到的那樣,它也引入了嚴重的運維問題。特別來講,這裡的核心認識是:事務協調者本身就是一種資料庫(儲存了事務的結果),因此需要像其他重要資料庫一樣小心地打交道: @@ -1478,13 +1464,13 @@ * 許多伺服器端應用都是使用無狀態模式開發的(受HTTP的青睞),所有持久狀態都儲存在資料庫中,因此具有應用伺服器可隨意按需新增刪除的優點。但是,當協調者成為應用伺服器的一部分時,它會改變部署的性質。突然間,協調者的日誌成為持久系統狀態的關鍵部分—— 與資料庫本身一樣重要,因為協調者日誌是為了在崩潰後恢復存疑事務所必需的。這樣的應用伺服器不再是無狀態的了。 -* 由於XA需要相容各種資料系統,因此它必須是所有系統的最小公分母。例如,它不能檢測不同系統間的死鎖(因為這將需要一個標準協議來讓系統交換每個事務正在等待的鎖的資訊),而且它無法與[SSI ](ch7.md#可序列快照隔離(SSI) )協同工作,因為這需要一個跨系統定位衝突的協議。 +* 由於XA需要相容各種資料系統,因此它必須是所有系統的最小公分母。例如,它不能檢測不同系統間的死鎖(因為這將需要一個標準協議來讓系統交換每個事務正在等待的鎖的資訊),而且它無法與SSI(參閱[可序列化快照隔離(SSI)](ch7.md#可序列化快照隔離(SSI) ))協同工作,因為這需要一個跨系統定位衝突的協議。 -* 對於資料庫內部的分散式事務(不是XA),限制沒有這麼大,例如,分散式版本的SSI 是可能的。然而仍然存在問題:2PC成功提交一個事務需要所有參與者的響應。因此,如果系統的**任何**部分損壞,事務也會失敗。因此,分散式事務又有**擴大失效(amplifying failures)**的趨勢,這又與我們構建容錯系統的目標背道而馳。 +* 對於資料庫內部的分散式事務(不是XA),限制沒有這麼大 —— 例如,分散式版本的SSI是可能的。然而仍然存在問題:2PC成功提交一個事務需要所有參與者的響應。因此,如果系統的**任何**部分損壞,事務也會失敗。因此,分散式事務又有**擴大失效(amplifying failures)**的趨勢,這又與我們構建容錯系統的目標背道而馳。 -這些事實是否意味著我們應該放棄保持幾個系統相互一致的所有希望?不完全是 —— 還有其他的辦法,可以讓我們在沒有異構分散式事務的痛苦的情況下實現同樣的事情。我們將在[第11章](ch11.md) 和[第12章](ch12.md) 回到這些章節。但首先,我們應該概括一下關於**共識**的話題。 +這些事實是否意味著我們應該放棄保持幾個系統相互一致的所有希望?不完全是 —— 還有其他的辦法,可以讓我們在沒有異構分散式事務的痛苦的情況下實現同樣的事情。我們將在[第11章](ch11.md) 和[第12章](ch12.md) 回到這些話題。但首先,我們應該概括一下關於**共識**的話題。 @@ -1500,7 +1486,7 @@ -​ 共識問題通常形式化如下:一個或多個節點可以**提議(propose)**某些值,而共識演算法**決定(decides)**採用其中的某個值。在座位預訂的例子中,當幾個顧客同時試圖訂購最後一個座位時,處理顧客請求的每個節點可以**提議**正在服務的顧客的ID,而**決定**指明瞭哪個顧客獲得了座位。 +​ 共識問題通常形式化如下:一個或多個節點可以**提議(propose)**某些值,而共識演算法**決定(decides)**採用其中的某個值。在座位預訂的例子中,當幾個顧客同時試圖訂購最後一個座位時,處理顧客請求的每個節點可以**提議**將要服務的顧客的ID,而**決定**指明瞭哪個顧客獲得了座位。 @@ -1550,7 +1536,7 @@ -​ **終止**屬性正式形成了容錯的思想。它實質上說的是,一個共識演算法不能簡單地永遠閒坐著等死 —— 換句話說,它必須取得進展。即使部分節點出現故障,其他節點也必須達成一項決定。 (**終止**是一種**活性屬性**,而另外三種是安全屬性 —— 參見“[安全性和活性](ch8.md#安全性和活性)”。) +​ **終止**屬性形式化了容錯的思想。它實質上說的是,一個共識演算法不能簡單地永遠閒坐著等死 —— 換句話說,它必須取得進展。即使部分節點出現故障,其他節點也必須達成一項決定。 (**終止**是一種**活性屬性**,而另外三種是**安全屬性** —— 參見“[安全性和活性](ch8.md#安全性和活性)”。) @@ -1566,7 +1552,7 @@ -​ 大多數共識演算法假設不存在**拜占庭式錯誤**,正如在“[拜占庭式錯誤](#拜占庭式錯誤)”一節中所討論的那樣。也就是說,如果一個節點沒有正確地遵循協議(例如,如果它向不同節點發送矛盾的訊息),它就可能會破壞協議的安全屬性。克服拜占庭故障,穩健地達成共識是可能的,只要少於三分之一的節點存在拜占庭故障【25,93】。但我們沒有地方在本書中詳細討論這些演算法了。 +​ 大多數共識演算法假設不存在**拜占庭式錯誤**,正如在“[拜占庭故障](ch8.md#拜占庭故障)”一節中所討論的那樣。也就是說,如果一個節點沒有正確地遵循協議(例如,如果它向不同節點發送矛盾的訊息),它就可能會破壞協議的安全屬性。克服拜占庭故障,穩健地達成共識是可能的,只要少於三分之一的節點存在拜占庭故障【25,93】。但我們沒有地方在本書中詳細討論這些演算法了。 @@ -1574,11 +1560,11 @@ -​ 最著名的容錯共識演算法是**檢視戳複製(VSR, viewstamped replication)**【94,95】,Paxos 【96,97,98,99】,Raft 【22,100,101】以及 Zab 【15,21,102】 。這些演算法之間有不少相似之處,但它們並不相同【103】。在本書中我們不會介紹各種演算法的詳細細節:瞭解一些它們共通的高階思想通常已經足夠了,除非你準備自己實現一個共識系統。(可能並不明智,相當難【98,104】) +​ 最著名的容錯共識演算法是**檢視戳複製(VSR, Viewstamped Replication)**【94,95】,Paxos 【96,97,98,99】,Raft 【22,100,101】以及 Zab 【15,21,102】 。這些演算法之間有不少相似之處,但它們並不相同【103】。在本書中我們不會介紹各種演算法的詳細細節:瞭解一些它們共通的高階思想通常已經足夠了,除非你準備自己實現一個共識系統。(可能並不明智,相當難【98,104】) -​ 大多數這些演算法實際上並不直接使用這裡描述的形式化模型(提議與決定單個值,一致同意,完整性,有效性和終止屬性)。取而代之的是,它們決定了值的**順序(sequence)**,這使它們成為全序廣播演算法,正如本章前面所討論的那樣(參閱“[全序廣播](#全序廣播)”)。 +​ 大多數這些演算法實際上並不直接使用這裡描述的形式化模型(提議與決定單個值,並滿足一致同意、完整性、有效性和終止屬性)。取而代之的是,它們決定了值的**順序(sequence)**,這使它們成為全序廣播演算法,正如本章前面所討論的那樣(參閱“[全序廣播](#全序廣播)”)。 @@ -1586,16 +1572,12 @@ -所以,全序廣播相當於重複進行多輪共識(每次共識決定與一次訊息傳遞相對應): +​ 所以,全序廣播相當於重複進行多輪共識(每次共識決定與一次訊息傳遞相對應): * 由於**一致同意**屬性,所有節點決定以相同的順序傳遞相同的訊息。 - - - - * 由於**完整性**屬性,訊息不會重複。 * 由於**有效性**屬性,訊息不會被損壞,也不能憑空編造。 @@ -1604,7 +1586,7 @@ -檢視戳複製,Raft和Zab直接實現了全序廣播,因為這樣做比重複**一次一值(one value a time)**的共識更高效。在Paxos的情況下,這種最佳化被稱為Multi-Paxos。 +​ 檢視戳複製,Raft和Zab直接實現了全序廣播,因為這樣做比重複**一次一值(one value a time)**的共識更高效。在Paxos的情況下,這種最佳化被稱為Multi-Paxos。 @@ -1612,7 +1594,7 @@ -​ 在[第5章](ch5.md)中,我們討論了單領導者複製(參見“[領導者和追隨者](ch5.md#領導者和追隨者)”),它將所有的寫入操作都交給主庫,並以相同的順序將它們應用到從庫,從而使副本保持在最新狀態。這實際上不就是一個全序廣播嗎?為什麼我們在[第五章](ch5.md)裡一點都沒擔心過共識問題呢? +​ 在[第5章](ch5.md)中,我們討論了單領導者複製(參見“[領導者與追隨者](ch5.md#領導者與追隨者)”),它將所有的寫入操作都交給主庫,並以相同的順序將它們應用到從庫,從而使副本保持在最新狀態。這實際上不就是一個全序廣播嗎?為什麼我們在[第五章](ch5.md)裡一點都沒擔心過共識問題呢? @@ -1632,27 +1614,27 @@ -#### 時代編號和法定人數 +#### 紀元編號和法定人數 -​ 迄今為止所討論的所有共識協議,在內部都以某種形式使用一個領導者,但它們並不能保證領導者是獨一無二的。相反,它們可以做出更弱的保證:協議定義了一個**時代編號(epoch number)**(在Paxos中稱為**投票編號(ballot number)**,檢視戳複製中的**檢視編號(view number)**,以及Raft中的**任期號碼(term number)**),並確保在每個時代中,領導者都是唯一的。 +​ 迄今為止所討論的所有共識協議,在內部都以某種形式使用一個領導者,但它們並不能保證領導者是獨一無二的。相反,它們可以做出更弱的保證:協議定義了一個**紀元編號(epoch number)**(在Paxos中稱為**投票編號(ballot number)**,檢視戳複製中的**檢視編號(view number)**,以及Raft中的**任期號碼(term number)**),並確保在每個時代中,領導者都是唯一的。 -​ 每次當現任領導被認為掛掉的時候,節點間就會開始一場投票,以選出一個新領導。這次選舉被賦予一個遞增的時代編號,因此時代編號是全序且單調遞增的。如果兩個不同的時代的領導者之間出現衝突(也許是因為前任領導者實際上並未死亡),那麼帶有更高時代編號的領導說了算。 +​ 每次當現任領導被認為掛掉的時候,節點間就會開始一場投票,以選出一個新領導。這次選舉被賦予一個遞增的紀元編號,因此紀元編號是全序且單調遞增的。如果兩個不同的時代的領導者之間出現衝突(也許是因為前任領導者實際上並未死亡),那麼帶有更高紀元編號的領導說了算。 -​ 在任何領導者被允許決定任何事情之前,必須先檢查是否存在其他帶有更高時代編號的領導者,它們可能會做出相互衝突的決定。領導者如何知道自己沒有被另一個節點趕下臺?回想一下在“[真理在多數人手中](ch8.md#真理在多數人手中)”中提到的:一個節點不一定能相信自己的判斷—— 因為只有節點自己認為自己是領導者,並不一定意味著其他節點接受它作為它們的領導者。 +​ 在任何領導者被允許決定任何事情之前,必須先檢查是否存在其他帶有更高紀元編號的領導者,它們可能會做出相互衝突的決定。領導者如何知道自己沒有被另一個節點趕下臺?回想一下在“[真相由多數所定義](ch8.md#真相由多數所定義)”中提到的:一個節點不一定能相信自己的判斷—— 因為只有節點自己認為自己是領導者,並不一定意味著其他節點接受它作為它們的領導者。 -​ 相反,它必須從**法定人數(quorum)**的節點中獲取選票(參閱“[讀寫的法定人數](ch5.md#讀寫的法定人數)”)。對領導者想要做出的每一個決定,都必須將提議值傳送給其他節點,並等待法定人數的節點響應並贊成提案。法定人數通常(但不總是)由多數節點組成【105】。只有在沒有意識到任何帶有更高時代編號的領導者的情況下,一個節點才會投票贊成提議。 +​ 相反,它必須從**法定人數(quorum)**的節點中獲取選票(參閱“[讀寫的法定人數](ch5.md#讀寫的法定人數)”)。對領導者想要做出的每一個決定,都必須將提議值傳送給其他節點,並等待法定人數的節點響應並贊成提案。法定人數通常(但不總是)由多數節點組成【105】。只有在沒有意識到任何帶有更高紀元編號的領導者的情況下,一個節點才會投票贊成提議。 -​ 因此,我們有兩輪投票:第一次是為了選出一位領導者,第二次是對領導者的提議進行表決。關鍵的洞察在於,這兩次投票的**法定人群**必須相互**重疊(overlap)**:如果一個提案的表決透過,則至少得有一個參與投票的節點也必須參加過最近的領導者選舉【105】。因此,如果在一個提案的表決過程中沒有出現更高的時代編號。那麼現任領導者就可以得出這樣的結論:沒有發生過更高時代的領導選舉,因此可以確定自己仍然在領導。然後它就可以安全地對提議值做出決定。 +​ 因此,我們有兩輪投票:第一次是為了選出一位領導者,第二次是對領導者的提議進行表決。關鍵的洞察在於,這兩次投票的**法定人群**必須相互**重疊(overlap)**:如果一個提案的表決透過,則至少得有一個參與投票的節點也必須參加過最近的領導者選舉【105】。因此,如果在一個提案的表決過程中沒有出現更高的紀元編號。那麼現任領導者就可以得出這樣的結論:沒有發生過更高時代的領導選舉,因此可以確定自己仍然在領導。然後它就可以安全地對提議值做出決定。 @@ -1664,7 +1646,7 @@ -​ 共識演算法對於分散式系統來說是一個巨大的突破:它為其他充滿不確定性的系統帶來了基礎的安全屬性(一致同意,完整性和有效性),然而它們還能保持容錯(只要多數節點正常工作且可達,就能取得進展)。它們提供了全序廣播,因此它們也可以以一種容錯的方式實現線性一致的原子操作(參見“[使用全序廣播實現線性一致性儲存](#使用全序廣播實現線性一致性儲存)”)。 +​ 共識演算法對於分散式系統來說是一個巨大的突破:它為其他充滿不確定性的系統帶來了基礎的安全屬性(一致同意,完整性和有效性),然而它們還能保持容錯(只要多數節點正常工作且可達,就能取得進展)。它們提供了全序廣播,因此它們也可以以一種容錯的方式實現線性一致的原子操作(參見“[使用全序廣播實現線性一致的儲存](#使用全序廣播實現線性一致的儲存)”)。 @@ -1672,7 +1654,7 @@ -​ 節點在做出決定之前對提議進行投票的過程是一種同步複製。如“[同步與非同步複製](ch5.md#同步與非同步複製)”中所述,通常資料庫會配置為非同步複製模式。在這種配置中發生故障切換時,一些已經提交的資料可能會丟失 —— 但是為了獲得更好的效能,許多人選擇接受這種風險。 +​ 節點在做出決定之前對提議進行投票的過程是一種同步複製。如“[同步複製與非同步複製](ch5.md#同步複製與非同步複製)”中所述,通常資料庫會配置為非同步複製模式。在這種配置中發生故障切換時,一些已經提交的資料可能會丟失 —— 但是為了獲得更好的效能,許多人選擇接受這種風險。 @@ -1724,7 +1706,7 @@ -如“[領導者與鎖定](ch8.md#領導者與鎖定)”中所述,當某個資源受到鎖或租約的保護時,你需要一個防護令牌來防止客戶端在程序暫停的情況下彼此衝突。防護令牌是每次鎖被獲取時單調增加的數字。 ZooKeeper透過全域性排序操作來提供這個功能,它為每個操作提供一個單調遞增的事務ID(`zxid`)和版本號(`cversion`)【15】。 +如“[領導者和鎖](ch8.md#領導者和鎖)”中所述,當某個資源受到鎖或租約的保護時,你需要一個防護令牌來防止客戶端在程序暫停的情況下彼此衝突。防護令牌是每次鎖被獲取時單調增加的數字。 ZooKeeper透過全序化所有操作來提供這個功能,它為每個操作提供一個單調遞增的事務ID(`zxid`)和版本號(`cversion`)【15】。 @@ -1732,7 +1714,7 @@ -​ 客戶端在ZooKeeper伺服器上維護一個長期會話,客戶端和伺服器週期性地交換心跳包來檢查節點是否還活著。即使連線暫時中斷,或者ZooKeeper節點失效,會話仍保持在活躍狀態。但如果心跳停止的持續時間超出會話超時,ZooKeeper會宣告該會話已死亡。當會話超時(ZooKeeper呼叫這些臨時節點)時,會話持有的任何鎖都可以配置為自動釋放(ZooKeeper稱之為**臨時節點(ephemeral nodes)**)。 +​ 客戶端在ZooKeeper伺服器上維護一個長期會話,客戶端和伺服器週期性地交換心跳包來檢查節點是否還活著。即使連線暫時中斷,或者ZooKeeper節點失效,會話仍保持在活躍狀態。但如果心跳停止的持續時間超出會話超時,ZooKeeper會宣告該會話已死亡。當會話超時時(ZooKeeper稱這些節點為**臨時節點(ephemeral nodes)**),會話持有的任何鎖都可以配置為自動釋放。 @@ -1756,7 +1738,7 @@ -​ 另一個例子是,當你有一些分割槽資源(資料庫,訊息流,檔案儲存,分散式Actor系統等),並需要決定將哪個分割槽分配給哪個節點時。當新節點加入叢集時,需要將某些分割槽從現有節點移動到新節點,以便重新平衡負載(參閱“[重新平衡分割槽](ch6.md#重新平衡分割槽)”)。當節點被移除或失效時,其他節點需要接管失效節點的工作。 +​ 另一個例子是,當你有一些分割槽資源(資料庫,訊息流,檔案儲存,分散式Actor系統等),並需要決定將哪個分割槽分配給哪個節點時。當新節點加入叢集時,需要將某些分割槽從現有節點移動到新節點,以便重新平衡負載(參閱“[分割槽再平衡](ch6.md#分割槽再平衡)”)。當節點被移除或失效時,其他節點需要接管失效節點的工作。 @@ -1768,7 +1750,7 @@ -​ 通常,由ZooKeeper管理的資料的型別變化十分緩慢:代表“分割槽 7 中的節點執行在 `10.1.1.23` 上”的資訊可能會在幾分鐘或幾小時的時間內發生變化。它不是用來儲存應用的執行時狀態的,每秒可能會改變數千甚至數百萬次。如果應用狀態需要從一個節點複製到另一個節點,則可以使用其他工具(如Apache BookKeeper 【108】)。 +​ 通常,由ZooKeeper管理的資料型別的變化十分緩慢:代表“分割槽 7 中的節點執行在 `10.1.1.23` 上”的資訊可能會在幾分鐘或幾小時的時間內發生變化。它不是用來儲存應用的執行時狀態的,後者每秒可能會改變數千甚至數百萬次。如果應用狀態需要從一個節點複製到另一個節點,則可以使用其他工具(如Apache BookKeeper 【108】)。 @@ -1776,7 +1758,7 @@ -​ ZooKeeper,etcd和Consul也經常用於服務發現——也就是找出你需要連線到哪個IP地址才能到達特定的服務。在雲資料中心環境中,虛擬機器連續來去常見,你通常不會事先知道服務的IP地址。相反,你可以配置你的服務,使其在啟動時註冊服務登錄檔中的網路端點,然後可以由其他服務找到它們。 +​ ZooKeeper,etcd和Consul也經常用於服務發現——也就是找出你需要連線到哪個IP地址才能到達特定的服務。在雲資料中心環境中,虛擬機器來來往往很常見,你通常不會事先知道服務的IP地址。相反,你可以配置你的服務,使其在啟動時註冊服務登錄檔中的網路端點,然後可以由其他服務找到它們。 @@ -1788,19 +1770,19 @@ -#### 成員服務 +#### 成員資格服務 -​ ZooKeeper和它的小夥伴們可以看作是成員服務研究的悠久歷史的一部分,這個歷史可以追溯到20世紀80年代,並且對建立高度可靠的系統(例如空中交通管制)非常重要【110】。 +​ ZooKeeper和它的小夥伴們可以看作是成員資格服務(membership services)研究的悠久歷史的一部分,這個歷史可以追溯到20世紀80年代,並且對建立高度可靠的系統(例如空中交通管制)非常重要【110】。 -​ 成員資格服務確定哪些節點當前處於活動狀態並且是群集的活動成員。正如我們在[第8章](ch8.md)中看到的那樣,由於無限的網路延遲,無法可靠地檢測到另一個節點是否發生故障。但是,如果你透過一致的方式進行故障檢測,那麼節點可以就哪些節點應該被認為是存在或不存在達成一致。 +​ 成員資格服務確定哪些節點當前處於活動狀態並且是叢集的活動成員。正如我們在[第8章](ch8.md)中看到的那樣,由於無限的網路延遲,無法可靠地檢測到另一個節點是否發生故障。但是,如果你透過共識來進行故障檢測,那麼節點可以就哪些節點應該被認為是存在或不存在達成一致。 -​ 即使它確實存在,仍然可能發生一個節點被共識錯誤地宣告死亡。但是對於一個系統來說,在哪些節點構成當前的成員關係方面是非常有用的。例如,選擇領導者可能意味著簡單地選擇當前成員中編號最小的成員,但如果不同的節點對現有成員的成員有不同意見,則這種方法將不起作用。 +​ 即使它確實存在,仍然可能發生一個節點被共識錯誤地宣告死亡。但是對於一個系統來說,知道哪些節點構成了當前的成員關係是非常有用的。例如,選擇領導者可能意味著簡單地選擇當前成員中編號最小的成員,但如果不同的節點對現有的成員都有誰有不同意見,則這種方法將不起作用。 @@ -1900,7 +1882,7 @@ -​ 儘管如此,並不是所有系統都需要共識:例如,無領導者複製和多領導者複製系統通常不會使用全域性的共識。這些系統中出現的衝突(參見“[處理衝突](ch5.md#處理衝突)”)正是不同領導者之間沒有達成共識的結果,但也這並沒有關係:也許我們只是需要接受沒有線性一致性的事實,並學會更好地與具有分支與合併版本歷史的資料打交道。 +​ 儘管如此,並不是所有系統都需要共識:例如,無領導者複製和多領導者複製系統通常不會使用全域性的共識。這些系統中出現的衝突(參見“[處理寫入衝突](ch5.md#處理寫入衝突)”)正是不同領導者之間沒有達成共識的結果,但這也許並沒有關係:也許我們只是需要接受沒有線性一致性的事實,並學會更好地與具有分支與合併版本歷史的資料打交道。 diff --git a/zh-tw/glossary.md b/zh-tw/glossary.md index 374d1df..d440344 100644 --- a/zh-tw/glossary.md +++ b/zh-tw/glossary.md @@ -22,7 +22,7 @@ -不等待某些事情完成(例如,將資料傳送到網路中的另一個節點),並且不會假設要花多長時間。請參閱第153頁上的“同步與非同步複製”,第284頁上的“同步與非同步網路”,以及第306頁上的“系統模型與現實”。 +不等待某些事情完成(例如,將資料傳送到網路中的另一個節點),並且不會假設要花多長時間。請參閱[同步複製與非同步複製](ch5.md#同步複製與非同步複製)”,“[同步網路與非同步網路](ch8.md#同步網路與非同步網路)”,以及“[系統模型與現實](ch8.md#系統模型與現實)”。 @@ -38,7 +38,7 @@ -2.在事務的上下文中:將一些寫入操作分為一組,這組寫入要麼全部提交成功,要麼遇到錯誤時全部回滾。參見第223頁的“原子性”和第354頁的“原子提交和兩階段提交(2PC)”。 +2.在事務的上下文中:將一些寫入操作分為一組,這組寫入要麼全部提交成功,要麼遇到錯誤時全部回滾。參見“[原子性(Atomicity)](ch7.md#原子性(Atomicity))”和“[原子提交與二階段提交(2PC)](ch9.md#原子提交與二階段提交(2PC))”。 @@ -50,7 +50,7 @@ -接收方接收資料速度較慢時,強制降低傳送方的資料傳送速度。也稱為流量控制。請參閱第441頁上的“訊息系統”。 +接收方接收資料速度較慢時,強制降低傳送方的資料傳送速度。也稱為流量控制。請參閱“[訊息系統](ch11.md#訊息系統)”。 @@ -62,7 +62,7 @@ -一種計算,它將一些固定的(通常是大的)資料集作為輸入,並將其他一些資料作為輸出,而不修改輸入。見第十章。 +一種計算,它將一些固定的(通常是大的)資料集作為輸入,並將其他一些資料作為輸出,而不修改輸入。見[第十章](ch10.md)。 @@ -74,7 +74,7 @@ -有一些已知的上限或大小。例如,網路延遲情況(請參閱“超時和未定義的延遲”在本頁281)和資料集(請參閱第11章的介紹)。 +有一些已知的上限或大小。例如,網路延遲情況(請參閱“[超時與無窮的延遲](ch8.md#超時與無窮的延遲)”)和資料集(請參閱[第11章](ch11.md)的介紹)。 @@ -86,7 +86,7 @@ -表現異常的節點,這種異常可能以任意方式出現,例如向其他節點發送矛盾或惡意訊息。請參閱第304頁上的“拜占庭故障”。 +表現異常的節點,這種異常可能以任意方式出現,例如向其他節點發送矛盾或惡意訊息。請參閱“[拜占庭故障](ch8.md#拜占庭故障)”。 @@ -110,7 +110,7 @@ -一個被廣泛誤解的理論結果,在實踐中是沒有用的。參見第336頁的“CAP定理”。 +一個被廣泛誤解的理論結果,在實踐中是沒有用的。參見“[CAP定理](ch9.md#CAP定理)”。 @@ -122,7 +122,7 @@ -事件之間的依賴關係,當一件事發生在另一件事情之前。例如,後面的事件是對早期事件的迴應,或者依賴於更早的事件,或者應該根據先前的事件來理解。請參閱第186頁上的“發生之前的關係和併發性”和第339頁上的“排序和因果關係”。 +事件之間的依賴關係,當一件事發生在另一件事情之前。例如,後面的事件是對早期事件的迴應,或者依賴於更早的事件,或者應該根據先前的事件來理解。請參閱“[“此前發生”的關係和併發](ch5.md#“此前發生”的關係和併發)”和“[順序與因果關係](ch5.md#順序與因果關係)”。 @@ -134,7 +134,7 @@ -分散式計算的一個基本問題,就是讓幾個節點同意某些事情(例如,哪個節點應該是資料庫叢集的領導者)。問題比乍看起來要困難得多。請參閱第364頁上的“容錯共識”。 +分散式計算的一個基本問題,就是讓幾個節點同意某些事情(例如,哪個節點應該是資料庫叢集的領導者)。問題比乍看起來要困難得多。請參閱“[容錯共識](ch9.md#容錯共識)”。 @@ -146,7 +146,7 @@ -一個數據庫,其中來自幾個不同的OLTP系統的資料已經被合併和準備用於分析目的。請參閱第91頁上的“資料倉庫”。 +一個數據庫,其中來自幾個不同的OLTP系統的資料已經被合併和準備用於分析目的。請參閱“[資料倉庫](ch3.md#資料倉庫)”。 @@ -158,7 +158,7 @@ -描述某些東西應有的屬性,但不知道如何實現它的確切步驟。在查詢的上下文中,查詢最佳化器採用宣告性查詢並決定如何最好地執行它。請參閱第42頁上的“資料的查詢語言”。 +描述某些東西應有的屬性,但不知道如何實現它的確切步驟。在查詢的上下文中,查詢最佳化器採用宣告性查詢並決定如何最好地執行它。請參閱“[資料查詢語言](ch2.md#資料查詢語言)”。 @@ -170,7 +170,7 @@ -為了加速讀取,在標準資料集中引入一些冗餘或重複資料,通常採用快取或索引的形式。非規範化的值是一種預先計算的查詢結果,像物化檢視。請參見“單物件和多物件操作”(第228頁)和“從同一事件日誌中派生多個檢視”(第461頁)。 +為了加速讀取,在標準資料集中引入一些冗餘或重複資料,通常採用快取或索引的形式。非規範化的值是一種預先計算的查詢結果,像物化檢視。請參見“[單物件和多物件操作](ch7.md#單物件和多物件操作)”和“[從同一事件日誌中派生多個檢視](ch11.md#從同一事件日誌中派生多個檢視)”。 @@ -182,7 +182,7 @@ -一種資料集,根據其他資料透過可重複執行的流程建立。必要時,你可以執行該流程再次建立衍生資料。衍生資料通常用於提高特定資料的讀取速度。常見的衍生資料有索引、快取和物化檢視。參見第三部分的介紹。 +一種資料集,根據其他資料透過可重複執行的流程建立。必要時,你可以執行該流程再次建立衍生資料。衍生資料通常用於提高特定資料的讀取速度。常見的衍生資料有索引、快取和物化檢視。參見[第三部分](part-iii.md)的介紹。 @@ -206,7 +206,7 @@ -在由網路連線的多個節點上執行。對於部分節點故障,具有容錯性:系統的一部分發生故障時,其他部分仍可以正常工作,通常情況下,軟體無需瞭解故障相關的確切情況。請參閱第274頁上的“故障和部分故障”。 +在由網路連線的多個節點上執行。對於部分節點故障,具有容錯性:系統的一部分發生故障時,其他部分仍可以正常工作,通常情況下,軟體無需瞭解故障相關的確切情況。請參閱“[故障與部分失效](ch8.md#故障與部分失效)”。 @@ -218,7 +218,7 @@ -以某種方式儲存資料,即使發生各種故障,也不會丟失資料。請參閱第226頁上的“永續性”。 +以某種方式儲存資料,即使發生各種故障,也不會丟失資料。請參閱“[永續性(Durability)](ch7.md#永續性(Durability))”。 @@ -230,7 +230,7 @@ -提取-轉換-載入(Extract-Transform-Load)。從源資料庫中提取資料,將其轉換為更適合分析查詢的形式,並將其載入到資料倉庫或批處理系統中的過程。請參閱第91頁上的“資料倉庫”。 +提取-轉換-載入(Extract-Transform-Load)。從源資料庫中提取資料,將其轉換為更適合分析查詢的形式,並將其載入到資料倉庫或批處理系統中的過程。請參閱“[資料倉庫](ch3.md#資料倉庫)”。 @@ -242,7 +242,7 @@ -在具有單一領導者的系統中,故障切換是將領導角色從一個節點轉移到另一個節點的過程。請參閱第156頁的“處理節點故障”。 +在具有單一領導者的系統中,故障切換是將領導角色從一個節點轉移到另一個節點的過程。請參閱“[處理節點宕機](ch5.md#處理節點宕機)”。 @@ -254,7 +254,7 @@ -如果出現問題(例如,機器崩潰或網路連線失敗),可以自動恢復。請參閱第6頁上的“可靠性”。 +如果出現問題(例如,機器崩潰或網路連線失敗),可以自動恢復。請參閱“[可靠性](ch1.md#可靠性)”。 @@ -278,7 +278,7 @@ -一種資料副本,僅處理領導者發出的資料變更,不直接接受來自客戶端的任何寫入。也稱為輔助、僕從、只讀副本或熱備份。請參閱第152頁上的“領導和追隨者”。 +一種資料副本,僅處理領導者發出的資料變更,不直接接受來自客戶端的任何寫入。也稱為輔助、僕從、只讀副本或熱備份。請參閱“[領導者與追隨者](ch5.md#領導者與追隨者)”。 @@ -290,7 +290,7 @@ -透過任意關鍵字來搜尋文字,通常具有附加特徵,例如匹配類似的拼寫詞或同義詞。全文索引是一種支援這種查詢的次級索引。請參閱第88頁上的“全文搜尋和模糊索引”。 +透過任意關鍵字來搜尋文字,通常具有附加特徵,例如匹配類似的拼寫詞或同義詞。全文索引是一種支援這種查詢的次級索引。請參閱“[全文搜尋和模糊索引](ch3.md#全文搜尋和模糊索引)”。 @@ -302,7 +302,7 @@ -一種資料結構,由頂點(可以指向的東西,也稱為節點或實體)和邊(從一個頂點到另一個頂點的連線,也稱為關係或弧)組成。請參閱第49頁上的“和圖相似的資料模型”。 +一種資料結構,由頂點(可以指向的東西,也稱為節點或實體)和邊(從一個頂點到另一個頂點的連線,也稱為關係或弧)組成。請參閱“[圖資料模型](ch2.md#圖資料模型)”。 @@ -314,7 +314,7 @@ -將輸入轉換為看起來像隨機數值的函式。相同的輸入會轉換為相同的數值,不同的輸入一般會轉換為不同的數值,也可能轉換為相同數值(也被稱為衝突)。請參閱第203頁的“根據鍵的雜湊值分隔”。 +將輸入轉換為看起來像隨機數值的函式。相同的輸入會轉換為相同的數值,不同的輸入一般會轉換為不同的數值,也可能轉換為相同數值(也被稱為衝突)。請參閱“[根據鍵的雜湊分割槽](ch6.md#根據鍵的雜湊分割槽)”。 @@ -326,7 +326,7 @@ -用於描述一種操作可以安全地重試執行,即執行多次的效果和執行一次的效果相同。請參閱第478頁的“冪等”。 +用於描述一種操作可以安全地重試執行,即執行多次的效果和執行一次的效果相同。請參閱“[冪等性](ch11.md#冪等性)”。 @@ -338,7 +338,7 @@ -一種資料結構。透過索引,你可以根據特定欄位的值,在所有資料記錄中進行高效檢索。請參閱第70頁的“讓資料庫更強大的資料結構”。 +一種資料結構。透過索引,你可以根據特定欄位的值,在所有資料記錄中進行高效檢索。請參閱“[驅動資料庫的資料結構](ch3.md#驅動資料庫的資料結構)”。 @@ -350,7 +350,7 @@ -在事務上下文中,用於描述併發執行事務的互相干擾程度。序列執行具有最強的隔離性,不過其它程度的隔離也通常被使用。請參閱第225頁的“隔離”。 +在事務上下文中,用於描述併發執行事務的互相干擾程度。序列執行具有最強的隔離性,不過其它程度的隔離也通常被使用。請參閱“[隔離性(Isolation)](ch7.md#隔離性(Isolation))”。 @@ -362,7 +362,7 @@ -彙集有共同點的記錄。在一個記錄與另一個記錄有關(外來鍵,文件參考,圖中的邊)的情況下最常用,查詢需要獲取參考所指向的記錄。請參閱第33頁上的“多對一和多對多關係”和第393頁上的“減少端連線和分組”。 +彙集有共同點的記錄。在一個記錄與另一個記錄有關(外來鍵,文件參考,圖中的邊)的情況下最常用,查詢需要獲取參考所指向的記錄。請參閱“[多對一和多對多的關係](ch2.md#多對一和多對多的關係)”和“[Reduce側連線與分組](ch10.md#Reduce側連線與分組)”。 @@ -374,7 +374,7 @@ -當資料或服務被複制到多個節點時,由領導者分發已授權變更的資料副本。領導者可以透過某些協議選舉產生,也可以由管理者手動選擇。也被稱為主人。請參閱第152頁的“領導者和追隨者”。 +當資料或服務被複制到多個節點時,由領導者分發已授權變更的資料副本。領導者可以透過某些協議選舉產生,也可以由管理者手動選擇。也被稱為主人。請參閱“[領導者與追隨者](ch5.md#領導者與追隨者)”。 @@ -386,7 +386,7 @@ -表現為系統中只有一份透過原子操作更新的資料副本。請參閱第324頁的“線性化”。 +表現為系統中只有一份透過原子操作更新的資料副本。請參閱“[線性一致性](ch9.md#線性一致性)”。 @@ -398,7 +398,7 @@ -一種效能最佳化方式,如果經常在相同的時間請求一些離散資料,把這些資料放到一個位置。請參閱第41頁的“請求資料的區域性性”。 +一種效能最佳化方式,如果經常在相同的時間請求一些離散資料,把這些資料放到一個位置。請參閱“[查詢的資料區域性性](ch2.md#查詢的資料區域性性)”。 @@ -410,7 +410,7 @@ -一種保證只有一個執行緒、節點或事務可以訪問的機制,如果其它執行緒、節點或事務想訪問相同元素,則必須等待鎖被釋放。請參閱第257頁的“兩段鎖(2PL)”和301頁的“領導者和鎖”。 +一種保證只有一個執行緒、節點或事務可以訪問的機制,如果其它執行緒、節點或事務想訪問相同元素,則必須等待鎖被釋放。請參閱“[兩階段鎖定(2PL)](ch7.md#兩階段鎖定(2PL))”和“[領導者和鎖](ch8.md#領導者和鎖)”。 @@ -422,7 +422,7 @@ -日誌是一個只能以追加方式寫入的檔案,用於存放資料。預寫式日誌用於在儲存引擎崩潰時恢復資料(請參閱第82頁的“使二叉樹更穩定”);結構化日誌儲存引擎使用日誌作為它的主要儲存格式(請參閱第76頁的“有序字串表和日誌結構的合併樹”);複製型日誌用於把寫入從領導者複製到追隨者(請參閱第152頁的“領導者和追隨者”);事件性日誌可以表現為資料流(請參閱第446頁的“分段日誌”)。 +日誌是一個只能以追加方式寫入的檔案,用於存放資料。預寫式日誌用於在儲存引擎崩潰時恢復資料(請參閱“[讓B樹更可靠](ch3.md#讓B樹更可靠)”);結構化日誌儲存引擎使用日誌作為它的主要儲存格式(請參閱“[SSTables和LSM樹](ch3.md#SSTables和LSM樹)”);複製型日誌用於把寫入從領導者複製到追隨者(請參閱“[領導者與追隨者](ch5.md#領導者與追隨者)”);事件性日誌可以表現為資料流(請參閱“[分割槽日誌](ch11.md#分割槽日誌)”)。 @@ -434,7 +434,7 @@ -急切地計算並寫出結果,而不是在請求時計算。請參閱第101頁的“聚合:資料立方和物化檢視”和419頁的“中間狀態的物化”。 +急切地計算並寫出結果,而不是在請求時計算。請參閱“[聚合:資料立方體和物化檢視](ch3.md#聚合:資料立方體和物化檢視)”和“[物化中間狀態](ch10.md#物化中間狀態)”。 @@ -458,7 +458,7 @@ -以沒有冗餘或重複的方式進行結構化。 在規範化資料庫中,當某些資料發生變化時,您只需要在一個地方進行更改,而不是在許多不同的地方複製很多次。 請參閱第33頁上的“多對一和多對多關係”。 +以沒有冗餘或重複的方式進行結構化。 在規範化資料庫中,當某些資料發生變化時,您只需要在一個地方進行更改,而不是在許多不同的地方複製很多次。 請參閱“[多對一和多對多的關係](ch2.md#多對一和多對多的關係)”。 @@ -470,7 +470,7 @@ -線上分析處理。 透過對大量記錄進行聚合(例如,計數,總和,平均)來表徵的訪問模式。 請參閱第90頁上的“交易處理或分析?”。 +線上分析處理。 透過對大量記錄進行聚合(例如,計數,總和,平均)來表徵的訪問模式。 請參閱“[事務處理還是分析?](ch3.md#事務處理還是分析?)”。 @@ -482,7 +482,7 @@ -線上事務處理。 訪問模式的特點是快速查詢,讀取或寫入少量記錄,這些記錄通常透過鍵索引。 請參閱第90頁上的“交易處理或分析?”。 +線上事務處理。 訪問模式的特點是快速查詢,讀取或寫入少量記錄,這些記錄通常透過鍵索引。 請參閱“[事務處理還是分析?](ch3.md#事務處理還是分析?)”。 @@ -494,7 +494,7 @@ -將單機上的大型資料集或計算結果拆分為較小部分,並將其分佈到多臺機器上。 也稱為分片。 見第6章。 +將單機上的大型資料集或計算結果拆分為較小部分,並將其分佈到多臺機器上。 也稱為分片。見[第6章](ch6.md)。 @@ -506,7 +506,7 @@ -透過計算有多少值高於或低於某個閾值來衡量值分佈的方法。 例如,某個時間段的第95個百分位響應時間是時間t,則該時間段中,95%的請求完成時間小於t,5%的請求完成時間要比t長。 請參閱第13頁上的“描述效能”。 +透過計算有多少值高於或低於某個閾值來衡量值分佈的方法。 例如,某個時間段的第95個百分位響應時間是時間t,則該時間段中,95%的請求完成時間小於t,5%的請求完成時間要比t長。 請參閱“[描述效能](ch1.md#描述效能)”。 @@ -530,7 +530,7 @@ -在操作完成之前,需要對操作進行投票的最少節點數量。 請參閱第179頁上的“讀寫的法定人數”。 +在操作完成之前,需要對操作進行投票的最少節點數量。 請參閱“[讀寫的法定人數](ch5.md#讀寫的法定人數)”。 @@ -542,7 +542,7 @@ -將資料或服務從一個節點移動到另一個節點以實現負載均衡。 請參閱第209頁上的“再平衡分割槽”。 +將資料或服務從一個節點移動到另一個節點以實現負載均衡。 請參閱“[分割槽再平衡](ch6.md#分割槽再平衡)”。 @@ -554,7 +554,7 @@ -在幾個節點(副本)上保留相同資料的副本,以便在某些節點無法訪問時,資料仍可訪問。請參閱第5章。 +在幾個節點(副本)上保留相同資料的副本,以便在某些節點無法訪問時,資料仍可訪問。請參閱[第5章](ch5.md)。 @@ -566,7 +566,7 @@ -一些資料結構的描述,包括其欄位和資料型別。 可以在資料生命週期的不同點檢查某些資料是否符合模式(請參閱第39頁上的“文件模型中的模式靈活性”),模式可以隨時間變化(請參閱第4章)。 +一些資料結構的描述,包括其欄位和資料型別。 可以在資料生命週期的不同點檢查某些資料是否符合模式(請參閱“[文件模型中的模式靈活性](ch2.md#文件模型中的模式靈活性)”),模式可以隨時間變化(請參閱[第4章](ch4.md))。 @@ -578,7 +578,7 @@ -與主要資料儲存器一起維護的附加資料結構,使您可以高效地搜尋與某種條件相匹配的記錄。 請參閱第85頁上的“其他索引結構”和第206頁上的“分割槽和二級索引”。 +與主要資料儲存器一起維護的附加資料結構,使您可以高效地搜尋與某種條件相匹配的記錄。 請參閱“[其他索引結構](ch3.md#其他索引結構)”和“[分割槽與次級索引](ch6.md#分割槽與次級索引)”。 @@ -602,7 +602,7 @@ -與共享記憶體或共享磁碟架構相比,獨立節點(每個節點都有自己的CPU,記憶體和磁碟)透過傳統網路連線。 見第二部分的介紹。 +與共享記憶體或共享磁碟架構相比,獨立節點(每個節點都有自己的CPU,記憶體和磁碟)透過傳統網路連線。 見[第二部分](part-ii.md)的介紹。 @@ -614,11 +614,11 @@ -1.各分割槽負載不平衡,例如某些分割槽有大量請求或資料,而其他分割槽則少得多。也被稱為熱點。請參閱第205頁上的“工作負載偏斜和減輕熱點”和第407頁上的“處理偏斜”。 +1.各分割槽負載不平衡,例如某些分割槽有大量請求或資料,而其他分割槽則少得多。也被稱為熱點。請參閱“[負載偏斜和熱點消除](ch6.md#負載偏斜和熱點消除)”和“[處理偏斜](ch10.md#處理偏斜)”。 -2.時間線異常導致事件以不期望的順序出現。 請參閱第237頁上的“快照隔離和可重複讀取”中的關於讀取偏斜的討論,第246頁上的“寫入偏斜和模糊”中的寫入偏斜以及第291頁上的“訂購事件的時間戳”中的時鐘偏斜。 +2.時間線異常導致事件以不期望的順序出現。 請參閱“[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)”中的關於讀取偏斜的討論,“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”中的寫入偏斜以及“[有序事件的時間戳](ch8.md#有序事件的時間戳)”中的時鐘偏斜。 @@ -630,7 +630,7 @@ -兩個節點同時認為自己是領導者的情況,這種情況可能違反系統擔保。 請參閱第156頁的“處理節點中斷”和第300頁的“真相由多數定義”。 +兩個節點同時認為自己是領導者的情況,這種情況可能違反系統擔保。 請參閱“[處理節點宕機](ch5.md#處理節點宕機)”和“[真相由多數所定義](ch8.md#真相由多數所定義)”。 @@ -642,7 +642,7 @@ -一種對事務邏輯進行編碼的方式,它可以完全在資料庫伺服器上執行,事務執行期間無需與客戶端通訊。 請參閱第252頁的“實際序列執行”。 +一種對事務邏輯進行編碼的方式,它可以完全在資料庫伺服器上執行,事務執行期間無需與客戶端通訊。 請參閱“[真的序列執行](ch7.md#真的序列執行)”。 @@ -654,7 +654,7 @@ -持續執行的計算。可以持續接收事件流作為輸入,並得出一些輸出。 見第11章。 +持續執行的計算。可以持續接收事件流作為輸入,並得出一些輸出。 見[第11章](ch11.md)。 @@ -678,7 +678,7 @@ -一個儲存主要權威版本資料的系統,也被稱為真相的來源。首先在這裡寫入資料變更,其他資料集可以從記錄系統衍生。 參見第三部分的介紹。 +一個儲存主要權威版本資料的系統,也被稱為真相的來源。首先在這裡寫入資料變更,其他資料集可以從記錄系統衍生。 參見[第三部分](part-iii.md)的介紹。 @@ -690,7 +690,7 @@ -檢測故障的最簡單方法之一,即在一段時間內觀察是否缺乏響應。 但是,不可能知道超時是由於遠端節點的問題還是網路中的問題造成的。 請參閱第281頁上的“超時和無限延遲”。 +檢測故障的最簡單方法之一,即在一段時間內觀察是否缺乏響應。 但是,不可能知道超時是由於遠端節點的問題還是網路中的問題造成的。 請參閱“[超時與無窮的延遲](ch8.md#超時與無窮的延遲)”。 @@ -702,7 +702,7 @@ -一種比較事物的方法(例如時間戳),可以讓您總是說出兩件事中哪一件更大,哪件更小。 總的來說,有些東西是無法比擬的(不能說哪個更大或更小)的順序稱為偏序。 請參見第341頁的“因果順序不是全序”。 +一種比較事物的方法(例如時間戳),可以讓您總是說出兩件事中哪一件更大,哪件更小。 總的來說,有些東西是無法比擬的(不能說哪個更大或更小)的順序稱為偏序。 請參見“[因果順序不是全序的](ch9.md#因果順序不是全序的)”。 @@ -714,7 +714,7 @@ -為了簡化錯誤處理和併發問題,將幾個讀寫操作分組到一個邏輯單元中。 見第7章。 +為了簡化錯誤處理和併發問題,將幾個讀寫操作分組到一個邏輯單元中。 見[第7章](ch7.md)。 @@ -726,7 +726,7 @@ -一種確保多個數據庫節點全部提交或全部中止事務的演算法。 請參閱第354頁上的“原子提交和兩階段提交(2PC)”。 +一種確保多個數據庫節點全部提交或全部中止事務的演算法。 請參閱[原子提交與二階段提交(2PC)](ch9.md#原子提交與二階段提交(2PC))”。 @@ -738,7 +738,7 @@ -一種用於實現可序列化隔離的演算法,該演算法透過事務獲取對其讀取或寫入的所有資料的鎖,直到事務結束。 請參閱第257頁上的“兩階段鎖定(2PL)”。 +一種用於實現可序列化隔離的演算法,該演算法透過事務獲取對其讀取或寫入的所有資料的鎖,直到事務結束。 請參閱“[兩階段鎖定(2PL)](ch7.md#兩階段鎖定(2PL))”。 diff --git a/zh-tw/part-ii.md b/zh-tw/part-ii.md index 4b36ed1..4898af1 100644 --- a/zh-tw/part-ii.md +++ b/zh-tw/part-ii.md @@ -48,7 +48,7 @@ -### 伸縮至更高的載荷 +## 伸縮至更高的載荷 @@ -76,7 +76,7 @@ -#### 無共享架構 +### 無共享架構 @@ -120,11 +120,11 @@ -複製和分割槽是不同的機制,但它們經常同時使用。如[圖II-1](../img/figii-1.png)所示。 +複製和分割槽是不同的機制,但它們經常同時使用。如[圖II-1](img/figii-1.png)所示。 -![](../img/figii-1.png) +![](img/figii-1.png) diff --git a/zh-tw/part-iii.md b/zh-tw/part-iii.md index 7941993..ebf47d5 100644 --- a/zh-tw/part-iii.md +++ b/zh-tw/part-iii.md @@ -14,7 +14,7 @@ -## 記錄和衍生資料系統 +## 記錄系統和衍生資料系統 @@ -22,15 +22,15 @@ -#### 記錄系統(System of record) +***記錄系統(System of record)*** -**記錄系統**,也被稱為**真相源(source of truth)**,持有資料的權威版本。當新的資料進入時(例如,使用者輸入)首先會記錄在這裡。每個事實正正好好表示一次(表示通常是**標準化的(normalized)**)。如果其他系統和**記錄系統**之間存在任何差異,那麼記錄系統中的值是正確的(根據定義)。 +**記錄系統**,也被稱為**真相源(source of truth)**,持有資料的權威版本。當新的資料進入時(例如,使用者輸入)首先會記錄在這裡。每個事實正正好好表示一次(表示通常是**正規化的(normalized)**)。如果其他系統和**記錄系統**之間存在任何差異,那麼記錄系統中的值是正確的(根據定義)。 -#### 衍生資料系統(Derived data systems) +***衍生資料系統(Derived data systems)***