KuDu論文解讀


kudu是cloudera在2012開始秘密研發的一款介於hdfs和hbase之間的高速分布式存儲數據庫。兼具了hbase的實時性、hdfs的高吞吐,以及傳統數據庫的sql支持。作為一款實時、離線之間的存儲系統。定位和spark在計算系統中的地位非常相似。如果把mr+hdfs作為離線計算標配,storm+hbase作為實時計算標配。spark+kudu有可能成為未來最有競爭力的一種架構。

      也就是kafka->spark->kudu這種架構,未來此架構是否會風靡,暫且不表。來分析下kudu的一些特性:

傳統的實時/離線計算架構幾乎都是分離的,用戶需要把實時的數據從一個實時隊列系統導入到在線存儲,再導出到離線存儲系統。有時候離線和實時的數據又存再join或並用的可能性。在計算領域spark已經幾乎做到的離線+准實時(秒)的架構一體化覆蓋。但是畢竟spark只是計算系統,還缺乏一套存儲系統與之相配合。hbase查詢不友好,alluxis數據結構和程度差。kudu目前看來是最優的候選系統,再加上有cloudera背書,應該不錯。

      傳統系統幾大痛點:

      1、應用系統需要在實時、離線系統之間把數據倒來倒去,寫很復雜的code

      2、系統龐雜,各種備份,安全策略,監控系統

      3、系統從實時系統中流出到離線系統才好做OLAP分析,這層轉換存在延遲

      4、現實上來說,系統總是會存在落后的數據、對過去數據的修改,刪除。然而當過去的數據已經被歸檔,這些操作需要昂貴的重寫以及partiion交換或者各種人工干預

 

Kudu彌補了高速順序吞吐系統和低延遲隨機訪問系統之間的gap。它同時提供行級別的插入,更新,刪除。也提供類似Parquet的批量scan,列讀取等。

 

2.1 表結構和schema

      和nosql不同,kudu中的字段帶type,例如Int32或者String。幾個列組合起來形成主鍵(和hbase類比rowkey需要自己來維護),就是mysql中唯一索引概念同時又是主鍵。作為update或者deleted的index(但並不是說只能通過這個組合鍵更新或刪除)

      看起來和關系數據庫比較像吧,但kudu又可以隨時更新表字段添加或刪除(主鍵列們除外)

      nosql中提倡“所有數據都是bytes”,kudu不這樣做:

      1、明確的類型讓我們可以更好的做packing,例如integer

      2、更好的和sql類型的工具系統、bi系統、數據導出系統集成

      不過kudu不提供除了主鍵之外的二級索引或者唯一鍵索引,當前kudu需要用戶指定固定的主鍵列,未來可能會生成代替的key

 

2.2 寫操作

      insert、update、delete的aip必須指定主鍵,當然謂詞操作在高級操作API中是支持的。kudu提供java和c++的api,python在實驗中

 

2.3讀操作

      只提供Scan接口,條件提供<,>,=幾種過濾符

 

一致性模型:

      這塊主要是如何協調多個client端讀寫時的先后順序以及加鎖操作等。kudu學習了spanner的commit-wait的模式。start,之后commit-wait,兩階段提交。中間的時間采用HybridTime算法實現

 

時間戳:

      和hbase反向設計,寫不支持時間戳,避免用戶亂用。讀可以加時間戳讀取過去一個時間的快照。這一點上感覺各有優劣吧,對讀更加友好

 

經典的主從結構,和bigtable、gfs一脈相承。

 

分區比較特別:

      同時支持hash partition和range partition。這一點和hbase的rowkey設計比較像。例如插入的是時間順序字段,就按照時間戳做hash partition,利於寫壓力分擔到各個tablet。range-partition和hbase類似,根據主鍵列的順序來划分range

      文中還有一段話是說可以按時間range分區+metirice名稱、hostname的hash分區一起來決定真實的分區是什么

 

