Designing Data-Intensive Applications


相對於讀書筆記,本文更像是一篇閱讀大綱,在初步閱讀本書后,尚有許多疑難,借用此大綱,以后溫故而知新

DDIA講了什么

什么是data-intensive

原文這樣定義  Data-intensive applications are pushing the boundaries of what is possible by making use of these technological developments. We call an application data-intensive if data is its primary challenge—the quantity of data, the complexity of data, or the speed at which it is changing—as opposed to compute-intensive, where CPU cycles are the bottleneck.

即“數據密集型應用系統”(data-intensive)中數據的規模、復雜度或者數據產生與變化的速率等成為一個系統成敗的決定性因素,與數據密集型系統相對應的是計算密集型系統(compute-intensive)。

主要內容

  本書講解了如何更好地駕馭處理數據和存儲數據的相關技術,剖析成功的數據系統案例技術要點,分析有效應對當前環境苛刻要求的關鍵技術。

  在DDIA這本書中,對數據系統有概要的介紹,然后是區分各自的優缺點與特性,然后分析這些特性是如何實現。 

  DDIA一書分為三部分,第一部分是數據系統的基石,一些基本的思想和組件;第二部分是分布式數據系統;第三部分是派生數據系統。

Part I.Founding of Data Systems

Chapter 1. Reliable、Scalable and Maintainable Applications

本章介紹數據系統的衡量標准 (對於一個系統(應用),都希望達到:Reliable, Scalable, and Maintainable)

  數據密集型應用由以下標准模塊構建而成

  • 數據庫:用以存儲數據,之后的應用可以再次訪問
  • 高速索引:緩存那些復雜或者操作代價昂貴的結果,加快下一次訪問
  • 索引:用戶可以按照關鍵字搜索數據並支持各種過濾
  • 流式處理:持續發送消息至另一個進程,處理采用異步方式
  • 批處理:定期處理大量的累計數據

Reliability

  Reliability means making systems work correctly, even when faults occur. Faults can be in hardware (typically random and uncorrelated), software (bugs are typically sys‐tematic and hard to deal with), and humans (who inevitably make mistakes from time to time). Fault-tolerance techniques can hide certain types of faults from the end user.

  即使發生故障,系統通過容錯技術確保正常工作,避免影響最終用戶。

Scalability
  As the system grows (in data volume, traffic volume, or complexity), there should be reasonable ways of dealing with that growth. Scalability means having strategies for keeping performance good, even when load increases.In a scalable system, you can add processing capacity in order to remain reliable under high load.

  對於可擴展的系統,增加處理能力的同時,還可以在高負載情況下持續保持系統的高可靠性。

  伸縮性,當系統的規模增長的時候,系統能保持穩定的性能。這就有兩個問題:如何定義負載(load parameter)、如何衡量性能(performance)。

  這兩個參數(指標)都取決於應用類型,比如web服務,那么負載就是每秒的請求數,而性能就是系統每秒能處理的請求數目。

  當負載增大的時候,有兩種方式衡量性能:

  • 如果系統資源不變,系統性能會有什么變化
  • 為了保證性能不變,需要增加多少資源

Maintainability
  Over time, many different people will work on the system (engineering and oper‐ations, both maintaining current behavior and adapting the system to new use cases), and they should all be able to work on it productively. Maintainability has many facets, but in essence it’s about making life better for the engineering and operations teams who need to work with the system. Good abstrac‐tions can help reduce complexity and make the system easier to modify and adapt for new use cases. Good operability means having good visibility into the system’s health,and having effective ways of managing it.

  可維護性本質是為了讓工程和運營團隊更為輕松。良好的抽象可以幫助降低復雜性,並使系統更易於修改和適配新場景;良好的可操作性意味着對系統健康狀況有良好的觀測性和高效的管理方式。

  提高軟件可維護性的三個設計原則:

  • 可運維性:方便運營團隊保持系統平穩運行
  • 簡單性:簡化系統復雜性,便於新工程師理解系統
  • 可塑性:后續工程師輕松對系統進行改進

Chapter 2. Data Models and Query Languages

