一、系統架構
runtime framework v.s. mpp
在SQL on Hadoop系統中,有兩種架構:
1、一種是基於某個運行時框架來構建查詢引擎,典型案例是Hive;
2、另一種是仿照過去關系數據庫的MPP架構,就是參考過去的MPP數據庫架構打造一個專門的系統,於是就有了Impala,Presto等等。
前者現有運行時框架,然后套上sql層,后者則是從頭打造一個一體化的查詢引擎。
對於SQL on Hadoop系統很重要的一個評價指標就是:快。
DAG v.s. MR:最主要的優勢,中間結果不寫磁盤(除非內存不夠),一氣呵成。
- 流水線計算:上游stage一出結果馬上推送或者拉到下一個stage處理,比如多表join時前兩個表有結果直接給第三個表,不像MR要等兩個表完全join完再給第三個表join。
- 高效的IO:本地查詢沒有多余的消耗,充分利用磁盤。這個后面細說。
- 線程級別的並發:相比之下MR每個task要啟動JVM,本身就有很大延遲,占用資源也多。
MPP模式也有其劣勢:
- 一個是擴展性不是很高,這在關系數據庫時代就已經有過結論;
- 另一個是容錯性差,對於Impala來說一旦運行過程中出點問題,整個查詢就掛了。
但是,經過不斷的發展,Hive也能跑在DAG框架上了,不僅有Tez,還有Spark。上面提到的一些劣勢,其實大都也可以在計算模型中解決。基於Spark的Spark SQL完全不遜色於Presto,基於Tez的Hive也不算很差,至少在並發模式下能超過Presto,足見MPP模式並不是絕對占上風的。
二、核心組件
不管是上面提到的那種架構,一個SQL on Hadoop系統一般都會有一些通用的核心組件,這些組件根據設計者的考慮放在不同的節點角色中,在物理上節點都按照master/worker的方式去做
三、執行計划
編譯流程
從SQL到執行計划,大致分為5步。
- 第一步將SQL轉換成抽象語法樹AST。這一步一般都有第三方工具庫可以完成,比如antlr。
- 第二步對AST進行語義分析,比如表是否存在,字段是否存在,SQL語義是否有誤(比如select中被判定為聚合的字段在group by中有沒有出現)。
- 第三步生成邏輯執行計划,這是一個由邏輯操作符組成的DAG。比如對於Hive來說掃表會產生TableScanOperator,聚合會產生GroupByOperator。對於類MPP系統來說,情況稍微有點不同。邏輯操作符的種類還是差不多,但是會先生成單機版本,然后生成多機版本。多機版本主要是把aggregate,join,還有top n這幾個操作並行化,比如aggregate會分成類似MR那樣的本地aggregate,shuffle和全局aggregate三步。
- 第四步做邏輯執行計划做優化。
- 第五步把邏輯執行計划轉換成可以在機器上運行的物理計划。
四、優化器
關於執行計划的優化,雖然不一定是整個編譯流程中最難的部分,但卻是最有看點的部分,而且目前還在不斷發展中。Spark系之所以放棄Shark另起爐灶做Spark SQL,很大一部分原因是想自己做優化策略,避免受Hive的限制。早期在Hive中只有一些簡單的規則優化,比如謂詞下推(把過濾條件盡可能的放在table scan之后就完成),操作合並(連續的filter用and合並成一個operator,連續的projection也可以合並)。后來逐漸增加了一些略復雜的規則,比如相同key的join + group by合並為1個MR,還有star schema join。
但是,基於規則的優化(RBO)不能解決所有問題。在關系數據庫中早有另一種優化方式,也就是基於代價的優化CBO。CBO通過收集表的數據信息(比如字段的基數,數據分布直方圖等等)來對一些問題作出解答,其中最主要的問題就是確定多表join的順序。CBO通過搜索join順序的所有解空間(表太多的情況下可以用有限深度的貪婪算法),並且算出對應的代價,可以找到最好的順序。這些都已經在關系數據庫中得到了實踐。
五、存儲格式
對於分析類型的workload來說,最好的存儲格式自然是列存儲,這已經在關系數據庫時代得到了證明。目前hadoop生態中有兩大列存儲格式,一個是由Hortonworks和Microsoft開發的ORCFile,另一個是由Cloudera和Twitter開發的Parquet。
ORCFile顧名思義,是在RCFile的基礎之上改造的。RCFile雖然號稱列存儲,但是只是“按列存儲”而已,將數據先划分成row group,然后row group內部按照列進行存儲。
ORCFile已經彌補了這些特性,包括:
- 塊過濾與塊統計:每一列按照固定行數或大小進一步切分,對於切分出來的每一個數據單元,預先計算好這些單元的min/max/sum/count/null值,min/max用於在過濾數據的時候直接跳過數據單元,而所有這些統計值則可以在做聚合操作的時候直接采用,而不必解開這個數據單元做進一步的計算。
- 更高效的編碼方式:RCFile中沒有標注每一列的類型,事實上當知道數據類型時,可以采取特定的編碼方式,本身就能很大程度上進行數據的壓縮。常見的針對列存儲的編碼方式有RLE(大量重復數據),字典(字符串),位圖(數字且基數不大),級差(排序過的數據,比如日志中用戶訪問時間)等等。
Parquet的設計原理跟ORC類似,不過它有兩個特點:
- 通用性:相比ORCFile專門給Hive使用而言,Parquet不僅僅是給Impala使用,還可以給其他查詢工具使用,如Hive、Pig,進一步還能對接avro/thrift/pb等序列化格式。
- 基於Dremel思想的嵌套格式存儲:關系數據庫設計模式中反對存儲復雜格式(違反第一范式),但是現在的大數據計算不僅出現了這種需求(半結構化數據),也能夠高效的實現存儲和查詢效率,在語法上也有相應的支持(各種UDF,Hive的lateral view等)。Google Dremel就在實現層面做出了范例,Parquet則完全仿照了Dremel。
多數據源查詢:Presto支持從mysql,cassandra,甚至kafka中去讀取數據,這就大大減少了數據整合時間,不需要放到HDFS里才能查詢。Impala和Hive也支持查詢hbase。
近似查詢:count distinct(基數估計)一直是sql性能殺手之一,如果能接受一定誤差的話可以采用近似算法。Impala中已經實現了近似算法(ndv),Presto則是請blinkDB合作完成。兩者都是采用了HyperLogLog Counting。