hive 結合執行計劃 分析 limit 執行原理

在hive查詢中要限制查詢輸出條數, 可以用limit 關鍵詞指定,如 select columnname1 from table1 limit 10; 這樣hive將輸出符合查詢條件的10個記錄,從根本上說, hive是hadoop提交作業的客戶端,它使用antlr詞法語法分析工具,對SQL進行分析優化後翻譯成一系列MapReduce作業,向hadoop提交運行作業以得到結果.
   看一條簡單的SQL語句:

  1. select deviceid from t_aa_pc_log where pt='2012-07-07-00' limit 1;  

   這條語句指定分區字段 pt爲2012-07-07-00, 限制結果爲 limit 1. 假設運行這個MR作業需要5個map, 那麼每個map應該輸出一條記錄,從jobtrack 的 jobdetails頁面中的計數器中 Map Input Records 一項應該顯示爲5(即該作業中Map階段總共輸入5條記錄),結果是否如預計的那樣, 通過運行改SQL來驗證:

  1.    > select deviceid from t_aa_pc_log where pt='2012-07-07-00' limit 1;                                  
  2. Total MapReduce jobs = 1 
  3. Launching Job 1 out of 1 
  4. Number of reduce tasks is set to 0 since there's no reduce operator 
  5. Starting Job = job_201205162059_1547550, Tracking URL = http://jt.dc.sh-wgq.sdo.com:50030/jobdetails.jsp?jobid=job_201205162059_1547550 
  6. Kill Command = /home/hdfs/hadoop-current/bin/hadoop job  -Dmapred.job.tracker=10.133.10.103:50020 -kill job_201205162059_1547550 
  7. 2012-07-07 16:22:42,570 Stage-1 map = 0%,  reduce = 0
  8. 2012-07-07 16:22:48,628 Stage-1 map = 80%,  reduce = 0
  9. 2012-07-07 16:22:49,640 Stage-1 map = 100%,  reduce = 0
  10. 2012-07-07 16:22:50,654 Stage-1 map = 100%,  reduce = 100
  11. Ended Job = job_201205162059_1547550 
  12. OK 
  13. 0cf49387a23d9cec25da3d76d6988546 
  14. Time taken: 13.499 seconds 
  15. hive>  