備份:

       為了提供高可用以及大規模,kudu采用自己的備份機制,一般是3、或者5份,master會管理這些備份。kudu采用Raft算法來復制這些tablets。客戶端先找到leader replice,若果當前已經不是就轉給leader,本地說記錄,通過MVCC機制來管理讀寫並發。這些操作會被同步費followers。主備份機器上如果寫完了wal,這個寫就可以被提交給其他備份。但是kudu又並不是牆紙指定這個機制,一定要在操作日志寫完之后commit,避免因為leader的磁盤慢導致性能問題。對於磁盤寫“成功”,kudu提供兩種模式供用戶選擇——寫入buffer cache或者一定要調用fsync才算成功

       當followers掛掉,leader會再次同步數據給其他備份機,如果leader掛了,Raft這個時候會被啟用選舉一個新的leader(和paxos的區別還待研究)。kudu采用500ms心跳,1500ms的選舉時間

       Kudu采用Raft時做了兩個改進:

      1、指數級的回退機制,保障在集群繁忙時的選舉效率

      2、在leader聯系一個follower時,會每次一次進一步來發現點位。但是kudu會直接跳到最后一次所知道的committedIndex,加快尋找速度

      同時,這里的replicate有個大膽的設計——同步operation而非數據本身。這和hdfs分3份存儲很不一樣。hbase利用了hdfs的文件系統保持3份備份。而kudu則同步oplog同步實體文件,看起來每個備份都會有額外的replay的操作

      看看kudu怎么說吧:

      1、當主備份數據在做flushes或者compaction時,從備份不需要做這些操作,避免對集群的沖擊以及額外的性能損耗

      2、開發過程中發現會有少量的物理存儲層競爭,因為物理存儲並不會被復制,所以這些問題不會導致不可恢復的錯誤。我們可以探測到任何一個備份的錯誤,並對其做修復

 

      這一點來說kudu避開了操作的復雜復制,因為對主備份數據的操作具有事物的復雜性,這些再同步到從備份代價就很大了。hbase因為底層采用hdfs所以不存在此問題。所以從kudu的角度來看這確實是一個好的設計。

      然而據我分析還有另一層原因——kudu底層假設目前的物理存儲硬件並沒有那么脆弱,恢復並不是一個經常性事件。區別於hdfs認為失效是常規事件(上萬台服務器硬盤故障的頻率和吃飯沒兩樣,廉價硬件堆出來存儲)。kudu明顯是高富帥方案,既然要告訴分析也不能用太差的硬件吧。另外還特別和intel合作優化享受最新硬件IO方面的性能提升。

 

      配置變更

      主要講了新增備份數,以及減少備份或者server配置的一些操作,還ok。沒有太特別的

      

      KuDu Master

      1、作為目錄管理機,保存了tables的櫃機,tablets是否存在,他們的schemas,復制級別,其他的元數據信息。當創建、alter、刪除表,master都會向tablets協調這些操作並保證最終完成

      2、集群協調者,存儲當前哪些server存在,哪些是存活的。server掛掉后啟動復制機制

      3、tablet目錄,每個tablet都存儲在哪些server上

      Kudu采用集中式、可復制的設計,而非P2P模式設計簡化實現,debuging以及操作

 

     解讀:主從式設計目前來看是實現簡單,效率最高的一種方式。只要把master元信息做備份master快速恢復,幾乎能解決大部分問題。

 

     目錄管理機:

      catalog信息也是一個tablet,類似hbase里的meta表,這個信息會在內存中穿寫。這種單層目錄樹(hbase有雙層)在目前的硬件架構下並不會成為擴展瓶頸。kudu開發者任務如果未來可能成為瓶頸就把它緩存到頁cache中。(又一次證明通過硬件來簡化軟件設計的思路,這種簡化並不能說是退步,感覺bigtable已經把存儲系統設計到極致了,kudu的改進盡量通過硬件能力來彌補,同時帶來效率和性能的提升)

     目錄管理機存儲了表當前狀態,當前版本的table schema,哪些tablet屬於table。建表語句會先到master這邊往catalog寫一個數據,更新為CREATING state。並且異步選擇一些servers作為tablets的存儲,在master端建好tablet metadata,再異步請求到每個server。replica 創建失敗或者超時,master可以回滾重做都很方便。master如果自己掛了,下次啟動時會確保每個操作被傳遞給server,這種協議設計成冪等,也就是可以反復重做。(應該是server端簡單判斷下發的重復消息,然后做出忽略操作)

     catalog table也是Kudu的一個tablet,所以master信息很好被重載到另外一個master接管這一切。  這里設計和hbase比較像,hdfs中的namenode需要引入一個外部存儲來做ha也是增加了復雜性。只要記住最初的幾個備份就行,不過hdfs有個特殊就是元數據信息可能很大,一兩個block也存不下。不像hbase、kudu元信息大小可控好存。

     

      集群協調者:

      普通的協調者作用,不過里面有一點對於復制,master並不直接參與,而是放手讓leader replica的server來協調。master會關注大局,比如有些本該刪除的備份還依然匯報心跳,他就會出面發出deleteRPC。check最終結果沒問題,避免leader replica有時候出些岔子。 master充分授權(replica leader),制定規則(Raft),但是最后結果檢查並且兜底。感覺是一個好的技術管理模式,分布式系統到最后和團隊管理都有點像了。

 

      tablet目錄:

      client一般像master請求tablet位置,並在本地做緩存。然后直接聯系server,如果server此時已經不是leader replica,返回失敗,client再去master請求。這樣減少了client對master請求的壓力。對於不經常改變的信息這種快速失敗重試的機制是很ok的。目前kudu在270節點的cluseter,一次tablet location 查詢99.99%在3.2ms內,95%在374微秒,75%在91微秒。kudu開發者任務當前這個不會成為瓶頸,后續也可以通過再把location信息做分區,給幾個不同的機器來提供服務。(走hbase的metatable路線,做起來也沒什么問題)

     

      Tablet 存儲

      tablet server並不采用分布式文件系統來存儲物理文件。這樣kudu就可以自由的開發基於table、基於tablet或者基於replica的存儲層。當前還只是有一個存儲層。存儲設計目標:

      1、列掃描要快:像Parquet和ORCFile一樣,快速的scan各個列中的數據

      2、低延遲:快速的找到隨機指定的行

      3、相比於高峰性能,用戶更願意接受一個持續穩定性能的組件

      所以,kudu自己設計了一套列存架構。這個應該是核心了,列存在目前有好多優秀設計的前提下自己設計一套符合kudu獨特架構的,也確實是有必要。

 

      RowSets,又分為MemRowSets,DiskRowSets,分別對應內存和磁盤上的數據存儲。

      MemRowSets采用B-tree的數據結構,樂觀鎖。

      1、不支持刪除元素,MVCC來表示刪除

      2、不支持隨意update,但是運行value 的size不改變的update。這種許可通過linked list來對比和置換操作實現

      3、我們把葉子節點都加上next指針,形成鏈表,提升scan性能

      4、不實現全量的字典樹,而只是做了一顆單tree,因為我們並不准備考慮大規模的隨機查詢吞吐量

 

      memRowSet按照行存儲數據,采用了LLVM、JIT-complile、memcmp等一些特殊技術來家屬查詢,當然這種查詢可以是根據主鍵范圍(更快)或者根據獨立的字段查詢

 

      DiskRowSet實現

      按照32MB一個來flush一個到磁盤上,這個大小是保障每個DiskRowSet不會太大,而且能支持不斷增長的compaction需求。DiskRowSets按照primary的范圍分段,互補重疊。這個和hbase類似,但是kudu是預先划定好partition的,所以基本上可以認為這個划分和memRowSet中能很好對應上。

      DiskRowSet包含了base data 和delta 存儲。base data是按照列來組織,每個列被分block存儲在硬盤上。這種存儲在加上B-tree索引讓row可以被快速查詢到。

      列存儲采用bitshuffle來encoding,fromt coding也可選。壓縮采用LZ4,gzip,bzip2.用戶可以根據列內容需要去定制——text列對應gzip,integers要按bit來packed,還有其他的一些頁參考了Impala和Parquet的設計。

      primary所包含的列單獨拿出來做encode,再配合Bloom filter做快速判存

      列編碼想要update非常困難,一旦flush就不會再改變。update和deletes會被存儲在一個叫delta的存儲中。delta store可以存在內存、可以存在磁盤。DeltaMemStore和上面的設計一樣是一個B-tree。這里面存儲了row_offset和修改時間timestamp。有點類似SET column id = 'foo' 或者delete。

      所以flush的時候memRowSet會帶上DeltaStore一起往DiskRowSet中存儲。flush的時候回直接刷delta 到磁盤。(刷的時候還不和base data做合並操作)

      

      插入方法:

      kudu區分insert和upsert,對於insert可以直接保存,對於upsert會查詢原有記錄中是否存在。所以這也是overwrite和append的坑 。再就是由於唯一鍵的設計導致每次插入都要查詢下這個鍵是否存在於歷史數據中。。。這一點決定了寫入速度不可能比hbase快呀。而且特別是對於同一個主鍵的反復寫性能就更加局限了,因為無法擴展。醉了

      kudu提出了兩個補救方案:1、縮減查找的DiskRowSet范圍;2、加速單個DiskRowSet的查找效率

      Bloom filter再加上LRU page cache,做到1.;通過index+page cache來做到2  (hbase也同樣做了這兩個,但是插入時不用look up是絕對優勢)

 

      讀方法:

      讀操作經常是批量的,KUDU每次只讀一個列。找到row對應的列值,並讀取。

     

       DeltaCompaction,RowSetCompaction這些和hbase非常類似了,就不再細寫了。總的來說當前的存儲如何支撐sql查詢

 

       還有幾點補充:

       Kudu很好的結合MR(雖然現在沒什么人用了),spark,impala。甚至sql引擎直接用的impala的。在讀寫性能上的表現類比了impala on parquet/HDFS,讀稍微會快一些。比phoenix快很多。隨機讀方面性能指數也不錯。

      但是寫方面,就表現一般了,特別是和hbase比差很多。原因前面也說過寫的時候要去lookup primary key。目前看來寫最快的就是hbase了,對於寫已經設計到極致沒有之一!

 

      總結一句,對於近似online的分布式數據庫kudu是一個甜品級應用,享受了impala的查詢,以及稍弱於hbase的寫入。還是實時寫實時讀的!配上spark會是一個不錯的選擇。但是由於目前還是孵化中的版本,坑還是有點多的。生產系統使用的時候需要慎重!

 

原文鏈接:http://blog.csdn.net/kirayuan/article/details/51824564

 


免責聲明!

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



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