update zh-tw content

This commit is contained in:
Yin Gang 2021-08-19 11:41:47 +08:00
parent 782919eb26
commit 552b851ab2
22 changed files with 441 additions and 11584 deletions

View File

@ -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)
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)
[CC-BY 4.0](LICENSE)

View File

@ -1,45 +1,22 @@
# Summary
* [簡介](README.md)
* [序言](preface.md)
* [第一部分:資料系統的基石](part-i.md)
* [第一章:可靠性、可伸縮性、可維護性](ch1.md)
* [第二章:資料模型與查詢語言](ch2.md)
* [第三章:儲存與檢索](ch3.md)
* [第四章:編碼與演化](ch4.md)
* [第二部分:分散式資料](part-ii.md)
* [第五章:複製](ch5.md)
* [第六章:分割槽](ch6.md)
* [第七章:事務](ch7.md)
* [第八章:分散式系統的麻煩](ch8.md)
* [第九章:一致性與共識](ch9.md)
* [第三部分:衍生資料](part-iii.md)
* [第十章:批處理](ch10.md)
* [第十一章:流處理](ch11.md)
* [第十二章:資料系統的未來](ch12.md)
* [術語表](glossary.md)
* [後記](colophon.md)

View File

@ -1,5 +1,3 @@
- Language
- [:cn: 簡體](/)
- [:cn: 繁體](/zh-tw/)

View File

@ -1,35 +1,18 @@
- [序言](preface.md)
- [第一部分:資料系統的基石](part-i.md)
- [第一章:可靠性、可伸縮性、可維護性](ch1.md)
- [第二章:資料模型與查詢語言](ch2.md)
- [第三章:儲存與檢索](ch3.md)
- [第四章:編碼與演化](ch4.md)
- [第二部分:分散式資料](part-ii.md)
- [第五章:複製](ch5.md)
- [第六章:分割槽](ch6.md)
- [第七章:事務](ch7.md)
- [第八章:分散式系統的麻煩](ch8.md)
- [第九章:一致性與共識](ch9.md)
- [第三部分:衍生資料](part-iii.md)
- [第十章:批處理](ch10.md)
- [第十一章:流處理](ch11.md)
- [第十二章:資料系統的未來](ch12.md)
- [術語表](glossary.md)
- [後記](colophon.md)

View File