正如limit 1限制,輸出一條記錄,再通過 http://jt.dc.sh-wgq.sdo.com:50030/jobdetails.jsp?jobid=job_201205162059_1547550
查看Map Input Records項:

  上圖顯示Map Input Records實際上是35,並非之前設想的每個MAP一條,總共5條,那多出來的30條記錄又是怎麼來的? 實際上這個跟hive mapreduce實現有關,先來看看上面這條SQL的執行計劃:

  1.     > explain select deviceid from t_aa_pc_log where pt='2012-07-07-00' limit 1; 
  2. OK 
  3. STAGE DEPENDENCIES: 
  4.   Stage-1 is a root stage 
  5.   Stage-0 is a root stage 
  6.  
  7. STAGE PLANS: 
  8.   Stage: Stage-1 
  9.     Map Reduce 
  10.       Alias -> Map Operator Tree: 
  11.         t_aa_pc_log  
  12.           TableScan 
  13.             alias: t_aa_pc_log 
  14.             Filter Operator 
  15.               predicate: 
  16.                   expr: (pt = '2012-07-07-00'
  17.                   type: boolean 
  18.               Select Operator 
  19.                 expressions: 
  20.                       expr: deviceid 
  21.                       type: string 
  22.                 outputColumnNames: _col0 
  23.                 Limit 
  24.                   File Output Operator 
  25.                     compressed: false 
  26.                     GlobalTableId: 0 
  27.                     table: 
  28.                         input format: org.apache.hadoop.mapred.TextInputFormat 
  29.                         output format: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat 
  30.  
  31.   Stage: Stage-0 
  32.     Fetch Operator 
  33.       limit: 1 
  34.  
  35.  
  36. Time taken: 0.418 seconds 

   改執行計劃顯示,Stage-1 是一個MR程序,且只有map過程, 沒有reduce過程,也就是說在Map過程就直接將結果輸出到HDFS文件系統, Stage-0是依賴於Stage-1的文件讀取操作,它不是MR作業,只是一個基於hadoop文件系統客戶端的分佈式文件讀取程序。
   重點分析Stage-1過程,一條記錄被讀取後調用hive自定義mapper函數,依次經過
TableScan Operator -> Filter Operator -> Select Operator -> Limit Operator-> File Output Operator, 以上每一個Operator都是hive定義的一個處理過程, 每一個 Operator都定義有:

  1. protected List<Operator<? extends Serializable>> childOperators; 
  2. protected List<Operator<? extends Serializable>> parentOperators; 

這樣就構成了一個 Operator圖,hive正是基於這些圖關係來處理諸如limit, group by, join等操作. Operator 基類定義一個:

  1. protected boolean done; // 初始化值爲false

這個字段指示某一個層級的Operator是否已經處理完成,每當一條記錄進入特定的Operator操作時,當前Operator會判斷自己的childOperators 的done是否全部爲true, 如果是, 表示childOperators已去全部處理完畢, 當前這個Operator也把自己的 done設置爲true, 這樣層層返回,直到最外層的Operator, 這個查詢中涉及的部分Operator如下圖:

 該hive MR作業中指定的mapper是:

  1. mapred.mapper.class = org.apache.hadoop.hive.ql.exec.ExecMapper 

input format是:

  1. hive.input.format   org.apache.hadoop.hive.ql.io.CombineHiveInputFormat 

部分執行流程:

MapRunner會循環調用CombineHiveRecordReader的doNext方法讀入行記錄,直到doNext方法返回false, doNext方法中有一個重要的邏輯來控制記錄讀取是否結束

  1. @Override 
  2. public boolean doNext(K key, V value) throws IOException { 
  3.   if (ExecMapper.getDone()) {
  4.     return false; 
  5.   } 
  6.   return recordReader.next(key, value); 

   每讀取一條記錄都會判斷 MapRunner.getDone()是否爲真, 如果是則結束Mapper讀取過程,  ExecMapper類中定義了一個靜態變量done(靜態非常重要,因爲在hadoop框架下執行時 CombineHiveRecordReader無法拿到 ExecMapper實例), 當 MapRunner讀取一條記錄後就會調用 MapRunner的map函數,  ExecMapper中定義了一個MapOperator,MapOperator的 childOperators 列表中持有TableScanOperator實例,依次類推, 各Operator遞歸包含.
    ExecMapper的map函數被調用時會先判斷 MapOperator的done是否爲true, 如果是,則將自己的靜態變量done設置爲true(這樣 CombineHiveRecordReader在下一次讀取記錄時發現 ExecMapper的done爲true, 結束mapper記錄讀取),  否則執行MapOperator的process方法, 具體邏輯如下:

  1. public void map(Object key, Object value, OutputCollector output, 
  2.       Reporter reporter) throws IOException { 
  3.     if (oc == null) { 
  4.       oc = output
  5.       rp = reporter
  6.       mo.setOutputCollector(oc); 
  7.       mo.setReporter(rp); 
  8.     } 
  9.     // reset the execContext for each new row 
  10.     execContext.resetRow(); 
  11.  
  12.     try { 
  13.       if (mo.getDone()) { 
  14.         done = true
  15.       } else { 
  16.         // Since there is no concept of a group, we don't invoke 
  17.         // startGroup/endGroup for a mapper 
  18.         mo.process((Writable)value); 

接下來再看看各Operator如何判斷自己狀態是否爲執行完成:

  1. int childrenDone = 0
  2. for (int i = 0; i < childOperatorsArray.length; i++) { 
  3.   Operator<? extends Serializable> o = childOperatorsArray[i]; 
  4.   if (o.getDone()) { 
  5.     childrenDone++; 
  6.   } else { 
  7.     o.process(row, childOperatorsTag[i]); 
  8.   } 
  9.  
  10. // if all children are done, this operator is also done 
  11. if (childrenDone == childOperatorsArray.length) { 
  12.   setDone(true); 

每個Operator都判斷自己的子Operator狀態是否全部完成, 如果是則把自己的狀態也設置成done=true.
最後再看LimitOperator的判斷邏輯:

  1. @Override 
  2. public void processOp(Object row, int tag) throws HiveException { 
  3.   if (currCount < limit) { 
  4.     forward(row, inputObjInspectors[tag]); 
  5.     currCount++; 
  6.   } else { 
  7.     setDone(true); 
  8.   } 

currCount 是一個記錄處理的計數器, 初始值爲0, 當該值大於等於limit後,將自己標識成處理完成狀態,即設置done=true.
   分析到現在, 已經可以非常清晰的解釋最初的疑問了, 爲什麼 limit 1, map數爲5的前提下, Map Input Records 是35而不是5
1. 第一條記錄進入LimitOperator done 爲false
2. 第二條記錄進入LimitOperator done 爲true
3. 第三條記錄進入SelectOperator done 設置爲true
4. 第四條記錄進入FilterOperator done設置爲true
5. 第五條記錄進入TableScanOperator done設置爲true
6. 第六條記錄進入MapOperator done設置爲true
7. 第7條記錄進入ExecMapper 靜態變量done設置爲true
8. 讀取第八條記錄時 CombineHiveRecordReader 發現 ExecMapper 的done已經爲true, 結束數據讀取,從而 MapRunner 退出循環, 結束mapper過程.
從上面8個步驟看出, 每個map會讀取7條記錄, 5個map, 正好是35條記錄.
   在平時工作中, 通過分析 hive 執行計劃可以讓我們清楚的知道MR中的每一個過程,理解HIVE執行過程, 進而對SQL優化.


原文:http://yaoyinjie.blog.51cto.com/3189782/923378

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章