本章對比了多種不同的數據模型和查詢語言,本章設計的專業名詞過多,太過陌生,還需細致了解。

  Data model是數據的組織形式,在這一部分,介紹了relational model、document model、graph-like data model,不同的數據模型的存儲方式、查詢方式差異很大。因此,應用需要根據數據本身的關聯關系、常用查詢方式來來選擇合適的數據模型。

  數據與數據之間,有不同的關聯形式:one to one,one to many,many to one,many to many。one to one,one to many都較好表示,困難的是如何高效表示many to one,many to many。早在1970s年代,就有兩個流派嘗試來解決many to many的問題,relational model, network model,自然,network model是更加自然、更好理解的抽象,但是相比relational model而言,難以使用,難以維護。因此relational model逐漸成為了主流的解決方案。

  relatioal model將數據抽象為關系(relation,sql中稱之為table),每一個關系是一組形式類似的數據的集合。對於many to many的數據關聯,relational model將數據分散在不同的relation中,在查詢時通過join聚合。

  sql是典型的聲明式查詢語言(declarative query language),只要描述需要做什么,而不需關心具體怎么做,給用戶提供的是一個更簡潔的編程界面。

Nosql

  2009年左右,Nosql(not only sql)逐漸進入人們的視野,近幾年在各個領域得到了廣泛的發展與應用。NoSQL具有以下特點:

  • 天生分布式,更好的伸縮性,更大的數據規模與吞吐
  • 開源
  • 滿足應用的特定需求
  • 避免sql約束,動態數據模型

  在Nosql陣營中,其中一支是以mongodb為代表的document db,對於one 2 many采用了層次模型的nested record;而對於many 2 one、many 2 many類似關系數據庫的外鍵

  這里有兩個很有意思的概念:

  schema-on-read (the structure of the data is implicit, and only interpreted when the data is read)

  schema-on-write (the traditional approach of relational databases, where the schema is explicit and the database ensures all written data conforms to it)

  顯然,前者是document db采用的形式,后者是關系型數據采用的形式。前者像動態類型語言,后者則像靜態類型語言,那么當schema修改的時候,前者要在代碼中兼容;后者需要alter table(並為舊數據 增加默認值, 或者立即處理舊數據)。

Graph model

  適合用於解決many to many的數據關聯關系。

  A graph consists of two kinds of objects: vertices (also known as nodes or entities) and edges (also known as relationships or arcs)

  data model:property graph model; triple-store model(三元存儲模型)

  declarative query languages for graphs: Cypher, SPARQL, and Datalog

Chapter 3. Storage and Retrieval

在這一部分,主要是講從數據庫的角度來看,如何存儲數據(store the data),如何查詢數據(give data back to user)。

涉及到兩種存儲引擎: log-structured storage engines, andpage-oriented storage engines such as B-trees.

  一個最簡單的數據庫:

  

  這兩個命令組成了一個數據庫需要的最基本的操作:存儲數據(db_set),讀取數據(db_get)。不難發現,db_set是非常高效的,但db_get性能會非常之差,尤其是db中擁有大量數據的時候。

  事實上,絕大多數數據庫寫入性能都很好,而為了提高讀取效率,都會使用到索引(Index):

  • the general idea behind them is to keep some additional metadata on the side, which acts as a signpost and helps you to locate the data you want

  索引是從原始數據(primary data)派生而來的結構,其目的是加速查詢(query),索引的添加刪除並不會影響到原始數據。但索引並不是銀彈:在加速查詢的同時,也會影響到寫入速度,即在寫入(更新)原始數據的同時,也需要同步維護索引數據。

Hash Index

  前面的這個最簡單的數據庫,就是就是一個Log structure的例子,數據以append only的形式組織,即使是對同一個key的修改,也是添加一條新的數據記錄。

  hash是最為常見的數據結構中,在絕大多數編程語言都有對應的實現。hash在通過key獲取value時速度很快,因此也非常適合用在DB查詢。具體而言,value是key在文件中的偏移,這樣,在db_set的同時修改key對用的文件偏移,在db-get的時候先從hash index中通過key讀取偏移位置,然后再從文件讀取數據。

  hash index的優點在於以很簡單的形式加速了查詢,但缺點也很明顯:hashindex是內存中的數據結構,因此需要內存足夠大以容納所有key-value對,另外hash index對於range query支持不太好。

SSTables and LSM-Trees

  在前面simplest db中 log-structured segment中的key是無序的,數據按寫入順序存儲。而另外一種格式,Sorted String Table, or SSTable:key則是有序的(磁盤上有序),同一個key在一個SSTable中只會出現一次。

  SSTable具有優勢:

  • segment merge很容易,即使超過內存空間,歸並排序
  • 由於key有序,更容易查找
  • 基於Sparse index,可以將兩個key之間的record打包壓縮有存儲,節省磁盤和帶寬

  sstable是數據在文件上的組織形式,顯然不大可能直接通過移動數據來保證key的有序性。因此都是在內存中用memtable中排序,當memtable的數據量達到一定程度,在以sstable的形式寫到文件。關於sstable,memtable,《典型分布式系統分析:Bigtable》有一些介紹。