@ -1,923 +1,462 @@
# 第一章:可靠性,可伸縮性,可維護性
![](img/ch1.png)
![](../img/ch1.png)
> 網際網路做得太棒了,以至於大多數人將它看作像太平洋這樣的自然資源,而不是什麼人工產物。上一次出現這種大規模且無差錯的技術, 你還記得是什麼時候嗎?
>
> ——阿蘭·凱在接受Dobb博士雜誌採訪時說2012年
-----------------------
[TOC]
現今很多應用程式都是 **資料密集型data-intensive** 的,而非 **計算密集型compute-intensive** 的。因此CPU很少成為這類應用的瓶頸更大的問題通常來自資料量、資料複雜性、以及資料的變更速度。
資料密集型應用通常由標準組件構建而成,標準組件提供了很多通用的功能;例如,許多應用程式都需要:
- 儲存資料,以便自己或其他應用程式之後能再次找到 ***資料庫database***
- 記住開銷昂貴操作的結果,加快讀取速度(***快取cache***
- 允許使用者按關鍵字搜尋資料,或以各種方式對資料進行過濾(***搜尋索引search indexes***
- 向其他程序傳送訊息,進行非同步處理(***流處理stream processing***
- 定期處理累積的大批次資料(***批處理batch processing***
如果這些功能聽上去平淡無奇,那是因為這些 **資料系統data system** 是非常成功的抽象:我們一直不假思索地使用它們並習以為常。絕大多數工程師不會幻想從零開始編寫儲存引擎,因為在開發應用時,資料庫已經是足夠完美的工具了。
但現實沒有這麼簡單。不同的應用有著不同的需求,因而資料庫系統也是百花齊放,有著各式各樣的特性。實現快取有很多種手段,建立搜尋索引也有好幾種方法,諸如此類。因此在開發應用前,我們依然有必要先弄清楚最適合手頭工作的工具和方法。而且當單個工具解決不了你的問題時,組合使用這些工具可能還是有些難度的。
本書將是一趟關於資料系統原理、實踐與應用的旅程,並講述了設計資料密集型應用的方法。我們將探索不同工具之間的共性與特性,以及各自的實現原理。
本章將從我們所要實現的基礎目標開始:可靠、可伸縮、可維護的資料系統。我們將澄清這些詞語的含義,概述考量這些目標的方法。並回顧一些後續章節所需的基礎知識。在接下來的章節中我們將抽絲剝繭,研究設計資料密集型應用時可能遇到的設計決策。
## 關於資料系統的思考
我們通常認為,資料庫、訊息佇列、快取等工具分屬於幾個差異顯著的類別。雖然資料庫和訊息隊列表面上有一些相似性——它們都會儲存一段時間的資料——但它們有迥然不同的訪問模式,這意味著迥異的效能特徵和實現手段。
那我們為什麼要把這些東西放在 **資料系統data system** 的總稱之下混為一談呢?
近些年來出現了許多新的資料儲存工具與資料處理工具。它們針對不同應用場景進行最佳化因此不再適合生硬地歸入傳統類別【1】。類別之間的界限變得越來越模糊例如資料儲存可以被當成訊息佇列用Redis訊息佇列則帶有類似資料庫的持久保證Apache Kafka
其次,越來越多的應用程式有著各種嚴格而廣泛的要求,單個工具不足以滿足所有的資料處理和儲存需求。取而代之的是,總體工作被拆分成一系列能被單個工具高效完成的任務,並透過應用程式碼將它們縫合起來。
例如如果將快取應用管理的快取層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)
**圖1-1 一個可能的組合使用多個元件的資料系統架構**
當你將多個工具組合在一起提供服務時,服務的介面或**應用程式程式設計介面API, Application Programming Interface** 通常向客戶端隱藏這些實現細節。現在,你基本上已經使用較小的通用元件建立了一個全新的、專用的資料系統。這個新的複合資料系統可能會提供特定的保證,例如:快取在寫入時會作廢或更新,以便外部客戶端獲取一致的結果。現在你不僅是應用程式開發人員,還是資料系統設計人員了。
設計資料系統或服務時可能會遇到很多棘手的問題例如當系統出問題時如何確保資料的正確性和完整性當部分系統退化降級時如何為客戶提供始終如一的良好效能當負載增加時如何擴容應對什麼樣的API才是好的API
影響資料系統設計的因素很多,包括參與人員的技能和經驗、歷史遺留問題、系統路徑依賴、交付時限、公司的風險容忍度、監管約束等,這些因素都需要具體問題具體分析。
本書著重討論三個在大多數軟體系統中都很重要的問題:
***可靠性Reliability***
系統在**困境adversity**(硬體故障、軟體故障、人為錯誤)中仍可正常工作(正確完成功能,並能達到期望的效能水準)。
***可伸縮性Scalability***
有合理的辦法應對系統的增長(資料量、流量、複雜性)(參閱“[可伸縮性](#可伸縮性)”)
***可維護性Maintainability***
許多不同的人(工程師、運維)在不同的生命週期,都能高效地在系統上工作(使系統保持現有行為,並適應新的應用場景)。(參閱”[可維護性](#可維護性)“)
人們經常追求這些詞彙,卻沒有清楚理解它們到底意味著什麼。為了工程的嚴謹性,本章的剩餘部分將探討可靠性、可伸縮性、可維護性的含義。為實現這些目標而使用的各種技術,架構和演算法將在後續的章節中研究。
## 可靠性
人們對於一個東西是否可靠,都有一個直觀的想法。人們對可靠軟體的典型期望包括:
* 應用程式表現出使用者所期望的功能。
* 允許使用者犯錯,允許使用者以出乎意料的方式使用軟體。
* 在預期的負載和資料量下,效能滿足要求。
* 系統能防止未經授權的訪問和濫用。
如果所有這些在一起意味著“正確工作”,那麼可以把可靠性粗略理解為“即使出現問題,也能繼續正確工作”。
造成錯誤的原因叫做**故障fault**,能預料並應對故障的系統特性可稱為**容錯fault-tolerant**或**韌性resilient**。“**容錯**”一詞可能會產生誤導,因為它暗示著系統可以容忍所有可能的錯誤,但在實際中這是不可能的。比方說,如果整個地球(及其上的所有伺服器)都被黑洞吞噬了,想要容忍這種錯誤,需要把網路託管到太空中——這種預算能不能批准就祝你好運了。所以在討論容錯時,只有談論特定型別的錯誤才有意義。
注意**故障fault**不同於**失效failure**【2】。**故障**通常定義為系統的一部分狀態偏離其標準,而**失效**則是系統作為一個整體停止向用戶提供服務。故障的概率不可能降到零,因此最好設計容錯機制以防因**故障**而導致**失效**。本書中我們將介紹幾種用不可靠的部件構建可靠系統的技術。
反直覺的是,在這類容錯系統中,透過故意觸發來**提高**故障率是有意義的例如在沒有警告的情況下隨機地殺死單個程序。許多高危漏洞實際上是由糟糕的錯誤處理導致的【3】因此我們可以透過故意引發故障來確保容錯機制不斷執行並接受考驗從而提高故障自然發生時系統能正確處理的信心。Netflix公司的*Chaos Monkey*【4】就是這種方法的一個例子。
儘管比起**阻止錯誤prevent error**,我們通常更傾向於**容忍錯誤**。但也有**預防勝於治療**的情況(比如不存在治療方法時)。安全問題就屬於這種情況。例如,如果攻擊者破壞了系統,並獲取了敏感資料,這種事是撤銷不了的。但本書主要討論的是可以恢復的故障種類,正如下面幾節所述。
### 硬體故障
當想到系統失效的原因時,**硬體故障hardware faults**總會第一個進入腦海。硬碟崩潰、記憶體出錯、機房斷電、有人拔錯網線……任何與大型資料中心打過交道的人都會告訴你:一旦你擁有很多機器,這些事情**總**會發生!
據報道稱,硬碟的 **平均無故障時間MTTF mean time to failure** 約為10到50年【5】【6】。因此從數學期望上講在擁有10000個磁碟的儲存叢集上平均每天會有1個磁碟出故障。
為了減少系統的故障率第一反應通常都是增加單個硬體的冗餘度例如磁碟可以組建RAID伺服器可能有雙路電源和熱插拔CPU資料中心可能有電池和柴油發電機作為後備電源某個元件掛掉時冗餘元件可以立刻接管。這種方法雖然不能完全防止由硬體問題導致的系統失效但它簡單易懂通常也足以讓機器不間斷執行很多年。
直到最近,硬體冗餘對於大多數應用來說已經足夠了,它使單臺機器完全失效變得相當罕見。只要你能快速地把備份恢復到新機器上,故障停機時間對大多數應用而言都算不上災難性的。只有少量高可用性至關重要的應用才會要求有多套硬體冗餘。
但是隨著資料量和應用計算需求的增加,越來越多的應用開始大量使用機器,這會相應地增加硬體故障率。此外在一些雲平臺(**如亞馬遜網路服務AWS, Amazon Web Services**虛擬機器例項不可用卻沒有任何警告也是很常見的【7】因為雲平臺的設計就是優先考慮**靈活性flexibility**和**彈性elasticity**[^i],而不是單機可靠性。
如果在硬體冗餘的基礎上進一步引入軟體容錯機制,那麼系統在容忍整個(單臺)機器故障的道路上就更進一步了。這樣的系統也有運維上的便利,例如:如果需要重啟機器(例如應用作業系統安全補丁),單伺服器系統就需要計劃停機。而允許機器失效的系統則可以一次修復一個節點,無需整個系統停機。
[^i]: 在[應對負載的方法](#應對負載的方法)一節定義
### 軟體錯誤
我們通常認為硬體故障是隨機的、相互獨立的:一臺機器的磁碟失效並不意味著另一臺機器的磁碟也會失效。大量硬體元件不可能同時發生故障,除非它們存在比較弱的相關性(同樣的原因導致關聯性錯誤,例如伺服器機架的溫度)。
另一類錯誤是內部的**系統性錯誤systematic error**【7】。這類錯誤難以預料而且因為是跨節點相關的所以比起不相關的硬體故障往往可能造成更多的**系統失效**【5】。例子包括
* 接受特定的錯誤輸入便導致所有應用伺服器例項崩潰的BUG。例如2012年6月30日的閏秒由於Linux核心中的一個錯誤許多應用同時掛掉了。
* 失控程序會用盡一些共享資源包括CPU時間、記憶體、磁碟空間或網路頻寬。
* 系統依賴的服務變慢,沒有響應,或者開始返回錯誤的響應。
* 級聯故障一個元件中的小故障觸發另一個元件中的故障進而觸發更多的故障【10】。
導致這類軟體故障的BUG通常會潛伏很長時間直到被異常情況觸發為止。這種情況意味著軟體對其環境做出了某種假設——雖然這種假設通常來說是正確的但由於某種原因最後不再成立了【11】。
雖然軟體中的系統性故障沒有速效藥,但我們還是有很多小辦法,例如:仔細考慮系統中的假設和互動;徹底的測試;程序隔離;允許程序崩潰並重啟;測量、監控並分析生產環境中的系統行為。如果系統能夠提供一些保證(例如在一個訊息佇列中,進入與發出的訊息數量相等),那麼系統就可以在執行時不斷自檢,並在出現**差異discrepancy** 時報警【12】。
### 人為錯誤
設計並構建了軟體系統的工程師是人類維持系統執行的運維也是人類。即使他們懷有最大的善意人類也是不可靠的。舉個例子一項關於大型網際網路服務的研究發現運維配置錯誤是導致服務中斷的首要原因而硬體故障伺服器或網路僅導致了10-25的服務中斷【13】。
儘管人類不可靠,但怎麼做才能讓系統變得可靠?最好的系統會組合使用以下幾種辦法:
* 以最小化犯錯機會的方式設計系統。例如精心設計的抽象、API和管理後臺使做對事情更容易搞砸事情更困難。但如果介面限制太多人們就會忽略它們的好處而想辦法繞開。很難正確把握這種微妙的平衡。
* 將人們最容易犯錯的地方與可能導致失效的地方**解耦decouple**。特別是提供一個功能齊全的非生產環境**沙箱sandbox**,使人們可以在不影響真實使用者的情況下,使用真實資料安全地探索和實驗。
* 在各個層次進行徹底的測試【3】從單元測試、全系統整合測試到手動測試。自動化測試易於理解已經被廣泛使用特別適合用來覆蓋正常情況中少見的**邊緣場景corner case**。
* 允許從人為錯誤中簡單快速地恢復,以最大限度地減少失效情況帶來的影響。 例如,快速回滾配置變更,分批發布新程式碼(以便任何意外錯誤隻影響一小部分使用者),並提供資料重算工具(以備舊的計算出錯)。
* 配置詳細和明確的監控,比如效能指標和錯誤率。 在其他工程學科中這指的是**遙測telemetry**。 (一旦火箭離開了地面,遙測技術對於跟蹤發生的事情和理解失敗是至關重要的。)監控可以向我們發出預警訊號,並允許我們檢查是否有任何地方違反了假設和約束。當出現問題時,指標資料對於問題診斷是非常寶貴的。
* 良好的管理實踐與充分的培訓——一個複雜而重要的方面,但超出了本書的範圍。
### 可靠性有多重要?
可靠性不僅僅是針對核電站和空中交通管制軟體而言,我們也期望更多平凡的應用能可靠地執行。商務應用中的錯誤會導致生產力損失(也許資料報告不完整還會有法律風險),而電商網站的中斷則可能會導致收入和聲譽的巨大損失。
即使在“非關鍵”應用中我們也對使用者負有責任。試想一位家長把所有的照片和孩子的影片儲存在你的照片應用裡【15】。如果資料庫突然損壞他們會感覺如何他們可能會知道如何從備份恢復嗎
在某些情況下,我們可能會選擇犧牲可靠性來降低開發成本(例如為未經證實的市場開發產品原型)或運營成本(例如利潤率極低的服務),但我們偷工減料時,應該清楚意識到自己在做什麼。
## 可伸縮性
系統今天能可靠執行,並不意味未來也能可靠執行。服務 **降級degradation** 的一個常見原因是負載增加,例如:系統負載已經從一萬個併發使用者增長到十萬個併發使用者,或者從一百萬增長到一千萬。也許現在處理的資料量級要比過去大得多。
**可伸縮性Scalability** 是用來描述系統應對負載增長能力的術語。但是請注意這不是貼在系統上的一維標籤說“X可伸縮”或“Y不可伸縮”是沒有任何意義的。相反討論可伸縮性意味著考慮諸如“如果系統以特定方式增長有什麼選項可以應對增長”和“如何增加計算資源來處理額外的負載”等問題。
### 描述負載
在討論增長問題(如果負載加倍會發生什麼?)前,首先要能簡要描述系統的當前負載。負載可以用一些稱為 **負載引數load parameters** 的數字來描述。引數的最佳選擇取決於系統架構它可能是每秒向Web伺服器發出的請求、資料庫中的讀寫比率、聊天室中同時活躍的使用者數量、快取命中率或其他東西。除此之外也許平均情況對你很重要也許你的瓶頸是少數極端場景。
為了使這個概念更加具體我們以推特在2012年11月釋出的資料【16】為例。推特的兩個主要業務是
***釋出推文***
使用者可以向其粉絲髮布新訊息(平均 4.6k請求/秒,峰值超過 12k請求/秒)。
***主頁時間線***
使用者可以查閱他們關注的人釋出的推文300k請求/秒)。
處理每秒12,000次寫入發推文的速率峰值還是很簡單的。然而推特的伸縮性挑戰並不是主要來自推特量而是來自**扇出fan-out**——每個使用者關注了很多人,也被很多人關注。
[^ii]: 扇出:從電子工程學中借用的術語,它描述了輸入連線到另一個門輸出的邏輯閘數量。 輸出需要提供足夠的電流來驅動所有連線的輸入。 在事務處理系統中,我們使用它來描述為了服務一個傳入請求而需要執行其他服務的請求數量。
大體上講,這一對操作有兩種實現方式。
1. 釋出推文時,只需將新推文插入全域性推文集合即可。當一個使用者請求自己的主頁時間線時,首先查詢他關注的所有人,查詢這些被關注使用者釋出的推文並按時間順序合併。在如[圖1-2](img/fig1-2.png)所示的關係型資料庫中,可以編寫這樣的查詢:
1. 釋出推文時,只需將新推文插入全域性推文集合即可。當一個使用者請求自己的主頁時間線時,首先查詢他關注的所有人,查詢這些被關注使用者釋出的推文並按時間順序合併。在如[圖1-2](../img/fig1-2.png)所示的關係型資料庫中,可以編寫這樣的查詢:
```sql
SELECT tweets.*, users.*
FROM tweets
JOIN users ON tweets.sender_id = users.id
JOIN follows ON follows.followee_id = users.id
WHERE follows.follower_id = current_user
```
![](img/fig1-2.png)
![](../img/fig1-2.png)
**圖1-2 推特主頁時間線的關係型模式簡單實現**
2. 為每個使用者的主頁時間線維護一個快取,就像每個使用者的推文收件箱([圖1-3](../img/fig1-3.png))。 當一個使用者釋出推文時,查詢所有關注該使用者的人,並將新的推文插入到每個主頁時間線快取中。 因此讀取主頁時間線的請求開銷很小,因為結果已經提前計算好了。
2. 為每個使用者的主頁時間線維護一個快取,就像每個使用者的推文收件箱([圖1-3](img/fig1-3.png))。 當一個使用者釋出推文時,查詢所有關注該使用者的人,並將新的推文插入到每個主頁時間線快取中。 因此讀取主頁時間線的請求開銷很小,因為結果已經提前計算好了。
![](img/fig1-3.png)
![](../img/fig1-3.png)
**圖1-3 用於分發推特至關注者的資料流水線2012年11月的負載引數【16】**
推特的第一個版本使用了方法1但系統很難跟上主頁時間線查詢的負載。所以公司轉向了方法2方法2的效果更好因為發推頻率比查詢主頁時間線的頻率幾乎低了兩個數量級所以在這種情況下最好在寫入時做更多的工作而在讀取時做更少的工作。
然而方法2的缺點是發推現在需要大量的額外工作。平均來說一條推文會發往約75個關注者所以每秒4.6k的發推寫入變成了對主頁時間線快取每秒345k的寫入。但這個平均值隱藏了使用者粉絲數差異巨大這一現實一些使用者有超過3000萬的粉絲這意味著一條推文就可能會導致主頁時間線快取的3000萬次寫入及時完成這種操作是一個巨大的挑戰 —— 推特嘗試在5秒內向粉絲髮送推文。
在推特的例子中,每個使用者粉絲數的分佈(可能按這些使用者的發推頻率來加權)是探討可伸縮性的一個關鍵負載引數,因為它決定了扇出負載。你的應用程式可能具有非常不同的特徵,但可以採用相似的原則來考慮它的負載。
推特軼事的最終轉折現在已經穩健地實現了方法2推特逐步轉向了兩種方法的混合。大多數使用者發的推文會被扇出寫入其粉絲主頁時間線快取中。但是少數擁有海量粉絲的使用者即名流會被排除在外。當用戶讀取主頁時間線時分別地獲取出該使用者所關注的每位名流的推文再與使用者的主頁時間線快取合併如方法1所示。這種混合方法能始終如一地提供良好效能。在[第12章](ch12.md)中我們將重新討論這個例子,這在覆蓋更多技術層面之後。
### 描述效能
一旦系統的負載被描述好,就可以研究當負載增加會發生什麼。我們可以從兩種角度來看:
* 增加負載引數並保持系統資源CPU、記憶體、網路頻寬等不變時系統性能將受到什麼影響
* 增加負載引數並希望保持效能不變時,需要增加多少系統資源?
這兩個問題都需要效能資料,所以讓我們簡單地看一下如何描述系統性能。
對於Hadoop這樣的批處理系統通常關心的是**吞吐量throughput**,即每秒可以處理的記錄數量,或者在特定規模資料集上執行作業的總時間[^iii]。對於線上系統,通常更重要的是服務的**響應時間response time**,即客戶端傳送請求到接收響應之間的時間。
[^iii]: 理想情況下,批次作業的執行時間是資料集的大小除以吞吐量。 在實踐中由於資料傾斜(資料不是均勻分佈在每個工作程序中),需要等待最慢的任務完成,所以執行時間往往更長。
> #### 延遲和響應時間
>
> **延遲latency****響應時間response time** 經常用作同義詞,但實際上它們並不一樣。響應時間是客戶所看到的,除了實際處理請求的時間( **服務時間service time** )之外,還包括網路延遲和排隊延遲。延遲是某個請求等待處理的**持續時長**,在此期間它處於 **休眠latent** 狀態並等待服務【17】。
即使不斷重複傳送同樣的請求,每次得到的響應時間也都會略有不同。現實世界的系統會處理各式各樣的請求,響應時間可能會有很大差異。因此我們需要將響應時間視為一個可以測量的數值**分佈distribution**,而不是單個數值。
在[圖1-4](../img/fig1-4.png)中每個灰條代表一次對服務的請求其高度表示請求花費了多長時間。大多數請求是相當快的但偶爾會出現需要更長的時間的異常值。這也許是因為緩慢的請求實質上開銷更大例如它們可能會處理更多的資料。但即使你認為所有請求都花費相同時間的情況下隨機的附加延遲也會導致結果變化例如上下文切換到後臺程序網路資料包丟失與TCP重傳垃圾收集暫停強制從磁碟讀取的頁面錯誤伺服器機架中的震動【18】還有很多其他原因。
在[圖1-4](img/fig1-4.png)中每個灰條代表一次對服務的請求其高度表示請求花費了多長時間。大多數請求是相當快的但偶爾會出現需要更長的時間的異常值。這也許是因為緩慢的請求實質上開銷更大例如它們可能會處理更多的資料。但即使你認為所有請求都花費相同時間的情況下隨機的附加延遲也會導致結果變化例如上下文切換到後臺程序網路資料包丟失與TCP重傳垃圾收集暫停強制從磁碟讀取的頁面錯誤伺服器機架中的震動【18】還有很多其他原因。
![](img/fig1-4.png)
![](../img/fig1-4.png)
**圖1-4 展示了一個服務100次請求響應時間的均值與百分位數**
通常報表都會展示服務的平均響應時間。 (嚴格來講“平均”一詞並不指代任何特定公式,但實際上它通常被理解為**算術平均值arithmetic mean**:給定 n 個值,加起來除以 n )。然而如果你想知道“**典型typical**”響應時間,那麼平均值並不是一個非常好的指標,因為它不能告訴你有多少使用者實際上經歷了這個延遲。
通常使用**百分位點percentiles** 會更好。如果將響應時間列表按最快到最慢排序,那麼**中位數median** 就在正中間舉個例子如果你的響應時間中位數是200毫秒這意味著一半請求的返回時間少於200毫秒另一半比這個要長。
如果想知道典型場景下使用者需要等待多長時間那麼中位數是一個好的度量標準一半使用者請求的響應時間少於響應時間的中位數另一半服務時間比中位數長。中位數也被稱為第50百分位點有時縮寫為p50。注意中位數是關於單個請求的如果使用者同時發出幾個請求在一個會話過程中或者由於一個頁面中包含了多個資源則至少一個請求比中位數慢的概率遠大於50
為了弄清異常值有多糟糕可以看看更高的百分位點例如第95、99和99.9百分位點縮寫為p95p99和p999。它們意味著9599或99.9的請求響應時間要比該閾值快例如如果第95百分位點響應時間是1.5秒則意味著100個請求中的95個響應時間快於1.5秒而100個請求中的5個響應時間超過1.5秒。如[圖1-4](img/fig1-4.png)所示。
為了弄清異常值有多糟糕可以看看更高的百分位點例如第95、99和99.9百分位點縮寫為p95p99和p999。它們意味著9599或99.9的請求響應時間要比該閾值快例如如果第95百分位點響應時間是1.5秒則意味著100個請求中的95個響應時間快於1.5秒而100個請求中的5個響應時間超過1.5秒。如[圖1-4](../img/fig1-4.png)所示。
響應時間的高百分位點(也稱為**尾部延遲tail latencies**非常重要因為它們直接影響使用者的服務體驗。例如亞馬遜在描述內部服務的響應時間要求時以99.9百分位點為準,即使它隻影響一千個請求中的一個。這是因為請求響應最慢的客戶往往也是資料最多的客戶,也可以說是最有價值的客戶 —— 因為他們掏錢了【19】。保證網站響應迅速對於保持客戶的滿意度非常重要亞馬遜觀察到響應時間增加100毫秒銷售量就減少1【20】而另一些報告說慢 1 秒鐘會讓客戶滿意度指標減少16%【2122】。
另一方面最佳化第99.99百分位點(一萬個請求中最慢的一個)被認為太昂貴了,不能為亞馬遜的目標帶來足夠好處。減小高百分位點處的響應時間相當困難,因為它很容易受到隨機事件的影響,這超出了控制範圍,而且效益也很小。
百分位點通常用於**服務級別目標SLO, service level objectives**和**服務級別協議SLA, service level agreements**,即定義服務預期效能和可用性的合同。 SLA可能會宣告如果服務響應時間的中位數小於200毫秒且99.9百分位點低於1秒則認為服務工作正常如果響應時間更長就認為服務不達標。這些指標為客戶設定了期望值並允許客戶在SLA未達標的情況下要求退款。
**排隊延遲queueing delay** 通常佔了高百分位點處響應時間的很大一部分。由於伺服器只能並行處理少量的事務如受其CPU核數的限制所以只要有少量緩慢的請求就能阻礙後續請求的處理這種效應有時被稱為 **頭部阻塞head-of-line blocking** 。即使後續請求在伺服器上處理的非常迅速,由於需要等待先前請求完成,客戶端最終看到的是緩慢的總體響應時間。因為存在這種效應,測量客戶端的響應時間非常重要。
為測試系統的可伸縮性而人為產生負載時產生負載的客戶端要獨立於響應時間不斷髮送請求。如果客戶端在傳送下一個請求之前等待先前的請求完成這種行為會產生人為排隊的效果使得測試時的佇列比現實情況更短使測量結果產生偏差【23】。
> #### 實踐中的百分位點
>
> 在多重呼叫的後端服務裡,高百分位數變得特別重要。即使並行呼叫,終端使用者請求仍然需要等待最慢的並行呼叫完成。如[圖1-5](img/fig1-5.png)所示只需要一個緩慢的呼叫就可以使整個終端使用者請求變慢。即使只有一小部分後端呼叫速度較慢如果終端使用者請求需要多個後端呼叫則獲得較慢呼叫的機會也會增加因此較高比例的終端使用者請求速度會變慢效果稱為尾部延遲放大【24】
> 在多重呼叫的後端服務裡,高百分位數變得特別重要。即使並行呼叫,終端使用者請求仍然需要等待最慢的並行呼叫完成。如[圖1-5](../img/fig1-5.png)所示只需要一個緩慢的呼叫就可以使整個終端使用者請求變慢。即使只有一小部分後端呼叫速度較慢如果終端使用者請求需要多個後端呼叫則獲得較慢呼叫的機會也會增加因此較高比例的終端使用者請求速度會變慢效果稱為尾部延遲放大【24】
>
> 如果您想將響應時間百分點新增到您的服務的監視儀表板則需要持續有效地計算它們。例如您可能希望在最近10分鐘內保持請求響應時間的滾動視窗。每一分鐘您都會計算出該視窗中的中值和各種百分數並將這些度量值繪製在圖上。
>
> 簡單的實現是在時間視窗內儲存所有請求的響應時間列表並且每分鐘對列表進行排序。如果對你來說效率太低那麼有一些演算法能夠以最小的CPU和記憶體成本如前向衰減【25】t-digest【26】或HdrHistogram 【27】來計算百分位數的近似值。請注意平均百分比例如減少時間解析度或合併來自多臺機器的資料在數學上沒有意義 - 聚合響應時間資料的正確方法是新增直方圖【28】。
![](img/fig1-5.png)
![](../img/fig1-5.png)
**圖1-5 當一個請求需要多個後端請求時,單個後端慢請求就會拖慢整個終端使用者的請求**
### 應對負載的方法
現在我們已經討論了用於描述負載的引數和用於衡量效能的指標。可以開始認真討論可伸縮性了:當負載引數增加時,如何保持良好的效能?
適應某個級別負載的架構不太可能應付10倍於此的負載。如果你正在開發一個快速增長的服務那麼每次負載發生數量級的增長時你可能都需要重新考慮架構——或者更頻繁。
人們經常討論**縱向伸縮scaling up****垂直伸縮vertical scaling**,轉向更強大的機器)和**橫向伸縮scaling out** **水平伸縮horizontal scaling**,將負載分佈到多臺小機器上)之間的對立。跨多臺機器分配負載也稱為“**無共享shared-nothing**”架構。可以在單臺機器上執行的系統通常更簡單,但高階機器可能非常貴,所以非常密集的負載通常無法避免地需要橫向伸縮。現實世界中的優秀架構需要將這兩種方法務實地結合,因為使用幾臺足夠強大的機器可能比使用大量的小型虛擬機器更簡單也更便宜。
有些系統是 **彈性elastic** 的,這意味著可以在檢測到負載增加時自動增加計算資源,而其他系統則是手動伸縮(人工分析容量並決定向系統新增更多的機器)。如果負載**極難預測highly unpredictable**,則彈性系統可能很有用,但手動伸縮系統更簡單,並且意外操作可能會更少(參閱“[分割槽再平衡](ch6.md#分割槽再平衡)”)。
跨多臺機器部署 **無狀態服務stateless services** 非常簡單,但將帶狀態的資料系統從單節點變為分散式配置則可能引入許多額外複雜度。出於這個原因,常識告訴我們應該將資料庫放在單個節點上(縱向伸縮),直到伸縮成本或可用性需求迫使其改為分散式。
隨著分散式系統的工具和抽象越來越好,至少對於某些型別的應用而言,這種常識可能會改變。可以預見分散式資料系統將成為未來的預設設定,即使對不處理大量資料或流量的場景也如此。本書的其餘部分將介紹多種分散式資料系統,不僅討論它們在可伸縮性方面的表現,還包括易用性和可維護性。
大規模的系統架構通常是應用特定的—— 沒有一招鮮吃遍天的通用可伸縮架構(不正式的叫法:**萬金油magic scaling sauce** )。應用的問題可能是讀取量、寫入量、要儲存的資料量、資料的複雜度、響應時間要求、訪問模式或者所有問題的大雜燴。
舉個例子用於處理每秒十萬個請求每個大小為1 kB的系統與用於處理每分鐘3個請求每個大小為2GB的系統看上去會非常不一樣儘管兩個系統有同樣的資料吞吐量。
一個良好適配應用的可伸縮架構,是圍繞著**假設assumption** 建立的:哪些操作是常見的?哪些操作是罕見的?這就是所謂負載引數。如果假設最終是錯誤的,那麼為伸縮所做的工程投入就白費了,最糟糕的是適得其反。在早期創業公司或非正式產品中,通常支援產品快速迭代的能力,要比可伸縮至未來的假想負載要重要的多。
儘管這些架構是應用程式特定的,但可伸縮的架構通常也是從通用的積木塊搭建而成的,並以常見的模式排列。在本書中,我們將討論這些構件和模式。
## 可維護性
眾所周知,軟體的大部分開銷並不在最初的開發階段,而是在持續的維護階段,包括修復漏洞、保持系統正常執行、調查失效、適配新的平臺、為新的場景進行修改、償還技術債、新增新的功能等等。
不幸的是,許多從事軟體系統行業的人不喜歡維護所謂的**遺留legacy** 系統,——也許因為涉及修復其他人的錯誤、和過時的平臺打交道,或者系統被迫使用於一些份外工作。每一個遺留系統都以自己的方式讓人不爽,所以很難給出一個通用的建議來和它們打交道。
但是我們可以,也應該以這樣一種方式來設計軟體:在設計之初就儘量考慮儘可能減少維護期間的痛苦,從而避免自己的軟體系統變成遺留系統。為此,我們將特別關注軟體系統的三個設計原則:
***可操作性Operability***
便於運維團隊保持系統平穩執行。
***簡單性Simplicity***
從系統中消除儘可能多的**複雜度complexity**,使新工程師也能輕鬆理解系統。(注意這和使用者介面的簡單性不一樣。)
***可演化性evolability***
使工程師在未來能輕鬆地對系統進行更改,當需求變化時為新應用場景做適配。也稱為**可伸縮性extensibility****可修改性modifiability**或**可塑性plasticity**。
和之前提到的可靠性、可伸縮性一樣,實現這些目標也沒有簡單的解決方案。不過我們會試著想象具有可操作性,簡單性和可演化性的系統會是什麼樣子。
### 可操作性:人生苦短,關愛運維
有人認為,“良好的運維經常可以繞開垃圾(或不完整)軟體的侷限性,而再好的軟體攤上垃圾運維也沒法可靠執行”。儘管運維的某些方面可以,而且應該是自動化的,但在最初建立正確運作的自動化機制仍然取決於人。
運維團隊對於保持軟體系統順利執行至關重要。一個優秀運維團隊的典型職責如下或者更多【29】
* 監控系統的執行狀況,並在服務狀態不佳時快速恢復服務
* 跟蹤問題的原因,例如系統故障或效能下降
* 及時更新軟體和平臺,比如安全補丁
* 瞭解系統間的相互作用,以便在異常變更造成損失前進行規避。
* 預測未來的問題,並在問題出現之前加以解決(例如,容量規劃)
* 建立部署,配置、管理方面的良好實踐,編寫相應工具
* 執行復雜的維護任務,例如將應用程式從一個平臺遷移到另一個平臺
* 當配置變更時,維持系統的安全性
* 定義工作流程,使運維操作可預測,並保持生產環境穩定。
* 鐵打的營盤流水的兵,維持組織對系統的瞭解。
良好的可操作性意味著更輕鬆的日常工作,進而運維團隊能專注於高價值的事情。資料系統可以透過各種方式使日常任務更輕鬆:
* 透過良好的監控,提供對系統內部狀態和執行時行為的**可見性visibility**
* 為自動化提供良好支援,將系統與標準化工具相整合
* 避免依賴單臺機器(在整個系統繼續不間斷執行的情況下允許機器停機維護)
* 提供良好的文件和易於理解的操作模型“如果做X會發生Y”
* 提供良好的預設行為,但需要時也允許管理員自由覆蓋預設值
* 有條件時進行自我修復,但需要時也允許管理員手動控制系統狀態
* 行為可預測,最大限度減少意外
### 簡單性:管理複雜度
小型軟體專案可以使用簡單討喜的、富表現力的程式碼,但隨著專案越來越大,程式碼往往變得非常複雜,難以理解。這種複雜度拖慢了所有系統相關人員,進一步增加了維護成本。一個陷入複雜泥潭的軟體專案有時被描述為 **爛泥潭a big ball of mud** 【30】。
**複雜度complexity** 有各種可能的症狀例如狀態空間激增、模組間緊密耦合、糾結的依賴關係、不一致的命名和術語、解決效能問題的Hack、需要繞開的特例等等現在已經有很多關於這個話題的討論【31,32,33】。
因為複雜度導致維護困難時,預算和時間安排通常會超支。在複雜的軟體中進行變更,引入錯誤的風險也更大:當開發人員難以理解系統時,隱藏的假設、無意的後果和意外的互動就更容易被忽略。相反,降低複雜度能極大地提高軟體的可維護性,因此簡單性應該是構建系統的一個關鍵目標。
簡化系統並不一定意味著減少功能;它也可以意味著消除**額外的accidental** 的複雜度。 Moseley和Marks【32】把 **額外複雜度** 定義為:由具體實現中湧現,而非(從使用者視角看,系統所解決的)問題本身固有的複雜度。
用於消除**額外複雜度** 的最好工具之一是**抽象abstraction**。一個好的抽象可以將大量實現細節隱藏在一個乾淨,簡單易懂的外觀下面。一個好的抽象也可以廣泛用於各類不同應用。比起重複造很多輪子,重用抽象不僅更有效率,而且有助於開發高質量的軟體。抽象元件的質量改進將使所有使用它的應用受益。
例如高階程式語言是一種抽象隱藏了機器碼、CPU暫存器和系統呼叫。 SQL也是一種抽象隱藏了複雜的磁碟/記憶體資料結構、來自其他客戶端的併發請求、崩潰後的不一致性。當然在用高階語言程式設計時,我們仍然用到了機器碼;只不過沒有**直接directly** 使用罷了,正是因為程式語言的抽象,我們才不必去考慮這些實現細節。
抽象可以幫助我們將系統的複雜度控制在可管理的水平,不過,找到好的抽象是非常困難的。在分散式系統領域雖然有許多好的演算法,但我們並不清楚它們應該打包成什麼樣抽象。
本書將緊盯那些允許我們將大型系統的部分提取為定義明確的、可重用的元件的優秀抽象。
### 可演化性:擁抱變化
系統的需求永遠不變,基本是不可能的。更可能的情況是,它們處於常態的變化中,例如:你瞭解了新的事實、出現意想不到的應用場景、業務優先順序發生變化、使用者要求新功能、新平臺取代舊平臺、法律或監管要求發生變化、系統增長迫使架構變化等。
在組織流程方面, **敏捷agile** 工作模式為適應變化提供了一個框架。敏捷社群還開發了對在頻繁變化的環境中開發軟體很有幫助的技術工具和模式,如 **測試驅動開發TDD, test-driven development****重構refactoring**
這些敏捷技術的大部分討論都集中在相當小的規模同一個應用中的幾個程式碼檔案。本書將探索在更大資料系統層面上提高敏捷性的方法可能由幾個不同的應用或服務組成。例如為了將裝配主頁時間線的方法從方法1變為方法2你會如何“重構”推特的架構
修改資料系統並使其適應不斷變化需求的容易程度,是與**簡單性**和**抽象性**密切相關的:簡單易懂的系統通常比複雜系統更容易修改。但由於這是一個非常重要的概念,我們將用一個不同的詞來指代資料系統層面的敏捷性: **可演化性evolvability** 【34】。
## 本章小結
本章探討了一些關於資料密集型應用的基本思考方式。這些原則將指導我們閱讀本書的其餘部分,那裡將會深入技術細節。
一個應用必須滿足各種需求才稱得上有用。有一些**功能需求functional requirements**(它應該做什麼,比如允許以各種方式儲存,檢索,搜尋和處理資料)以及一些**非功能性需求nonfunctional **(通用屬性,例如安全性,可靠性,合規性,可伸縮性,相容性和可維護性)。在本章詳細討論了可靠性,可伸縮性和可維護性。
**可靠性Reliability** 意味著即使發生故障系統也能正常工作。故障可能發生在硬體通常是隨機的和不相關的軟體通常是系統性的Bug很難處理和人類不可避免地時不時出錯**容錯技術** 可以對終端使用者隱藏某些型別的故障。
**可伸縮性Scalability** 意味著即使在負載增加的情況下也有保持效能的策略。為了討論可伸縮性,我們首先需要定量描述負載和效能的方法。我們簡要了解了推特主頁時間線的例子,介紹描述負載的方法,並將響應時間百分位點作為衡量效能的一種方式。在可伸縮的系統中可以新增 **處理容量processing capacity** 以在高負載下保持可靠。
**可維護性Maintainability** 有許多方面,但實質上是關於工程師和運維團隊的生活質量的。良好的抽象可以幫助降低複雜度,並使系統易於修改和適應新的應用場景。良好的可操作性意味著對系統的健康狀態具有良好的可見性,並擁有有效的管理手段。
不幸的是,使應用可靠、可伸縮或可維護並不容易。但是某些模式和技術會不斷重新出現在不同的應用中。在接下來的幾章中,我們將看到一些資料系統的例子,並分析它們如何實現這些目標。
在本書後面的[第三部分](part-iii.md)中,我們將看到一種模式:幾個元件協同工作以構成一個完整的系統(如[圖1-1](img/fig1-1.png)中的例子)
在本書後面的[第三部分](part-iii.md)中,我們將看到一種模式:幾個元件協同工作以構成一個完整的系統(如[圖1-1](../img/fig1-1.png)中的例子)
## 參考文獻
1. Michael Stonebraker and Uğur Çetintemel: “['One Size Fits All': An Idea Whose Time Has Come and Gone](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.68.9136&rep=rep1&type=pdf),” at *21st International Conference on Data Engineering* (ICDE), April 2005.
1. Walter L. Heimerdinger and Charles B. Weinstock: “[A Conceptual Framework for System Fault Tolerance](http://www.sei.cmu.edu/reports/92tr033.pdf),” Technical Report CMU/SEI-92-TR-033, Software Engineering Institute, Carnegie Mellon University, October 1992.
2. Ding Yuan, Yu Luo, Xin Zhuang, et al.: “[Simple Testing Can Prevent Most Critical Failures: An Analysis of Production Failures in Distributed Data-Intensive Systems](https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf),” at *11th USENIX Symposium on Operating Systems Design and Implementation* (OSDI), October 2014.
3. Yury Izrailevsky and Ariel Tseitlin: “[The Netflix Simian Army](http://techblog.netflix.com/2011/07/netflix-simian-army.html),” *techblog.netflix.com*, July 19, 2011.
4. Daniel Ford, François Labelle, Florentina I. Popovici, et al.: “[Availability in Globally Distributed Storage Systems](http://research.google.com/pubs/archive/36737.pdf),” at *9th USENIX Symposium on Operating Systems Design and Implementation* (OSDI),
October 2010.
5. Brian Beach: “[Hard Drive Reliability Update Sep 2014](https://www.backblaze.com/blog/hard-drive-reliability-update-september-2014/),” *backblaze.com*, September 23, 2014.
6. Laurie Voss: “[AWS: The Good, the Bad and the Ugly](https://web.archive.org/web/20160429075023/http://blog.awe.sm/2012/12/18/aws-the-good-the-bad-and-the-ugly/),” *blog.awe.sm*, December 18, 2012.
7. Haryadi S. Gunawi, Mingzhe Hao, Tanakorn Leesatapornwongsa, et al.: “[What Bugs Live in the Cloud?](http://ucare.cs.uchicago.edu/pdf/socc14-cbs.pdf),” at *5th ACM Symposium on Cloud Computing* (SoCC), November 2014. [doi:10.1145/2670979.2670986](http://dx.doi.org/10.1145/2670979.2670986)
8. Nelson Minar: “[Leap Second Crashes Half the Internet](http://www.somebits.com/weblog/tech/bad/leap-second-2012.html),” *somebits.com*, July 3, 2012.
9. Amazon Web Services: “[Summary of the Amazon EC2 and Amazon RDS Service Disruption in the US East Region](http://aws.amazon.com/message/65648/),” *aws.amazon.com*, April 29, 2011.
10. Richard I. Cook: “[How Complex Systems Fail](http://web.mit.edu/2.75/resources/random/How%20Complex%20Systems%20Fail.pdf),” Cognitive Technologies Laboratory, April 2000.
11. Jay Kreps: “[Getting Real About Distributed System Reliability](http://blog.empathybox.com/post/19574936361/getting-real-about-distributed-system-reliability),” *blog.empathybox.com*, March 19, 2012.
12. David Oppenheimer, Archana Ganapathi, and David A. Patterson: “[Why Do Internet Services Fail, and What Can Be Done About It?](http://static.usenix.org/legacy/events/usits03/tech/full_papers/oppenheimer/oppenheimer.pdf),” at *4th USENIX Symposium on Internet Technologies and Systems* (USITS), March 2003.
13. Nathan Marz: “[Principles of Software Engineering, Part 1](http://nathanmarz.com/blog/principles-of-software-engineering-part-1.html),” *nathanmarz.com*, April 2, 2013.
14. Michael Jurewitz:“[The Human Impact of Bugs](http://jury.me/blog/2013/3/14/the-human-impact-of-bugs),” *jury.me*, March 15, 2013.
15. Raffi Krikorian: “[Timelines at Scale](http://www.infoq.com/presentations/Twitter-Timeline-Scalability),” at *QCon San Francisco*, November 2012.
16. Martin Fowler: *Patterns of Enterprise Application Architecture*. Addison Wesley, 2002. ISBN: 978-0-321-12742-6
17. Kelly Sommers: “[After all that run around, what caused 500ms disk latency even when we replaced physical server?](https://twitter.com/kellabyte/status/532930540777635840)” *twitter.com*, November 13, 2014.
18. Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, et al.: “[Dynamo: Amazon's Highly Available Key-Value Store](http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf),” at *21st ACM Symposium on Operating Systems Principles* (SOSP), October 2007.
19. Greg Linden: “[Make Data Useful](http://glinden.blogspot.co.uk/2006/12/slides-from-my-talk-at-stanford.html),” slides from presentation at Stanford University Data Mining class (CS345), December 2006.
20. Tammy Everts: “[The Real Cost of Slow Time vs Downtime](http://www.webperformancetoday.com/2014/11/12/real-cost-slow-time-vs-downtime-slides/),” *webperformancetoday.com*, November 12, 2014.
21. Jake Brutlag:“[Speed Matters for Google Web Search](http://googleresearch.blogspot.co.uk/2009/06/speed-matters.html),” *googleresearch.blogspot.co.uk*, June 22, 2009.
22. Tyler Treat: “[Everything You Know About Latency Is Wrong](http://bravenewgeek.com/everything-you-know-about-latency-is-wrong/),” *bravenewgeek.com*, December 12, 2015.
23. Jeffrey Dean and Luiz André Barroso: “[The Tail at Scale](http://cacm.acm.org/magazines/2013/2/160173-the-tail-at-scale/fulltext),” *Communications of the ACM*, volume 56, number 2, pages 7480, February 2013. [doi:10.1145/2408776.2408794](http://dx.doi.org/10.1145/2408776.2408794)
24. Graham Cormode, Vladislav Shkapenyuk, Divesh Srivastava, and Bojian Xu: “[Forward Decay: A Practical Time Decay Model for Streaming Systems](http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf),” at *25th IEEE International Conference on Data Engineering* (ICDE), March 2009.
25. Ted Dunning and Otmar Ertl: “[Computing Extremely Accurate Quantiles Using t-Digests](https://github.com/tdunning/t-digest),” *github.com*, March 2014.
26. Gil Tene: “[HdrHistogram](http://www.hdrhistogram.org/),” *hdrhistogram.org*.
27. Baron Schwartz: “[Why Percentiles Dont Work the Way You Think](https://www.vividcortex.com/blog/why-percentiles-dont-work-the-way-you-think),” *vividcortex.com*, December 7, 2015.
28. James Hamilton: “[On Designing and Deploying Internet-Scale Services](https://www.usenix.org/legacy/events/lisa07/tech/full_papers/hamilton/hamilton.pdf),” at *21st Large Installation
System Administration Conference* (LISA), November 2007.
29. Brian Foote and Joseph Yoder: “[Big Ball of Mud](http://www.laputan.org/pub/foote/mud.pdf),” at *4th Conference on Pattern Languages of Programs* (PLoP), September 1997.
30. Frederick P Brooks: “No Silver Bullet Essence and Accident in Software Engineering,” in *The Mythical Man-Month*, Anniversary edition, Addison-Wesley, 1995. ISBN: 978-0-201-83595-3
31. Ben Moseley and Peter Marks: “[Out of the Tar Pit](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.93.8928),” at *BCS Software Practice Advancement* (SPA), 2006.
32. Rich Hickey: “[Simple Made Easy](http://www.infoq.com/presentations/Simple-Made-Easy),” at *Strange Loop*, September 2011.
33. Hongyu Pei Breivold, Ivica Crnkovic, and Peter J. Eriksson: “[Analyzing Software Evolvability](http://www.mrtc.mdh.se/publications/1478.pdf),” at *32nd Annual IEEE International Computer Software and Applications Conference* (COMPSAC), July 2008. [doi:10.1109/COMPSAC.2008.50](http://dx.doi.org/10.1109/COMPSAC.2008.50)
------
| 上一章 | 目錄 | 下一章 |
| ----------------------------------- | ------------------------------- | ------------------------------------ |
| [第一部分:資料系統基礎](part-i.md) | [設計資料密集型應用](README.md) | [第二章:資料模型與查詢語言](ch2.md) |
| [第一部分:資料系統基礎](part-i.md) | [設計資料密集型應用](README.md) | [第二章:資料模型與查詢語言](ch2.md) |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,819 +1,409 @@
# 第六章:分割槽
![](img/ch6.png)
# 第六章:分割槽
![](../img/ch6.png)
> 我們必須跳出電腦指令序列的窠臼。 敘述定義、描述元資料、梳理關係,而不是編寫過程。
>
> —— Grace Murray Hopper未來的計算機及其管理1962
>
-------------
[TOC]
在[第5章](ch5.md)中,我們討論了複製——即資料在不同節點上的副本,對於非常大的資料集,或非常高的吞吐量,僅僅進行復制是不夠的:我們需要將資料進行**分割槽partitions**,也稱為**分片sharding**[^i]。
[^i]: 正如本章所討論的,分割槽是一種有意將大型資料庫分解成小型資料庫的方式。它與 **網路分割槽network partitions, netsplits** 無關,這是節點之間網路故障的一種。我們將在[第8章](ch8.md)討論這些錯誤。
> #### 術語澄清
>
> 上文中的**分割槽(partition)**在MongoDBElasticsearch和Solr Cloud中被稱為**分片(shard)**在HBase中稱之為**區域(Region)**Bigtable中則是 **表塊tablet**Cassandra和Riak中是**虛節點vnode)**Couchbase中叫做**虛桶(vBucket)**。但是**分割槽(partitioning)** 是最約定俗成的叫法。
>
通常情況下,每條資料(每條記錄,每行或每個文件)屬於且僅屬於一個分割槽。有很多方法可以實現這一點,本章將進行深入討論。實際上,每個分割槽都是自己的小型資料庫,儘管資料庫可能支援同時進行多個分割槽的操作。
分割槽主要是為了**可伸縮性**。不同的分割槽可以放在不共享叢集中的不同節點上(參閱[第二部分](part-ii.md)關於[無共享架構](part-ii.md#無共享架構)的定義)。因此,大資料集可以分佈在多個磁碟上,並且查詢負載可以分佈在多個處理器上。
對於在單個分割槽上執行的查詢,每個節點可以獨立執行對自己的查詢,因此可以透過新增更多的節點來擴大查詢吞吐量。大型,複雜的查詢可能會跨越多個節點並行處理,儘管這也帶來了新的困難。
分割槽資料庫在20世紀80年代由Teradata和NonStop SQL【1】等產品率先推出最近因為NoSQL資料庫和基於Hadoop的資料倉庫重新被關注。有些系統是為事務性工作設計的有些系統則用於分析參閱“[事務處理還是分析](ch3.md#事務處理還是分析)”):這種差異會影響系統的運作方式,但是分割槽的基本原理均適用於這兩種工作方式。
在本章中,我們將首先介紹分割大型資料集的不同方法,並觀察索引如何與分割槽配合。然後我們將討論[分割槽再平衡rebalancing](#分割槽再平衡),如果想要新增或刪除叢集中的節點,則必須進行再平衡。最後,我們將概述資料庫如何將請求路由到正確的分割槽並執行查詢。
## 分割槽與複製
分割槽通常與複製結合使用,使得每個分割槽的副本儲存在多個節點上。 這意味著,即使每條記錄屬於一個分割槽,它仍然可以儲存在多個不同的節點上以獲得容錯能力。
一個節點可能儲存多個分割槽。 如果使用主從複製模型,則分割槽和複製的組合如[圖6-1](img/fig6-1.png)所示。 每個分割槽領導者(主)被分配給一個節點,追隨者(從)被分配給其他節點。 每個節點可能是某些分割槽的領導者,同時是其他分割槽的追隨者。
一個節點可能儲存多個分割槽。 如果使用主從複製模型,則分割槽和複製的組合如[圖6-1](../img/fig6-1.png)所示。 每個分割槽領導者(主)被分配給一個節點,追隨者(從)被分配給其他節點。 每個節點可能是某些分割槽的領導者,同時是其他分割槽的追隨者。
我們在[第5章](ch5.md)討論的關於資料庫複製的所有內容同樣適用於分割槽的複製。 大多數情況下,分割槽方案的選擇與複製方案的選擇是獨立的,為簡單起見,本章中將忽略複製。
![](img/fig6-1.png)
![](../img/fig6-1.png)
**圖6-1 組合使用複製和分割槽:每個節點充當某些分割槽的領導者,其他分割槽充當追隨者。**
## 鍵值資料的分割槽
假設你有大量資料並且想要分割槽,如何決定在哪些節點上儲存哪些記錄呢?
分割槽目標是將資料和查詢負載均勻分佈在各個節點上。如果每個節點公平分享資料和負載那麼理論上10個節點應該能夠處理10倍的資料量和10倍的單個節點的讀寫吞吐量暫時忽略複製
如果分割槽是不公平的,一些分割槽比其他分割槽有更多的資料或查詢,我們稱之為**偏斜skew**。資料偏斜的存在使分割槽效率下降很多。在極端的情況下所有的負載可能壓在一個分割槽上其餘9個節點空閒的瓶頸落在這一個繁忙的節點上。不均衡導致的高負載的分割槽被稱為**熱點hot spot**。
避免熱點最簡單的方法是將記錄隨機分配給節點。這將在所有節點上平均分配資料,但是它有一個很大的缺點:當你試圖讀取一個特定的值時,你無法知道它在哪個節點上,所以你必須並行地查詢所有的節點。
我們可以做得更好。現在假設您有一個簡單的鍵值資料模型,其中您總是透過其主鍵訪問記錄。例如,在一本老式的紙質百科全書中,你可以透過標題來查詢一個條目;由於所有條目按字母順序排序,因此您可以快速找到您要查詢的條目。
### 根據鍵的範圍分割槽
一種分割槽的方法是為每個分割槽指定一塊連續的鍵範圍(從最小值到最大值),如紙質百科全書的卷([圖6-2](../img/fig6-2.png))。如果知道範圍之間的邊界,則可以輕鬆確定哪個分割槽包含某個值。如果您還知道分割槽所在的節點,那麼可以直接向相應的節點發出請求(對於百科全書而言,就像從書架上選取正確的書籍)。
一種分割槽的方法是為每個分割槽指定一塊連續的鍵範圍(從最小值到最大值),如紙質百科全書的卷([圖6-2](img/fig6-2.png))。如果知道範圍之間的邊界,則可以輕鬆確定哪個分割槽包含某個值。如果您還知道分割槽所在的節點,那麼可以直接向相應的節點發出請求(對於百科全書而言,就像從書架上選取正確的書籍)。
![](img/fig6-2.png)
![](../img/fig6-2.png)
**圖6-2 印刷版百科全書按照關鍵字範圍進行分割槽**
鍵的範圍不一定均勻分佈,因為資料也很可能不均勻分佈。例如在[圖6-2](img/fig6-2.png)中第1捲包含以A和B開頭的單詞但第12卷則包含以TUVXY和Z開頭的單詞。只是簡單的規定每個捲包含兩個字母會導致一些卷比其他卷大。為了均勻分配資料分割槽邊界需要依據資料調整。
鍵的範圍不一定均勻分佈,因為資料也很可能不均勻分佈。例如在[圖6-2](../img/fig6-2.png)中第1捲包含以A和B開頭的單詞但第12卷則包含以TUVXY和Z開頭的單詞。只是簡單的規定每個捲包含兩個字母會導致一些卷比其他卷大。為了均勻分配資料分割槽邊界需要依據資料調整。
分割槽邊界可以由管理員手動選擇,也可以由資料庫自動選擇(我們會在“[分割槽再平衡](#分割槽再平衡)”中更詳細地討論分割槽邊界的選擇)。 Bigtable使用了這種分割槽策略以及其開源等價物HBase 【2, 3】RethinkDB和2.4版本之前的MongoDB 【4】。
在每個分割槽中,我們可以按照一定的順序儲存鍵(參見“[SSTables和LSM樹](ch3.md#SSTables和LSM樹)”)。好處是進行範圍掃描非常簡單,您可以將鍵作為聯合索引來處理,以便在一次查詢中獲取多個相關記錄(參閱“[多列索引](ch3.md#多列索引)”)。例如,假設我們有一個程式來儲存感測器網路的資料,其中主鍵是測量的時間戳(年月日時分秒)。範圍掃描在這種情況下非常有用,因為我們可以輕鬆獲取某個月份的所有資料。
然而Key Range分割槽的缺點是某些特定的訪問模式會導致熱點。 如果主鍵是時間戳,則分割槽對應於時間範圍,例如,給每天分配一個分割槽。 不幸的是由於我們在測量發生時將資料從感測器寫入資料庫因此所有寫入操作都會轉到同一個分割槽即今天的分割槽這樣分割槽可能會因寫入而過載而其他分割槽則處於空閒狀態【5】。
為了避免感測器資料庫中的這個問題,需要使用除了時間戳以外的其他東西作為主鍵的第一個部分。 例如,可以在每個時間戳前新增感測器名稱,這樣會首先按感測器名稱,然後按時間進行分割槽。 假設有多個感測器同時執行,寫入負載將最終均勻分佈在不同分割槽上。 現在,當想要在一個時間範圍內獲取多個感測器的值時,您需要為每個感測器名稱執行一個單獨的範圍查詢。
### 根據鍵的雜湊分割槽
由於偏斜和熱點的風險,許多分散式資料儲存使用雜湊函式來確定給定鍵的分割槽。
一個好的雜湊函式可以將偏斜的資料均勻分佈。假設你有一個32位雜湊函式,無論何時給定一個新的字串輸入它將返回一個0到$2^{32}$ -1之間的“隨機”數。即使輸入的字串非常相似它們的雜湊也會均勻分佈在這個數字範圍內。
出於分割槽的目的雜湊函式不需要多麼強壯的加密演算法例如Cassandra和MongoDB使用MD5Voldemort使用Fowler-Noll-Vo函式。許多程式語言都有內建的簡單雜湊函式它們用於雜湊表但是它們可能不適合分割槽例如在Java的`Object.hashCode()`和Ruby的`Object#hash`同一個鍵可能在不同的程序中有不同的雜湊值【6】。
一旦你有一個合適的鍵雜湊函式,你可以為每個分割槽分配一個雜湊範圍(而不是鍵的範圍),每個透過雜湊雜湊落在分割槽範圍內的鍵將被儲存在該分割槽中。如[圖6-3](../img/fig6-3.png)所示。
一旦你有一個合適的鍵雜湊函式,你可以為每個分割槽分配一個雜湊範圍(而不是鍵的範圍),每個透過雜湊雜湊落在分割槽範圍內的鍵將被儲存在該分割槽中。如[圖6-3](img/fig6-3.png)所示。
![](img/fig6-3.png)
![](../img/fig6-3.png)
**圖6-3 按雜湊鍵分割槽**
這種技術擅長在分割槽之間公平地分配鍵。分割槽邊界可以是均勻間隔的,也可以是偽隨機選擇的(在這種情況下,該技術有時也被稱為**一致性雜湊consistent hashing**)。
> #### 一致性雜湊
>
> 一致性雜湊由Karger等人定義。【7】 用於跨網際網路級別的快取系統例如CDN中是一種能均勻分配負載的方法。它使用隨機選擇的 **分割槽邊界partition boundaries** 來避免中央控制或分散式共識的需要。 請注意,這裡的一致性與複製一致性(請參閱[第5章](ch5.md)或ACID一致性參閱[第7章](ch7.md)無關而只是描述了一種重新平衡reblancing的特定方法。
>
> 正如我們將在“[分割槽再平衡](#分割槽再平衡)”中所看到的,這種特殊的方法對於資料庫實際上並不是很好,所以在實際中很少使用(某些資料庫的文件仍然會使用一致性雜湊的說法,但是它往往是不準確的)。 因為有可能產生混淆,所以最好避免使用一致性雜湊這個術語,而只是把它稱為**雜湊分割槽hash partitioning**。
不幸的是透過使用鍵雜湊進行分割槽我們失去了鍵範圍分割槽的一個很好的屬性高效執行範圍查詢的能力。曾經相鄰的鍵現在分散在所有分割槽中所以它們之間的順序就丟失了。在MongoDB中如果您使用了基於雜湊的分割槽模式則任何範圍查詢都必須傳送到所有分割槽【4】。Riak 【9】Couchbase 【10】或Voldemort不支援主鍵上的範圍查詢。
Cassandra採取了折衷的策略【11, 12, 13】。 Cassandra中的表可以使用由多個列組成的複合主鍵來宣告。鍵中只有第一列會作為雜湊的依據而其他列則被用作Casssandra的SSTables中排序資料的連線索引。儘管查詢無法在複合主鍵的第一列中按範圍掃表但如果第一列已經指定了固定值則可以對該鍵的其他列執行有效的範圍掃描。
組合索引方法為一對多關係提供了一個優雅的資料模型。例如,在社交媒體網站上,一個使用者可能會發布很多更新。如果更新的主鍵被選擇為`(user_id, update_timestamp)`,那麼您可以有效地檢索特定使用者在某個時間間隔內按時間戳排序的所有更新。不同的使用者可以儲存在不同的分割槽上,對於每個使用者,更新按時間戳順序儲存在單個分割槽上。
### 負載偏斜與熱點消除
如前所述,雜湊分割槽可以幫助減少熱點。但是,它不能完全避免它們:在極端情況下,所有的讀寫操作都是針對同一個鍵的,所有的請求都會被路由到同一個分割槽。
這種場景也許並不常見但並非聞所未聞例如在社交媒體網站上一個擁有數百萬追隨者的名人使用者在做某事時可能會引發一場風暴【14】。這個事件可能導致同一個鍵的大量寫入鍵可能是名人的使用者ID或者人們正在評論的動作的ID。雜湊策略不起作用因為兩個相同ID的雜湊值仍然是相同的。
如今大多數資料系統無法自動補償這種高度偏斜的負載因此應用程式有責任減少偏斜。例如如果一個主鍵被認為是非常火爆的一個簡單的方法是在主鍵的開始或結尾新增一個隨機數。只要一個兩位數的十進位制隨機數就可以將主鍵分散為100種不同的主鍵,從而儲存在不同的分割槽中。
然而將主鍵進行分割之後任何讀取都必須要做額外的工作因為他們必須從所有100個主鍵分佈中讀取資料並將其合併。此技術還需要額外的記錄只需要對少量熱點附加隨機數對於寫入吞吐量低的絕大多數主鍵來說是不必要的開銷。因此您還需要一些方法來跟蹤哪些鍵需要被分割。
也許在將來,資料系統將能夠自動檢測和補償偏斜的工作負載;但現在,您需要自己來權衡。
## 分割槽與次級索引
到目前為止,我們討論的分割槽方案依賴於鍵值資料模型。如果只通過主鍵訪問記錄,我們可以從該鍵確定分割槽,並使用它來將讀寫請求路由到負責該鍵的分割槽。
如果涉及次級索引,情況會變得更加複雜(參考“[其他索引結構](ch3.md#其他索引結構)”。輔助索引通常並不能唯一地標識記錄而是一種搜尋記錄中出現特定值的方式查詢使用者123的所有操作查詢包含詞語`hogwash`的所有文章,查詢所有顏色為紅色的車輛等等。
次級索引是關係型資料庫的基礎並且在文件資料庫中也很普遍。許多鍵值儲存如HBase和Volde-mort為了減少實現的複雜度而放棄了次級索引但是一些如Riak已經開始新增它們因為它們對於資料模型實在是太有用了。並且次級索引也是Solr和Elasticsearch等搜尋伺服器的基石。
次級索引的問題是它們不能整齊地對映到分割槽。有兩種用二級索引對資料庫進行分割槽的方法:**基於文件的分割槽document-based**和**基於關鍵詞term-based的分割槽**。
### 基於文件的二級索引進行分割槽
假設你正在經營一個銷售二手車的網站(如[圖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等
你想讓使用者搜尋汽車,允許他們透過顏色和廠商過濾,所以需要一個在顏色和廠商上的次級索引(文件資料庫中這些是**欄位field**,關係資料庫中這些是**列column** )。 如果您聲明瞭索引,則資料庫可以自動執行索引[^ii]。例如,無論何時將紅色汽車新增到資料庫,資料庫分割槽都會自動將其新增到索引條目`color:red`的文件ID列表中。
[^ii]: 如果資料庫僅支援鍵值模型則你可能會嘗試在應用程式程式碼中建立從值到文件ID的對映來實現輔助索引。 如果沿著這條路線走下去,請萬分小心,確保您的索引與底層資料保持一致。 競爭條件和間歇性寫入失敗(其中一些更改已儲存,但其他更改未儲存)很容易導致資料不同步 - 參見“[多物件事務的需求](ch7.md#多物件事務的需求)”。
![](img/fig6-4.png)
![](../img/fig6-4.png)
**圖6-4 基於文件的二級索引進行分割槽**
在這種索引方法中每個分割槽是完全獨立的每個分割槽維護自己的二級索引僅覆蓋該分割槽中的文件。它不關心儲存在其他分割槽的資料。無論何時您需要寫入資料庫新增刪除或更新文件只需處理包含您正在編寫的文件ID的分割槽即可。出於這個原因**文件分割槽索引**也被稱為**本地索引local index**(而不是將在下一節中描述的**全域性索引global index**)。
但是從文件分割槽索引中讀取需要注意除非您對文件ID做了特別的處理否則沒有理由將所有具有特定顏色或特定品牌的汽車放在同一個分割槽中。在[圖6-4](img/fig6-4.png)中紅色汽車出現在分割槽0和分割槽1中。因此如果要搜尋紅色汽車則需要將查詢傳送到所有分割槽併合並所有返回的結果。
但是從文件分割槽索引中讀取需要注意除非您對文件ID做了特別的處理否則沒有理由將所有具有特定顏色或特定品牌的汽車放在同一個分割槽中。在[圖6-4](../img/fig6-4.png)中紅色汽車出現在分割槽0和分割槽1中。因此如果要搜尋紅色汽車則需要將查詢傳送到所有分割槽併合並所有返回的結果。
這種查詢分割槽資料庫的方法有時被稱為**分散/聚集scatter/gather**,並且可能會使二級索引上的讀取查詢相當昂貴。即使並行查詢分割槽,分散/聚集也容易導致尾部延遲放大(參閱“[實踐中的百分位點](ch1.md#實踐中的百分位點)”。然而它被廣泛使用MongoDBRiak 【15】Cassandra 【16】Elasticsearch 【17】SolrCloud 【18】和VoltDB 【19】都使用文件分割槽二級索引。大多數資料庫供應商建議您構建一個能從單個分割槽提供二級索引查詢的分割槽方案但這並不總是可行尤其是當在單個查詢中使用多個二級索引時例如同時需要按顏色和製造商查詢
### 基於關鍵詞(Term)的二級索引進行分割槽
我們可以構建一個覆蓋所有分割槽資料的**全域性索引**,而不是給每個分割槽建立自己的次級索引(本地索引)。但是,我們不能只把這個索引儲存在一個節點上,因為它可能會成為瓶頸,違背了分割槽的目的。全域性索引也必須進行分割槽,但可以採用與主鍵不同的分割槽方式。
[圖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)
**圖6-5 基於關鍵詞對二級索引進行分割槽**
我們將這種索引稱為**關鍵詞分割槽term-partitioned**,因為我們尋找的關鍵詞決定了索引的分割槽方式。例如,一個關鍵詞可能是:`color:red`。**關鍵詞(Term)** 這個名稱來源於全文搜尋索引(一種特殊的次級索引),指文件中出現的所有單詞。
和之前一樣,我們可以透過**關鍵詞**本身或者它的雜湊進行索引分割槽。根據關鍵詞本身來分割槽對於範圍掃描非常有用(例如對於數值類的屬性,像汽車的報價),而對關鍵詞的雜湊分割槽提供了負載均衡的能力。
關鍵詞分割槽的全域性索引優於文件分割槽索引的地方點是它可以使讀取更有效率:不需要**分散/收集**所有分割槽,客戶端只需要向包含關鍵詞的分割槽發出請求。全域性索引的缺點在於寫入速度較慢且較為複雜,因為寫入單個文件現在可能會影響索引的多個分割槽(文件中的每個關鍵詞可能位於不同的分割槽或者不同的節點上) 。
理想情況下,索引總是最新的,寫入資料庫的每個文件都會立即反映在索引中。但是,在關鍵詞分割槽索引中,這需要跨分割槽的分散式事務,並不是所有資料庫都支援(請參閱[第7章](ch7.md)和[第9章](ch9.md))。
在實踐中,對全域性二級索引的更新通常是**非同步**的也就是說如果在寫入之後不久讀取索引剛才所做的更改可能尚未反映在索引中。例如Amazon DynamoDB聲稱在正常情況下其全域性次級索引會在不到一秒的時間內更新但在基礎架構出現故障的情況下可能會有延遲【20】。
全域性關鍵詞分割槽索引的其他用途包括Riak的搜尋功能【21】和Oracle資料倉庫它允許您在本地和全域性索引之間進行選擇【22】。我們將在[第12章](ch12.md)中繼續關鍵詞分割槽二級索引實現的話題。
## 分割槽再平衡
隨著時間的推移,資料庫會有各種變化:
* 查詢吞吐量增加所以您想要新增更多的CPU來處理負載。
* 資料集大小增加所以您想新增更多的磁碟和RAM來儲存它。
* 機器出現故障,其他機器需要接管故障機器的責任。
所有這些更改都需要資料和請求從一個節點移動到另一個節點。 將負載從叢集中的一個節點向另一個節點移動的過程稱為**再平衡rebalancing**。
無論使用哪種分割槽方案,再平衡通常都要滿足一些最低要求:
* 再平衡之後,負載(資料儲存,讀取和寫入請求)應該在叢集中的節點之間公平地共享。
* 再平衡發生時,資料庫應該繼續接受讀取和寫入。
* 節點之間只移動必須的資料以便快速再平衡並減少網路和磁碟I/O負載。
### 再平衡策略
有幾種不同的分割槽分配方法【23】,讓我們依次簡要討論一下。
#### 反面教材hash mod N
我們在前面說過([圖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
也許你想知道為什麼我們不使用 ***取模mod***(許多程式語言中的%運算子)。例如,`hash(key) mod 10`會返回一個介於0和9之間的數字如果我們將雜湊寫為十進位制數雜湊模10將是最後一個數字。如果我們有10個節點編號為0到9這似乎是將每個鍵分配給一個節點的簡單方法。
模N$mod N$方法的問題是如果節點數量N發生變化大多數金鑰將需要從一個節點移動到另一個節點。例如假設$hash(key)=123456$。如果最初有10個節點那麼這個鍵一開始放在節點6上因為$123456\ mod\ 10 = 6$。當您增長到11個節點時金鑰需要移動到節點3$123456\ mod\ 11 = 3$當您增長到12個節點時需要移動到節點0$123456\ mod\ 12 = 0$)。這種頻繁的舉動使得重新平衡過於昂貴。
我們需要一種只移動必需資料的方法。
#### 固定數量的分割槽
幸運的是有一個相當簡單的解決方案建立比節點更多的分割槽併為每個節點分配多個分割槽。例如執行在10個節點的叢集上的資料庫可能會從一開始就被拆分為1,000個分割槽因此大約有100個分割槽被分配給每個節點。
現在,如果一個節點被新增到叢集中,新節點可以從當前每個節點中**竊取**一些分割槽,直到分割槽再次公平分配。這個過程如[圖6-6](img/fig6-6.png)所示。如果從叢集中刪除一個節點,則會發生相反的情況。
現在,如果一個節點被新增到叢集中,新節點可以從當前每個節點中**竊取**一些分割槽,直到分割槽再次公平分配。這個過程如[圖6-6](../img/fig6-6.png)所示。如果從叢集中刪除一個節點,則會發生相反的情況。
只有分割槽在節點之間的移動。分割槽的數量不會改變,鍵所指定的分割槽也不會改變。唯一改變的是分割槽所在的節點。這種變更並不是即時的 — 在網路上傳輸大量的資料需要一些時間 — 所以在傳輸過程中,原有分割槽仍然會接受讀寫操作。
![](../img/fig6-6.png)
![](img/fig6-6.png)
**圖6-6 將新節點新增到每個節點具有多個分割槽的資料庫群集。**
**圖6-6 將新節點新增到每個節點具有多個分割槽的資料庫叢集。**
原則上您甚至可以解決叢集中的硬體不匹配問題透過為更強大的節點分配更多的分割槽可以強制這些節點承載更多的負載。在Riak 【15】Elasticsearch 【24】Couchbase 【10】和Voldemort 【25】中使用了這種再平衡的方法。
在這種配置中,分割槽的數量通常在資料庫第一次建立時確定,之後不會改變。雖然原則上可以分割和合並分割槽(請參閱下一節),但固定數量的分割槽在操作上更簡單,因此許多固定分割槽資料庫選擇不實施分割槽分割。因此,一開始配置的分割槽數就是您可以擁有的最大節點數量,所以您需要選擇足夠多的分割槽以適應未來的增長。但是,每個分割槽也有管理開銷,所以選擇太大的數字會適得其反。
如果資料集的總大小難以預估(例如,可能它開始很小,但隨著時間的推移會變得更大),選擇正確的分割槽數是困難的。由於每個分割槽包含了總資料量固定比率的資料,因此每個分割槽的大小與叢集中的資料總量成比例增長。如果分割槽非常大,再平衡和從節點故障恢復變得昂貴。但是,如果分割槽太小,則會產生太多的開銷。當分割槽大小“恰到好處”的時候才能獲得很好的效能,如果分割槽數量固定,但資料量變動很大,則難以達到最佳效能。
#### 動態分割槽
對於使用鍵範圍分割槽的資料庫(參閱“[根據鍵的範圍分割槽](#根據鍵的範圍分割槽)”),具有固定邊界的固定數量的分割槽將非常不便:如果出現邊界錯誤,則可能會導致一個分割槽中的所有資料或者其他分割槽中的所有資料為空。手動重新配置分割槽邊界將非常繁瑣。
出於這個原因按鍵的範圍進行分割槽的資料庫如HBase和RethinkDB會動態建立分割槽。當分割槽增長到超過配置的大小時在HBase上預設值是10GB會被分成兩個分割槽每個分割槽約佔一半的資料【26】。與之相反如果大量資料被刪除並且分割槽縮小到某個閾值以下則可以將其與相鄰分割槽合併。此過程與B樹頂層發生的過程類似參閱“[B樹](ch3.md#B樹)”)。
每個分割槽分配給一個節點每個節點可以處理多個分割槽就像固定數量的分割槽一樣。大型分割槽拆分後可以將其中的一半轉移到另一個節點以平衡負載。在HBase中分割槽檔案的傳輸透過HDFS底層使用的分散式檔案系統來實現【3】。
動態分割槽的一個優點是分割槽數量適應總資料量。如果只有少量的資料少量的分割槽就足夠了所以開銷很小如果有大量的資料每個分割槽的大小被限制在一個可配置的最大值【23】。
需要注意的是一個空的資料庫從一個分割槽開始因為沒有關於在哪裡繪製分割槽邊界的先驗資訊。資料集開始時很小直到達到第一個分割槽的分割點所有寫入操作都必須由單個節點處理而其他節點則處於空閒狀態。為了解決這個問題HBase和MongoDB允許在一個空的資料庫上配置一組初始分割槽這被稱為**預分割pre-splitting**。在鍵範圍分割槽的情況中預分割需要提前知道鍵是如何進行分配的【4,26】。
動態分割槽不僅適用於資料的範圍分割槽而且也適用於雜湊分割槽。從版本2.4開始MongoDB同時支援範圍和雜湊分割槽並且都支援動態分割分割槽。
#### 按節點比例分割槽
透過動態分割槽,分割槽的數量與資料集的大小成正比,因為拆分和合並過程將每個分割槽的大小保持在固定的最小值和最大值之間。另一方面,對於固定數量的分割槽,每個分割槽的大小與資料集的大小成正比。在這兩種情況下,分割槽的數量都與節點的數量無關。
Cassandra和Ketama使用的第三種方法是使分割槽數與節點數成正比——換句話說每個節點具有固定數量的分割槽【23,27,28】。在這種情況下每個分割槽的大小與資料集大小成比例地增長而節點數量保持不變但是當增加節點數時分割槽將再次變小。由於較大的資料量通常需要較大數量的節點進行儲存因此這種方法也使每個分割槽的大小較為穩定。
當一個新節點加入叢集時它隨機選擇固定數量的現有分割槽進行拆分然後佔有這些拆分分割槽中每個分割槽的一半同時將每個分割槽的另一半留在原地。隨機化可能會產生不公平的分割但是平均在更大數量的分割槽上時在Cassandra中預設情況下每個節點有256個分割槽新節點最終從現有節點獲得公平的負載份額。 Cassandra 3.0引入了另一種再平衡的演算法來避免不公平的分割【29】。
隨機選擇分割槽邊界要求使用基於雜湊的分割槽可以從雜湊函式產生的數字範圍中挑選邊界。實際上這種方法最符合一致性雜湊的原始定義【7】參閱“[一致性雜湊](#一致性雜湊)”。最新的雜湊函式可以在較低元資料開銷的情況下達到類似的效果【8】。
### 運維:手動還是自動再平衡
關於再平衡有一個重要問題:自動還是手動進行?
在全自動重新平衡系統自動決定何時將分割槽從一個節點移動到另一個節點無須人工干預和完全手動分割槽指派給節點由管理員明確配置僅在管理員明確重新配置時才會更改之間有一個權衡。例如CouchbaseRiak和Voldemort會自動生成建議的分割槽分配但需要管理員提交才能生效。
全自動重新平衡可以很方便,因為正常維護的操作工作較少。但是,這可能是不可預測的。再平衡是一個昂貴的操作,因為它需要重新路由請求並將大量資料從一個節點移動到另一個節點。如果沒有做好,這個過程可能會使網路或節點負載過重,降低其他請求的效能。
這種自動化與自動故障檢測相結合可能十分危險。例如,假設一個節點過載,並且對請求的響應暫時很慢。其他節點得出結論:過載的節點已經死亡,並自動重新平衡叢集,使負載離開它。這會對已經超負荷的節點,其他節點和網路造成額外的負載,從而使情況變得更糟,並可能導致級聯失敗。
出於這個原因,再平衡的過程中有人参與是一件好事。這比完全自動的過程慢,但可以幫助防止運維意外。
## 請求路由
現在我們已經將資料集分割到多個機器上執行的多個節點上。但是仍然存在一個懸而未決的問題當客戶想要發出請求時如何知道要連線哪個節點隨著分割槽重新平衡分割槽對節點的分配也發生變化。為了回答這個問題需要有人知曉這些變化如果我想讀或寫鍵“foo”需要連線哪個IP地址和埠號
這個問題可以概括為 **服務發現(service discovery)** 它不僅限於資料庫。任何可透過網路訪問的軟體都有這個問題特別是如果它的目標是高可用性在多臺機器上執行冗餘配置。許多公司已經編寫了自己的內部服務發現工具其中許多已經作為開源釋出【30】。
概括來說這個問題有幾種不同的方案如圖6-7所示:
1. 允許客戶聯絡任何節點(例如,透過**迴圈策略的負載均衡Round-Robin Load Balancer**)。如果該節點恰巧擁有請求的分割槽,則它可以直接處理該請求;否則,它將請求轉發到適當的節點,接收回復並傳遞給客戶端。
2. 首先將所有來自客戶端的請求傳送到路由層,它決定了應該處理請求的節點,並相應地轉發。此路由層本身不處理任何請求;它僅負責分割槽的負載均衡。
3. 要求客戶端知道分割槽和節點的分配。在這種情況下,客戶端可以直接連線到適當的節點,而不需要任何中介。
以上所有情況中的關鍵問題是:作出路由決策的元件(可能是節點之一,還是路由層或客戶端)如何瞭解分割槽-節點之間的分配關係變化?
![](img/fig6-7.png)
![](../img/fig6-7.png)
**圖6-7 將請求路由到正確節點的三種不同方式。**
這是一個具有挑戰性的問題,因為重要的是所有參與者都同意 - 否則請求將被髮送到錯誤的節點,得不到正確的處理。 在分散式系統中有達成共識的協議,但很難正確地實現(見[第9章](ch9.md))。
許多分散式資料系統都依賴於一個獨立的協調服務比如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)
**圖6-8 使用ZooKeeper跟蹤分割槽分配給節點。**
例如LinkedIn的Espresso使用Helix 【31】進行叢集管理依靠ZooKeeper實現瞭如[圖6-8](../img/fig6-8.png)所示的路由層。 HBaseSolrCloud和Kafka也使用ZooKeeper來跟蹤分割槽分配。 MongoDB具有類似的體系結構但它依賴於自己的**配置伺服器config server** 實現和mongos守護程序作為路由層。
例如LinkedIn的Espresso使用Helix 【31】進行叢集管理依靠ZooKeeper實現瞭如[圖6-8](img/fig6-8.png)所示的路由層。 HBaseSolrCloud和Kafka也使用ZooKeeper來跟蹤分割槽分配。 MongoDB具有類似的體系結構但它依賴於自己的**配置伺服器config server** 實現和mongos守護程序作為路由層。
Cassandra和Riak採取不同的方法他們在節點之間使用**流言協議gossip protocol** 來傳播群集狀態的變化。請求可以傳送到任意節點,該節點會轉發到包含所請求的分割槽的適當節點([圖6-7](img/fig6-7.png)中的方法1。這個模型在資料庫節點中增加了更多的複雜性但是避免了對像ZooKeeper這樣的外部協調服務的依賴。
Cassandra和Riak採取不同的方法他們在節點之間使用**流言協議gossip protocol** 來傳播叢集狀態的變化。請求可以傳送到任意節點,該節點會轉發到包含所請求的分割槽的適當節點([圖6-7](../img/fig6-7.png)中的方法1。這個模型在資料庫節點中增加了更多的複雜性但是避免了對像ZooKeeper這樣的外部協調服務的依賴。
Couchbase不會自動重新平衡這簡化了設計。通常情況下它配置了一個名為moxi的路由層它會從叢集節點了解路由變化【32】。
當使用路由層或向隨機節點發送請求時客戶端仍然需要找到要連線的IP地址。這些地址並不像分割槽的節點分佈變化的那麼快所以使用DNS通常就足夠了。
### 執行並行查詢
到目前為止,我們只關注讀取或寫入單個鍵的非常簡單的查詢(加上基於文件分割槽的二級索引場景下的分散/聚集查詢。這也是大多數NoSQL分散式資料儲存所支援的訪問層級。
然而,通常用於分析的**大規模並行處理MPP, Massively parallel processing** 關係型資料庫產品在其支援的查詢型別方面要複雜得多。一個典型的資料倉庫查詢包含多個連線,過濾,分組和聚合操作。 MPP查詢最佳化器將這個複雜的查詢分解成許多執行階段和分割槽其中許多可以在資料庫叢集的不同節點上並行執行。涉及掃描大規模資料集的查詢特別受益於這種並行執行。
資料倉庫查詢的快速並行執行是一個專門的話題,由於分析有很重要的商業意義,可以帶來很多利益。我們將在[第10章](ch10.md)討論並行查詢執行的一些技巧。有關並行資料庫中使用的技術的更詳細的概述請參閱參考文獻【1,33】。
## 本章小結
在本章中,我們探討了將大資料集劃分成更小的子集的不同方法。資料量非常大的時候,在單臺機器上儲存和處理不再可行,而分割槽則十分必要。分割槽的目標是在多臺機器上均勻分佈資料和查詢負載,避免出現熱點(負載不成比例的節點)。這需要選擇適合於您的資料的分割槽方案,並在將節點新增到叢集或從叢集刪除時進行分割槽再平衡。
我們討論了兩種主要的分割槽方法:
***鍵範圍分割槽***
其中鍵是有序的,並且分割槽擁有從某個最小值到某個最大值的所有鍵。排序的優勢在於可以進行有效的範圍查詢,但是如果應用程式經常訪問相鄰的鍵,則存在熱點的風險。
在這種方法中,當分割槽變得太大時,通常將分割槽分成兩個子分割槽,動態地再平衡分割槽。
***雜湊分割槽***
雜湊函式應用於每個鍵,分割槽擁有一定範圍的雜湊。這種方法破壞了鍵的排序,使得範圍查詢效率低下,但可以更均勻地分配負載。
透過雜湊進行分割槽時,通常先提前建立固定數量的分割槽,為每個節點分配多個分割槽,並在新增或刪除節點時將整個分割槽從一個節點移動到另一個節點。也可以使用動態分割槽。
兩種方法搭配使用也是可行的,例如使用複合主鍵:使用鍵的一部分來標識分割槽,而使用另一部分作為排序順序。
我們還討論了分割槽和二級索引之間的相互作用。次級索引也需要分割槽,有兩種方法:
* 基於文件分割槽(本地索引),其中二級索引儲存在與主鍵和值相同的分割槽中。這意味著只有一個分割槽需要在寫入時更新,但是讀取二級索引需要在所有分割槽之間進行分散/收集。
* 基於關鍵詞分割槽(全域性索引),其中二級索引存在不同的分割槽中。輔助索引中的條目可以包括來自主鍵的所有分割槽的記錄。當文件寫入時,需要更新多個分割槽中的二級索引;但是可以從單個分割槽中進行讀取。
最後,我們討論了將查詢路由到適當的分割槽的技術,從簡單的分割槽負載平衡到複雜的並行查詢執行引擎。
按照設計,多數情況下每個分割槽是獨立執行的 — 這就是分割槽資料庫可以伸縮到多臺機器的原因。但是,需要寫入多個分割槽的操作結果可能難以預料:例如,如果寫入一個分割槽成功,但另一個分割槽失敗,會發生什麼情況?我們將在下面的章節中討論這個問題。
參考文獻
--------------------
1. David J. DeWitt and Jim N. Gray: “[Parallel Database Systems: The Future of High Performance Database Systems](),”
*Communications of the ACM*, volume 35, number 6, pages 8598, June 1992. [doi:10.1145/129888.129894](http://dx.doi.org/10.1145/129888.129894)
2. Lars George: “[HBase vs. BigTable Comparison](http://www.larsgeorge.com/2009/11/hbase-vs-bigtable-comparison.html),” *larsgeorge.com*, November 2009.
3. “[The Apache HBase Reference Guide](https://hbase.apache.org/book/book.html),” Apache Software Foundation, *hbase.apache.org*, 2014.
4. MongoDB, Inc.: “[New Hash-Based Sharding Feature in MongoDB 2.4](http://blog.mongodb.org/post/47633823714/new-hash-based-sharding-feature-in-mongodb-24),” *blog.mongodb.org*, April 10, 2013.
5. Ikai Lan: “[App Engine Datastore Tip: Monotonically Increasing Values Are Bad](http://ikaisays.com/2011/01/25/app-engine-datastore-tip-monotonically-increasing-values-are-bad/),” *ikaisays.com*,
January 25, 2011.
6. Martin Kleppmann: “[Java's hashCode Is Not Safe for Distributed Systems](http://martin.kleppmann.com/2012/06/18/java-hashcode-unsafe-for-distributed-systems.html),” *martin.kleppmann.com*, June 18, 2012.
7. David Karger, Eric Lehman, Tom Leighton, et al.: “[Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web](http://www.akamai.com/dl/technical_publications/ConsistenHashingandRandomTreesDistributedCachingprotocolsforrelievingHotSpotsontheworldwideweb.pdf),” at *29th Annual ACM Symposium on Theory of Computing* (STOC), pages 654663, 1997. [doi:10.1145/258533.258660](http://dx.doi.org/10.1145/258533.258660)
8. John Lamping and Eric Veach: “[A Fast, Minimal Memory, Consistent Hash Algorithm](http://arxiv.org/pdf/1406.2294v1.pdf),” *arxiv.org*, June 2014.
9. Eric Redmond: “[A Little Riak Book](http://littleriakbook.com/),” Version 1.4.0, Basho Technologies, September 2013.
10. “[Couchbase 2.5 Administrator Guide](http://docs.couchbase.com/couchbase-manual-2.5/cb-admin/),” Couchbase, Inc., 2014.
11. Avinash Lakshman and Prashant Malik: “[Cassandra A Decentralized Structured Storage System](http://www.cs.cornell.edu/Projects/ladis2009/papers/Lakshman-ladis2009.PDF),” at *3rd ACM SIGOPS International Workshop on
Large Scale Distributed Systems and Middleware* (LADIS), October 2009.
12. Jonathan Ellis: “[Facebooks Cassandra Paper, Annotated and Compared to Apache Cassandra 2.0](http://www.datastax.com/documentation/articles/cassandra/cassandrathenandnow.html),”
*datastax.com*, September 12, 2013.
13. “[Introduction to Cassandra Query Language](http://www.datastax.com/documentation/cql/3.1/cql/cql_intro_c.html),” DataStax, Inc., 2014.
14. Samuel Axon: “[3% of Twitter's Servers Dedicated to Justin Bieber](http://mashable.com/2010/09/07/justin-bieber-twitter/),” *mashable.com*, September 7, 2010.
15. “[Riak 1.4.8 Docs](http://docs.basho.com/riak/1.4.8/),” Basho Technologies, Inc., 2014.
16. Richard Low: “[The Sweet Spot for Cassandra Secondary Indexing](http://www.wentnet.com/blog/?p=77),” *wentnet.com*, October 21, 2013.
17. Zachary Tong: “[Customizing Your Document Routing](http://www.elasticsearch.org/blog/customizing-your-document-routing/),” *elasticsearch.org*, June 3, 2013.
18. “[Apache Solr Reference Guide](https://cwiki.apache.org/confluence/display/solr/Apache+Solr+Reference+Guide),” Apache Software Foundation, 2014.
19. Andrew Pavlo: “[H-Store Frequently Asked Questions](http://hstore.cs.brown.edu/documentation/faq/),” *hstore.cs.brown.edu*, October 2013.
20. “[Amazon DynamoDB Developer Guide](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/),” Amazon Web Services, Inc., 2014.
21. Rusty Klophaus: “[Difference Between 2I and Search](http://lists.basho.com/pipermail/riak-users_lists.basho.com/2011-October/006220.html),” email to *riak-users* mailing list, *lists.basho.com*, October 25, 2011.
22. Donald K. Burleson: “[Object Partitioning in Oracle](http://www.dba-oracle.com/art_partit.htm),”*dba-oracle.com*, November 8, 2000.
23. Eric Evans: “[Rethinking Topology in Cassandra](http://www.slideshare.net/jericevans/virtual-nodes-rethinking-topology-in-cassandra),” at *ApacheCon Europe*, November 2012.
24. Rafał Kuć: “[Reroute API Explained](http://elasticsearchserverbook.com/reroute-api-explained/),” *elasticsearchserverbook.com*, September 30, 2013.
25. “[Project Voldemort Documentation](http://www.project-voldemort.com/voldemort/),” *project-voldemort.com*.
26. Enis Soztutar: “[Apache HBase Region Splitting and Merging](http://hortonworks.com/blog/apache-hbase-region-splitting-and-merging/),” *hortonworks.com*, February 1, 2013.
27. Brandon Williams: “[Virtual Nodes in Cassandra 1.2](http://www.datastax.com/dev/blog/virtual-nodes-in-cassandra-1-2),” *datastax.com*, December 4, 2012.
28. Richard Jones: “[libketama: Consistent Hashing Library for Memcached Clients](https://www.metabrew.com/article/libketama-consistent-hashing-algo-memcached-clients),” *metabrew.com*, April 10, 2007.
29. Branimir Lambov: “[New Token Allocation Algorithm in Cassandra 3.0](http://www.datastax.com/dev/blog/token-allocation-algorithm),” *datastax.com*, January 28, 2016.
30. Jason Wilder: “[Open-Source Service Discovery](http://jasonwilder.com/blog/2014/02/04/service-discovery-in-the-cloud/),” *jasonwilder.com*, February 2014.
31. Kishore Gopalakrishna, Shi Lu, Zhen Zhang, et al.: “[Untangling Cluster Management with Helix](http://www.socc2012.org/helix_onecol.pdf?attredirects=0),” at *ACM Symposium on Cloud Computing* (SoCC), October 2012.
[doi:10.1145/2391229.2391248](http://dx.doi.org/10.1145/2391229.2391248)
32. “[Moxi 1.8 Manual](http://docs.couchbase.com/moxi-manual-1.8/),” Couchbase, Inc., 2014.
33. Shivnath Babu and Herodotos Herodotou: “[Massively Parallel Databases and MapReduce Systems](http://research.microsoft.com/pubs/206464/db-mr-survey-final.pdf),” *Foundations and Trends in Databases*, volume 5, number 1, pages 1104, November 2013.[doi:10.1561/1900000036](http://dx.doi.org/10.1561/1900000036)
------
| 上一章 | 目錄 | 下一章 |
| :--------------------: | :-----------------------------: | :--------------------: |
| [第五章:複製](ch5.md) | [設計資料密集型應用](README.md) | [第七章:事務](ch7.md) |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +1,35 @@
# 後記
## 關於作者
**Martin Kleppmann**是英國劍橋大學分散式系統的研究員。此前他曾在網際網路公司擔任過軟體工程師和企業家其中包括LinkedIn和Rapportive負責大規模資料基礎架構。在這個過程中他以艱難的方式學習了一些東西他希望這本書能夠讓你避免重蹈覆轍。
Martin是一位常規會議演講者博主和開源貢獻者。他認為每個人都應該有深刻的技術理念深層次的理解能幫助我們開發出更好的軟體。
![](http://martin.kleppmann.com/2017/03/ddia-poster.jpg)
## 關於譯者
[馮若航](https://vonng.com/about)
PostgreSQL DBA @ TanTan
Alibaba+-Finplus 架構師/全棧工程師 (2015 ~ 2017)
## 後記
《設計資料密集型應用》封面上的動物是**印度野豬Sus scrofa cristatus**,它是在印度、緬甸、尼泊爾、斯里蘭卡和泰國發現的一種野豬的亞種。與歐洲野豬不同,它們有更高的背部鬃毛,沒有體表絨毛,以及更大更直的頭骨。
印度野豬有一頭灰色或黑色的頭髮脊背上有短而硬的毛。雄性有突出的犬齒稱為T用來與對手戰鬥或抵禦掠食者。雄性比雌性大這些物種平均肩高33-35英寸體重200-300磅。他們的天敵包括熊、老虎和各種大型貓科動物。
這些動物夜行且雜食——它們吃各種各樣的東西包括根、昆蟲、腐肉、堅果、漿果和小動物。野豬經常因為破壞農作物的根被人們所熟知他們造成大量的破壞並被農民所敵視。他們每天需要攝入4,000 ~ 4,500卡路里的能量。野豬有發達的嗅覺這有助於尋找地下植物和挖掘動物。然而它們的視力很差。
野豬在人類文化中一直具有重要意義。在印度教傳說中,野豬是毗溼奴神的化身。在古希臘的喪葬紀念碑中,它是一個勇敢失敗者的象徵(與勝利的獅子相反)。由於它的侵略,它被描繪在斯堪的納維亞、日耳曼和盎格魯撒克遜戰士的盔甲和武器上。在中國十二生肖中,它象徵著決心和急躁。
O'Reilly封面上的許多動物都受到威脅這些動物對世界都很重要。要了解有關如何提供幫助的更多資訊請訪問animals.oreilly.com。
封面圖片來自Shaw's Zoology。封面字型是URW Typewriter和Guardian Sans。文字字型是Adobe Minion Pro圖中的字型是Adobe Myriad Pro標題字型是Adobe Myriad Condensed程式碼字型是Dalton Maag的Ubuntu Mono。
封面圖片來自Shaw's Zoology。封面字型是URW Typewriter和Guardian Sans。文字字型是Adobe Minion Pro圖中的字型是Adobe Myriad Pro標題字型是Adobe Myriad Condensed程式碼字型是Dalton Maag的Ubuntu Mono。

View File

@ -1,753 +1,377 @@
# 術語表 【DRAFT】
> 請注意,本術語表中的定義簡短而簡單,旨在傳達核心思想,而不是術語的完整細微之處。 有關更多詳細資訊,請參閱正文中的參考資料。
[TOC]
### 非同步asynchronous
不等待某些事情完成(例如,將資料傳送到網路中的另一個節點),並且不會假設要花多長時間。請參閱[同步複製與非同步複製](ch5.md#同步複製與非同步複製)”,“[同步網路與非同步網路](ch8.md#同步網路與非同步網路)”,以及“[系統模型與現實](ch8.md#系統模型與現實)”。
### 原子atomic
1.在併發操作的上下文中:描述一個在單個時間點看起來生效的操作,所以另一個併發程序永遠不會遇到處於“半完成”狀態的操作。另見隔離。
2.在事務的上下文中:將一些寫入操作分為一組,這組寫入要麼全部提交成功,要麼遇到錯誤時全部回滾。參見“[原子性Atomicity](ch7.md#原子性Atomicity)”和“[原子提交與二階段提交2PC](ch9.md#原子提交與二階段提交2PC)”。
### 背壓backpressure
接收方接收資料速度較慢時,強制降低傳送方的資料傳送速度。也稱為流量控制。請參閱“[訊息系統](ch11.md#訊息系統)”。
### 批處理batch process
一種計算,它將一些固定的(通常是大的)資料集作為輸入,並將其他一些資料作為輸出,而不修改輸入。見[第十章](ch10.md)。
### 邊界bounded
有一些已知的上限或大小。例如,網路延遲情況(請參閱“[超時與無窮的延遲](ch8.md#超時與無窮的延遲)”)和資料集(請參閱[第11章](ch11.md)的介紹)。
### 拜占庭故障Byzantine fault
表現異常的節點,這種異常可能以任意方式出現,例如向其他節點發送矛盾或惡意訊息。請參閱“[拜占庭故障](ch8.md#拜占庭故障)”。
### 快取cache
一種元件,透過儲存最近使用過的資料,加快未來對相同資料的讀取速度。快取中通常存放部分資料:因此,如果快取中缺少某些資料,則必須從某些底層較慢的資料儲存系統中,獲取完整的資料副本。
### CAP定理CAP theorem
一個被廣泛誤解的理論結果,在實踐中是沒有用的。參見“[CAP定理](ch9.md#CAP定理)”。
### 因果關係causality
事件之間的依賴關係,當一件事發生在另一件事情之前。例如,後面的事件是對早期事件的迴應,或者依賴於更早的事件,或者應該根據先前的事件來理解。請參閱“[“此前發生”的關係和併發](ch5.md#“此前發生”的關係和併發)”和“[順序與因果關係](ch5.md#順序與因果關係)”。
### 共識consensus
分散式計算的一個基本問題,就是讓幾個節點同意某些事情(例如,哪個節點應該是資料庫叢集的領導者)。問題比乍看起來要困難得多。請參閱“[容錯共識](ch9.md#容錯共識)”。
### 資料倉庫data warehouse
一個數據庫其中來自幾個不同的OLTP系統的資料已經被合併和準備用於分析目的。請參閱“[資料倉庫](ch3.md#資料倉庫)”。
### 宣告式declarative
描述某些東西應有的屬性,但不知道如何實現它的確切步驟。在查詢的上下文中,查詢最佳化器採用宣告性查詢並決定如何最好地執行它。請參閱“[資料查詢語言](ch2.md#資料查詢語言)”。
### 非規範化denormalize
為了加速讀取,在標準資料集中引入一些冗餘或重複資料,通常採用快取或索引的形式。非規範化的值是一種預先計算的查詢結果,像物化檢視。請參見“[單物件和多物件操作](ch7.md#單物件和多物件操作)”和“[從同一事件日誌中派生多個檢視](ch11.md#從同一事件日誌中派生多個檢視)”。
### 衍生資料derived data
一種資料集,根據其他資料透過可重複執行的流程建立。必要時,你可以執行該流程再次建立衍生資料。衍生資料通常用於提高特定資料的讀取速度。常見的衍生資料有索引、快取和物化檢視。參見[第三部分](part-iii.md)的介紹。
### 確定性deterministic
描述一個函式,如果給它相同的輸入,則總是產生相同的輸出。這意味著它不能依賴於隨機數字、時間、網路通訊或其他不可預測的事情。
### 分散式distributed
在由網路連線的多個節點上執行。對於部分節點故障,具有容錯性:系統的一部分發生故障時,其他部分仍可以正常工作,通常情況下,軟體無需瞭解故障相關的確切情況。請參閱“[故障與部分失效](ch8.md#故障與部分失效)”。
### 持久durable
以某種方式儲存資料,即使發生各種故障,也不會丟失資料。請參閱“[永續性Durability](ch7.md#永續性Durability)”。
### ETLExtract-Transform-Load
提取-轉換-載入Extract-Transform-Load。從源資料庫中提取資料將其轉換為更適合分析查詢的形式並將其載入到資料倉庫或批處理系統中的過程。請參閱“[資料倉庫](ch3.md#資料倉庫)”。
### 故障切換failover
在具有單一領導者的系統中,故障切換是將領導角色從一個節點轉移到另一個節點的過程。請參閱“[處理節點宕機](ch5.md#處理節點宕機)”。
### 容錯fault-tolerant
如果出現問題(例如,機器崩潰或網路連線失敗),可以自動恢復。請參閱“[可靠性](ch1.md#可靠性)”。
### 流量控制flow control
見背壓backpressure
### 追隨者follower
一種資料副本,僅處理領導者發出的資料變更,不直接接受來自客戶端的任何寫入。也稱為輔助、僕從、只讀副本或熱備份。請參閱“[領導者與追隨者](ch5.md#領導者與追隨者)”。
### 全文檢索full-text search
透過任意關鍵字來搜尋文字,通常具有附加特徵,例如匹配類似的拼寫詞或同義詞。全文索引是一種支援這種查詢的次級索引。請參閱“[全文搜尋和模糊索引](ch3.md#全文搜尋和模糊索引)”。
### 圖graph
一種資料結構,由頂點(可以指向的東西,也稱為節點或實體)和邊(從一個頂點到另一個頂點的連線,也稱為關係或弧)組成。請參閱“[圖資料模型](ch2.md#圖資料模型)”。
一種資料結構,由頂點(可以指向的東西,也稱為節點或實體)和邊(從一個頂點到另一個頂點的連線,也稱為關係或弧)組成。請參閱“[圖資料模型](ch2.md#圖資料模型)”。
### 雜湊hash
將輸入轉換為看起來像隨機數值的函式。相同的輸入會轉換為相同的數值,不同的輸入一般會轉換為不同的數值,也可能轉換為相同數值(也被稱為衝突)。請參閱“[根據鍵的雜湊分割槽](ch6.md#根據鍵的雜湊分割槽)”。
### 冪等idempotent
用於描述一種操作可以安全地重試執行,即執行多次的效果和執行一次的效果相同。請參閱“[冪等性](ch11.md#冪等性)”。
### 索引index
一種資料結構。透過索引,你可以根據特定欄位的值,在所有資料記錄中進行高效檢索。請參閱“[驅動資料庫的資料結構](ch3.md#驅動資料庫的資料結構)”。
### 隔離性isolation
在事務上下文中,用於描述併發執行事務的互相干擾程度。序列執行具有最強的隔離性,不過其它程度的隔離也通常被使用。請參閱“[隔離性Isolation](ch7.md#隔離性Isolation)”。
### 連線join
彙集有共同點的記錄。在一個記錄與另一個記錄有關(外來鍵,文件參考,圖中的邊)的情況下最常用,查詢需要獲取參考所指向的記錄。請參閱“[多對一和多對多的關係](ch2.md#多對一和多對多的關係)”和“[Reduce側連線與分組](ch10.md#Reduce側連線與分組)”。
### 領導者leader
當資料或服務被複制到多個節點時,由領導者分發已授權變更的資料副本。領導者可以透過某些協議選舉產生,也可以由管理者手動選擇。也被稱為主人。請參閱“[領導者與追隨者](ch5.md#領導者與追隨者)”。
### 線性化linearizable
表現為系統中只有一份透過原子操作更新的資料副本。請參閱“[線性一致性](ch9.md#線性一致性)”。
### 區域性性locality
一種效能最佳化方式,如果經常在相同的時間請求一些離散資料,把這些資料放到一個位置。請參閱“[查詢的資料區域性性](ch2.md#查詢的資料區域性性)”。
### 鎖lock
一種保證只有一個執行緒、節點或事務可以訪問的機制,如果其它執行緒、節點或事務想訪問相同元素,則必須等待鎖被釋放。請參閱“[兩階段鎖定2PL](ch7.md#兩階段鎖定2PL)”和“[領導者和鎖](ch8.md#領導者和鎖)”。
### 日誌log
日誌是一個只能以追加方式寫入的檔案,用於存放資料。預寫式日誌用於在儲存引擎崩潰時恢復資料(請參閱“[讓B樹更可靠](ch3.md#讓B樹更可靠)”);結構化日誌儲存引擎使用日誌作為它的主要儲存格式(請參閱“[SSTables和LSM樹](ch3.md#SSTables和LSM樹)”);複製型日誌用於把寫入從領導者複製到追隨者(請參閱“[領導者與追隨者](ch5.md#領導者與追隨者)”);事件性日誌可以表現為資料流(請參閱“[分割槽日誌](ch11.md#分割槽日誌)”)。
### 物化materialize
急切地計算並寫出結果,而不是在請求時計算。請參閱“[聚合:資料立方體和物化檢視](ch3.md#聚合:資料立方體和物化檢視)”和“[物化中間狀態](ch10.md#物化中間狀態)”。
### 節點node
計算機上執行的一些軟體的例項,透過網路與其他節點通訊以完成某項任務。
### 規範化normalized
以沒有冗餘或重複的方式進行結構化。 在規範化資料庫中,當某些資料發生變化時,您只需要在一個地方進行更改,而不是在許多不同的地方複製很多次。 請參閱“[多對一和多對多的關係](ch2.md#多對一和多對多的關係)”。
### OLAPOnline Analytic Processing
線上分析處理。 透過對大量記錄進行聚合(例如,計數,總和,平均)來表徵的訪問模式。 請參閱“[事務處理還是分析?](ch3.md#事務處理還是分析?)”。
### OLTPOnline Transaction Processing
線上事務處理。 訪問模式的特點是快速查詢,讀取或寫入少量記錄,這些記錄通常透過鍵索引。 請參閱“[事務處理還是分析?](ch3.md#事務處理還是分析?)”。
### 分割槽partitioning
將單機上的大型資料集或計算結果拆分為較小部分,並將其分佈到多臺機器上。 也稱為分片。見[第6章](ch6.md)。
### 百分位點percentile
透過計算有多少值高於或低於某個閾值來衡量值分佈的方法。 例如某個時間段的第95個百分位響應時間是時間t則該時間段中95的請求完成時間小於t5的請求完成時間要比t長。 請參閱“[描述效能](ch1.md#描述效能)”。
### 主鍵primary key
唯一標識記錄的值(通常是數字或字串)。 在許多應用程式中,主鍵由系統在建立記錄時生成(例如,按順序或隨機); 它們通常不由使用者設定。 另請參閱二級索引。
### 法定人數quorum
在操作完成之前,需要對操作進行投票的最少節點數量。 請參閱“[讀寫的法定人數](ch5.md#讀寫的法定人數)”。
### 再平衡rebalance
將資料或服務從一個節點移動到另一個節點以實現負載均衡。 請參閱“[分割槽再平衡](ch6.md#分割槽再平衡)”。
### 複製replication
在幾個節點(副本)上保留相同資料的副本,以便在某些節點無法訪問時,資料仍可訪問。請參閱[第5章](ch5.md)。
### 模式schema
一些資料結構的描述,包括其欄位和資料型別。 可以在資料生命週期的不同點檢查某些資料是否符合模式(請參閱“[文件模型中的模式靈活性](ch2.md#文件模型中的模式靈活性)”),模式可以隨時間變化(請參閱[第4章](ch4.md))。
### 次級索引secondary index
與主要資料儲存器一起維護的附加資料結構,使您可以高效地搜尋與某種條件相匹配的記錄。 請參閱“[其他索引結構](ch3.md#其他索引結構)”和“[分割槽與次級索引](ch6.md#分割槽與次級索引)”。
### 可序列化serializable
保證多個併發事務同時執行時,它們的行為與按順序逐個執行事務相同。 請參閱第7章的“[可序列化](ch7.md#可序列化)”。
### 無共享shared-nothing
與共享記憶體或共享磁碟架構相比獨立節點每個節點都有自己的CPU記憶體和磁碟透過傳統網路連線。 見[第二部分](part-ii.md)的介紹。
### 偏斜skew
1.各分割槽負載不平衡,例如某些分割槽有大量請求或資料,而其他分割槽則少得多。也被稱為熱點。請參閱“[負載偏斜和熱點消除](ch6.md#負載偏斜和熱點消除)”和“[處理偏斜](ch10.md#處理偏斜)”。
2.時間線異常導致事件以不期望的順序出現。 請參閱“[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)”中的關於讀取偏斜的討論,“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”中的寫入偏斜以及“[有序事件的時間戳](ch8.md#有序事件的時間戳)”中的時鐘偏斜。
### 腦裂split brain
兩個節點同時認為自己是領導者的情況,這種情況可能違反系統擔保。 請參閱“[處理節點宕機](ch5.md#處理節點宕機)”和“[真相由多數所定義](ch8.md#真相由多數所定義)”。
### 儲存過程stored procedure
一種對事務邏輯進行編碼的方式,它可以完全在資料庫伺服器上執行,事務執行期間無需與客戶端通訊。 請參閱“[真的序列執行](ch7.md#真的序列執行)”。
### 流處理stream process
持續執行的計算。可以持續接收事件流作為輸入,並得出一些輸出。 見[第11章](ch11.md)。
### 同步synchronous
非同步的反義詞。
### 記錄系統system of record
一個儲存主要權威版本資料的系統,也被稱為真相的來源。首先在這裡寫入資料變更,其他資料集可以從記錄系統衍生。 參見[第三部分](part-iii.md)的介紹。
### 超時timeout
檢測故障的最簡單方法之一,即在一段時間內觀察是否缺乏響應。 但是,不可能知道超時是由於遠端節點的問題還是網路中的問題造成的。 請參閱“[超時與無窮的延遲](ch8.md#超時與無窮的延遲)”。
### 全序total order
一種比較事物的方法(例如時間戳),可以讓您總是說出兩件事中哪一件更大,哪件更小。 總的來說,有些東西是無法比擬的(不能說哪個更大或更小)的順序稱為偏序。 請參見“[因果順序不是全序的](ch9.md#因果順序不是全序的)”。
### 事務transaction
為了簡化錯誤處理和併發問題,將幾個讀寫操作分組到一個邏輯單元中。 見[第7章](ch7.md)。
### 兩階段提交2PC, two-phase commit
一種確保多個數據庫節點全部提交或全部中止事務的演算法。 請參閱[原子提交與二階段提交2PC](ch9.md#原子提交與二階段提交2PC)”。
### 兩階段鎖定2PL, two-phase locking
一種用於實現可序列化隔離的演算法,該演算法透過事務獲取對其讀取或寫入的所有資料的鎖,直到事務結束。 請參閱“[兩階段鎖定2PL](ch7.md#兩階段鎖定2PL)”。
### 無邊界unbounded
沒有任何已知的上限或大小。 反義詞是邊界bounded

View File

@ -1,57 +1,29 @@
# 第一部分:資料系統的基石
# 第一部分:資料系統的基石
本書前四章介紹了資料系統底層的基礎概念,無論是在單臺機器上執行的單點資料系統,還是分佈在多臺機器上的分散式資料系統都適用。
1. [第一章](ch1.md)將介紹本書使用的術語和方法。**可靠性,可伸縮性和可維護性** ,這些詞彙到底意味著什麼?如何實現這些目標?
2. [第二章](ch2.md)將對幾種不同的**資料模型和查詢語言**進行比較。從程式設計師的角度看,這是資料庫之間最明顯的區別。不同的資料模型適用於不同的應用場景。
3. [第三章](ch3.md)將深入**儲存引擎**內部,研究資料庫如何在磁碟上擺放資料。不同的儲存引擎針對不同的負載進行最佳化,選擇合適的儲存引擎對系統性能有巨大影響。
4. [第四章](ch4)將對幾種不同的 **資料編碼**進行比較。特別研究了這些格式在應用需求經常變化、模式需要隨時間演變的環境中表現如何。
第二部分將專門討論在**分散式資料系統**中特有的問題。
## 目錄
1. [可靠性、可伸縮性、可維護性](ch1.md)
2. [資料模型與查詢語言](ch2.md)
3. [儲存與檢索](ch3.md)
4. [編碼與演化](ch4.md)
4. [編碼與演化](ch4.md)
------
| 上一章 | 目錄 | 下一章 |
| ------------------ | ------------------------------- | -------------------------------------------- |
| [序言](preface.md) | [設計資料密集型應用](README.md) | [第一章:可靠性、可伸縮性、可維護性](ch1.md) |
| [序言](preface.md) | [設計資料密集型應用](README.md) | [第一章:可靠性、可伸縮性、可維護性](ch1.md) |

View File

@ -1,168 +1,83 @@
# 第二部分:分散式資料
# 第二部分:分散式資料
> 一個成功的技術,現實的優先順序必須高於公關,你可以糊弄別人,但糊弄不了自然規律。
>
> ——羅傑斯委員會報告1986
>
-------
在本書的[第一部分](part-i.md)中,我們討論了資料系統的各個方面,但僅限於資料儲存在單臺機器上的情況。現在我們到了[第二部分](part-ii.md),進入更高的層次,並提出一個問題:如果**多臺機器**參與資料的儲存和檢索,會發生什麼?
你可能會出於各種各樣的原因,希望將資料庫分佈到多臺機器上:
***可伸縮性***
如果你的資料量、讀取負載、寫入負載超出單臺機器的處理能力,可以將負載分散到多臺計算機上。
***容錯/高可用性***
如果你的應用需要在單臺機器(或多臺機器,網路或整個資料中心)出現故障的情況下仍然能繼續工作,則可使用多臺機器,以提供冗餘。一臺故障時,另一臺可以接管。
***延遲***
如果在世界各地都有使用者,你也許會考慮在全球範圍部署多個伺服器,從而每個使用者可以從地理上最近的資料中心獲取服務,避免了等待網路資料包穿越半個世界。
## 伸縮至更高的載荷
如果你需要的只是伸縮至更高的**載荷load**,最簡單的方法就是購買更強大的機器(有時稱為**垂直伸縮vertical scaling**或**向上伸縮scale up**)。許多處理器,記憶體和磁碟可以在同一個作業系統下相互連線,快速的相互連線允許任意處理器訪問記憶體或磁碟的任意部分。在這種 **共享記憶體架構shared-memory architecture** 中,所有的元件都可以看作一臺單獨的機器[^i]。
[^i]: 在大型機中,儘管任意處理器都可以訪問記憶體的任意部分,但總有一些記憶體區域與一些處理器更接近(稱為**非均勻記憶體訪問nonuniform memory access, NUMA**【1】。 為了有效利用這種架構特性,需要對處理進行細分,以便每個處理器主要訪問臨近的記憶體,這意味著即使表面上看起來只有一臺機器在執行,**分割槽partitioning**仍然是必要的。
共享記憶體方法的問題在於,成本增長速度快於線性增長:一臺有著雙倍處理器數量,雙倍記憶體大小,雙倍磁碟容量的機器,通常成本會遠遠超過原來的兩倍。而且可能因為存在瓶頸,並不足以處理雙倍的載荷。
共享記憶體架構可以提供有限的容錯能力,高階機器可以使用熱插拔的元件(不關機更換磁碟,記憶體模組,甚至處理器)——但它必然囿於單個地理位置的桎梏。
另一種方法是**共享磁碟架構shared-disk architecture**,它使用多臺具有獨立處理器和記憶體的機器,但將資料儲存在機器之間共享的磁碟陣列上,這些磁碟透過快速網路連線[^ii]。這種架構用於某些資料倉庫但競爭和鎖定的開銷限制了共享磁碟方法的可伸縮性【2】。
[^ii]: 網路附屬儲存Network Attached Storage, NAS或**儲存區網路Storage Area Network, SAN**
### 無共享架構
相比之下,**無共享架構shared-nothing architecture**(有時稱為**水平伸縮horizontal scale** 或**向外伸縮scale out**)已經相當普及。在這種架構中,執行資料庫軟體的每臺機器/虛擬機器都稱為**節點node**。每個節點只使用各自的處理器,記憶體和磁碟。節點之間的任何協調,都是在軟體層面使用傳統網路實現的。
無共享系統不需要使用特殊的硬體所以你可以用任意機器——比如價效比最好的機器。你也許可以跨多個地理區域分佈資料從而減少使用者延遲或者在損失一整個資料中心的情況下倖免於難。隨著雲端虛擬機器部署的出現即使是小公司現在無需Google級別的運維也可以實現異地分散式架構。
在這一部分裡,我們將重點放在無共享架構上。它不見得是所有場景的最佳選擇,但它是最需要你謹慎從事的架構。如果你的資料分佈在多個節點上,你需要意識到這樣一個分散式系統中約束和權衡 ——資料庫並不能魔術般地把這些東西隱藏起來。
雖然分散式無共享架構有許多優點但它通常也會給應用帶來額外的複雜度有時也會限制你可用資料模型的表達力。在某些情況下一個簡單的單執行緒程式可以比一個擁有超過100個CPU核的叢集表現得更好【4】。另一方面無共享系統可以非常強大。接下來的幾章將詳細討論分散式資料會帶來的問題。
### 複製 vs 分割槽
資料分佈在多個節點上有兩種常見的方式:
***複製Replication***
在幾個不同的節點上儲存資料的相同副本,可能放在不同的位置。 複製提供了冗餘:如果一些節點不可用,剩餘的節點仍然可以提供資料服務。 複製也有助於改善效能。 [第五章](ch5.md)將討論複製。
***分割槽 (Partitioning)***
將一個大型資料庫拆分成較小的子集(稱為**分割槽partitions**),從而不同的分割槽可以指派給不同的**節點node**(亦稱**分片shard**)。 [第六章](ch6.md)將討論分割槽。
複製和分割槽是不同的機制,但它們經常同時使用。如[圖II-1](../img/figii-1.png)所示。
複製和分割槽是不同的機制,但它們經常同時使用。如[圖II-1](img/figii-1.png)所示。
![](img/figii-1.png)
![](../img/figii-1.png)
**圖II-1 一個數據庫切分為兩個分割槽,每個分割槽都有兩個副本**
理解了這些概念,就可以開始討論在分散式系統中需要做出的困難抉擇。[第七章](ch7.md)將討論**事務(Transaction)**,這對於瞭解資料系統中可能出現的各種問題,以及我們可以做些什麼很有幫助。[第八章](ch8.md)和[第九章](ch9.md)將討論分散式系統的根本侷限性。
在本書的[第三部分](part-iii.md)中,將討論如何將多個(可能是分散式的)資料儲存整合為一個更大的系統,以滿足複雜的應用需求。 但首先,我們來聊聊分散式的資料。
## 索引
5. [複製](ch5.md)
6. [分片](ch6.md)
7. [事務](ch7.md)
8. [分散式系統的麻煩](ch8.md)
9. [一致性與共識](ch9.md)
6. [分片](ch6.md)
7. [事務](ch7.md)
8. [分散式系統的麻煩](ch8.md)
9. [一致性與共識](ch9.md)
@ -170,30 +85,16 @@
## 參考文獻
1. Ulrich Drepper: “[What Every Programmer Should Know About Memory](https://people.freebsd.org/~lstewart/articles/cpumemory.pdf),” akkadia.org, November 21, 2007.
2. Ben Stopford: “[Shared Nothing vs. Shared Disk Architectures: An Independent View](http://www.benstopford.com/2009/11/24/understanding-the-shared-nothing-architecture/),” benstopford.com, November 24, 2009.
3. Michael Stonebraker: “[The Case for Shared Nothing](http://db.cs.berkeley.edu/papers/hpts85-nothing.pdf),” IEEE Database EngineeringBulletin, volume 9, number 1, pages 49, March 1986.
4. Frank McSherry, Michael Isard, and Derek G. Murray: “[Scalability! But at What COST?](http://www.frankmcsherry.org/assets/COST.pdf),” at 15th USENIX Workshop on Hot Topics in Operating Systems (HotOS),May 2015.
------
| 上一章 | 目錄 | 下一章 |
| ---------------------------- | ------------------------------- | ---------------------- |
| [第四章:編碼與演化](ch4.md) | [設計資料密集型應用](README.md) | [第五章:複製](ch5.md) |

View File

@ -1,87 +1,44 @@
# 第三部分:衍生資料
在本書的[第一部分](part-i.md)和[第二部分](part-ii.md)中,我們自底向上地把所有關於分散式資料庫的主要考量都過了一遍。從資料在磁碟上的佈局,一直到出現故障時分散式系統一致性的侷限。但所有的討論都假定了應用中只用了一種資料庫。
現實世界中的資料系統往往更為複雜。大型應用程式經常需要以多種方式訪問和處理資料,沒有一個數據庫可以同時滿足所有這些不同的需求。因此應用程式通常組合使用多種元件:資料儲存,索引,快取,分析系統,等等,並實現在這些元件中移動資料的機制。
本書的最後一部分,會研究將多個不同資料系統(可能有著不同資料模型,並針對不同的訪問模式進行最佳化)整合為一個協調一致的應用架構時,會遇到的問題。軟體供應商經常會忽略這一方面的生態建設,並聲稱他們的產品能夠滿足你的所有需求。在現實世界中,整合不同的系統是實際應用中最重要的事情之一。
## 記錄系統和衍生資料系統
從高層次上看,儲存和處理資料的系統可以分為兩大類:
***記錄系統System of record***
**記錄系統**,也被稱為**真相源source of truth**,持有資料的權威版本。當新的資料進入時(例如,使用者輸入)首先會記錄在這裡。每個事實正正好好表示一次(表示通常是**正規化的normalized**)。如果其他系統和**記錄系統**之間存在任何差異,那麼記錄系統中的值是正確的(根據定義)。
***衍生資料系統Derived data systems***
**衍生系統**中的資料,通常是另一個系統中的現有資料以某種方式進行轉換或處理的結果。如果丟失衍生資料,可以從原始來源重新建立。典型的例子是**快取cache**:如果資料在快取中,就可以由快取提供服務;如果快取不包含所需資料,則降級由底層資料庫提供。非規範化的值,索引和物化檢視亦屬此類。在推薦系統中,預測彙總資料通常衍生自使用者日誌。
從技術上講,衍生資料是**冗餘的redundant**,因為它重複了已有的資訊。但是衍生資料對於獲得良好的只讀查詢效能通常是至關重要的。它通常是非規範化的。可以從單個源頭衍生出多個不同的資料集,使你能從不同的“視角”洞察資料。
並不是所有的系統都在其架構中明確區分**記錄系統**和**衍生資料系統**,但是這是一種有用的區分方式,因為它明確了系統中的資料流:系統的哪一部分具有哪些輸入和哪些輸出,以及它們如何相互依賴。
大多數資料庫,儲存引擎和查詢語言,本質上既不是記錄系統也不是衍生系統。資料庫只是一個工具:如何使用它取決於你自己。**記錄系統和衍生資料系統之間的區別不在於工具,而在於應用程式中的使用方式。**
透過梳理資料的衍生關係,可以清楚地理解一個令人困惑的系統架構。這將貫穿本書的這一部分。
## 章節概述
我們將從[第十章](ch10.md)開始研究例如MapReduce這樣 **面向批處理batch-oriented** 的資料流系統。對於建設大規模資料系統,我們將看到,它們提供了優秀的工具和思想。[第十一章](ch11.md)將把這些思想應用到 **流式資料data streams** 中,使我們能用更低的延遲完成同樣的任務。[第十二章](ch12.md)將對本書進行總結,探討如何使用這些工具來構建可靠,可伸縮和可維護的應用。
## 索引
10. [批處理](ch10.md)
11. [流處理](ch11.md)
12. [資料系統的未來](ch12.md)
------
| 上一章 | 目錄 | 下一章 |
| ------------------------------ | ------------------------------- | ------------------------- |
| [第九章:一致性與共識](ch9.md) | [設計資料密集型應用](README.md) | [第十章:批處理](ch10.md) |

View File

@ -1,203 +1,102 @@
# 序言
如果近幾年從業於軟體工程,特別是伺服器端和後端系統開發,那麼您很有可能已經被大量關於資料儲存和處理的時髦詞彙轟炸過了: NoSQL大資料Web-Scale分片最終一致性ACID CAP定理雲服務MapReduce實時
在最近十年中,我們看到了很多有趣的進展,關於資料庫,分散式系統,以及在此基礎上構建應用程式的方式。這些進展有著各種各樣的驅動力:
* 谷歌,雅虎,亞馬遜,臉書,領英,微軟和推特等網際網路公司正在和巨大的流量/資料打交道,這迫使他們去創造能有效應對如此規模的新工具。
* 企業需要變得敏捷,需要低成本地檢驗假設,需要透過縮短開發週期和保持資料模型的靈活性,快速地響應新的市場洞察。
* 免費和開源軟體變得非常成功,在許多環境中比商業軟體和定製軟體更受歡迎。
* 處理器主頻幾乎沒有增長,但是多核處理器已經成為標配,網路也越來越快。這意味著並行化程度只增不減。
* 即使您在一個小團隊中工作現在也可以構建分佈在多臺計算機甚至多個地理區域的系統這要歸功於譬如亞馬遜網路服務AWS等基礎設施即服務IaaS概念的踐行者。
* 許多服務都要求高可用,因停電或維護導致的服務不可用,變得越來越難以接受。
**資料密集型應用data-intensive applications**正在透過使用這些技術進步來推動可能性的邊界。一個應用被稱為**資料密集型**的,如果**資料是其主要挑戰**(資料量,資料複雜度或資料變化速度)—— 與之相對的是**計算密集型**,即處理器速度是其瓶頸。
幫助資料密集型應用儲存和處理資料的工具與技術正迅速地適應這些變化。新型資料庫系統“NoSQL”已經備受關注而訊息佇列快取搜尋索引批處理和流處理框架以及相關技術也非常重要。很多應用組合使用這些工具與技術。
這些生意盎然的時髦詞彙體現出人們對新的可能性的熱情,這是一件好事。但是作為軟體工程師和架構師,如果要開發優秀的應用,我們還需要對各種層出不窮的技術及其利弊權衡有精準的技術理解。為了獲得這種洞察,我們需要深挖時髦詞彙背後的內容。
幸運的是,在技術迅速變化的背後總是存在一些持續成立的原則,無論您使用了特定工具的哪個版本。如果您理解了這些原則,就可以領會這些工具的適用場景,如何充分利用它們,以及如何避免其中的陷阱。這正是本書的初衷。
本書的目標是幫助您在飛速變化的資料處理和資料儲存技術大觀園中找到方向。本書並不是某個特定工具的教程,也不是一本充滿枯燥理論的教科書。相反,我們將看到一些成功資料系統的樣例:許多流行應用每天都要在生產中滿足可伸縮性、效能、以及可靠性的要求,而這些技術構成了這些應用的基礎。
我們將深入這些系統的內部,理清它們的關鍵演算法,討論背後的原則和它們必須做出的權衡。在這個過程中,我們將嘗試尋找**思考**資料系統的有效方式 —— 不僅關於它們**如何**工作,還包括它們**為什麼**以這種方式工作,以及哪些問題是我們需要問的。
閱讀本書後,你能很好地決定哪種技術適合哪種用途,並瞭解如何將工具組合起來,為一個良好應用架構奠定基礎。本書並不足以使你從頭開始構建自己的資料庫儲存引擎,不過幸運的是這基本上很少有必要。你將獲得對系統底層發生事情的敏銳直覺,這樣你就有能力推理它們的行為,做出優秀的設計決策,並追蹤任何可能出現的問題。
## 本書的目標讀者
如果你開發的應用具有用於儲存或處理資料的某種伺服器/後端系統而且使用網路例如Web應用移動應用或連線到網際網路的感測器那麼本書就是為你準備的。
本書是為軟體工程師,軟體架構師,以及喜歡寫程式碼的技術經理準備的。如果您需要對所從事系統的架構做出決策 —— 例如您需要選擇解決某個特定問題的工具,並找出如何最好地使用這些工具,那麼這本書對您尤有價值。但即使你無法選擇你的工具,本書仍將幫助你更好地瞭解所使用工具的長處和短處。
您應當具有一些開發Web應用或網路服務的經驗且應當熟悉關係型資料庫和SQL。任何您瞭解的非關係型資料庫和其他與資料相關工具都會有所幫助但不是必需的。對常見網路協議如TCP和HTTP的大概理解是有幫助的。程式語言或框架的選擇對閱讀本書沒有任何不同影響。
如果以下任意一條對您為真,你會發現這本書很有價值:
* 您想了解如何使資料系統可伸縮例如支援擁有數百萬使用者的Web或移動應用。
* 您需要提高應用程式的可用性(最大限度地減少停機時間),保持穩定執行。
* 您正在尋找使系統在長期執行過程易於維護的方法,即使系統規模增長,需求與技術也發生變化。
* 您對事物的運作方式有著天然的好奇心,並且希望知道一些主流網站和線上服務背後發生的事情。這本書打破了各種資料庫和資料處理系統的內幕,探索這些系統設計中的智慧是非常有趣的。
有時在討論可伸縮的資料系統時,人們會說:“你又不在谷歌或亞馬遜,別操心可伸縮性了,直接上關係型資料庫”。這個陳述有一定的道理:為了不必要的伸縮性而設計程式,不僅會浪費不必要的精力,並且可能會把你鎖死在一個不靈活的設計中。實際上這是一種“過早最佳化”的形式。不過,選擇合適的工具確實很重要,而不同的技術各有優缺點。我們將看到,關係資料庫雖然很重要,但絕不是資料處理的終章。
## 本書涉及的領域
本書並不會嘗試告訴讀者如何安裝或使用特定的軟體包或API因為已經有大量文件給出了詳細的使用說明。相反我們會討論資料系統的基石——各種原則與利弊權衡並探討了不同產品所做出的不同設計決策。
在電子書中包含了線上資源全文的連結。所有連結在出版時都進行了驗證但不幸的是由於網路的自然規律連結往往會頻繁地破損。如果您遇到連結斷開的情況或者正在閱讀本書的列印副本可以使用搜索引擎查詢參考文獻。對於學術論文您可以在Google學術中搜索標題查詢可以公開獲取的PDF檔案。或者您也可以在 https://github.com/ept/ddia-references 中找到所有的參考資料,我們在那兒維護最新的連結。
我們主要關注的是資料系統的**架構architecture**,以及它們被整合到資料密集型應用中的方式。本書沒有足夠的空間覆蓋部署,運維,安全,管理等領域 —— 這些都是複雜而重要的主題,僅僅在本書中用粗略的註解討論這些對它們很不公平。每個領域都值得用單獨的書去講。
本書中描述的許多技術都被涵蓋在 **大資料Big Data** 這個時髦詞的範疇中。然而“大資料”這個術語被濫用,缺乏明確定義,以至於在嚴肅的工程討論中沒有用處。這本書使用歧義更小的術語,如“單節點”之於”分散式系統“,或”線上/互動式系統“之於”離線/批處理系統“。
本書對 **自由和開源軟體FOSS** 有一定偏好,因為閱讀,修改和執行原始碼是瞭解某事物詳細工作原理的好方法。開放的平臺也可以降低供應商壟斷的風險。然而在適當的情況下,我們也會討論專利軟體(閉源軟體,軟體即服務 SaaS或一些在文獻中描述過但未公開發行的公司內部軟體
## 本書綱要
本書分為三部分:
1. 在[第一部分](part-i.md)中,我們會討論設計資料密集型應用所賴的基本思想。我們從[第1章](ch1.md)開始,討論我們實際要達到的目標:可靠性,可伸縮性和可維護性;我們該如何思考這些概念;以及如何實現它們。在[第2章](ch2.md)中,我們比較了幾種不同的資料模型和查詢語言,看看它們如何適用於不同的場景。在[第3章](ch3.md)中將討論儲存引擎:資料庫如何在磁碟上擺放資料,以便能高效地再次找到它。[第4章](ch4.md)轉向資料編碼(序列化),以及隨時間演化的模式。
2. 在[第二部分](part-ii.md)中,我們從討論儲存在一臺機器上的資料轉向討論分佈在多臺機器上的資料。這對於可伸縮性通常是必需的,但帶來了各種獨特的挑戰。我們首先討論複製([第5章](ch5.md)),分割槽/分片([第6章](ch6.md))和事務([第7章](ch7.md))。然後我們將探索關於分散式系統問題的更多細節([第8章](ch8.md)),以及在分散式系統中實現一致性與共識意味著什麼([第9章](ch9.md))。
3. 在[第三部分](part-iii.md)中,我們討論那些從其他資料集衍生出一些資料集的系統。衍生資料經常出現在異構系統中:當沒有單個數據庫可以把所有事情都做的很好時,應用需要整合幾種不同的資料庫,快取,索引等。在[第10章](ch10.md)中我們將從一種衍生資料的批處理方法開始,然後在此基礎上建立在[第11章](ch11.md)中討論的流處理。最後,在[第12章](ch12.md)中,我們將所有內容彙總,討論在將來構建可靠,可伸縮和可維護的應用程式的方法。
## 參考文獻與延伸閱讀
本書中討論的大部分內容已經在其它地方以某種形式出現過了 —— 會議簡報研究論文部落格文章程式碼BUG跟蹤器郵件列表以及工程習慣中。本書總結了不同來源資料中最重要的想法並在文字中包含了指向原始文獻的連結。 如果你想更深入地探索一個領域,那麼每章末尾的參考文獻都是很好的資源,其中大部分可以免費線上獲取。
## OReilly Safari
[Safari](http://oreilly.com/safari) (formerly Safari Books Online) is a membership-based training and reference platform for enterprise, government, educators, and individuals.
Members have access to thousands of books, training videos, Learning Paths, interac tive tutorials, and curated playlists from over 250 publishers, including OReilly Media, Harvard Business Review, Prentice Hall Professional, Addison-Wesley Pro fessional, Microsoft Press, Sams, Que, Peachpit Press, Adobe, Focal Press, Cisco Press, John Wiley & Sons, Syngress, Morgan Kaufmann, IBM Redbooks, Packt, Adobe Press, FT Press, Apress, Manning, New Riders, McGraw-Hill, Jones & Bartlett, and Course Technology, among others.
For more information, please visit http://oreilly.com/safari.
## 致謝
本書融合了學術研究和工業實踐的經驗融合並系統化了大量其他人的想法與知識。在計算領域我們往往會被各種新鮮花樣所吸引但我認為前人完成的工作中有太多值得我們學習的地方了。本書有800多處引用文章部落格講座文件等對我來說這些都是寶貴的學習資源。我非常感謝這些材料的作者分享他們的知識。
我也從與人交流中學到了很多東西,很多人花費了寶貴的時間與我討論想法並耐心解釋。特別感謝 Joe Adler, Ross Anderson, Peter Bailis, Márton Balassi, Alastair Beresford, Mark Callaghan, Mat Clayton, Patrick Collison, Sean Cribbs, Shirshanka Das, Niklas Ekström, Stephan Ewen, Alan Fekete, Gyula Fóra, Camille Fournier, Andres Freund, John Garbutt, Seth Gilbert, Tom Haggett, Pat Hel land, Joe Hellerstein, Jakob Homan, Heidi Howard, John Hugg, Julian Hyde, Conrad Irwin, Evan Jones, Flavio Junqueira, Jessica Kerr, Kyle Kingsbury, Jay Kreps, Carl Lerche, Nicolas Liochon, Steve Loughran, Lee Mallabone, Nathan Marz, Caitie McCaffrey, Josie McLellan, Christopher Meiklejohn, Ian Meyers, Neha Narkhede, Neha Narula, Cathy ONeil, Onora ONeill, Ludovic Orban, Zoran Perkov, Julia Powles, Chris Riccomini, Henry Robinson, David Rosenthal, Jennifer Rullmann, Matthew Sackman, Martin Scholl, Amit Sela, Gwen Shapira, Greg Spurrier, Sam Stokes, Ben Stopford, Tom Stuart, Diana Vasile, Rahul Vohra, Pete Warden, 以及 Brett Wooldridge.
更多人透過審閱草稿並提供反饋意見在本書的創作過程中做出了無價的貢獻。我要特別感謝Raul Agepati, Tyler Akidau, Mattias Andersson, Sasha Baranov, Veena Basavaraj, David Beyer, Jim Brikman, Paul Carey, Raul Castro Fernandez, Joseph Chow, Derek Elkins, Sam Elliott, Alexander Gallego, Mark Grover, Stu Halloway, Heidi Howard, Nicola Kleppmann, Stefan Kruppa, Bjorn Madsen, Sander Mak, Stefan Podkowinski, Phil Potter, Hamid Ramazani, Sam Stokes, 以及Ben Summers。當然對於本書中的任何遺留錯誤或難以接受的見解我都承擔全部責任。
為了幫助這本書落地並且耐心地處理我緩慢的寫作和不尋常的要求我要對編輯Marie BeaugureauMike LoukidesAnn Spencer和O'Reilly的所有團隊表示感謝。我要感謝Rachel Head幫我找到了合適的術語。我要感謝Alastair BeresfordSusan GoodhueNeha Narkhede和Kevin Scott在其他工作事務之外給了我充分地創作時間和自由。
特別感謝Shabbir Diwan和Edie Freedman他們非常用心地為各章配了地圖。他們提出了不落俗套的靈感創作了這些地圖美麗而引人入勝真是太棒了。
最後我要表達對家人和朋友們的愛,沒有他們,我將無法走完這個將近四年的寫作歷程。你們是最棒的。