merge calibration and update zh-tw content

This commit is contained in:
Vonng 2021-08-19 08:35:46 +08:00
parent b9d3f4e854
commit 4e4193a40a
19 changed files with 612 additions and 525 deletions

View File

@ -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. 感谢所有作出贡献,提出意见的朋友们:
<details>
<summary><a href="https://github.com/Vonng/ddia/pulls">Pull Requests</a> & <a href="https://github.com/Vonng/ddia/issues">Issues</a></summary>
| 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() |

View File

@ -1,6 +0,0 @@
# 设计数据密集型应用 <small></small>
> <b>Designing Data Intensive Applications</b>

View File

@ -27,7 +27,7 @@
name: '设计数据密集型应用',
repo: 'Vonng/ddia',
auto2top: true,
coverpage: true,
coverpage: false,
executeScript: true,
loadSidebar: true,
loadNavbar: true,

View File

@ -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) | 統一每章的標題格式 |

View File

@ -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百分位點縮寫為p95p99和p999。它們意味著9599或99.9的請求響應時間要比該閾值快例如如果第95百分位點響應時間是1.5秒則意味著100個請求中的95個響應時間快於1.5秒而100個請求中的5個響應時間超過1.5秒。如[圖1-4](../img/fig1-4.png)所示。
為了弄清異常值有多糟糕可以看看更高的百分位點例如第95、99和99.9百分位點縮寫為p95p99和p999。它們意味著9599或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)中的例子)

View File

@ -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)中的m1m2和m3標記
[圖10-1]()顯示了Hadoop MapReduce作業中的資料流。其並行化基於分割槽參見[第6章](ch6.md)作業的輸入通常是HDFS中的一個目錄輸入目錄中的每個檔案或檔案塊都被認為是一個單獨的分割槽可以單獨處理map任務[圖10-1](img/fig10-1.png)中的m1m2和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**)。

View File

@ -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按照m4m3m5的順序處理訊息。因此m3和m4的交付順序與以生產者1的傳送順序不同。
當與負載均衡相結合時,這種重傳行為對訊息的順序有種有趣的影響。在[圖11-2](img/fig11-2.png)中消費者通常按照生產者傳送的順序處理訊息。然而消費者2在處理訊息m3時崩潰與此同時消費者1正在處理訊息m4。未確認的訊息m3隨後被重新發送給消費者1結果消費者1按照m4m3m5的順序處理訊息。因此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】。

View File

@ -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】。

View File

@ -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模型SQL1999之前最常見的規範化表示形式是將職位教育和聯絡資訊放在單獨的表中對User表提供外來鍵引用如[圖2-1](../img/fig2-1.png)所示。
* 傳統SQL模型SQL1999之前最常見的規範化表示形式是將職位教育和聯絡資訊放在單獨的表中對User表提供外來鍵引用如[圖2-1](img/fig2-1.png)所示。
* 後續的SQL標準增加了對結構化資料型別和XML資料的支援;這允許將多值資料儲存在單行內並支援在這些文件內查詢和索引。這些功能在OracleIBM DB2MS SQL Server和PostgreSQL中都有不同程度的支援【6,7】。JSON資料型別也得到多個數據庫的支援包括IBM DB2MySQL和PostgreSQL 【8】。
@ -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
文件通常以單個連續字串形式進行儲存編碼為JSONXML或其二進位制變體如MongoDB的BSON。如果應用程式經常需要訪問整個文件例如將其渲染至網頁那麼儲存區域性性會帶來效能優勢。如果將資料分割到多個表中如[圖2-1](../img/fig2-1.png)所示),則需要進行多次索引查詢才能將其全部檢索出來,這可能需要更多的磁碟查詢並花費更多的時間。
文件通常以單個連續字串形式進行儲存編碼為JSONXML或其二進位制變體如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)

View File