BTree

  Btree是最為常用的索引結構,在關系型數據庫以及大多數Nosql中都有廣泛應用。如下圖:

  

  Btree中的基本單元稱之為page,一般來說大小為4KB,讀寫都是以page為單位。

  非葉子節點的page會有ref指向child page,這個ref有點像指針,只不過是在指向的是磁盤上的位置而不是內存地址。page的最大child page數目稱之為branching factor(上圖中branching factor為6),在存儲引擎中,branching factor一般是好幾百,因此,這個Btree深度只要三四層就足夠了。

聚簇索引(clustered index)

  前面介紹hash index,LSM的sparse index的時候,key映射的都是數據在文件中的偏移(offset),在Btree中,value既可以是數據本身,又可以是數據的位置信息。如果value就是數據本身,那么稱之為clustered index,聚簇索引。

  mysql常用的兩個存儲引擎Innodb,myisam都是用了Btree作為索引結構。但不同的是,Innodb的主索引(primary index)使用了聚簇索引,葉子節點的data域保存了完整的數據記錄,如果還建立有輔助索引(secondary index),那么輔助索引的date域是主鍵的值;而對於myisam,不管是主索引還是輔助索引,data域都是數據記錄的位置信息。

內存數據庫

  In memory db也是使用非常廣泛的一類數據庫,如redis,memcache,內存數據庫的數據維護在內存中,即使提供某種程度上的持久化(如redis),也還是屬於內存數據庫,因為數據的讀操作完全在內存中進行,而磁盤僅僅是為了數據持久化。

  為什么In memory db 更快:核心不是因為不用讀取磁盤(即使disk based storage也會緩存);而是不用為了持久化,而encoding in memory data structure。

Transaction Processing or Analytics?

  online transaction processing(OLTP)與online analytic processing (OLAP)具有顯著的區別,如下表所示

  

  一般來說,數據庫(不管是sql,還是nosql)既支持OLTP,又支持OLAP。但一般來說,線上數據庫並不會同時服務OLTP與OLAP,因為OLAP一般是跨表、大量記錄的查詢與聚合,消耗很大,可能影響到正常的OLTP。

  因此有了為數據分析定制化的數據庫--數據倉庫(Data Warehousing),數據的倉庫的數據通過Extract–Transform–Load (ETL)導入,如下圖所示:

  

  數據分析又一個特點:一次分析可能只會使用到table中的很少的幾列,為了減少從磁盤讀取更少的數據、以及更好的壓縮存儲,Column-Oriented Storage是一個不錯的選擇。

Chapter 4. Encoding and Evolution

數據有兩種形態:

  • 內存中:稱之為對象(object)或者數據結構( structure)
  • 網絡或者文件中:二進制序列

  數據經常要在這兩種形態之間轉換。

  • in-memory representation to a byte sequence:encoding (serialization、marshalling), and the reverse is called decoding (parsing, deserialization, unmarshalling).

  在本文中,翻譯為序列化與反序列化。

  應用在持續運營、迭代的過程中,代碼和數據格式也會跟着發生變化。但代碼的變更並不是一簇而就的,對於服務端應用,通常需要灰度升級(rolling upgrade),而客戶端應用不能保證用戶同時更新。因此,在一定的時間內,會存在新老代碼、新老數據格式並存的問題。這就存在產生了兼容性問題.

  • Backward compatibility: Newer code can read data that was written by older code.
  • Forward compatibility:Older code can read data that was written by newer code.

  在本章中,討論了幾種數據序列化協議、各個協議兼容性問題,以及數據是如何在各個進程之間流動的。

語言內置的序列化方式

  大多數編程語言都天然支持內存數據與字節流的相互轉換(即序列化與反序列化),如Java的java.io.Serializable, Ruby的Marshal , Python的pickle。但這些內置模塊或多或少都有一些缺點:

  • 與特定編程語言綁定,限制了以后的演化
  • 安全性問題:

In order to restore data in the same object types, the decoding process needs to be able to instantiate arbitrary classes.

  • 一般不考慮向前兼容性或向后兼容性問題
  • 效率問題:包括速度與序列化后的size

