設計數據密集型應用第一部分:數據系統的基石

  《Designing Data-Intensive Applications》這本書,今年在不一樣的地方都看到有推薦,簡單瀏覽了一下內容,感受仍是值得一讀的。因爲是英文,讀起來仍是有點慢,最近讀完了本書的第一部分,寫篇文章記錄一下。本文主要是讀書摘要和筆記,也有一些本身的總結和思考。html

  對我而言,看這本書的收穫在於擴寬了知識面,對一些之前只是知其然的東西,知其因此然。另外,本書該出了大量詳實資料的連接,有助於對某一領域的進一步學習。java

  本文地址:http://www.javashuo.com/article/p-xqxwmsvc-ho.htmlnode

DDIA講了什麼

  於我而言,仍是第一次據說數據密集型(data-intensive)這個屬於。以前在分析一個程序(軟件)的時候,常常用到的CPU Bound、IO Bound之類的詞彙。那麼什麼是data-intensive呢python

  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.mysql

  便是說,應用的核心挑戰是數據:大量的數據,複雜、豐富多樣的數據,快速變化的數據。每一個程序員或多或少都在於數據系統打交道,包括但不限於:database、message queues、 caches,、search indexes, frameworks for batch and stream processing。不一樣的數據系統知足了不一樣的應用需求,即便是同一種數據系統,如database,也有各類不一樣的設計哲學與實現方案。git

  固然,也許不少人並不直接從事數據系統的開發工做,但瞭解這些數據系統的工做原理是頗有益處的。當咱們瞭解了原理以後,能爲咱們的應用需求選擇最合適的數據系統,能解釋系統的一些約束與現象,能將這些數據系統有效的組合起來,服務於應用。程序員

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

  DDIA一書分爲三部分,第一部分是數據系統的基石,一些基本的思想和組件;第二部分是分佈式數據系統;第三部分是派生數據系統。本文介紹第一部分。redis

數據系統的衡量標準

  一個應用每每是由多個數據系統組合而來,包括但不限於:sql

• Store data so that they, or another application, can find it again later (databases)
• Remember the result of an expensive operation, to speed up reads (caches)
• Allow users to search data by keyword or filter it in various ways (search indexes)
• Send a message to another process, to be handled asynchronously (stream processing)
• Periodically crunch a large amount of accumulated data (batch processing)

  這些數據系統就像積木,經過程序員的精心搭配構建成應用這座大廈。

  對於一個系統(應用),都但願達到如下標準:Reliable, Scalable, and Maintainable

Reliability

The system should continue to work correctly (performing the correct function at the desired level of performance) even in the face of adversity (hardware or software faults, and even human error). 

  即便系統中的某些部分出錯了,整個系統也能繼續對外提供服務,所以可靠性也常常稱爲容錯性( fault-tolerant)。錯誤可能來源於硬件錯誤(hardware hardware)、軟件錯誤(software error)以及人工錯誤(human error)

  在一個7*24運行的大型分佈式系統中,硬件錯誤是很是常見的,但硬件錯誤通常影響範圍介紹 -- 只會影響出問題的計算機或者磁盤,通常經過冗餘來應對硬件錯誤。相比而言。軟件錯誤影響範圍更大,例如:代碼的bug影響每個程序實例;單個程序耗光共享資源(CPU 內存 網絡 service);一個底層service掛掉或者異常影響全部上層服務。

  不容忽視的是human error,這個時有發生,好比數據庫、網絡的錯誤配置,好比常常看到的「從刪庫到跑路」。

  one study of large internet services found that configuration errors by operators were the leading cause of outages

  人是不可靠的,儘可能自動化能減小悲劇的產生。

Scalability

As the system grows (in data volume, traffic volume, or complexity), there should be reasonable ways of dealing with that growth

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

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

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

  • 若是系統資源不變,系統性能會有什麼變化
  • 爲了保證性能不變,須要增長多少資源

Maintainability

  Over time, many different people will work on the system (engineering and operations,both maintaining current behavior and adapting the system to new use cases), and they should all be able to work on it productively.

  可維護性是衡量代碼的一個重要標準,軟件寫出來以後,還要修bug、知足新需求、添加新功能、配合其餘產品升級等,維護軟件的人極可能不是寫代碼的人,所以可維護性就顯得尤其重要。

  如下三個原則有助於提升軟件的可維護性:

  • Operability

  Make it easy for operations teams to keep the system running smoothly.

  • Simplicity

  Make it easy for new engineers to understand the system

  • Evolvability

  Make it easy for engineers to make changes to the system in the future

常見數據模型

  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

數據的存儲與查詢

  在這一部分,主要是講從數據庫的角度來看,如何存儲數據(store the data),如何查詢數據(give data back to user)。涉及到兩種存儲引擎: log-structured storage engines, and page-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是一個不錯的選擇。

數據序列化與數據演進

  數據有兩種形態:

  •   內存中:稱之爲對象(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),提升可用性
  • 能夠重複投遞消息,提升可靠性
  • 解耦合(無需知道消息消費者)
  • 多個消費者

總結

  第一章介紹了數據系統的衡量指標: reliability, scalability, and maintainability。

  第二章介紹了不一樣的數據模型與查詢語言,包括relational mode, document mode, graph mode,須要解決的問題是如何表示many to one,many to many的數據關係,有兩個有意思的概念:schema-on-read 、schema-on-write。

  第三章介紹了存儲引擎:即數據是如何在磁盤上存儲的,如何經過索引加速查詢。內容包括Log structured,update-in-place;OLTP VS OLAP,dataware等。

  第四章介紹數據的序列化與反序列化,以及各類序列化協議的兼容性問題。包括JSON、BSON、Thrift&protobuffer、Arvo。

references

Designing Data-Intensive Applications

protocol buffers:encoding

相關文章
相關標籤/搜索