第1章 可靠、可擴展與可維護的應用系統(Reliable, Scalable, and Maintainable Applications)
可靠性(Reliability)
即便發生了某些錯誤,系統仍能夠繼續正常工做。
- 硬件故障:硬件冗餘方案,軟件容錯。
- 軟件錯誤
- 人爲失誤
可靠性的重要性:錯誤會致使效率降低,損失營收和聲譽。
可擴展性(Scalability)
描述負載
描述性能
延遲(latency)和響應時間(response time)容易說淆使用,但它們並不徹底同樣。一般響應時間是客戶端看到的:除了處理請求時間(服務時間,service time )外,還包括來回網絡延遲和各類排隊延遲。延遲則是請求花費在處理上的時間。
爲了弄清楚異常值有多糟糕,須要關注更大的百分位數如常見的第9五、99和99.9(縮寫爲p9五、p99和p999)值。做爲典型的響應時間闊值,它們分別表示有95% 、99% 或99.9%的請求響應時間快於閾值。例如,若是95百分位數響應時間爲l.5s ,這意味着100 個請求中的95個請求快於1.5s,而5個請求則須要1.5s或更長時間。
採用較高的響應時間百分位數(tail latencies,尾部延遲或長尾效應)很重要,由於它們直接影響用戶的整體服務體驗。例如,亞馬遜採用99.9百分位數來定義其內部服務的響應時間標準,或許它僅影響1000個請求中的1個。
百分位數一般用於描述、定義服務質量目標(Service Level Objectives, SLO)和服務質量協議(Service Level Agreements,SLA),這些是規定服務預期質量和可用性的合同。例如一份SLA合約一般會聲明,響應時間中位數小於200ms,99%請求的響應時間小於1s,且要求至少99.9%的時間都要達到上述服務指標。這些指標明確了服務質量預期,井容許客戶在不符合SLA的狀況下進行賠償。
-應對負載增長的方法
-垂直擴展、水平擴展
可維護性(Maintainability)
咱們將特別關注軟件系統的三個設計原則:
- 可運維性:運維更輕鬆
- 簡單性:簡化複雜度。簡化系統設計並不意味着減小系統功能,而主要意味着消除意外方面的複雜性。消除意外複雜性最好手段之一是抽象。
- 可演化性:易於改變
第2章 數據模型與查詢語言(Data Models and Query Languages)
關係模型與文檔模型(Relational Model Versus Document Model)
如今最著名的數據模型多是SQL,關係數據庫的核心在於商業數據處理。
- NoSQL的誕生
- 對象-關係不匹配
- 多對一與多對多的關係
文檔數據庫是否在重演歷史?
- 網絡模型,又稱CODASYL模型
- 關係模型
- 文檔數據庫的比較
關係數據庫與文檔數據庫現狀
- 哪一種數據模型的應用代碼更簡單?
- 文檔模型中的模式靈活性
- 文檔數據庫應該是讀時模式(數據的結構是隱式的,只有在讀取時才解釋),與寫時模式(關係數據庫的一種傳統方法,模式是顯式的,而且數據庫確保數據寫入時都必須遵循)相對應。
- 查詢的數據局部性
- 文檔數據庫與關係數據庫的整合
常見的文檔數據庫有MongoDB, Amazon DynamoDB, Couchbase, Azure Cosmos DB, CouchDB等。
常見的關係數據庫有Oracle, MySQL, SQL Server, PostgreSQL, Db2等
數據查詢語言(Query Languages for Data)
SQL是一種聲明式查詢語言,而不少經常使用的編程語言都是命令式,如Java,Python,JavaScript等。
聲明式查詢語言頗有吸引力,它比命令式API更加簡潔和容易使用。但更重要的是,它對外隱藏了數據庫引擎的不少實現細節,這樣數據庫系統可以在不改變查詢語句的狀況下提升性能。
聲明式語言一般適合於並行執行。
Web上的聲明式查詢
如聲明式CSS樣式表li.selected > p {...}
MapReduce查詢
MapReduce既不是聲明式查詢語言,也不是一個徹底命令式的查詢API,而是介於二者之間:查詢的邏輯用代碼片斷來表示,這些代碼片斷能夠被處理框架重複地調用。它主要基於許多函數式編程語言中的map(也稱爲collect)和reduce(也稱爲fold或inject)函數。
MapReduce是一個至關底層的編程模型,用於在計算集羣上分佈執行。而SQL這樣的更高層次的查詢語言能夠經過一些MapReduce操做pipeline來實現,固然也有不少SQL的分佈式實現並不藉助MapReduce。
圖狀數據模型(Graph-Like Data Models)
- 屬性圖,包括頂點和邊。
- Cypher查詢語言:一種用於屬性圖的聲明式查詢語言。
- SQL中的圖查詢
- 三元存儲與SPARQL,三元存儲包含三部分,主體,謂語,客體。
- 語義網
- RDF數據模型
- SPARQL查詢語言
- Datalog基礎
小結
新的非關係「NoSQL」數據存儲在兩個主要方向上存在分歧:
- 文檔數據庫的目標用例是數據來自於自包含文擋,且一個文檔與其餘文檔之間的關聯不多。
- 圖數據庫則針對相反的場景,目標用例是全部數據均可能會互相關聯。
全部這三種模型(文檔模型、關係模型和圖模型),現在都有普遍使用,而且在各自的目標領域都足夠優秀。
文檔數據庫和圖數據庫有一個共同點,那就是它們一般不會對存儲的數據強加某個模式,這可使應用程序更容易適應不斷變化的需求。可是,應用程序極可能仍然假定數據具備必定的結構,只不過是模式是顯式(寫時強制)仍是隱式(讀時處理)的問題。
第3章 數據存儲與檢索(Storage and Retrieval)
數據庫核心:數據結構(Data Structures That Power Your Database)
爲了高效地查找數據庫中特定鍵的值,須要新的數據結構:索引。
存儲系統中重要的權衡設計:適當的索引能夠加速讀取查詢,但每一個索引都會減慢寫速度。
哈希索引(Hash Indexes)
追加式日誌的設計很是不錯,主要緣由有如下幾個:
- 追加和分段合併主要是順序寫,它一般比隨機寫入快得多,特別是在旋轉式磁性硬盤上。
- 若是段文件是追加的或不可變的,則併發和崩潰恢復要簡單得多。
- 合併舊段能夠避免隨着時間的推移數據文件出現碎片化的問題。
哈希表索引也有其侷限性:
- 哈希表必須所有放入內存,若是有大量的鍵,就沒那麼幸運了。
- 區間查詢效率不高。
SSTables和LSM-Tree
排序字符串表,簡稱爲SSTable。它要求每一個鍵在每一個合併的段文件中只能出現一次(壓縮過程已經確保了)。SSTable相比哈希索引的日誌段,具備如下優勢:
- 合併段更加簡單高效,即便文件大於可用內存。
- 在文件中查找特定的鍵時,再也不須要在內存中保存全部鍵的索引。
- 因爲讀請求每每須要掃描請求範圍內的多個key-value對,能夠考慮將這些記錄保存到一個塊中並在寫磁盤以前將其壓縮。
構建和維護SSTables
內存排序有不少廣爲人知的樹狀數據結構,例如紅黑樹或AVL樹。使用這些數據結構,能夠按任意順序插入鍵並以排序後的順序讀取它們。
從SSTables到LSM-Tree
日誌結構的合併樹(Log-Structured Merge Tree,或LSM-Tree)
基於合併和壓縮排序文件原理的存儲引擎一般都被稱爲LSM存儲引擎。
性能優化
存儲引擎一般使用額外的布隆過濾器來優化鍵不存在的訪問。
最多見的SSTables壓縮和合並的策略是大小分級和分層壓縮。LevelDB和RocksDB使用分層壓縮,HBase使用大小壓縮。
B-trees
它是幾乎全部關係數據庫中的標準索引實現。
B-tree保留按鍵排序的key-value對,這樣能夠實現高效的key-value查找和區間查詢。B-tree將數據庫分解成固定大小的塊或頁,傳統上大小爲4KB,頁是內部讀/寫的最小單元。
若是要更新B-tree中現有鍵的值,首先搜索包含該鍵的葉子頁,更改該頁的值,並將頁寫回到磁盤。
使B-tree可靠
爲了使數據庫能從崩潰中恢復,常見B-tree的實現須要支持磁盤上的額外的數據結構:預寫日誌(write-ahead log, WAL),也稱爲重作日誌。
優化B-tree
- 使用寫時複製方案進行崩潰恢復。
- 保存鍵的縮略信息,而不是完整的鍵。
- 。。。
對比B-tree和LSM-tree
經過經驗,LSM-tree一般對於寫入更快,而B-tree被認爲對於讀取更快。
LSM-tree的優勢
B-tree索引必須至少寫兩次數據:一次寫入預寫日誌,一次寫入樹的頁自己。
LSM-Tree一般可以承受比B-tree更高的寫入吞吐量,部分是由於它們有時具備較低的寫放大,部分緣由是它們以順序方式寫入緊湊的SSTable文件,而沒必要重寫樹中的多個頁。
LSM-tree的缺點
日誌結構存儲的缺點是壓縮過程有時會干擾正在進行的讀寫操做。
高寫入吞吐量時,壓縮的另外一個問題就會冒出來:磁盤的有限寫入帶寬須要在初始寫入(記錄並刷新內存表到磁盤)和後臺運行的壓縮線程之間所共享。
其它索引結構
- 在索引中存儲值。將索引行直接存儲在索引中,被稱爲彙集索引。例如,MySQL InnoDB存儲引擎中,表的主鍵始終是彙集索引,二級索引引用主鍵(而不是堆文件位置)。
- 多列索引。
- 最多見的多列索引類型稱爲級聯索引,它經過將一列追加到另外一列,將幾個字段簡單地組合成一個鍵(索引的定義指定字段鏈接的順序)。
- 多維索引是更廣泛的一次查詢多列的方法,這對地理空間數據尤其重要。
- 全文索引和模糊索引。如Lucene。
- 在內存中保存全部內容。如Redis。
事務處理與分析處理(Transaction Processing or Analytics)
在線事務處理(online transaction processing,OLTP),在線分析處理(online analytic processing, OLAP)
數據倉庫
數據倉庫是單獨的數據庫,分析人員能夠在不影響OLTP操做的狀況下盡情的使用。數據倉庫包含公司全部各類OLTP系統的只讀副本。
OLTP數據庫和數據倉庫之間的差別
數據倉庫的數據模型最多見的是關係型,由於SQL一般適合分析查詢。
星型和雪花型分析模式
許多數據倉庫都至關公式化的使用了星型模型,也稱爲維度建模。
事實表中的列是屬性,其餘列可能會引用其餘表的外鍵,稱爲維度表。
星型模型的一個變體稱爲雪花模型,其中維度進一步細分爲子空間。
雪花模型比星型模型更規範化,可是星型模型一般是首選,主要是由於對於分析人員,星型模型使用起來更簡單。
列式存儲(Column-Oriented Storage)
面向列存儲的想法很簡單:不要將一行中的全部值存儲在一塊兒,而是將每列中的全部值存儲在一塊兒。
面向列的存儲佈局依賴一組列文件,每一個文件以相同順序保存着數據行。
列壓縮,一種技術是位圖編碼(Bitmap)。
內存帶寬和矢量化處理
列存儲中的排序
基於第一個排序鍵的壓縮效果一般最好。
列存儲的寫操做
一個很好的解決方案是LSM-tree。
聚合:數據立方體與物化視圖
建立這種緩存的一種方式是物化視圖。物化視圖常見的一種特殊狀況稱爲數據立方體或OLAP立方體。
物化數據立方體的優勢是某些查詢會很是快,主要是它們已被預先計算出來。
缺點則是,數據立方體缺少像查詢原始數據那樣的靈活性。
小結(Summary)
歸納來講,存儲引擎分爲兩大類:針對事務處理(OLTP)優化的架構,以及針對分析型(OLAP)的優化架構。
- OLTP系統一般面向用戶,這意味着它們可能收到大量的請求。
- 因爲不是直接面對最終用戶,數據倉庫和相似的分析型系統相對並不太廣爲人知,它們主要由業務分析師使用。
在OLTP方面,由兩個主流流派的存儲引擎:
- 日誌結構流派,它只容許追加式更新文件和刪除過期的文件,但不會修改已寫入的文件。BitCask, SSTables、LSM-tree、LevleDB、Cassandra、HBase、Lucene等屬於此類。
- 原地更新流派,將磁盤視爲能夠覆蓋的一組固定大小的頁。B-tree是這一哲學的最典型表明,它已用於全部主要的關係數據庫,以及大量的非關係數據庫。
第4章 數據編碼與演化(Encoding and Evolution)
向後兼容:較新的代碼能夠讀取由舊代碼編寫的數據。
向前兼容:較舊的代碼能夠讀取由新代碼編寫的數據。
向後兼容一般不難實現:做爲新代碼的做者,清楚舊代碼所編寫的數據格式,所以能夠比較明確地處理這些舊數據。向前兼容可能會比較棘手,它須要舊代碼忽略新版本的代碼所作的添加。
數據編碼格式(Formats for Encoding Data)
從內存中的表示(如對象、結構體、列表、數組、哈希表等)到字節序列(如JSON文檔)的轉化稱爲編碼(或序列化等),相反的過程稱爲解碼(或解析,反序列化)。
語言特定的格式
如Java有java.io.Serializable,Python有pickle等
JSON、XML與二進制變體
儘管存在一些缺陷,但JSON、XML和CSV已經可用於不少應用。特別是做爲數據交換格式(即將數據從一個組織發送到另外一個組織)。
Thrift和Protocol Buffers
Apache Thrift和Protocol Buffers是基於相同原理的二進制編碼庫。
Thrift和PB都須要模式來編碼任意的數據。
字段標籤和模式演化
數據類型和模式演化
Avro
Apache Avro是另外一種二進制編碼格式,也使用模式來指定編碼的數據結構。
寫模式和讀模式:Avro的關鍵思想是,寫模式和讀模式沒必要是徹底如出一轍,它們只需保持兼容。
模式演化規則:爲了保持兼容性,只能添加或刪除具備默認值的字段。
動態生成的模式:Avro的一個優勢是不包含任何標籤號。關鍵之處在於Avro對動態生成的模式更友好。
代碼生成和動態類型語言:Avro爲靜態類型編程語言提供了可選的代碼生成,可是它也能夠在不生成代碼的狀況下直接使用。
模式的優勢
經過演化支持與無模式/讀時模式的JSON數據庫相同的靈活性,同時還提供了有關數據和工具方面更好的保障。
數據流模式(Modes of Dataflow)
基於數據庫的數據流(Dataflow Through Databases)
在數據庫中,寫入數據庫的進程對數據進行編碼,而讀取數據庫的進程對數據進行解碼。
不一樣的時間寫入不一樣的值
數據比代碼更長久。五年前的數據還在,但代碼可能廢棄了。
歸檔存儲
基於服務的數據流:REST和RPC(Dataflow Through Services: REST and RPC)
面向服務/微服務體系結構的一個關鍵設計目標是,經過使服務可獨立部署和演化,讓應用程序更易於更改和維護。
網絡服務
有兩種流行的Web服務方法:REST和SOAP。與SOAP相比,REST已經愈來愈受歡迎,至少在跨組織服務集成的背景下,並常常與微服務相關聯。
遠程過程調用(RPC)的問題
嘗試使遠程服務看起來像編程語言中的本地對象同樣毫無心義,由於它們是根本不一樣的事情。REST的部分吸引力在於,它並不試圖隱藏它是網絡協議的事實。
RPC的發展方向
REST彷佛是公共API的主流風格。RPC框架主要側重於同一組織內多項服務之間的請求,一般發生在同一數據中心內。
RPC的數據編碼和演化
RPC方案的向後和向前兼容性屬性取決於它所使用的具體編碼技術。
基於消息傳遞的數據流
消息代理
如Kafka、RabbitMQ等。主題只提供單向數據流。
分佈式Actor框架
Actor模型是用於單個進程中併發的編程模型。流行的如Akka、Orleans、Erlang OTP等。
小結(Summary)
本章,咱們研究了將內存數據結構轉換爲網絡或磁盤上字節流的多種方法。咱們看到這些編碼的細節不只影響其效率,更重要的是還影響應用程序的體系結構和部署時的支持選項。
特別地,許多服務須要支持滾動升級,即每次將新版本的服務逐步部署到幾個節點,而不是同時部署到全部節點。滾動升級容許在不停機的狀況下發布新版本的服務(所以鼓勵頻繁地發佈小版本而不是大版本),並下降部署風險(容許錯誤版本在影響大量用戶以前檢測井回滾)。這些特性很是有利於應用程序的演化和更改。
在滾動升級期間,或者因爲各類其緣由,必須假設不一樣的節點正在運行應用代碼的不一樣版本。所以,在系統內流動的全部數據都以提供向後兼容性(新代碼能夠讀取舊數據)和向前兼容性(舊代碼能夠讀取新數據)的方式進行編碼顯得很是重要。
本章還討論了多種數據編碼格式及其兼容性狀況:
- 編程語言特定的編碼僅限於某種編程語言,每每沒法提供向前和向後兼容性。
- JSON、XML和csv等文本格式很是廣泛,其兼容性取決於你如何使用他們。它們有可選的模式語言,這有時是有用的,有時倒是障礙。這些格式對某些數據類型的支持有些模糊,必須當心處理數字和二進制字符串等問題。
- Thrift、Protocol Buffers和Avro這樣的二進制的模式驅動格式,支持使用清晰定義的向前和向後兼容性語義進行緊湊、高效的編碼。這些模式對於靜態類型語言中的文檔和代碼生成很是有用。然而,它們有個缺點,即只有在數據解碼後纔是人類可讀的。
咱們還討論了數據流的幾種模型,說明了數據編碼在不一樣場景下很是重要:
- 數據庫,其中寫入數據庫的進程對數據進行編碼,而讀取數據庫的進程對數據進行解碼。
- RPC、REST API,其中客戶端對請求進行編碼,服務器對請求進行解碼井對響應進行編碼,客戶端最終對應進行解碼。
- 異步消息傳遞(使用消息代理或Actor),節點之間經過互相發送消息進行通訊,消息由發送者編碼並由接收者解碼。
咱們能夠得出這樣的結論,只要稍加當心,向後/向前兼容性和滾動升級是徹底能夠實現的。