@ -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 @@ TeradataVerticaSAP HANA和ParAccel等資料倉庫供應商通常使用昂
![](../img/fig3-9.png)
![](img/fig3-9.png)
@ -868,7 +868,7 @@ TeradataVerticaSAP 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`)。然後,您可以沿著每行或每列應用相同的彙總,並獲得一個維度減少的彙總(按產品的銷售額,無論日期,還是按日期銷售,無論產品如何)。

View File

@ -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))。

View File

@ -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 @@
* 為每個副本分配一個唯一的IDID編號更高的寫入具有更高的優先順序。這種方法也意味著資料丟失。
* 以某種方式將這些值合併在一起 - 例如,按字母順序排序,然後連線它們(在[圖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 = 3w = 2r = 2$,我們可以容忍一個不可用的節點。
* 對於$n = 5w = 3r = 3$,我們可以容忍兩個不可用的節點。 這個案例如[圖5-11](../img/fig5-11.png)所示。
* 對於$n = 5w = 3r = 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**)。版本向量允許資料庫區分覆蓋寫入和併發寫入。

View File

@ -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卷則包含以TUVXY和Z開頭的單詞。只是簡單的規定每個捲包含兩個字母會導致一些卷比其他卷大。為了均勻分配資料分割槽邊界需要依據資料調整。
鍵的範圍不一定均勻分佈,因為資料也很可能不均勻分佈。例如在[圖6-2](img/fig6-2.png)中第1捲包含以A和B開頭的單詞但第12卷則包含以TUVXY和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)<b_0$則將鍵分配給分割槽0如果$b_0 hash(key) <b_1$則分配給分割槽1
我們在前面說過([圖6-3](img/fig6-3.png)),最好將可能的雜湊分成不同的範圍,並將每個範圍分配給一個分割槽(例如,如果$0≤hash(key)<b_0$則將鍵分配給分割槽0如果$b_0 hash(key) <b_1$則分配給分割槽1
@ -402,7 +402,7 @@
現在,如果一個節點被新增到叢集中,新節點可以從當前每個節點中**竊取**一些分割槽,直到分割槽再次公平分配。這個過程如[圖6-6](../img/fig6-6.png)所示。如果從叢集中刪除一個節點,則會發生相反的情況。
現在,如果一個節點被新增到叢集中,新節點可以從當前每個節點中**竊取**一些分割槽,直到分割槽再次公平分配。這個過程如[圖6-6](img/fig6-6.png)所示。如果從叢集中刪除一個節點,則會發生相反的情況。
@ -410,7 +410,7 @@
![](../img/fig6-6.png)
![](img/fig6-6.png)
@ -530,7 +530,7 @@
![](../img/fig6-7.png)
![](img/fig6-7.png)
@ -542,11 +542,11 @@
許多分散式資料系統都依賴於一個獨立的協調服務比如ZooKeeper來跟蹤叢集元資料如[圖6-8](../img/fig6-8.png)所示。 每個節點在ZooKeeper中註冊自己ZooKeeper維護分割槽到節點的可靠對映。 其他參與者如路由層或分割槽感知客戶端可以在ZooKeeper中訂閱此資訊。 只要分割槽分配發生了改變或者叢集中新增或刪除了一個節點ZooKeeper就會通知路由層使路由資訊保持最新狀態。
許多分散式資料系統都依賴於一個獨立的協調服務比如ZooKeeper來跟蹤叢集元資料如[圖6-8](img/fig6-8.png)所示。 每個節點在ZooKeeper中註冊自己ZooKeeper維護分割槽到節點的可靠對映。 其他參與者如路由層或分割槽感知客戶端可以在ZooKeeper中訂閱此資訊。 只要分割槽分配發生了改變或者叢集中新增或刪除了一個節點ZooKeeper就會通知路由層使路由資訊保持最新狀態。
![](../img/fig6-8.png)
![](img/fig6-8.png)
@ -554,11 +554,11 @@
例如LinkedIn的Espresso使用Helix 【31】進行叢集管理依靠ZooKeeper實現瞭如[圖6-8](../img/fig6-8.png)所示的路由層。 HBaseSolrCloud和Kafka也使用ZooKeeper來跟蹤分割槽分配。 MongoDB具有類似的體系結構但它依賴於自己的**配置伺服器config server** 實現和mongos守護程序作為路由層。
例如LinkedIn的Espresso使用Helix 【31】進行叢集管理依靠ZooKeeper實現瞭如[圖6-8](img/fig6-8.png)所示的路由層。 HBaseSolrCloud和Kafka也使用ZooKeeper來跟蹤分割槽分配。 MongoDB具有類似的體系結構但它依賴於自己的**配置伺服器config server** 實現和mongos守護程序作為路由層。
Cassandra和Riak採取不同的方法他們在節點之間使用**流言協議gossip protocol** 來傳播群集狀態的變化。請求可以傳送到任意節點,該節點會轉發到包含所請求的分割槽的適當節點([圖6-7](../img/fig6-7.png)中的方法1。這個模型在資料庫節點中增加了更多的複雜性但是避免了對像ZooKeeper這樣的外部協調服務的依賴。
Cassandra和Riak採取不同的方法他們在節點之間使用**流言協議gossip protocol** 來傳播群集狀態的變化。請求可以傳送到任意節點,該節點會轉發到包含所請求的分割槽的適當節點([圖6-7](img/fig6-7.png)中的方法1。這個模型在資料庫節點中增加了更多的複雜性但是避免了對像ZooKeeper這樣的外部協調服務的依賴。

View File

@ -2,7 +2,7 @@
![](../img/ch7.png)
![](img/ch7.png)
@ -182,7 +182,7 @@ ACID一致性的概念是**對資料的一組特定約束必須始終成立**
[圖7-1](../img/fig7-1.png)是這類問題的一個簡單例子。假設你有兩個客戶端同時在資料庫中增長一個計數器。(假設資料庫沒有內建的自增操作)每個客戶端需要讀取計數器的當前值,加 1 ,再回寫新值。[圖7-1](../img/fig7-1.png) 中因為發生了兩次增長計數器應該從42增至44但由於競態條件實際上只增至 43 。
[圖7-1](img/fig7-1.png)是這類問題的一個簡單例子。假設你有兩個客戶端同時在資料庫中增長一個計數器。(假設資料庫沒有內建的自增操作)每個客戶端需要讀取計數器的當前值,加 1 ,再回寫新值。[圖7-1](img/fig7-1.png) 中因為發生了兩次增長計數器應該從42增至44但由於競態條件實際上只增至 43 。
@ -190,7 +190,7 @@ ACID意義上的隔離性意味著**同時執行的事務是相互隔離的**
![](../img/fig7-1.png)
![](img/fig7-1.png)
@ -276,7 +276,7 @@ ACID意義上的隔離性意味著**同時執行的事務是相互隔離的**
這些定義假設你想同時修改多個物件(行,文件,記錄)。通常需要**多物件事務multi-object transaction** 來保持多塊資料同步。[圖7-2](../img/fig7-2.png)展示了一個來自電郵應用的例子。執行以下查詢來顯示使用者未讀郵件數量:
這些定義假設你想同時修改多個物件(行,文件,記錄)。通常需要**多物件事務multi-object transaction** 來保持多塊資料同步。[圖7-2](img/fig7-2.png)展示了一個來自電郵應用的例子。執行以下查詢來顯示使用者未讀郵件數量:
@ -292,7 +292,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
在[圖7-2](../img/fig7-2.png)中使用者2 遇到異常情況:郵件列表裡顯示有未讀訊息,但計數器顯示為零未讀訊息,因為計數器增長還沒有發生[^ii]。隔離性可以避免這個問題透過確保使用者2 要麼同時看到新郵件和增長後的計數器,要麼都看不到。反正不會看到執行到一半的中間結果。
在[圖7-2](img/fig7-2.png)中使用者2 遇到異常情況:郵件列表裡顯示有未讀訊息,但計數器顯示為零未讀訊息,因為計數器增長還沒有發生[^ii]。隔離性可以避免這個問題透過確保使用者2 要麼同時看到新郵件和增長後的計數器,要麼都看不到。反正不會看到執行到一半的中間結果。
@ -300,7 +300,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
![](../img/fig7-2.png)
![](img/fig7-2.png)
@ -308,11 +308,11 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
[圖7-3](../img/fig7-3.png)說明了對原子性的需求:如果在事務過程中發生錯誤,郵箱和未讀計數器的內容可能會失去同步。在原子事務中,如果對計數器的更新失敗,事務將被中止,並且插入的電子郵件將被回滾。
[圖7-3](img/fig7-3.png)說明了對原子性的需求:如果在事務過程中發生錯誤,郵箱和未讀計數器的內容可能會失去同步。在原子事務中,如果對計數器的更新失敗,事務將被中止,並且插入的電子郵件將被回滾。
![](../img/fig7-3.png)
![](img/fig7-3.png)
@ -352,7 +352,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
一些資料庫也提供更復雜的原子操作[^iv],例如自增操作,這樣就不再需要像 [圖7-1](../img/fig7-1.png) 那樣的讀取-修改-寫入序列了。同樣流行的是 **[比較和設定CAS, compare-and-set](#比較並設定CAS)** 操作,僅當值沒有被其他併發修改過時,才允許執行寫操作。
一些資料庫也提供更復雜的原子操作[^iv],例如自增操作,這樣就不再需要像 [圖7-1](img/fig7-1.png) 那樣的讀取-修改-寫入序列了。同樣流行的是 **[比較和設定CAS, compare-and-set](#比較並設定CAS)** 操作,僅當值沒有被其他併發修改過時,才允許執行寫操作。
@ -382,7 +382,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
* 在關係資料模型中,一個表中的行通常具有對另一個表中的行的外來鍵引用。(類似的是,在一個圖資料模型中,一個頂點有著到其他頂點的邊)。多物件事務使你確保這些引用始終有效:當插入幾個相互引用的記錄時,外來鍵必須是正確的和最新的,不然資料就沒有意義。
* 在文件資料模型中,需要一起更新的欄位通常在同一個文件中,這被視為單個物件——更新單個文件時不需要多物件事務。但是,缺乏連線功能的文件資料庫會鼓勵非規範化(參閱“[關係型資料庫與文件資料庫在今日的對比](ch2.md#關係型資料庫與文件資料庫在今日的對比)”)。當需要更新非規範化的資訊時,如 [圖7-2](../img/fig7-2.png) 所示,需要一次更新多個文件。事務在這種情況下非常有用,可以防止非規範化的資料不同步。
* 在文件資料模型中,需要一起更新的欄位通常在同一個文件中,這被視為單個物件——更新單個文件時不需要多物件事務。但是,缺乏連線功能的文件資料庫會鼓勵非規範化(參閱“[關係型資料庫與文件資料庫在今日的對比](ch2.md#關係型資料庫與文件資料庫在今日的對比)”)。當需要更新非規範化的資訊時,如 [圖7-2](img/fig7-2.png) 所示,需要一次更新多個文件。事務在這種情況下非常有用,可以防止非規範化的資料不同步。
* 在具有二級索引的資料庫中(除了純粹的鍵值儲存以外幾乎都有),每次更改值時都需要更新索引。從事務角度來看,這些索引是不同的資料庫物件:例如,如果沒有事務隔離性,記錄可能出現在一個索引中,但沒有出現在另一個索引中,因為第二個索引的更新還沒有發生。
@ -490,11 +490,11 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
在**讀已提交**隔離級別執行的事務必須防止髒讀。這意味著事務的任何寫入操作只有在該事務提交時才能被其他人看到(然後所有的寫入操作都會立即變得可見)。如[圖7-4](../img/fig7-4.png)所示使用者1 設定了`x = 3`但使用者2 的 `get x `仍舊返回舊值2 當用戶1 尚未提交時)。
在**讀已提交**隔離級別執行的事務必須防止髒讀。這意味著事務的任何寫入操作只有在該事務提交時才能被其他人看到(然後所有的寫入操作都會立即變得可見)。如[圖7-4](img/fig7-4.png)所示使用者1 設定了`x = 3`但使用者2 的 `get x `仍舊返回舊值2 當用戶1 尚未提交時)。
![](../img/fig7-4.png)
![](img/fig7-4.png)
@ -506,9 +506,9 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
- 如果事務需要更新多個物件,髒讀取意味著另一個事務可能會只看到一部分更新。例如,在[圖7-2](../img/fig7-2.png)中,使用者看到新的未讀電子郵件,但看不到更新的計數器。這就是電子郵件的髒讀。看到處於部分更新狀態的資料庫會讓使用者感到困惑,並可能導致其他事務做出錯誤的決定。
- 如果事務需要更新多個物件,髒讀取意味著另一個事務可能會只看到一部分更新。例如,在[圖7-2](img/fig7-2.png)中,使用者看到新的未讀電子郵件,但看不到更新的計數器。這就是電子郵件的髒讀。看到處於部分更新狀態的資料庫會讓使用者感到困惑,並可能導致其他事務做出錯誤的決定。
- 如果事務中止,則所有寫入操作都需要回滾(如[圖7-3](../img/fig7-3.png)所示)。如果資料庫允許髒讀,那就意味著一個事務可能會看到稍後需要回滾的資料,即從未實際提交給資料庫的資料。想想後果就讓人頭大。
- 如果事務中止,則所有寫入操作都需要回滾(如[圖7-3](img/fig7-3.png)所示)。如果資料庫允許髒讀,那就意味著一個事務可能會看到稍後需要回滾的資料,即從未實際提交給資料庫的資料。想想後果就讓人頭大。
@ -528,13 +528,13 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
- 如果事務更新多個物件,髒寫會導致不好的結果。例如,考慮 [圖7-5](../img/fig7-5.png)以一個二手車銷售網站為例Alice和Bob兩個人同時試圖購買同一輛車。購買汽車需要兩次資料庫寫入網站上的商品列表需要更新以反映買家的購買銷售發票需要傳送給買家。在[圖7-5](../img/fig7-5.png)的情況下銷售是屬於Bob的因為他成功更新了商品列表但發票卻寄送給了愛麗絲因為她成功更新了發票表。讀已提交會阻止這樣的事故。
- 如果事務更新多個物件,髒寫會導致不好的結果。例如,考慮 [圖7-5](img/fig7-5.png)以一個二手車銷售網站為例Alice和Bob兩個人同時試圖購買同一輛車。購買汽車需要兩次資料庫寫入網站上的商品列表需要更新以反映買家的購買銷售發票需要傳送給買家。在[圖7-5](img/fig7-5.png)的情況下銷售是屬於Bob的因為他成功更新了商品列表但發票卻寄送給了愛麗絲因為她成功更新了發票表。讀已提交會阻止這樣的事故。
- 但是,讀已提交併不能防止[圖7-1](../img/fig7-1.png)中兩個計數器增量之間的競爭狀態。在這種情況下,第二次寫入發生在第一個事務提交後,所以它不是一個髒寫。這仍然是不正確的,但是出於不同的原因,在“[防止更新丟失](#防止丟失更新)”中將討論如何使這種計數器增量安全。
- 但是,讀已提交併不能防止[圖7-1](img/fig7-1.png)中兩個計數器增量之間的競爭狀態。在這種情況下,第二次寫入發生在第一個事務提交後,所以它不是一個髒寫。這仍然是不正確的,但是出於不同的原因,在“[防止更新丟失](#防止丟失更新)”中將討論如何使這種計數器增量安全。
![](../img/fig7-5.png)
![](img/fig7-5.png)
@ -562,7 +562,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
出於這個原因,大多數資料庫[^vi]使用[圖7-4](../img/fig7-4.png)的方式防止髒讀:對於寫入的每個物件,資料庫都會記住舊的已提交值,和由當前持有寫入鎖的事務設定的新值。當事務正在進行時,任何其他讀取物件的事務都會拿到舊值。 只有當新值提交後,事務才會切換到讀取新值。
出於這個原因,大多數資料庫[^vi]使用[圖7-4](img/fig7-4.png)的方式防止髒讀:對於寫入的每個物件,資料庫都會記住舊的已提交值,和由當前持有寫入鎖的事務設定的新值。當事務正在進行時,任何其他讀取物件的事務都會拿到舊值。 只有當新值提交後,事務才會切換到讀取新值。
@ -578,11 +578,11 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
但是在使用此隔離級別時,仍然有很多地方可能會產生併發錯誤。例如[圖7-6](../img/fig7-6.png)說明了讀已提交時可能發生的問題。
但是在使用此隔離級別時,仍然有很多地方可能會產生併發錯誤。例如[圖7-6](img/fig7-6.png)說明了讀已提交時可能發生的問題。
![](../img/fig7-6.png)
![](img/fig7-6.png)
@ -642,7 +642,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
為了實現快照隔離,資料庫使用了我們看到的用於防止[圖7-4](../img/fig7-4.png)中的髒讀的機制的一般化。資料庫必須可能保留一個物件的幾個不同的提交版本,因為各種正在進行的事務可能需要看到資料庫在不同的時間點的狀態。因為它同時維護著單個物件的多個版本,所以這種技術被稱為**多版本併發控制MVCC, multi-version concurrency control**。
為了實現快照隔離,資料庫使用了我們看到的用於防止[圖7-4](img/fig7-4.png)中的髒讀的機制的一般化。資料庫必須可能保留一個物件的幾個不同的提交版本,因為各種正在進行的事務可能需要看到資料庫在不同的時間點的狀態。因為它同時維護著單個物件的多個版本,所以這種技術被稱為**多版本併發控制MVCC, multi-version concurrency control**。
@ -650,7 +650,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
[圖7-7](../img/fig7-7.png)說明了如何在PostgreSQL中實現基於MVCC的快照隔離【31】其他實現類似。當一個事務開始時它被賦予一個唯一的永遠增長[^vii]的事務ID`txid`。每當事務向資料庫寫入任何內容時它所寫入的資料都會被標記上寫入者的事務ID。
[圖7-7](img/fig7-7.png)說明了如何在PostgreSQL中實現基於MVCC的快照隔離【31】其他實現類似。當一個事務開始時它被賦予一個唯一的永遠增長[^vii]的事務ID`txid`。每當事務向資料庫寫入任何內容時它所寫入的資料都會被標記上寫入者的事務ID。
@ -658,7 +658,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
![](../img/fig7-7.png)
![](img/fig7-7.png)
@ -674,7 +674,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
`UPDATE` 操作在內部翻譯為 `DELETE``INSERT` 。例如,在[圖7-7](../img/fig7-7.png)中事務13 從賬戶2 中扣除100美元將餘額從500美元改為400美元。實際上包含兩條賬戶2 的記錄:餘額為 \$500 的行被標記為**被事務13刪除**,餘額為 \$400 的行**由事務13建立**。
`UPDATE` 操作在內部翻譯為 `DELETE``INSERT` 。例如,在[圖7-7](img/fig7-7.png)中事務13 從賬戶2 中扣除100美元將餘額從500美元改為400美元。實際上包含兩條賬戶2 的記錄:餘額為 \$500 的行被標記為**被事務13刪除**,餘額為 \$400 的行**由事務13建立**。
@ -696,7 +696,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
這些規則適用於建立和刪除物件。在[圖7-7](../img/fig7-7.png)中當事務12 從賬戶2 讀取時,它會看到 \$500 的餘額,因為 \$500 餘額的刪除是由事務13 完成的根據規則3事務12 看不到事務13 執行的刪除且400美元記錄的建立也是不可見的按照相同的規則
這些規則適用於建立和刪除物件。在[圖7-7](img/fig7-7.png)中當事務12 從賬戶2 讀取時,它會看到 \$500 的餘額,因為 \$500 餘額的刪除是由事務13 完成的根據規則3事務12 看不到事務13 執行的刪除且400美元記錄的建立也是不可見的按照相同的規則
@ -762,7 +762,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
併發的寫入事務之間還有其他幾種有趣的衝突。其中最著名的是**丟失更新lost update** 問題,如[圖7-1](../img/fig7-1.png)所示,以兩個併發計數器增量為例。
併發的寫入事務之間還有其他幾種有趣的衝突。其中最著名的是**丟失更新lost update** 問題,如[圖7-1](img/fig7-1.png)所示,以兩個併發計數器增量為例。
@ -928,7 +928,7 @@ UPDATE wiki_pages SET content = '新內容'
### 寫入偏與幻讀
### 寫入偏與幻讀
@ -944,11 +944,11 @@ UPDATE wiki_pages SET content = '新內容'
現在想象一下Alice和Bob是兩位值班醫生。兩人都感到不適所以他們都決定請假。不幸的是他們恰好在同一時間點選按鈕下班。[圖7-8](../img/fig7-8.png)說明了接下來的事情。
現在想象一下Alice和Bob是兩位值班醫生。兩人都感到不適所以他們都決定請假。不幸的是他們恰好在同一時間點選按鈕下班。[圖7-8](img/fig7-8.png)說明了接下來的事情。
![](../img/fig7-8.png)
![](img/fig7-8.png)
@ -1244,11 +1244,11 @@ COMMIT;
出於這個原因,具有單執行緒序列事務處理的系統不允許互動式的多語句事務。取而代之,應用程式必須提前將整個事務程式碼作為儲存過程提交給資料庫。這些方法之間的差異如[圖7-9](../img/fig7-9.png) 所示。如果事務所需的所有資料都在記憶體中則儲存過程可以非常快地執行而不用等待任何網路或磁碟I/O。
出於這個原因,具有單執行緒序列事務處理的系統不允許互動式的多語句事務。取而代之,應用程式必須提前將整個事務程式碼作為儲存過程提交給資料庫。這些方法之間的差異如[圖7-9](img/fig7-9.png) 所示。如果事務所需的所有資料都在記憶體中則儲存過程可以非常快地執行而不用等待任何網路或磁碟I/O。
![](../img/fig7-9.png)
![](img/fig7-9.png)
@ -1360,7 +1360,7 @@ VoltDB還使用儲存過程進行復制但不是將事務的寫入結果從
- 如果事務A讀取了一個物件並且事務B想要寫入該物件那麼B必須等到A提交或中止才能繼續。 這確保B不能在A底下意外地改變物件。
- 如果事務A寫入了一個物件並且事務B想要讀取該物件則B必須等到A提交或中止才能繼續。 (像[圖7-1](../img/fig7-1.png)那樣讀取舊版本的物件在2PL下是不可接受的。
- 如果事務A寫入了一個物件並且事務B想要讀取該物件則B必須等到A提交或中止才能繼續。 (像[圖7-1](img/fig7-1.png)那樣讀取舊版本的物件在2PL下是不可接受的。
@ -1546,7 +1546,7 @@ WHERE room_id = 123 AND
先前討論了快照隔離中的寫入偏差(參閱“[寫入偏差與幻讀](#寫入偏差與幻讀)”)時,我們觀察到一個迴圈模式:事務從資料庫讀取一些資料,檢查查詢的結果,並根據它看到的結果決定採取一些操作(寫入資料庫)。但是,在快照隔離的情況下,原始查詢的結果在事務提交時可能不再是最新的,因為資料可能在同一時間被修改。
先前討論了快照隔離中的寫入偏差(參閱“[寫入偏斜與幻讀](#寫入偏斜與幻讀)”)時,我們觀察到一個迴圈模式:事務從資料庫讀取一些資料,檢查查詢的結果,並根據它看到的結果決定採取一些操作(寫入資料庫)。但是,在快照隔離的情況下,原始查詢的結果在事務提交時可能不再是最新的,因為資料可能在同一時間被修改。
@ -1572,11 +1572,11 @@ WHERE room_id = 123 AND
回想一下快照隔離通常是透過多版本併發控制MVCC見[圖7-10](../img/fig7-10.png)來實現的。當一個事務從MVCC資料庫中的一致快照讀時它將忽略取快照時尚未提交的任何其他事務所做的寫入。在[圖7-10](../img/fig7-10.png)中事務43 認為Alice的 `on_call = true` 因為事務42修改Alice的待命狀態未被提交。然而在事務43想要提交時事務42 已經提交。這意味著在讀一致性快照時被忽略的寫入已經生效事務43 的前提不再為真。
回想一下快照隔離通常是透過多版本併發控制MVCC見[圖7-10](img/fig7-10.png)來實現的。當一個事務從MVCC資料庫中的一致快照讀時它將忽略取快照時尚未提交的任何其他事務所做的寫入。在[圖7-10](img/fig7-10.png)中事務43 認為Alice的 `on_call = true` 因為事務42修改Alice的待命狀態未被提交。然而在事務43想要提交時事務42 已經提交。這意味著在讀一致性快照時被忽略的寫入已經生效事務43 的前提不再為真。
![](../img/fig7-10.png)
![](img/fig7-10.png)
@ -1596,11 +1596,11 @@ WHERE room_id = 123 AND
第二種情況要考慮的是另一個事務在讀取資料之後修改資料。這種情況如[圖7-11](../img/fig7-11.png)所示。
第二種情況要考慮的是另一個事務在讀取資料之後修改資料。這種情況如[圖7-11](img/fig7-11.png)所示。
![](../img/fig7-11.png)
![](img/fig7-11.png)
@ -1612,7 +1612,7 @@ WHERE room_id = 123 AND
在[圖7-11]()中事務42 和43 都在班次1234 查詢值班醫生。如果在`shift_id`上有索引則資料庫可以使用索引項1234 來記錄事務42 和43 讀取這個資料的事實。 (如果沒有索引,這個資訊可以在表級別進行跟蹤)。這個資訊只需要保留一段時間:在一個事務完成(提交或中止),並且所有的併發事務完成之後,資料庫就可以忘記它讀取的資料了。
在[圖7-11](img/fig7-11.png)中事務42 和43 都在班次1234 查詢值班醫生。如果在`shift_id`上有索引則資料庫可以使用索引項1234 來記錄事務42 和43 讀取這個資料的事實。 (如果沒有索引,這個資訊可以在表級別進行跟蹤)。這個資訊只需要保留一段時間:在一個事務完成(提交或中止),並且所有的併發事務完成之後,資料庫就可以忘記它讀取的資料了。
@ -1620,7 +1620,7 @@ WHERE room_id = 123 AND
在[圖7-11](../img/fig7-11.png)中事務43 通知事務42 其先前讀已過時反之亦然。事務42首先提交併成功儘管事務43 的寫影響了42 但因為事務43 尚未提交所以寫入尚未生效。然而當事務43 想要提交時來自事務42 的衝突寫入已經被提交所以事務43 必須中止。
在[圖7-11](img/fig7-11.png)中事務43 通知事務42 其先前讀已過時反之亦然。事務42首先提交併成功儘管事務43 的寫影響了42 但因為事務43 尚未提交所以寫入尚未生效。然而當事務43 想要提交時來自事務42 的衝突寫入已經被提交所以事務43 必須中止。

View File

@ -2,7 +2,7 @@
![](../img/ch8.png)
![](img/ch8.png)
@ -194,7 +194,7 @@
網際網路和資料中心(通常是乙太網)中的大多數內部網路都是**非同步分組網路asynchronous packet networks**。在這種網路中,一個節點可以向另一個節點發送一個訊息(一個數據包),但是網路不能保證它什麼時候到達,或者是否到達。如果您傳送請求並期待響應,則很多事情可能會出錯(其中一些如[圖8-1](../img/fig8-1.png)所示):
網際網路和資料中心(通常是乙太網)中的大多數內部網路都是**非同步分組網路asynchronous packet networks**。在這種網路中,一個節點可以向另一個節點發送一個訊息(一個數據包),但是網路不能保證它什麼時候到達,或者是否到達。如果您傳送請求並期待響應,則很多事情可能會出錯(其中一些如[圖8-1](img/fig8-1.png)所示):
@ -212,7 +212,7 @@
![](../img/fig8-1.png)
![](img/fig8-1.png)
@ -336,7 +336,7 @@
* 如果多個不同的節點同時嘗試將資料包傳送到同一目的地,則網路交換機必須將它們排隊並將它們逐個送入目標網路鏈路(如[圖8-2](../img/fig8-2.png)所示)。在繁忙的網路鏈路上,資料包可能需要等待一段時間才能獲得一個插槽(這稱為網路擁塞)。如果傳入的資料太多,交換機佇列填滿,資料包將被丟棄,因此需要重新發送資料包 - 即使網路執行良好。
* 如果多個不同的節點同時嘗試將資料包傳送到同一目的地,則網路交換機必須將它們排隊並將它們逐個送入目標網路鏈路(如[圖8-2](img/fig8-2.png)所示)。在繁忙的網路鏈路上,資料包可能需要等待一段時間才能獲得一個插槽(這稱為網路擁塞)。如果傳入的資料太多,交換機佇列填滿,資料包將被丟棄,因此需要重新發送資料包 - 即使網路執行良好。
* 當資料包到達目標機器時如果所有CPU核心當前都處於繁忙狀態則來自網路的傳入請求將被作業系統排隊直到應用程式準備好處理它為止。根據機器上的負載這可能需要一段任意的時間。
@ -346,7 +346,7 @@
![](../img/fig8-2.png)
![](img/fig8-2.png)
@ -388,7 +388,7 @@
### 同步網路 vs 非同步網路
### 同步網路非同步網路
@ -630,11 +630,11 @@
[圖8-3](../img/fig8-3.png)顯示了在具有多領導者複製的資料庫中對時鐘的危險使用(該例子類似於[圖5-9](../img/fig5-9.png))。 客戶端A在節點1上寫入`x = 1`寫入被複制到節點3客戶端B在節點3上增加x我們現在有`x = 2`最後這兩個寫入都被複制到節點2。
[圖8-3](img/fig8-3.png)顯示了在具有多領導者複製的資料庫中對時鐘的危險使用(該例子類似於[圖5-9](img/fig5-9.png))。 客戶端A在節點1上寫入`x = 1`寫入被複制到節點3客戶端B在節點3上增加x我們現在有`x = 2`最後這兩個寫入都被複制到節點2。
![](../img/fig8-3.png)
![](img/fig8-3.png)
@ -642,11 +642,11 @@
在[圖8-3](../img/fig8-3.png)中當一個寫入被複制到其他節點時它會根據發生寫入的節點上的日曆時鐘標記一個時間戳。在這個例子中時鐘同步是非常好的節點1和節點3之間的偏差小於3ms這可能比你在實踐中能預期的更好。
在[圖8-3](img/fig8-3.png)中當一個寫入被複制到其他節點時它會根據發生寫入的節點上的日曆時鐘標記一個時間戳。在這個例子中時鐘同步是非常好的節點1和節點3之間的偏差小於3ms這可能比你在實踐中能預期的更好。
儘管如此,[圖8-3](../img/fig8-3.png)中的時間戳卻無法正確排列事件:寫入`x = 1`的時間戳為42.004秒,但寫入`x = 2`的時間戳為42.003秒,即使`x = 2`在稍後出現。當節點2接收到這兩個事件時會錯誤地推斷出`x = 1`是最近的值,而丟棄寫入`x = 2`。效果上表現為客戶端B的增量操作會丟失。
儘管如此,[圖8-3](img/fig8-3.png)中的時間戳卻無法正確排列事件:寫入`x = 1`的時間戳為42.004秒,但寫入`x = 2`的時間戳為42.003秒,即使`x = 2`在稍後出現。當節點2接收到這兩個事件時會錯誤地推斷出`x = 1`是最近的值,而丟棄寫入`x = 2`。效果上表現為客戶端B的增量操作會丟失。
@ -656,7 +656,7 @@
* 資料庫寫入可能會神祕地消失具有滯後時鐘的節點無法覆蓋之前具有快速時鐘的節點寫入的值直到節點之間的時鐘偏差消逝【54,55】。此方案可能導致一定數量的資料被悄悄丟棄而未嚮應用報告任何錯誤。
* LWW無法區分**高頻順序寫入**(在[圖8-3](../img/fig8-3.png)中客戶端B的增量操作**一定**發生在客戶端A的寫入之後和**真正併發寫入**(寫入者意識不到其他寫入者)。需要額外的因果關係跟蹤機制(例如版本向量),以防止違背因果關係(請參閱“[檢測併發寫入](ch5.md#檢測併發寫入)”)。
* LWW無法區分**高頻順序寫入**(在[圖8-3](img/fig8-3.png)中客戶端B的增量操作**一定**發生在客戶端A的寫入之後和**真正併發寫入**(寫入者意識不到其他寫入者)。需要額外的因果關係跟蹤機制(例如版本向量),以防止違背因果關係(請參閱“[檢測併發寫入](ch5.md#檢測併發寫入)”)。
* 兩個節點很可能獨立地生成具有相同時間戳的寫入,特別是在時鐘僅具有毫秒解析度的情況下。為了解決這樣的衝突,還需要一個額外的**決勝值tiebreaker**可以簡單地是一個大隨機數但這種方法也可能會導致違背因果關係【53】。
@ -962,11 +962,11 @@ while (true) {
例如,[圖8-4](../img/fig8-4.png)顯示了由於不正確的鎖實現導致的資料損壞錯誤。 這個錯誤不僅僅是理論上的HBase曾經有這個問題【74,75】假設你要確保一個儲存服務中的檔案一次只能被一個客戶訪問因為如果多個客戶試圖對此寫入該檔案將被損壞。您嘗試透過在訪問檔案之前要求客戶端從鎖定服務獲取租約來實現此目的。
例如,[圖8-4](img/fig8-4.png)顯示了由於不正確的鎖實現導致的資料損壞錯誤。 這個錯誤不僅僅是理論上的HBase曾經有這個問題【74,75】假設你要確保一個儲存服務中的檔案一次只能被一個客戶訪問因為如果多個客戶試圖對此寫入該檔案將被損壞。您嘗試透過在訪問檔案之前要求客戶端從鎖定服務獲取租約來實現此目的。
![](../img/fig8-4.png)
![](img/fig8-4.png)
@ -982,11 +982,11 @@ while (true) {
當使用鎖或租約來保護對某些資源(如[圖8-4](../img/fig8-4.png)中的檔案儲存)的訪問時,需要確保一個被誤認為自己是“天選者”的節點不能擾亂系統的其它部分。實現這一目標的一個相當簡單的技術就是**防護fencing**,如[圖8-5](../img/fig8-5.png)所示
當使用鎖或租約來保護對某些資源(如[圖8-4](img/fig8-4.png)中的檔案儲存)的訪問時,需要確保一個被誤認為自己是“天選者”的節點不能擾亂系統的其它部分。實現這一目標的一個相當簡單的技術就是**防護fencing**,如[圖8-5](img/fig8-5.png)所示
![](../img/fig8-5.png)
![](img/fig8-5.png)
@ -998,7 +998,7 @@ while (true) {
在[圖8-5](../img/fig8-5.png)中客戶端1以33的令牌獲得租約但隨後進入一個長時間的停頓並且租約到期。客戶端2以34的令牌該數字總是增加獲取租約然後將其寫入請求傳送到儲存服務包括34的令牌。稍後客戶端1恢復生機並將其寫入儲存服務包括其令牌值33。但是儲存伺服器會記住它已經處理了一個具有更高令牌編號34的寫入因此它會拒絕帶有令牌33的請求。
在[圖8-5](img/fig8-5.png)中客戶端1以33的令牌獲得租約但隨後進入一個長時間的停頓並且租約到期。客戶端2以34的令牌該數字總是增加獲取租約然後將其寫入請求傳送到儲存服務包括34的令牌。稍後客戶端1恢復生機並將其寫入儲存服務包括其令牌值33。但是儲存伺服器會記住它已經處理了一個具有更高令牌編號34的寫入因此它會拒絕帶有令牌33的請求。
@ -1174,7 +1174,7 @@ while (true) {
***唯一性***
***唯一性uniqueness***
@ -1182,7 +1182,7 @@ while (true) {
***單調序列***
***單調序列monotonic sequence***
@ -1190,7 +1190,7 @@ while (true) {
***可用性***
***可用性availability***
@ -1206,7 +1206,7 @@ while (true) {
為了澄清這種情況,有必要區分兩種不同的性質:**安全性safety**和**活性liveness**。在剛剛給出的例子中,**唯一性uniqueness**和**單調序列monotonic sequence**是安全屬性,但**可用性**是**活性liveness**屬性。
為了澄清這種情況,有必要區分兩種不同的屬性:**安全safety屬性**和**活性liveness屬性**。在剛剛給出的例子中,**唯一性**和**單調序列**是安全屬性,而**可用性**是活性屬性。
@ -1214,7 +1214,7 @@ while (true) {
安全性通常被非正式地定義為,**沒有壞事發生**,而活性通常就類似:**最終好事發生**。但是,最好不要過多地閱讀那些非正式的定義,因為好與壞的含義是主觀的。安全和活性的實際定義是精確的和數學的【90】
安全通常被非正式地定義為:**沒有壞事發生**,而活性通常就類似:**最終好事發生**。但是最好不要過多地閱讀那些非正式的定義因為好與壞的含義是主觀的。安全和活性的實際定義是精確的和數學的【90】
@ -1224,7 +1224,7 @@ while (true) {
區分安全性和活性屬性的一個優點是可以幫助我們處理困難的系統模型。對於分散式演算法,在系統模型的所有可能情況下,要求**始終**保持安全屬性是常見的【88】。也就是說即使所有節點崩潰或者整個網路出現故障演算法仍然必須確保它不會返回錯誤的結果即保證安全性得到滿足
區分安全性和活性屬性的一個優點是可以幫助我們處理困難的系統模型。對於分散式演算法,在系統模型的所有可能情況下,要求**始終**保持安全屬性是常見的【88】。也就是說即使所有節點崩潰或者整個網路出現故障演算法仍然必須確保它不會返回錯誤的結果即保證安全性得到滿足)。
@ -1236,7 +1236,7 @@ while (true) {
安全性和活性屬性以及系統模型對於推理分散式演算法的正確性非常有用。然而,在實踐中實施演算法時,現實的混亂事實再一次地讓你咬牙切齒,很明顯系統模型是對現實的簡化抽象。
安全性和活性屬性以及系統模型對於推理分散式演算法的正確性非常有用。然而,在實踐中實施演算法時,現實的混亂事實再一次地讓你咬牙切齒,很明顯系統模型是對現實的簡化抽象。

File diff suppressed because it is too large Load Diff

View File

@ -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的請求完成時間小於t5的請求完成時間要比t長。 請參閱第13頁上的“描述效能”。
透過計算有多少值高於或低於某個閾值來衡量值分佈的方法。 例如某個時間段的第95個百分位響應時間是時間t則該時間段中95的請求完成時間小於t5的請求完成時間要比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)”。

View File

@ -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)

View File

@ -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***