mirror of
https://github.com/Vonng/ddia.git
synced 2024-12-06 15:20:12 +08:00
fix zh-tw image links
This commit is contained in:
parent
4e4193a40a
commit
bafe8819c3
@ -1,3 +1,4 @@
|
||||
- Language
|
||||
- [:cn: 简体](/)
|
||||
- [:cn: 繁体](/zh-tw/)
|
||||
- 语言版本
|
||||
- [简体中文](/)
|
||||
- [繁体中文](/zh-tw/)
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
<div id="app">Loading ...</div>
|
||||
<script>
|
||||
// Set html "lang" attribute based on URL
|
||||
var lang = location.hash.match(/#\/(zh-cn)\//);
|
||||
var lang = location.hash.match(/#\/(zh-tw)\//);
|
||||
if (lang) {
|
||||
document.documentElement.setAttribute('lang', lang[1]);
|
||||
}
|
||||
@ -51,8 +51,7 @@
|
||||
'/zh-tw/': '搜索',
|
||||
},
|
||||
pathNamespaces: ['/zh-tw']
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js"></script>
|
||||
|
259
zh-tw/README.md
259
zh-tw/README.md
@ -1,489 +1,248 @@
|
||||
# 設計資料密集型應用 - 中文翻譯
|
||||
|
||||
|
||||
|
||||
- 作者: [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/#/))。
|
||||
|
||||
## 譯序
|
||||
|
||||
|
||||
|
||||
> 不懂資料庫的全棧工程師不是好架構師
|
||||
|
||||
>
|
||||
|
||||
> —— Vonng
|
||||
|
||||
|
||||
|
||||
現今,尤其是在網際網路領域,大多數應用都屬於資料密集型應用。本書從底層資料結構到頂層架構設計,將資料系統設計中的精髓娓娓道來。其中的寶貴經驗無論是對架構師,DBA、還是後端工程師、甚至產品經理都會有幫助。
|
||||
|
||||
|
||||
|
||||
這是一本理論結合實踐的書,書中很多問題,譯者在實際場景中都曾遇到過,讀來讓人擊節扼腕。如果能早點讀到這本書,該少走多少彎路啊!
|
||||
|
||||
|
||||
|
||||
這也是一本深入淺出的書,講述概念的來龍去脈而不是賣弄定義,介紹事物發展演化歷程而不是事實堆砌,將複雜的概念講述的淺顯易懂,但又直擊本質不失深度。每章最後的引用質量非常好,是深入學習各個主題的絕佳索引。
|
||||
|
||||
|
||||
|
||||
本書為資料系統的設計、實現、與評價提供了很好的概念框架。讀完並理解本書內容後,讀者可以輕鬆看破大多數的技術忽悠,與技術磚家撕起來虎虎生風🤣。
|
||||
|
||||
|
||||
|
||||
這是2017年譯者讀過最好的一本技術類書籍,這麼好的書沒有中文翻譯,實在是遺憾。某不才,願為先進技術文化的傳播貢獻一分力量。既可以深入學習有趣的技術主題,又可以鍛鍊中英文語言文字功底,何樂而不為?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 前言
|
||||
|
||||
|
||||
|
||||
> 在我們的社會中,技術是一種強大的力量。資料、軟體、通訊可以用於壞的方面:不公平的階級固化,損害公民權利,保護既得利益集團。但也可以用於好的方面:讓底層人民發出自己的聲音,讓每個人都擁有機會,避免災難。本書獻給所有將技術用於善途的人們。
|
||||
|
||||
|
||||
|
||||
---------
|
||||
|
||||
|
||||
|
||||
> 計算是一種流行文化,流行文化鄙視歷史。 流行文化關乎個體身份和參與感,但與合作無關。流行文化活在當下,也與過去和未來無關。 我認為大部分(為了錢)編寫程式碼的人就是這樣的, 他們不知道自己的文化來自哪裡。
|
||||
|
||||
>
|
||||
|
||||
> ——阿蘭·凱接受Dobb博士的雜誌採訪時(2012年)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 目錄
|
||||
|
||||
|
||||
|
||||
### [序言](preface.md)
|
||||
|
||||
|
||||
|
||||
### [第一部分:資料系統的基石](part-i.md)
|
||||
|
||||
|
||||
|
||||
* [第一章:可靠性、可伸縮性、可維護性](ch1.md)
|
||||
|
||||
* [關於資料系統的思考](ch1.md#關於資料系統的思考)
|
||||
|
||||
* [可靠性](ch1.md#可靠性)
|
||||
|
||||
* [可伸縮性](ch1.md#可伸縮性)
|
||||
|
||||
* [可維護性](ch1.md#可維護性)
|
||||
|
||||
* [本章小結](ch1.md#本章小結)
|
||||
|
||||
* [第二章:資料模型與查詢語言](ch2.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#領導者與追隨者)
|
||||
|
||||
* [複製延遲問題](ch5.md#複製延遲問題)
|
||||
|
||||
* [多主複製](ch5.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#本章小結)
|
||||
|
||||
|
||||
|
||||
### [第三部分:衍生資料](part-iii.md)
|
||||
|
||||
|
||||
|
||||
* [第十章:批處理](ch10.md)
|
||||
|
||||
* [使用Unix工具的批處理](ch10.md#使用Unix工具的批處理)
|
||||
|
||||
* [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#本章小結)
|
||||
|
||||
|
||||
|
||||
### [術語表](glossary.md)
|
||||
|
||||
|
||||
|
||||
### [後記](colophon.md)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 法律宣告
|
||||
|
||||
|
||||
|
||||
從原作者處得知,已經有簡體中文的翻譯計劃,將於2018年末完成。[購買地址](https://search.jd.com/Search?keyword=設計資料密集型應用)
|
||||
|
||||
|
||||
|
||||
譯者純粹出於**學習目的**與**個人興趣**翻譯本書,不追求任何經濟利益。
|
||||
|
||||
|
||||
|
||||
譯者保留對此版本譯文的署名權,其他權利以原作者和出版社的主張為準。
|
||||
|
||||
|
||||
|
||||
本譯文只供學習研究參考之用,不得公開傳播發行或用於商業用途。有能力閱讀英文書籍者請購買正版支援。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 貢獻
|
||||
|
||||
|
||||
|
||||
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() |
|
||||
|
||||
| [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) | 統一每章的標題格式 |
|
||||
|
||||
| [115](https://github.com/Vonng/ddia/pull/115) | [@NageNalock](https://github.com/NageNalock) | 第七章病句修改: 重複詞語 |
|
||||
|
||||
| [114](https://github.com/Vonng/ddia/pull/114) | [@Sunt-ing](https://github.com/Sunt-ing) | Update README.md: correct the book name |
|
||||
|
||||
| [113](https://github.com/Vonng/ddia/pull/113) | [@lpxxn](https://github.com/lpxxn) | 修改語句 |
|
||||
|
||||
| [112](https://github.com/Vonng/ddia/pull/112) | [@ibyte2011](https://github.com/ibyte2011) | Update ch9.md |
|
||||
|
||||
| [110](https://github.com/Vonng/ddia/pull/110) | [@lpxxn](https://github.com/lpxxn) | 讀已寫入資料 |
|
||||
|
||||
| [107](https://github.com/Vonng/ddia/pull/107) | [@abbychau](https://github.com/abbychau) | 單調鐘和好死還是賴活著 |
|
||||
|
||||
| [106](https://github.com/Vonng/ddia/pull/106) | [@enochii](https://github.com/enochii) | typo in ch2: fix braces typo |
|
||||
|
||||
| [105](https://github.com/Vonng/ddia/pull/105) | [@LiminCode](https://github.com/LiminCode) | Chronicle translation error |
|
||||
|
||||
| [104](https://github.com/Vonng/ddia/pull/104) | [@Sunt-ing](https://github.com/Sunt-ing) | several advice for better translation |
|
||||
|
||||
| [103](https://github.com/Vonng/ddia/pull/103) | [@Sunt-ing](https://github.com/Sunt-ing) | typo in ch4: should be 完成 rather than 完全 |
|
||||
|
||||
| [102](https://github.com/Vonng/ddia/pull/102) | [@Sunt-ing](https://github.com/Sunt-ing) | ch4: better-translation: 扼殺 → 破壞 |
|
||||
|
||||
| [101](https://github.com/Vonng/ddia/pull/101) | [@Sunt-ing](https://github.com/Sunt-ing) | typo in Ch4: should be "改變" rathr than "蓋面" |
|
||||
|
||||
| [100](https://github.com/Vonng/ddia/pull/100) | [@LiminCode](https://github.com/LiminCode) | fix missing translation |
|
||||
|
||||
| [99 ](https://github.com/Vonng/ddia/pull/99) | [@mrdrivingduck](https://github.com/mrdrivingduck) | ch6: fix the word rebalancing |
|
||||
|
||||
| [98 ](https://github.com/Vonng/ddia/pull/98) | [@jacklightChen](https://github.com/jacklightChen) | fix ch7.md: fix wrong references |
|
||||
|
||||
| [97 ](https://github.com/Vonng/ddia/pull/97) | [@jenac](https://github.com/jenac) | 96 |
|
||||
|
||||
| [96 ](https://github.com/Vonng/ddia/pull/96) | [@PragmaTwice](https://github.com/PragmaTwice) | ch2: fix typo about 'may or may not be' |
|
||||
|
||||
| [95 ](https://github.com/Vonng/ddia/pull/95) | [@EvanMu96](https://github.com/EvanMu96) | fix translation of "the battle cry" in ch5 |
|
||||
|
||||
| [94 ](https://github.com/Vonng/ddia/pull/94) | [@kemingy](https://github.com/kemingy) | ch6: fix markdown and punctuations |
|
||||
|
||||
| [93 ](https://github.com/Vonng/ddia/pull/93) | [@kemingy](https://github.com/kemingy) | ch5: fix markdown and some typos |
|
||||
|
||||
| [92 ](https://github.com/Vonng/ddia/pull/92) | [@Gilbert1024](https://github.com/Gilbert1024) | Merge pull request #1 from Vonng/master |
|
||||
|
||||
| [88 ](https://github.com/Vonng/ddia/pull/88) | [@kemingy](https://github.com/kemingy) | fix typo for ch1, ch2, ch3, ch4 |
|
||||
|
||||
| [87 ](https://github.com/Vonng/ddia/pull/87) | [@wynn5a](https://github.com/wynn5a) | Update ch3.md |
|
||||
|
||||
| [86 ](https://github.com/Vonng/ddia/pull/86) | [@northmorn](https://github.com/northmorn) | Update ch1.md |
|
||||
|
||||
| [85 ](https://github.com/Vonng/ddia/pull/85) | [@sunbuhui](https://github.com/sunbuhui) | fix ch2.md: fix ch2 ambiguous translation |
|
||||
|
||||
| [84 ](https://github.com/Vonng/ddia/pull/84) | [@ganler](https://github.com/ganler) | Fix translation: use up |
|
||||
|
||||
| [83 ](https://github.com/Vonng/ddia/pull/83) | [@afunTW](https://github.com/afunTW) | Using OpenCC to convert from zh-cn to zh-tw |
|
||||
|
||||
| [82 ](https://github.com/Vonng/ddia/pull/82) | [@kangni](https://github.com/kangni) | fix gitbook url |
|
||||
|
||||
| [78 ](https://github.com/Vonng/ddia/pull/78) | [@hanyu2](https://github.com/hanyu2) | Fix unappropriated translation |
|
||||
|
||||
| [77 ](https://github.com/Vonng/ddia/pull/77) | [@Ozarklake](https://github.com/Ozarklake) | fix typo |
|
||||
|
||||
| [75 ](https://github.com/Vonng/ddia/pull/75) | [@2997ms](https://github.com/2997ms) | Fix typo |
|
||||
|
||||
| [74 ](https://github.com/Vonng/ddia/pull/74) | [@2997ms](https://github.com/2997ms) | Update ch9.md |
|
||||
|
||||
| [70 ](https://github.com/Vonng/ddia/pull/70) | [@2997ms](https://github.com/2997ms) | Update ch7.md |
|
||||
|
||||
| [67 ](https://github.com/Vonng/ddia/pull/67) | [@jiajiadebug](https://github.com/jiajiadebug) | fix issues in ch2 - ch9 and glossary |
|
||||
|
||||
| [66 ](https://github.com/Vonng/ddia/pull/66) | [@blindpirate](https://github.com/blindpirate) | Fix typo |
|
||||
|
||||
| [63 ](https://github.com/Vonng/ddia/pull/63) | [@haifeiWu](https://github.com/haifeiWu) | Update ch10.md |
|
||||
|
||||
| [62 ](https://github.com/Vonng/ddia/pull/62) | [@ych](https://github.com/ych) | fix ch1.md typesetting problem |
|
||||
|
||||
| [61 ](https://github.com/Vonng/ddia/pull/61) | [@xianlaioy](https://github.com/xianlaioy) | docs:鍾-->種,去掉ou |
|
||||
|
||||
| [60 ](https://github.com/Vonng/ddia/pull/60) | [@Zombo1296](https://github.com/Zombo1296) | 否則 -> 或者 |
|
||||
|
||||
| [59 ](https://github.com/Vonng/ddia/pull/59) | [@AlexanderMisel](https://github.com/AlexanderMisel) | 呼叫->呼叫,顯著->顯著 |
|
||||
|
||||
| [58 ](https://github.com/Vonng/ddia/pull/58) | [@ibyte2011](https://github.com/ibyte2011) | Update ch8.md |
|
||||
|
||||
| [55 ](https://github.com/Vonng/ddia/pull/55) | [@saintube](https://github.com/saintube) | ch8: 修改連結錯誤 |
|
||||
|
||||
| [54 ](https://github.com/Vonng/ddia/pull/54) | [@Panmax](https://github.com/Panmax) | Update ch2.md |
|
||||
|
||||
| [53 ](https://github.com/Vonng/ddia/pull/53) | [@ibyte2011](https://github.com/ibyte2011) | Update ch9.md |
|
||||
|
||||
| [52 ](https://github.com/Vonng/ddia/pull/52) | [@hecenjie](https://github.com/hecenjie) | Update ch1.md |
|
||||
|
||||
| [51 ](https://github.com/Vonng/ddia/pull/51) | [@latavin243](https://github.com/latavin243) | fix 修正ch3 ch4幾處翻譯 |
|
||||
|
||||
| [50 ](https://github.com/Vonng/ddia/pull/50) | [@AlexZFX](https://github.com/AlexZFX) | 幾個疏漏和格式錯誤 |
|
||||
|
||||
| [49 ](https://github.com/Vonng/ddia/pull/49) | [@haifeiWu](https://github.com/haifeiWu) | Update ch1.md |
|
||||
|
||||
| [48 ](https://github.com/Vonng/ddia/pull/48) | [@scaugrated](https://github.com/scaugrated) | fix typo |
|
||||
|
||||
| [47 ](https://github.com/Vonng/ddia/pull/47) | [@lzwill](https://github.com/lzwill) | Fixed typos in ch2 |
|
||||
|
||||
| [45 ](https://github.com/Vonng/ddia/pull/45) | [@zenuo](https://github.com/zenuo) | 刪除一個多餘的右括號 |
|
||||
|
||||
| [44 ](https://github.com/Vonng/ddia/pull/44) | [@akxxsb](https://github.com/akxxsb) | 修正第7章底部連結錯誤 |
|
||||
|
||||
| [43 ](https://github.com/Vonng/ddia/pull/43) | [@baijinping](https://github.com/baijinping) | "更假簡單"->"更加簡單" |
|
||||
|
||||
| [42 ](https://github.com/Vonng/ddia/pull/42) | [@tisonkun](https://github.com/tisonkun) | 修復 ch1 中的無序列表格式 |
|
||||
|
||||
| [38 ](https://github.com/Vonng/ddia/pull/38) | [@renjie-c](https://github.com/renjie-c) | 糾正多處的翻譯小錯誤 |
|
||||
|
||||
| [37 ](https://github.com/Vonng/ddia/pull/37) | [@tankilo](https://github.com/tankilo) | fix translation mistakes in ch4.md |
|
||||
|
||||
| [36 ](https://github.com/Vonng/ddia/pull/36) | [@wwek](https://github.com/wwek) | 1.修復多個連結錯誤 2.名詞最佳化修訂 3.錯誤修訂 |
|
||||
|
||||
| [35 ](https://github.com/Vonng/ddia/pull/35) | [@wwek](https://github.com/wwek) | fix ch7.md to ch8.md link error |
|
||||
|
||||
| [34 ](https://github.com/Vonng/ddia/pull/34) | [@wwek](https://github.com/wwek) | Merge pull request #1 from Vonng/master |
|
||||
|
||||
| [33 ](https://github.com/Vonng/ddia/pull/33) | [@wwek](https://github.com/wwek) | fix part-ii.md link error |
|
||||
|
||||
| [32 ](https://github.com/Vonng/ddia/pull/32) | [@JCYoky](https://github.com/JCYoky) | Update ch2.md |
|
||||
|
||||
| [31 ](https://github.com/Vonng/ddia/pull/31) | [@elsonLee](https://github.com/elsonLee) | Update ch7.md |
|
||||
|
||||
| [26 ](https://github.com/Vonng/ddia/pull/26) | [@yjhmelody](https://github.com/yjhmelody) | 修復一些明顯錯誤 |
|
||||
|
||||
| [25 ](https://github.com/Vonng/ddia/pull/25) | [@lqbilbo](https://github.com/lqbilbo) | 修復連結錯誤 |
|
||||
|
||||
| [24 ](https://github.com/Vonng/ddia/pull/24) | [@artiship](https://github.com/artiship) | 修改詞語順序 |
|
||||
|
||||
| [23 ](https://github.com/Vonng/ddia/pull/23) | [@artiship](https://github.com/artiship) | 修正錯別字 |
|
||||
|
||||
| [22 ](https://github.com/Vonng/ddia/pull/22) | [@artiship](https://github.com/artiship) | 糾正翻譯錯誤 |
|
||||
|
||||
| [21 ](https://github.com/Vonng/ddia/pull/21) | [@zhtisi](https://github.com/zhtisi) | 修正目錄和本章標題不符的情況 |
|
||||
|
||||
| [20 ](https://github.com/Vonng/ddia/pull/20) | [@rentiansheng](https://github.com/rentiansheng) | Update ch7.md |
|
||||
|
||||
| [19 ](https://github.com/Vonng/ddia/pull/19) | [@LHRchina](https://github.com/LHRchina) | 修復語句小bug |
|
||||
|
||||
| [16 ](https://github.com/Vonng/ddia/pull/16) | [@MuAlex](https://github.com/MuAlex) | Master |
|
||||
|
||||
| [15 ](https://github.com/Vonng/ddia/pull/15) | [@cg-zhou](https://github.com/cg-zhou) | Update translation progress |
|
||||
|
||||
| [14 ](https://github.com/Vonng/ddia/pull/14) | [@cg-zhou](https://github.com/cg-zhou) | Translate glossary |
|
||||
|
||||
| [13 ](https://github.com/Vonng/ddia/pull/13) | [@cg-zhou](https://github.com/cg-zhou) | 詳細修改了後記中和印度野豬相關的描述 |
|
||||
|
||||
| [12 ](https://github.com/Vonng/ddia/pull/12) | [@ibyte2011](https://github.com/ibyte2011) | 修改了部分翻譯 |
|
||||
|
||||
| [11 ](https://github.com/Vonng/ddia/pull/11) | [@jiajiadebug](https://github.com/jiajiadebug) | ch2 100% |
|
||||
|
||||
| [10 ](https://github.com/Vonng/ddia/pull/10) | [@jiajiadebug](https://github.com/jiajiadebug) | ch2 20% |
|
||||
|
||||
| [9 ](https://github.com/Vonng/ddia/pull/9) | [@jiajiadebug](https://github.com/jiajiadebug) | Preface, ch1, part-i translation minor fixes |
|
||||
|
||||
| [7 ](https://github.com/Vonng/ddia/pull/7) | [@MuAlex](https://github.com/MuAlex) | Ch6 translation pull request |
|
||||
|
||||
| [6 ](https://github.com/Vonng/ddia/pull/6) | [@MuAlex](https://github.com/MuAlex) | Ch6 change version1 |
|
||||
|
||||
| [5 ](https://github.com/Vonng/ddia/pull/5) | [@nevertiree](https://github.com/nevertiree) | Chapter 01語法微調 |
|
||||
|
||||
| [2 ](https://github.com/Vonng/ddia/pull/2) | [@seagullbird](https://github.com/seagullbird) | 序言初翻 |
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 協議
|
||||
|
||||
|
||||
|
||||
[CC-BY 4.0](LICENSE)
|
||||
|
@ -1,5 +1,7 @@
|
||||
- Language
|
||||
- 語言版本
|
||||
|
||||
- [簡體中文](/)
|
||||
|
||||
- [繁體中文](/zh-tw/)
|
||||
|
||||
- [:cn: 簡體](/)
|
||||
|
||||
- [:cn: 繁體](/zh-tw/)
|
26
zh-tw/ch1.md
26
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)中的例子)
|
||||
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
||||
@ -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)**)。
|
||||
|
||||
|
||||
|
||||
|
@ -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的存在。
|
||||
|
||||
* 在事件溯源中,應用邏輯顯式構建在寫入事件日誌的不可變事件之上。在這種情況下,事件儲存是僅追加寫入的,更新與刪除是不鼓勵的或禁止的。事件被設計為旨在反映應用層面發生的事情,而不是底層的狀態變更。
|
||||
|
||||
@ -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】。這對於許多其他儲存和索引系統(如搜尋伺服器)來說是很有意義的,當系統要從分散式日誌中獲取輸入時亦然(參閱“[保持系統同步](#保持系統同步)”)。
|
||||
|
||||
|
||||
|
||||
@ -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】。
|
||||
|
||||
|
||||
|
||||
|
@ -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))。
|
||||
|
||||
|
||||
|
||||
|
44
zh-tw/ch2.md
44
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)
|
||||
|
||||
|
||||
|
||||
|
68
zh-tw/ch3.md
68
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,11 +310,11 @@ $ cat database
|
||||
|
||||
|
||||
|
||||
1. 合併段是簡單而高效的,即使檔案大於可用記憶體。這種方法就像歸併排序演算法中使用的方法一樣,如[圖3-4](img/fig3-4.png)所示:您開始並排讀取輸入檔案,檢視每個檔案中的第一個鍵,複製最低鍵(根據排序順序)到輸出檔案,並重復。這產生一個新的合併段檔案,也按鍵排序。
|
||||
1. 合併段是簡單而高效的,即使檔案大於可用記憶體。這種方法就像歸併排序演算法中使用的方法一樣,如[圖3-4](../img/fig3-4.png)所示:您開始並排讀取輸入檔案,檢視每個檔案中的第一個鍵,複製最低鍵(根據排序順序)到輸出檔案,並重復。這產生一個新的合併段檔案,也按鍵排序。
|
||||
|
||||
|
||||
|
||||
![](img/fig3-4.png)
|
||||
![](../img/fig3-4.png)
|
||||
|
||||
|
||||
|
||||
@ -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.png)所示。
|
||||
相比之下,資料倉庫是一個獨立的資料庫,分析人員可以查詢他們想要的內容而不影響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`)。然後,您可以沿著每行或每列應用相同的彙總,並獲得一個維度減少的彙總(按產品的銷售額,無論日期,還是按日期銷售,無論產品如何)。
|
||||
|
||||
|
||||
|
||||
|
32
zh-tw/ch4.md
32
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)
|
||||
|
||||
|
||||
|
||||
@ -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))。
|
||||
|
||||
|
||||
|
||||
|
86
zh-tw/ch5.md
86
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](img/fig5-2.png)的示例中,從庫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)
|
||||
|
||||
|
||||
|
||||
@ -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)**)。版本向量允許資料庫區分覆蓋寫入和併發寫入。
|
||||
|
||||
|
||||
|
||||
|
42
zh-tw/ch6.md
42
zh-tw/ch6.md
@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
|
||||
![](img/ch6.png)
|
||||
![](../img/ch6.png)
|
||||
|
||||
|
||||
|
||||
@ -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](img/fig6-2.png))。如果知道範圍之間的邊界,則可以輕鬆確定哪個分割槽包含某個值。如果您還知道分割槽所在的節點,那麼可以直接向相應的節點發出請求(對於百科全書而言,就像從書架上選取正確的書籍)。
|
||||
一種分割槽的方法是為每個分割槽指定一塊連續的鍵範圍(從最小值到最大值),如紙質百科全書的卷([圖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)<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)所示的路由層。 HBase,SolrCloud和Kafka也使用ZooKeeper來跟蹤分割槽分配。 MongoDB具有類似的體系結構,但它依賴於自己的**配置伺服器(config server)** 實現和mongos守護程序作為路由層。
|
||||
例如,LinkedIn的Espresso使用Helix 【31】進行叢集管理(依靠ZooKeeper),實現瞭如[圖6-8](../img/fig6-8.png)所示的路由層。 HBase,SolrCloud和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這樣的外部協調服務的依賴。
|
||||
|
||||
|
||||
|
||||
|
74
zh-tw/ch7.md
74
zh-tw/ch7.md
@ -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)所示,以兩個併發計數器增量為例。
|
||||
|
||||
|
||||
|
||||
@ -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下是不可接受的。)
|
||||
|
||||
|
||||
|
||||
@ -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](img/fig7-11.png)中,事務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 必須中止。
|
||||
|
||||
|
||||
|
||||
|
30
zh-tw/ch8.md
30
zh-tw/ch8.md
@ -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)
|
||||
|
||||
|
||||
|
||||
@ -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的請求。
|
||||
|
||||
|
||||
|
||||
|
96
zh-tw/ch9.md
96
zh-tw/ch9.md
@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
|
||||
![](img/ch9.png)
|
||||
![](../img/ch9.png)
|
||||
|
||||
|
||||
|
||||
@ -120,7 +120,7 @@
|
||||
|
||||
|
||||
|
||||
![](img/fig9-1.png)
|
||||
![](../img/fig9-1.png)
|
||||
|
||||
|
||||
|
||||
@ -128,7 +128,7 @@
|
||||
|
||||
|
||||
|
||||
[圖9-1 ](img/fig9-1.png)展示了一個關於體育網站的非線性一致例子【9】。Alice和Bob正坐在同一個房間裡,都盯著各自的手機,關注著2014年FIFA世界盃決賽的結果。在最後得分公佈後,Alice重新整理頁面,看到宣佈了獲勝者,並興奮地告訴Bob。Bob難以置信地重新整理了自己的手機,但他的請求路由到了一個落後的資料庫副本上,手機顯示比賽仍在進行。
|
||||
[圖9-1 ](../img/fig9-1.png)展示了一個關於體育網站的非線性一致例子【9】。Alice和Bob正坐在同一個房間裡,都盯著各自的手機,關注著2014年FIFA世界盃決賽的結果。在最後得分公佈後,Alice重新整理頁面,看到宣佈了獲勝者,並興奮地告訴Bob。Bob難以置信地重新整理了自己的手機,但他的請求路由到了一個落後的資料庫副本上,手機顯示比賽仍在進行。
|
||||
|
||||
|
||||
|
||||
@ -144,11 +144,11 @@
|
||||
|
||||
|
||||
|
||||
[圖9-2](img/fig9-2.png) 顯示了三個客戶端線上性一致資料庫中同時讀寫相同的鍵`x`。在分散式系統文獻中,`x`被稱為**暫存器(register)**,例如,它可以是鍵值儲存中的一個**鍵**,關係資料庫中的一**行**,或文件資料庫中的一個**文件**。
|
||||
[圖9-2](../img/fig9-2.png) 顯示了三個客戶端線上性一致資料庫中同時讀寫相同的鍵`x`。在分散式系統文獻中,`x`被稱為**暫存器(register)**,例如,它可以是鍵值儲存中的一個**鍵**,關係資料庫中的一**行**,或文件資料庫中的一個**文件**。
|
||||
|
||||
|
||||
|
||||
![](img/fig9-2.png)
|
||||
![](../img/fig9-2.png)
|
||||
|
||||
|
||||
|
||||
@ -156,7 +156,7 @@
|
||||
|
||||
|
||||
|
||||
為了簡單起見,[圖9-2](img/fig9-2.png)採用了使用者請求的視角,而不是資料庫內部的視角。每個柱都是由客戶端發出的請求,其中柱頭是請求傳送的時刻,柱尾是客戶端收到響應的時刻。因為網路延遲變化無常,客戶端不知道資料庫處理其請求的精確時間——只知道它發生在傳送請求和接收響應的之間的某個時刻。[^i]
|
||||
為了簡單起見,[圖9-2](../img/fig9-2.png)採用了使用者請求的視角,而不是資料庫內部的視角。每個柱都是由客戶端發出的請求,其中柱頭是請求傳送的時刻,柱尾是客戶端收到響應的時刻。因為網路延遲變化無常,客戶端不知道資料庫處理其請求的精確時間——只知道它發生在傳送請求和接收響應的之間的某個時刻。[^i]
|
||||
|
||||
|
||||
|
||||
@ -174,7 +174,7 @@
|
||||
|
||||
|
||||
|
||||
在[圖9-2](img/fig9-2.png) 中,`x` 的值最初為 `0`,客戶端C 執行寫請求將其設定為 `1`。發生這種情況時,客戶端A和B反覆輪詢資料庫以讀取最新值。 A和B的請求可能會收到怎樣的響應?
|
||||
在[圖9-2](../img/fig9-2.png) 中,`x` 的值最初為 `0`,客戶端C 執行寫請求將其設定為 `1`。發生這種情況時,客戶端A和B反覆輪詢資料庫以讀取最新值。 A和B的請求可能會收到怎樣的響應?
|
||||
|
||||
|
||||
|
||||
@ -194,11 +194,11 @@
|
||||
|
||||
|
||||
|
||||
為了使系統線性一致,我們需要新增另一個約束,如[圖9-3](img/fig9-3.png)所示
|
||||
為了使系統線性一致,我們需要新增另一個約束,如[圖9-3](../img/fig9-3.png)所示
|
||||
|
||||
|
||||
|
||||
![](img/fig9-3.png)
|
||||
![](../img/fig9-3.png)
|
||||
|
||||
**圖9-3 任何一個讀取返回新值後,所有後續讀取(在相同或其他客戶端上)也必須返回新值。**
|
||||
|
||||
@ -208,15 +208,15 @@
|
||||
|
||||
|
||||
|
||||
[圖9-3](img/fig9-3.png)中的箭頭說明了這個時序依賴關係。客戶端A 是第一個讀取新的值 `1` 的位置。在A 的讀取返回之後,B開始新的讀取。由於B的讀取嚴格在發生於A的讀取之後,因此即使C的寫入仍在進行中,也必須返回 `1`。 (與[圖9-1](img/fig9-1.png)中的Alice和Bob的情況相同:在Alice讀取新值之後,Bob也希望讀取新的值。)
|
||||
[圖9-3](../img/fig9-3.png)中的箭頭說明了這個時序依賴關係。客戶端A 是第一個讀取新的值 `1` 的位置。在A 的讀取返回之後,B開始新的讀取。由於B的讀取嚴格在發生於A的讀取之後,因此即使C的寫入仍在進行中,也必須返回 `1`。 (與[圖9-1](../img/fig9-1.png)中的Alice和Bob的情況相同:在Alice讀取新值之後,Bob也希望讀取新的值。)
|
||||
|
||||
|
||||
|
||||
我們可以進一步細化這個時序圖,展示每個操作是如何在特定時刻原子性生效的。[圖9-4](img/fig9-4.png)顯示了一個更復雜的例子【10】。
|
||||
我們可以進一步細化這個時序圖,展示每個操作是如何在特定時刻原子性生效的。[圖9-4](../img/fig9-4.png)顯示了一個更復雜的例子【10】。
|
||||
|
||||
|
||||
|
||||
在[圖9-4](img/fig9-4.png)中,除了讀寫之外,還增加了第三種類型的操作:
|
||||
在[圖9-4](../img/fig9-4.png)中,除了讀寫之外,還增加了第三種類型的操作:
|
||||
|
||||
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
|
||||
|
||||
|
||||
[圖9-4](img/fig9-4.png)中的每個操作都在我們認為執行操作的時候用豎線標出(在每個操作的條柱之內)。這些標記按順序連在一起,其結果必須是一個有效的暫存器讀寫序列(**每次讀取都必須返回最近一次寫入設定的值**)。
|
||||
[圖9-4](../img/fig9-4.png)中的每個操作都在我們認為執行操作的時候用豎線標出(在每個操作的條柱之內)。這些標記按順序連在一起,其結果必須是一個有效的暫存器讀寫序列(**每次讀取都必須返回最近一次寫入設定的值**)。
|
||||
|
||||
|
||||
|
||||
@ -232,7 +232,7 @@
|
||||
|
||||
|
||||
|
||||
![](img/fig9-4.png)
|
||||
![](../img/fig9-4.png)
|
||||
|
||||
|
||||
|
||||
@ -240,7 +240,7 @@
|
||||
|
||||
|
||||
|
||||
[圖9-4](img/fig9-4.png)中有一些有趣的細節需要指出:
|
||||
[圖9-4](../img/fig9-4.png)中有一些有趣的細節需要指出:
|
||||
|
||||
|
||||
|
||||
@ -256,7 +256,7 @@
|
||||
|
||||
|
||||
|
||||
* 客戶B的最後一次讀取(陰影條柱中)不是線性一致性的。 該操作與C的**cas**寫操作併發(它將 `x` 從 `2` 更新為 `4` )。在沒有其他請求的情況下,B的讀取返回 `2` 是可以的。然而,在B的讀取開始之前,客戶端A已經讀取了新的值 `4` ,因此不允許B讀取比A更舊的值。再次,與[圖9-1](img/fig9-1.png)中的Alice和Bob的情況相同。
|
||||
* 客戶B的最後一次讀取(陰影條柱中)不是線性一致性的。 該操作與C的**cas**寫操作併發(它將 `x` 從 `2` 更新為 `4` )。在沒有其他請求的情況下,B的讀取返回 `2` 是可以的。然而,在B的讀取開始之前,客戶端A已經讀取了新的值 `4` ,因此不允許B讀取比A更舊的值。再次,與[圖9-1](../img/fig9-1.png)中的Alice和Bob的情況相同。
|
||||
|
||||
|
||||
|
||||
@ -358,27 +358,27 @@
|
||||
|
||||
|
||||
|
||||
注意[圖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伺服器不會將整個照片放在佇列中,因為大多數訊息代理都是針對較短的訊息而設計的,而一張照片的空間佔用可能達到幾兆位元組。取而代之的是,首先將照片寫入檔案儲存服務,寫入完成後再將給縮放器的指令放入訊息佇列。
|
||||
|
||||
![](img/fig9-5.png)
|
||||
![](../img/fig9-5.png)
|
||||
|
||||
**圖9-5 Web伺服器和影象縮放器透過檔案儲存和訊息佇列進行通訊,開啟競爭條件的可能性。**
|
||||
|
||||
|
||||
|
||||
如果檔案儲存服務是線性一致的,那麼這個系統應該可以正常工作。如果它不是線性一致的,則存在競爭條件的風險:訊息佇列([圖9-5](img/fig9-5.png)中的步驟3和4)可能比儲存服務內部的複製(replication)更快。在這種情況下,當縮放器讀取影象(步驟5)時,可能會看到影象的舊版本,或者什麼都沒有。如果它處理的是舊版本的影象,則檔案儲存中的全尺寸圖和略縮圖就產生了永久性的不一致。
|
||||
如果檔案儲存服務是線性一致的,那麼這個系統應該可以正常工作。如果它不是線性一致的,則存在競爭條件的風險:訊息佇列([圖9-5](../img/fig9-5.png)中的步驟3和4)可能比儲存服務內部的複製(replication)更快。在這種情況下,當縮放器讀取影象(步驟5)時,可能會看到影象的舊版本,或者什麼都沒有。如果它處理的是舊版本的影象,則檔案儲存中的全尺寸圖和略縮圖就產生了永久性的不一致。
|
||||
|
||||
|
||||
|
||||
出現這個問題是因為Web伺服器和縮放器之間存在兩個不同的通道:檔案儲存與訊息佇列。沒有線性一致性的新鮮性保證,這兩個通道之間的競爭條件是可能的。這種情況類似於[圖9-1](img/fig9-1.png),資料庫複製與Alice的嘴到Bob耳朵之間的真人音訊通道之間也存在競爭條件。
|
||||
出現這個問題是因為Web伺服器和縮放器之間存在兩個不同的通道:檔案儲存與訊息佇列。沒有線性一致性的新鮮性保證,這兩個通道之間的競爭條件是可能的。這種情況類似於[圖9-1](../img/fig9-1.png),資料庫複製與Alice的嘴到Bob耳朵之間的真人音訊通道之間也存在競爭條件。
|
||||
|
||||
|
||||
|
||||
@ -450,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)
|
||||
|
||||
|
||||
|
||||
@ -462,11 +462,11 @@
|
||||
|
||||
|
||||
|
||||
在[圖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))
|
||||
|
||||
|
||||
|
||||
@ -494,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)
|
||||
|
||||
|
||||
|
||||
@ -612,7 +612,7 @@
|
||||
|
||||
|
||||
|
||||
之前說過,線性一致暫存器的行為就好像只有單個數據副本一樣,且每個操作似乎都是在某個時間點以原子性的方式生效的。這個定義意味著操作是按照某種良好定義的順序執行的。我們將操作以看上去被執行的順序連線起來,以此說明了[圖9-4](img/fig9-4.png)中的順序。
|
||||
之前說過,線性一致暫存器的行為就好像只有單個數據副本一樣,且每個操作似乎都是在某個時間點以原子性的方式生效的。這個定義意味著操作是按照某種良好定義的順序執行的。我們將操作以看上去被執行的順序連線起來,以此說明了[圖9-4](../img/fig9-4.png)中的順序。
|
||||
|
||||
|
||||
|
||||
@ -640,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.png)所示)。
|
||||
* 在事務快照隔離的上下文中(“[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)”),我們說事務是從一致性快照中讀取的。但此語境中“一致”到底又是什麼意思?這意味著**與因果關係保持一致(consistent with causality)**:如果快照包含答案,它也必須包含被回答的問題【48】。在某個時間點觀察整個資料庫,與因果關係保持一致意味著:因果上在該時間點之前發生的所有操作,其影響都是可見的,但因果上在該時間點之後發生的操作,其影響對觀察者不可見。**讀偏差(read skew)**意味著讀取的資料處於違反因果關係的狀態(不可重複讀,如[圖7-6](../img/fig7-6.png)所示)。
|
||||
|
||||
* 事務之間**寫偏差(write skew)**的例子(參見“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”)也說明了因果依賴:在[圖7-8](img/fig7-8.png)中,愛麗絲被允許離班,因為事務認為鮑勃仍在值班,反之亦然。在這種情況下,離班的動作因果依賴於對當前值班情況的觀察。[可序列化快照隔離(SSI)](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)),在聽到愛麗絲驚呼比賽結果後,鮑勃從伺服器得到陳舊結果的事實違背了因果關係:愛麗絲的驚呼因果依賴於得分宣告,所以鮑勃應該也能在聽到愛麗斯驚呼後查詢到比分。相同的模式在“[跨通道的時序依賴](#跨通道的時序依賴)”一節中,以“影象大小調整服務”的偽裝再次出現。
|
||||
|
||||
|
||||
|
||||
@ -690,7 +690,7 @@
|
||||
|
||||
|
||||
|
||||
線上性一致的系統中,操作是全序的:如果系統表現的就好像只有一個數據副本,並且所有操作都是原子性的,這意味著對任何兩個操作,我們總是能判定哪個操作先發生。這個全序在[圖9-4](img/fig9-4.png)中以時間線表示。
|
||||
線上性一致的系統中,操作是全序的:如果系統表現的就好像只有一個數據副本,並且所有操作都是原子性的,這意味著對任何兩個操作,我們總是能判定哪個操作先發生。這個全序在[圖9-4](../img/fig9-4.png)中以時間線表示。
|
||||
|
||||
|
||||
|
||||
@ -706,7 +706,7 @@
|
||||
|
||||
|
||||
|
||||
併發意味著時間線會分岔然後合併 —— 在這種情況下,不同分支上的操作是無法比較的(即併發操作)。在[第五章](ch5.md)中我們看到了這種現象:例如,[圖5-14](img/fig5-14.png) 並不是一條直線的全序關係,而是一堆不同的操作併發進行。圖中的箭頭指明瞭因果依賴 —— 操作的偏序。
|
||||
併發意味著時間線會分岔然後合併 —— 在這種情況下,不同分支上的操作是無法比較的(即併發操作)。在[第五章](ch5.md)中我們看到了這種現象:例如,[圖5-14](../img/fig5-14.png) 並不是一條直線的全序關係,而是一堆不同的操作併發進行。圖中的箭頭指明瞭因果依賴 —— 操作的偏序。
|
||||
|
||||
|
||||
|
||||
@ -718,7 +718,7 @@
|
||||
|
||||
|
||||
|
||||
那麼因果順序和線性一致性之間的關係是什麼?答案是線性一致性**隱含著(implies)**因果關係:任何線性一致的系統都能正確保持因果性【7】。特別是,如果系統中有多個通訊通道(如[圖9-5](img/fig9-5.png) 中的訊息佇列和檔案儲存服務),線性一致性可以自動保證因果性,系統無需任何特殊操作(如在不同元件間傳遞時間戳)。
|
||||
那麼因果順序和線性一致性之間的關係是什麼?答案是線性一致性**隱含著(implies)**因果關係:任何線性一致的系統都能正確保持因果性【7】。特別是,如果系統中有多個通訊通道(如[圖9-5](../img/fig9-5.png) 中的訊息佇列和檔案儲存服務),線性一致性可以自動保證因果性,系統無需任何特殊操作(如在不同元件間傳遞時間戳)。
|
||||
|
||||
|
||||
|
||||
@ -758,7 +758,7 @@
|
||||
|
||||
|
||||
|
||||
為了確定因果順序,資料庫需要知道應用讀取了哪個版本的資料。這就是為什麼在 [圖5-13 ](img/fig5-13.png)中,來自先前操作的版本號在寫入時被傳回到資料庫的原因。在SSI 的衝突檢測中會出現類似的想法,如“[可序列化快照隔離(SSI)](ch7.md#可序列化快照隔離(SSI))”中所述:當事務要提交時,資料庫將檢查它所讀取的資料版本是否仍然是最新的。為此,資料庫跟蹤哪些資料被哪些事務所讀取。
|
||||
為了確定因果順序,資料庫需要知道應用讀取了哪個版本的資料。這就是為什麼在 [圖5-13 ](../img/fig5-13.png)中,來自先前操作的版本號在寫入時被傳回到資料庫的原因。在SSI 的衝突檢測中會出現類似的想法,如“[可序列化快照隔離(SSI)](ch7.md#可序列化快照隔離(SSI))”中所述:當事務要提交時,資料庫將檢查它所讀取的資料版本是否仍然是最新的。為此,資料庫跟蹤哪些資料被哪些事務所讀取。
|
||||
|
||||
|
||||
|
||||
@ -822,7 +822,7 @@
|
||||
|
||||
|
||||
|
||||
* 來自物理時鐘的時間戳會受到時鐘偏移的影響,這可能會使其與因果不一致。例如[圖8-3](img/fig8-3.png) 展示了一個例子,其中因果上晚發生的操作,卻被分配了一個更早的時間戳。[^vii]
|
||||
* 來自物理時鐘的時間戳會受到時鐘偏移的影響,這可能會使其與因果不一致。例如[圖8-3](../img/fig8-3.png) 展示了一個例子,其中因果上晚發生的操作,卻被分配了一個更早的時間戳。[^vii]
|
||||
|
||||
|
||||
|
||||
@ -846,11 +846,11 @@
|
||||
|
||||
|
||||
|
||||
[圖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)
|
||||
|
||||
|
||||
|
||||
@ -868,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` 。
|
||||
|
||||
|
||||
|
||||
@ -996,7 +996,7 @@
|
||||
|
||||
|
||||
|
||||
如 [圖9-4](img/fig9-4.png) 所示,線上性一致的系統中,存在操作的全序。這是否意味著線性一致與全序廣播一樣?不盡然,但兩者之間有著密切的聯絡[^x]。
|
||||
如 [圖9-4](../img/fig9-4.png) 所示,線上性一致的系統中,存在操作的全序。這是否意味著線性一致與全序廣播一樣?不盡然,但兩者之間有著密切的聯絡[^x]。
|
||||
|
||||
|
||||
|
||||
@ -1190,7 +1190,7 @@
|
||||
|
||||
|
||||
|
||||
如果某些節點提交了事務,但其他節點卻放棄了這些事務,那麼這些節點就會彼此不一致(如 [圖7-3](img/fig7-3.png) 所示)。而且一旦在某個節點上提交了一個事務,如果事後發現它在其它節點上被中止了,它是無法撤回的。出於這個原因,一旦確定事務中的所有其他節點也將提交,節點就必須進行提交。
|
||||
如果某些節點提交了事務,但其他節點卻放棄了這些事務,那麼這些節點就會彼此不一致(如 [圖7-3](../img/fig7-3.png) 所示)。而且一旦在某個節點上提交了一個事務,如果事後發現它在其它節點上被中止了,它是無法撤回的。出於這個原因,一旦確定事務中的所有其他節點也將提交,節點就必須進行提交。
|
||||
|
||||
|
||||
|
||||
@ -1210,11 +1210,11 @@
|
||||
|
||||
|
||||
|
||||
[圖9-9](img/fig9-9.png)說明了2PC的基本流程。2PC中的提交/中止過程分為兩個階段(因此而得名),而不是單節點事務中的單個提交請求。
|
||||
[圖9-9](../img/fig9-9.png)說明了2PC的基本流程。2PC中的提交/中止過程分為兩個階段(因此而得名),而不是單節點事務中的單個提交請求。
|
||||
|
||||
|
||||
|
||||
![](img/fig9-9.png)
|
||||
![](../img/fig9-9.png)
|
||||
|
||||
|
||||
|
||||
@ -1294,11 +1294,11 @@
|
||||
|
||||
|
||||
|
||||
情況如[圖9-10](img/fig9-10.png) 所示。在這個特定的例子中,協調者實際上決定提交,資料庫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不知道是否提交或中止**
|
||||
|
||||
@ -1424,7 +1424,7 @@
|
||||
|
||||
|
||||
|
||||
在事務提交或中止之前,資料庫不能釋放這些鎖(如[圖9-9](img/fig9-9.png)中的陰影區域所示)。因此,在使用兩階段提交時,事務必須在整個存疑期間持有這些鎖。如果協調者已經崩潰,需要20分鐘才能重啟,那麼這些鎖將會被持有20分鐘。如果協調者的日誌由於某種原因徹底丟失,這些鎖將被永久持有 —— 或至少在管理員手動解決該情況之前。
|
||||
在事務提交或中止之前,資料庫不能釋放這些鎖(如[圖9-9](../img/fig9-9.png)中的陰影區域所示)。因此,在使用兩階段提交時,事務必須在整個存疑期間持有這些鎖。如果協調者已經崩潰,需要20分鐘才能重啟,那麼這些鎖將會被持有20分鐘。如果協調者的日誌由於某種原因徹底丟失,這些鎖將被永久持有 —— 或至少在管理員手動解決該情況之前。
|
||||
|
||||
|
||||
|
||||
|
@ -120,11 +120,11 @@
|
||||
|
||||
|
||||
|
||||
複製和分割槽是不同的機制,但它們經常同時使用。如[圖II-1](img/figii-1.png)所示。
|
||||
複製和分割槽是不同的機制,但它們經常同時使用。如[圖II-1](../img/figii-1.png)所示。
|
||||
|
||||
|
||||
|
||||
![](img/figii-1.png)
|
||||
![](../img/figii-1.png)
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user