跨語言的文本序列化協議 JSON XML

  Json和Xml是兩種使用非常廣泛的序列化協議,二者最大的特點在於跨語言、自描述、可讀性好。Json經常用於http請求的參數傳遞。

  json和xml也有以下缺陷:

  • 對數字的encoding不太友好,會有歧義(XML不能區分number、digital string;JSON不能區分整數與浮點數)
  • 支持text string,但不支持binary string(sequences of bytes without a character encoding)。 所以經常需要額外使用base64先對binary string進行換換,這就是額外增加33%的空間(3Byte的binary string轉化成4Byte的text string)

Binary Json

  JSON協議的二進制進化版本核心是為了使用更少的空間,包括 MessagePack, BSON, BJSON, UBJSON, BISON等,其中由於MongoDB采樣了BSON作為序列化協議,使用比較廣泛。

  除了更小的空間,Binary JSON還有以下優點

  • 區分整數浮點數
  • 支持binary string

  下面是一個內存對象,后文用來對比各種序列化協議的效率(編碼后size)

{
    "userName": "Martin",
    "favoriteNumber": 1337,
    "interests": ["daydreaming", "hacking"]
}

  在這里用python json模塊來序列化:

>>> dd = json.dumps(d, separators=(',', ':'))
>>> dd
'{"userName":"Martin","favoriteNumber":1337,"interests":["daydreaming","hacking"]}'
>>> len(dd)
81

  在去除了空格的情況下需要81字節.

  而使用msgpack編碼如下:

  

  只需要66字節,與json序列化后的內容對比,很容易發現哪里使用了更少的字節.

Thrift and Protocol Buffers

  binary json相關json而言,優化了空間,但幅度不是很大(81字節到66字節),原因在於,不管是JSON還是BSON都是自描述、自包含的(self-contained):在序列化結果中包含了fileld name。那么如果去掉field name,就能進一步壓縮空間。

  Apache Thrift 和 Protocol Buffers就是這樣的二進制序列化協議:通過使用格式描述文件(schema),在序列化后的字節流中,不再包含fieldname,而是使用與fieldname對應的filed tag.

  以protocol buffer為例,需要定義格式文件(.proto)

message Person {
    required string user_name = 1;
    optional int64 favorite_number = 2;
    repeated string interests = 3;
}

  然后就可以通過工具轉化成響應語言的代碼,在代碼里面,就包含了fieldname與tag的映射,比如上面user_name就映射到了1。一般來說,數字比字符串更省空間。下面是protocol buffer序列化后的結果:

  

  可以看到總共只需要33字節,相比Magpack的66字節有巨大的提升。優化來自於一下幾點:

  • 使用了field tag而不是fieldname, field tag還不到一個字節
  • filed tag 與 field type壓縮到了一個字節里面
  • 使用了varint,用最少的字節標識一個整數

  Thrift兩種格式:BinaryProtocol and CompactProtocol,后者采用了與Protocol Buffer類似的壓縮策略

Field tags and schema evolution

  使用field tag之后,序列化后的數據就不在是自包含的,需要結合schema定義文件(產生的代碼)來解讀數據。那么在這種情況下如何保證兼容性呢。

  首先向前兼容不是什么問題,即使在新的數據定義中增加了字段,舊代碼只用忽略這個字段就行了。當然,在新的數據定義中如果要刪除字段,那么只能刪除可選的(optional)字段,而且不能使用相同的field tag

  向后兼容性也好說,如果增加了字段,那么這個字段只要是可選的(optioanl),或者有默認值就行(default value)。

數據流動(DataFlow)

  數據從一個節點(進程)流向另一個節點,大約有以下幾種形式

  • Via databases
  • Via service calls
  • Via asynchronous message passing

  對於database,需要注意的是:當新加filed之后,舊的application level code(DAO)讀到新代碼所寫入的數據(包含new filed)的時候,會忽略掉new field,那么舊代碼之后寫入到數據庫的時候,會不會覆蓋掉new filed。

  service call有兩種形式REST和RPC。

  message queue相比RPC優點:

  • 緩存(buffer),提高可用性
  • 可以重復投遞消息,提高可靠性
  • 解耦合(無需知道消息消費者)
  • 多個消費者

Part II. Distributed Data

Chapter 5. Replication

Chapter 6. Partitioning

Chapter 7. Transactions

Chapter 8. The Trouble with Distributed Systems

Chapter 9. Consistency and Consensus

Part III. Derived Data

Chapter 10. Batch Processing

Chapter 11. Stream Processing

Chapter 12. The Future of Data Systems

參考博文


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM