hive初始化、處理流程詳解
CliDriver
初始化過程
CliDriver.main 是 Cli 的入口
(1) 解析(Parse)args,放入cmdLine,處理 –hiveconf var=val 用於增加或者覆蓋hive/hadoop配置,設定到System的屬性中。
(2) 配置log4j,載入hive-log4j.properties裡的配置資訊。
(3)建立一個HiveConf,設定hiveJar= hive-exec-0.6.0.jar ,初始化載入hive-default.xml、 hive-site.xml。
(4) 建立一個CliSessionState(SessionState)
(5) 處理-S, -e, -f, -h,-i等資訊,儲存在SessionState中。如果是-h,列印提示資訊,並退出。
(6) –hiveconf var=val 設定的屬性設定到HiveConf中。
(7) ShimLoader,load HadoopShims
(8) CliSessionState設定到SessionState中,建立一個hive_job_log_ xxx檔案(用於記錄Hive的一些操作資訊)儲存到SessionState的hiveHist 。
(9) 建立CliDriver.
(10)在接受hivesql命令前,執行一些初始化命令,這些命令存在檔案中,檔案可以通過-i選項設定,如果沒有設定就去查詢是否有$HIVE_HOME/bin/.hiverc和System.getProperty("user.home")/.hiverc兩個檔案,如果有就執行這兩個檔案中的命令。
(11) 如果是–e,執行命令並退出,如果是-f,執行檔案中的命令並退出。
(12)建立ConsoleReader,讀取使用者輸入,遇到“;”為一個完整的命令,執行該命令(CliDriver.processLine ),接著讀取處理使用者的輸入。使用者輸入的命令記錄在user.home/.hivehistory檔案中。
讀取使用者輸入hivesql,處理執行過程
CliDriver.processLine 去掉命令末尾的;,
CliDriver.processCmd
Split命令,分析第一個單詞:
(1)如果是quit或者exit,不區分大小寫,退出。
(2)source,執行檔案中的HiveQL
(3)!,執行命令,如!ls,列出當前目錄的檔案資訊。
(4)list,列出jar/file/archive。
(5)如果是其他,則生成呼叫相應的CommandProcessor處理。
CommandProcessor
CommandProcessorFactory
(1)set SetProcessor,設定修改引數,設定到SessionState的HiveConf裡。
(2)dfs DfsProcessor,使用hadoop的 FsShell執行hadoop的命令。
(3)add AddResourceProcessor 新增到SessionState的resource_map裡,執行提交job的時候會寫入 Hadoop的Distributed Cache。
(4)delete DeleteResourceProcessor從SessionState的resource_map裡刪除。
(5)其他 Driver
Driver
Driver.run(String command) // 處理一條命令
{
int ret = compile(command); // 分析命令,生成Task。
ret = execute(); // 執行Task。
}
(1)詞法分析,生成AST樹,ParseDriver完成。
(2)分析AST樹,AST拆分成查詢子塊,資訊記錄在QB,這個QB在下面幾個階段都需要用到,SemanticAnalyzer.doPhase1完成。
(3)從metastore中獲取表的資訊,SemanticAnalyzer.getMetaData完成。
(4)生成邏輯執行計劃,SemanticAnalyzer.genPlan完成。
(5)優化邏輯執行計劃,Optimizer完成,ParseContext作為上下文資訊進行傳遞。
(6)生成物理執行計劃,SemanticAnalyzer.genMapRedTasks完成。
(7)物理計劃優化,PhysicalOptimizer完成,PhysicalContext作為上下文資訊進行傳遞。
(8)執行生成的物理計劃,獲得結果。
(1)~(7)在Driver的compile中完成。
(8)在Driver的execute中完成,在執行階段一個一個Task執行,不會改變物理計劃。
整個Hive程式碼架構還不夠清晰,傳遞的上下文資訊比較臃腫,比較難理解。
Driver.compile
Driver.compile(String command) // 處理一條命令
{
(1) Context
ctx = new Context(conf); // private Context ctx; Driver的一個欄位變數
(2) Parser(antlr):HiveQL->AbstractSyntaxTree(AST)
ParseDriver pd = new ParseDriver();
ASTNode tree = pd.parse(command, ctx);
(3) SemanticAnalyzer
BaseSemanticAnalyzer sem = SemanticAnalyzerFactory.get(conf, tree);
// Do semantic analysis and plan generation
sem.analyze(tree, ctx);
// 說明:如果有SEMANTIC_ANALYZER_HOOK("hive.semantic.analyzer.hook",null)這個hook,那麼會在sem.analyze(tree, ctx);前執行hook.preAnalyze(hookCtx, tree);在sem.analyze(tree, ctx);後執行hook.postAnalyze(hookCtx, sem.getRootTasks(), sem.getFetchTask()); 這裡的hook有多個
(4) QueryPlan
plan = new QueryPlan(command, sem);
(5) Schema
schema = getSchema(sem, conf); // / get the output schema
}
Parser是:
使用antlr,語法規則是 Hive.g
ql/src/java目錄下面的:org.apache.hadoop.hive.ql. ParseDriver
SemanticAnalyzerFactory/SemanticAnalyzer
多種SemanticAnalyzer:
(1)ExplainSemanticAnalyzer(會呼叫SemanticAnalyzer獲得相應資訊)
explain 某條HiveSQL時呼叫
EXPLAIN [EXTENDED] query
(2)LoadSemanticAnalyzer
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]
(3)DDLSemanticAnalyzer
SHOW TABLES、DROP TABLE、DESC TABLE等時
(4)FunctionSemanticAnalyzer
CREATE/DROP FUNCTION
(5)SemanticAnalyzer
select 等
(6)其他SemanticAnalyzer,Hive-0.6、0.7只有上面5種,trunk裡面針對新功能新特性新增了相應的SemanticAnalyzer
Driver.execute
Driver.execute() // 執行命令生成的Task(一個或多個)
{
(1) Get all the pre execution hooks and execute them.
(2) 把root Tasks 加到 runnable佇列
(3) 執行該SQL產生的Task
while (running.size() != 0 || runnable.peek() != null) { //task running佇列不為空,或者runnable不為空。
while (runnable.peek() != null && running.size() < maxthreads) {//runnable佇列不為空
Task<? extends Serializable> tsk = runnable.remove();//刪除runnable佇列頭的task
launchTask(tsk, queryId, noName, running, jobname, jobs, driverCxt); //執行Task,如果開啟了併發提交會通過新的執行緒去執行Task,否則就是主執行緒執行Task,直到Task執行完畢,把Task對應的TaskResult和TaskRunner加入running佇列
}
//從running佇列中獲取一個執行完的Task
TaskResult tskRes = pollTasks(running.keySet());
TaskRunner tskRun = running.remove(tskRes);
Task<? extends Serializable> tsk = tskRun.getTask();
int exitVal = tskRes.getExitVal(); //task完成的狀態
if (exitVal != 0) { //Task失敗
獲得task的backupTask
有backup,把backup加入到runnable佇列,沒有就需要返回return 9;,表示HiveSQL執行失敗。而不是System.exit(9);
}
// 把task的ChildTasks加入到runnable佇列。
}
(4) Get all the post execution hooks and execute them.
}
Driver.launchTask(Task<? extends Serializable> tsk, String queryId, boolean noName,
Map<TaskResult, TaskRunner> running, String jobname, int jobs, DriverContext cxt){
tsk.initialize(conf, plan, cxt); // Task初始化
TaskResult tskRes = new TaskResult(); // task資訊:是否成功執行,是否執行
TaskRunner tskRun = new TaskRunner(tsk, tskRes);
// Launch Task
if (HiveConf.getBoolVar(conf, HiveConf.ConfVars.EXECPARALLEL) && tsk.isMapRedTask()) { //併發提交開啟並且這個是MR task,在另一個執行緒中執行。
// Launch it in the parallel mode, as a separate thread only for MR tasks
tskRun.start();
} else {
tskRun.runSequential(); // 主執行緒執行
}
running.put(tskRes, tskRun); // 放入running佇列
}
Task:
(1) ConditionalTask
(2) CopyTask
(3) DDLTask
(4) ExecDriver
(5) MapRedTask
(6) ExplainTask
(7) FetchTask
(8) FunctionTask
(9) MapredLocalTask
(10) MoveTask
核心之一:SemanticAnalyzer
ql/src/java目錄下面的: org.apache.hadoop.hive.ql.SemanticAnalyzer
SemanticAnalyzer. analyzeInternal(ASTNode ast)
{
// analyze create table command
if (ast.getToken().getType() == HiveParser.TOK_CREATETABLE) { //帶有create
isCreateTable = true;
// if it is not CTAS, we don't need to go further and just return
if ((child = analyzeCreateTable(ast, qb)) == null) { // create-table-as-select 返回查詢子樹
regular create-table or create-table-like statements 返回null
return;
}
}
doPhase1(child, qb, initPhase1Ctx());//分析AST樹
getMetaData(qb); //從資料庫中獲得表的資訊
Operator sinkOp = genPlan(qb);// AST-〉operator trees
Optimizer optm = new Optimizer();
pCtx = optm.optimize();// 優化 operator trees -〉operator trees
// At this point we have the complete operator tree
// from which we want to find the reduce operator
genMapRedTasks(qb); // operator trees-〉MapReduce Tasks
}
Hive原理分析:
(1) 從HQL語句到AST的轉化過程是很機械,使用ANTLR,根據Hive.g的語法分析規則,生成AST。
(2) 從AST轉化到QB,再到DAG圖不是那麼很容易明白,所以需要理清楚一下。
AST-〉QB就是把AST裡面的一些資訊和子查詢分析出來,如所有涉及的表和表的別名(如果這條HQL查詢語句中沒有為表取別名,那麼取別名為表名)的對應關係儲存到QB的aliasToTabs,目標表(目標表即輸出的table)的子AST。where子句,select子句,join子句,等一些子查詢的AST分析出來,儲存起來。
QBMetaData是查詢相關的後設資料資訊,如所有源表(源表即從哪些表取得輸入資料)到該表的Table關聯。表的Table用來記錄Table有哪些欄位,各個欄位的型別,表的分隔符等等資訊。目標表名(存放輸出結果的表)到表的Table的關聯,目標表可以有多個,因為輸出可能是寫入多個表。
QB-〉DAG圖的轉化過程。
從QB生成operator,從生成的QB中的子查詢生成Operator並儲存記錄它們之間的父子關係,還可能插入一些operator,這些operator是一些必要的輔助功能。
後面需要對這個DAG圖,即operator圖進行拆分,生成一些mapreduce作業(job),如有一個map階段可能有多個operator,完成這些operator的功能,如某個Job的map執行多個operator,TableScanOperator是第一個operator,從讀取一個表的資料開始(一條一條記錄,record),在接著可能就是跟據where生成的operator(FilterOperator),過濾哪些不符合規則的記錄(record,key/value),在接著是執行根據select生成的Select Operator(該operator選擇僅需要的欄位,過濾無關的欄位,從而減少中間資料),最後是一個Reduce Output Operator,該operator完成map的輸出,生成中間key和value。
作業的reduce也是可以執行多個operator的。
從QB生成的Operator裡面有父子關係,生成mapreduce時,會對這個具有父子關係的operator圖進行切分,生成一個個階段,有些階段是mapreduce作業,這些作業執行多個operator的功能。
ReduceSinkOperator是map的最後一個Operator,因為該operator需要生成一個map的輸出,即輸出key和輸出value。
生成Operator樹的過程:SemanticAnalyzer.genPlan(QB qb)
(1) 子查詢必須有一個別名即alias,遍歷所有的子查詢,出現多個子查詢在Join時出現,join兩邊的表都是來自子查詢。
(2) 遍歷所有的源表,出現多個在join時出現。
(3)處理join,在on條件中的過濾條件會推到join前即ReduceSinkOperator前,如果是一個join,那麼先生成兩個ReduceSinkOperator,然後再生成JoinOperator,join這兩個表。
下面的是在SemanticAnalyzer.genBodyPlan(QB qb, Operator input) 裡面完成。
(4) optimizeMultiGroupBy
(4.1)optimizeMultiGroupBy可以優化時走的路徑跟下面的不相同。
(4.2) 對每個select進行處理,多個select出現在Multi-Group-By Inserts、Multi Table/File Inserts、Dynamic-partition Insert等情況下。multi_insert.q
SemanticAnalyzer.doPhase1(ASTNode ast, QB qb, Phase1Ctx ctx_1) {
switch (ast.getToken().getType()) {
case HiveParser.TOK_SELECTDI:
case HiveParser.TOK_SELECT:
(1) 在QBParseInfo裡儲存select查詢子節點,Map<String, ASTNode> destToSelExpr;
(2)有hint,在QBParseInfo儲存hints子節點,ASTNode hints;
(3)處理ast子樹的聚合函式,HiveParser.TOK_FUNCTION、TOK_FUNCTIONDI、TOK_FUNCTIONSTAR,
(4)處理select中column別名Map<ASTNode, String> exprToColumnAlias;
(5)儲存聚合函式,QBParseInfo的LinkedHashMap<String, LinkedHashMap<String, ASTNode>> destToAggregationExprs;
(6)TOK_FUNCTIONDI,抽取儲存distinct聚合函式,HashMap<String, List<ASTNode>> destToDistinctFuncExprs;
case HiveParser.TOK_WHERE:
在QBParseInfo裡儲存where查詢子節點,HashMap<String, ASTNode> destToWhereExpr;
case HiveParser.TOK_DESTINATION:
在QBParseInfo裡面儲存目標地址子節點資訊,HashMap<String, ASTNode> nameToDest;
case HiveParser.TOK_FROM:
只有一個子節點,有四種子節點
(1) 一種是表,資料來源於一個表。processTable,處理別名,沒有別名錶名就是別名。
(2)一種是子查詢,資料來源於子查詢,processSubQuery,子查詢必須要有個別名,子查詢可能是單獨的一個query或者是兩個query的union。子查詢也是遞迴呼叫doPhase1來完成相關分析。
(3)一種是檢視,資料來源於一個檢視,processLateralView
(4)一種是Join,資料來源於幾個表的join,processJoin,join子節點的孩子節點是兩個或者三個,孩子節點可以是表、子查詢、join子節點,儲存join子查詢ASTNode joinExpr;
case HiveParser.TOK_CLUSTERBY:
在QBParseInfo裡儲存cluster by查詢子節點,HashMap<String, ASTNode> destToClusterby;
case HiveParser.TOK_DISTRIBUTEBY:
在QBParseInfo裡儲存distribute by查詢子節點,有distribute by的時候不能有cluster by和order by,HashMap<String, ASTNode> destToDistributeby;
case HiveParser.TOK_SORTBY:
在QBParseInfo裡儲存sort by查詢子節點,有sort by的時候不能有cluster by和order by,HashMap<String, ASTNode> destToSortby;
case HiveParser.TOK_ORDERBY:
在QBParseInfo裡儲存order by查詢子節點,有order by的時候不能有cluster by,HashMap<String, ASTNode> destToOrderby;
case HiveParser.TOK_GROUPBY:
在QBParseInfo裡儲存group by查詢子節點,HashMap<String, ASTNode> destToGroupby;
case HiveParser.TOK_LIMIT:
在QBParseInfo裡儲存limit查詢子節點,HashMap<String, Integer> destToLimit;
case HiveParser.TOK_UNION:
}
if (!skipRecursion) {
// Iterate over the rest of the children
int child_count = ast.getChildCount();
for (int child_pos = 0; child_pos < child_count; ++child_pos) {
// Recurse
doPhase1((ASTNode) ast.getChild(child_pos), qb, ctx_1); //遞迴處理各個孩子節點
}
}
}
SemanticAnalyzer.getMetaData(QB qb) {
(1)從資料庫中獲取表的資訊,這些表是記錄在QB的HashMap<String, String> aliasToTabs;中
表的別名和對應的org.apache.hadoop.hive.ql.metadata.Table儲存記錄在QB的QBMetaData qbm;的HashMap<String, Table> aliasToTable;中。
(2)如果有子查詢,遞迴呼叫getMetaData(QB qb)從資料庫獲取表的資訊
(3)獲取目的表的資訊
目的子節點儲存在QBParseInfo的HashMap<String, ASTNode> nameToDest;
目的節點有2種:(3.1)目的是表,表分分割槽表和非分割槽表(3.2)目的是本地目錄或者hdfs目錄,獲得設定一箇中間臨時目錄
}
SemanticAnalyzer.genPlan(QB qb){
(1)處理子查詢,生成子查詢的operator tree
(2)遍歷source tables,記錄儲存在QB的HashMap<String, String> aliasToTabs;裡面,對每個源表生成一個TableScanOperator,儲存到SemanticAnalyzer的HashMap<TableScanOperator, Table> topToTable;裡
(3)處理檢視
(4)處理join
(5)genBodyPlan,生成剩下的operator tree.
}
SemanticAnalyzer.genBodyPlan(QB qb, Operator input) {
(1)multi-group by優化
(2)遍歷所有的destination tables,儲存記錄在QBParseInfo的Map<String, ASTNode> destToSelExpr;裡,從select獲得。
(2.1)有where語句生成FilterOperator,從QBParseInfo.destToWhereExpr裡查詢
(2.2)有group by或者聚合函式,根據相關配置生成相應operator tree.
(2.3)生成SelectOperator,選取相應欄位,來自select語句
(2.4)有cluster by 或者distribute by或者order by或者sort by生成相應的ReduceSinkOperator和ExtractOperator,如果是order by設定reduce數為1
(2.5)分兩種情況,qbp是子查詢與qbp不是子查詢
(2.5.1)是子查詢
(2.5.2)不是子查詢
有limit,生成相應的LimitOperator,這裡需要分情況,是否需要兩個MR
如果需要進行型別轉換則生成相應的SelectOperator,生成FileSinkOperator
}
Optimizer.optimize() {
}
SemanticAnalyzer.genMapRedTasks(QB qb) {
}
核心之二:MapRedTask
TaskRunner:
public void runSequential() {
int exitVal = -101;
try {
exitVal = tsk.executeTask(); //執行Task.executeTask()
} catch (Throwable t) {
t.printStackTrace();
}
result.setExitVal(exitVal);
}
Task:
public int executeTask() {
int retval = execute(driverContext); //各個子類實現該方法
}
protected abstract int execute(DriverContext driverContext);
這裡介紹MapRedTask這個Task.
MapRedTask:
public int execute(DriverContext driverContext) {
(1) setNumberOfReducers(); // estimate number of reducers 推測reduce個數
(2) if (!ctx.isLocalOnlyExecutionMode() &&
conf.getBoolVar(HiveConf.ConfVars.LOCALMODEAUTO)) { //HiveConf.ConfVars.HADOOPJT不是local,並且LOCALMODEAUTO("hive.exec.mode.local.auto", true)開啟
//hive.exec.mode.local.auto用於小job自動轉換為本地執行,should hive determine whether to run in local mode automatically
判斷job能否本地執行,目前的判斷條件是:(一)輸入資料小於等於128M (二)map數小於等於4 (三) reduce數小於等於1,這3個條件都滿足,該任務就在本地執行。
}
(3)計算得到 runningViaChild
runningViaChild =
"local".equals(conf.getVar(HiveConf.ConfVars.HADOOPJT)) ||
conf.getBoolVar(HiveConf.ConfVars.SUBMITVIACHILD);
//如果是本地執行或者通過子程式提交作業,runningViaChild為true
(3.1) 如果runningViaChild為false,super.execute(driverContext); ExecDriver.execute完成task。
(3.2) 如果runningViaChild為true,通過子程式完成
executor = Runtime.getRuntime().exec(cmdLine, env, new File(workDir));
子程式的入口main函式是ExecDriver.main()
}
核心之三:ExecDriver
/home/tianzhao/apache/hive-0.6.0/build/hadoopcore/hadoop-0.19.1/bin/hadoop jar /home/tianzhao/apache/hive-0.6.0/build/ql/hive-exec-0.6.0.jar org.apache.hadoop.hive.ql.exec.ExecDriver -plan /tmp/hive-tianzhao/hive_2011-05-31_09-30-02_222_4000721282829102058/plan5577504731701425227.xml -jobconf datanucleus.connectionPoolingType=DBCP
使用hadoop jar hive-exec-0.6.0.jar org.apache.hadoop.hive.ql.exec.ExecDriver提交job給hadoop,作業的資訊諸如operator等資訊序列化到了 -plan plan5577504731701425227.xml裡面。
ExecMapper、ExecReducer在configure(JobConf job)執行的時候會反序列化出來。
hive提交給hadoop的MapReduce作業,map階段執行ExecMapper,reduce階段執行ExecReducer。
add jar/add file/add archive,這些archive、jar和檔案會寫入 Distributed Cache裡面。在MapTask和ReduceTask執行的時候讀取呼叫。 寫入Distributed Cache參考ExecDriver。
ExecDriver使用JobClient提交Job後,定期檢視Job的進展情況,Job完成後,呼叫operator的jobClose()方法。
hive.exec.plan
org.apache.hadoop.hive.ql.exec.Utilities.setMapRedWork() 會設定plan的ID
ExecDriver.execute(DriverContext driverContext) {
(1) 建立ScratchDir目錄
(2) 設定mapper類,reducer類等等
job.setMapperClass(ExecMapper.class);
(3) 如果有MapredLocalWork,並且不是localMode,那麼上傳檔案到HDFS,該檔案加入DistributedCache。場景用於auto map join,auto map join會產生兩個Task:MapredLocalTask+MapRedTask。 MapredLocalTask將小表的資料從hdfs fetch下來,put到一個HashTable,寫入到本地的一個檔案中。在MapRedTask中把本地的這個檔案寫入hdfs,add到DistributedCache,就是當前的部分。這裡的小表寫入HashTable合併相同的key,只需要在client端做一次,在map端只需要讀取使用即可。加入DistributedCache是為了一個TaskTracker多次執行MapTask使用到一個檔案時不需要多次下載,只需一次下載即可。
(4)MapredWork寫入hdfs,副本數設定為10,加入DistributedCache
Utilities.setMapRedWork(job, work, ctx.getMRTmpFileURI());
(5)建立JobClient
JobClient jc = new JobClient(job);
(6)執行PrejobHooks
runPreJobHooks(); // Call the Pre-job hooks' list
(7)提交job
orig_rj = rj = jc.submitJob(job);
(8)定時檢測job時候執行完成
private void progress(ExecDriverTaskHandle th) throws IOException {
while (!rj.isComplete()) {
Thread.sleep(pullInterval); // pullInterval預設是1000L,HIVECOUNTERSPULLINTERVAL("hive.exec.counters.pull.interval", 1000L),通過hive.exec.counters.pull.interval可以設定
updateCounters(th); //獲取更新進度資訊
String report = " " + getId() + " map = " + mapProgress + "%, reduce = " + reduceProgress
+ "%"; // 列印這行進度資訊
}
// while迴圈外,job已經結束
runPostJobHooks(rj); //執行PostJobHooks
}
(9)清理操作
(10)執行Operator的jobClose方法
for (Operator<? extends Serializable> op : work.getAliasToWork().values()) {
op.jobClose(job, success, feedBack);
}
work.getReducer().jobClose(job, success, feedBack);
(11)return (returnVal); 返回
}
ExecDriver.main( )
兩個地方呼叫
(1)MapRedTask :
在本地執行或者通過子程式提交兩種方式下會呼叫。
ExecDriver.main(String[] args) {
} else {
MapredWork plan = Utilities.deserializeMapRedWork(pathData, conf);
ExecDriver ed = new ExecDriver(plan, conf, isSilent);
ret = ed.execute(new DriverContext());
}
}
(2)MapredLocalTask :
啟動子程式執行
ExecDriver.main(String[] args) {
if (localtask) {
memoryMXBean = ManagementFactory.getMemoryMXBean();
MapredLocalWork plan = Utilities.deserializeMapRedLocalWork(pathData, conf);
MapredLocalTask ed = new MapredLocalTask(plan, conf, isSilent);
ret = ed.executeFromChildJVM(new DriverContext()); // 從hdfs上面獲取小表資料,寫到HashTable中,然後dump到本地的一個檔案。
}
}
http://bupt04406.iteye.com/blog/1096504
初始化過程
CliDriver.main 是 Cli 的入口
(1) 解析(Parse)args,放入cmdLine,處理 –hiveconf var=val 用於增加或者覆蓋hive/hadoop配置,設定到System的屬性中。
(2) 配置log4j,載入hive-log4j.properties裡的配置資訊。
(3)建立一個HiveConf,設定hiveJar= hive-exec-0.6.0.jar ,初始化載入hive-default.xml、 hive-site.xml。
(4) 建立一個CliSessionState(SessionState)
(5) 處理-S, -e, -f, -h,-i等資訊,儲存在SessionState中。如果是-h,列印提示資訊,並退出。
(6) –hiveconf var=val 設定的屬性設定到HiveConf中。
(7) ShimLoader,load HadoopShims
(8) CliSessionState設定到SessionState中,建立一個hive_job_log_ xxx檔案(用於記錄Hive的一些操作資訊)儲存到SessionState的hiveHist 。
(9) 建立CliDriver.
(10)在接受hivesql命令前,執行一些初始化命令,這些命令存在檔案中,檔案可以通過-i選項設定,如果沒有設定就去查詢是否有$HIVE_HOME/bin/.hiverc和System.getProperty("user.home")/.hiverc兩個檔案,如果有就執行這兩個檔案中的命令。
(11) 如果是–e,執行命令並退出,如果是-f,執行檔案中的命令並退出。
(12)建立ConsoleReader,讀取使用者輸入,遇到“;”為一個完整的命令,執行該命令(CliDriver.processLine ),接著讀取處理使用者的輸入。使用者輸入的命令記錄在user.home/.hivehistory檔案中。
讀取使用者輸入hivesql,處理執行過程
CliDriver.processLine 去掉命令末尾的;,
CliDriver.processCmd
Split命令,分析第一個單詞:
(1)如果是quit或者exit,不區分大小寫,退出。
(2)source,執行檔案中的HiveQL
(3)!,執行命令,如!ls,列出當前目錄的檔案資訊。
(4)list,列出jar/file/archive。
(5)如果是其他,則生成呼叫相應的CommandProcessor處理。
CommandProcessor
CommandProcessorFactory
(1)set SetProcessor,設定修改引數,設定到SessionState的HiveConf裡。
(2)dfs DfsProcessor,使用hadoop的 FsShell執行hadoop的命令。
(3)add AddResourceProcessor 新增到SessionState的resource_map裡,執行提交job的時候會寫入 Hadoop的Distributed Cache。
(4)delete DeleteResourceProcessor從SessionState的resource_map裡刪除。
(5)其他 Driver
Driver
Driver.run(String command) // 處理一條命令
{
int ret = compile(command); // 分析命令,生成Task。
ret = execute(); // 執行Task。
}
(1)詞法分析,生成AST樹,ParseDriver完成。
(2)分析AST樹,AST拆分成查詢子塊,資訊記錄在QB,這個QB在下面幾個階段都需要用到,SemanticAnalyzer.doPhase1完成。
(3)從metastore中獲取表的資訊,SemanticAnalyzer.getMetaData完成。
(4)生成邏輯執行計劃,SemanticAnalyzer.genPlan完成。
(5)優化邏輯執行計劃,Optimizer完成,ParseContext作為上下文資訊進行傳遞。
(6)生成物理執行計劃,SemanticAnalyzer.genMapRedTasks完成。
(7)物理計劃優化,PhysicalOptimizer完成,PhysicalContext作為上下文資訊進行傳遞。
(8)執行生成的物理計劃,獲得結果。
(1)~(7)在Driver的compile中完成。
(8)在Driver的execute中完成,在執行階段一個一個Task執行,不會改變物理計劃。
整個Hive程式碼架構還不夠清晰,傳遞的上下文資訊比較臃腫,比較難理解。
Driver.compile
Driver.compile(String command) // 處理一條命令
{
(1) Context
ctx = new Context(conf); // private Context ctx; Driver的一個欄位變數
(2) Parser(antlr):HiveQL->AbstractSyntaxTree(AST)
ParseDriver pd = new ParseDriver();
ASTNode tree = pd.parse(command, ctx);
(3) SemanticAnalyzer
BaseSemanticAnalyzer sem = SemanticAnalyzerFactory.get(conf, tree);
// Do semantic analysis and plan generation
sem.analyze(tree, ctx);
// 說明:如果有SEMANTIC_ANALYZER_HOOK("hive.semantic.analyzer.hook",null)這個hook,那麼會在sem.analyze(tree, ctx);前執行hook.preAnalyze(hookCtx, tree);在sem.analyze(tree, ctx);後執行hook.postAnalyze(hookCtx, sem.getRootTasks(), sem.getFetchTask()); 這裡的hook有多個
(4) QueryPlan
plan = new QueryPlan(command, sem);
(5) Schema
schema = getSchema(sem, conf); // / get the output schema
}
Parser是:
使用antlr,語法規則是 Hive.g
ql/src/java目錄下面的:org.apache.hadoop.hive.ql. ParseDriver
SemanticAnalyzerFactory/SemanticAnalyzer
多種SemanticAnalyzer:
(1)ExplainSemanticAnalyzer(會呼叫SemanticAnalyzer獲得相應資訊)
explain 某條HiveSQL時呼叫
EXPLAIN [EXTENDED] query
(2)LoadSemanticAnalyzer
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]
(3)DDLSemanticAnalyzer
SHOW TABLES、DROP TABLE、DESC TABLE等時
(4)FunctionSemanticAnalyzer
CREATE/DROP FUNCTION
(5)SemanticAnalyzer
select 等
(6)其他SemanticAnalyzer,Hive-0.6、0.7只有上面5種,trunk裡面針對新功能新特性新增了相應的SemanticAnalyzer
Driver.execute
Driver.execute() // 執行命令生成的Task(一個或多個)
{
(1) Get all the pre execution hooks and execute them.
(2) 把root Tasks 加到 runnable佇列
(3) 執行該SQL產生的Task
while (running.size() != 0 || runnable.peek() != null) { //task running佇列不為空,或者runnable不為空。
while (runnable.peek() != null && running.size() < maxthreads) {//runnable佇列不為空
Task<? extends Serializable> tsk = runnable.remove();//刪除runnable佇列頭的task
launchTask(tsk, queryId, noName, running, jobname, jobs, driverCxt); //執行Task,如果開啟了併發提交會通過新的執行緒去執行Task,否則就是主執行緒執行Task,直到Task執行完畢,把Task對應的TaskResult和TaskRunner加入running佇列
}
//從running佇列中獲取一個執行完的Task
TaskResult tskRes = pollTasks(running.keySet());
TaskRunner tskRun = running.remove(tskRes);
Task<? extends Serializable> tsk = tskRun.getTask();
int exitVal = tskRes.getExitVal(); //task完成的狀態
if (exitVal != 0) { //Task失敗
獲得task的backupTask
有backup,把backup加入到runnable佇列,沒有就需要返回return 9;,表示HiveSQL執行失敗。而不是System.exit(9);
}
// 把task的ChildTasks加入到runnable佇列。
}
(4) Get all the post execution hooks and execute them.
}
Driver.launchTask(Task<? extends Serializable> tsk, String queryId, boolean noName,
Map<TaskResult, TaskRunner> running, String jobname, int jobs, DriverContext cxt){
tsk.initialize(conf, plan, cxt); // Task初始化
TaskResult tskRes = new TaskResult(); // task資訊:是否成功執行,是否執行
TaskRunner tskRun = new TaskRunner(tsk, tskRes);
// Launch Task
if (HiveConf.getBoolVar(conf, HiveConf.ConfVars.EXECPARALLEL) && tsk.isMapRedTask()) { //併發提交開啟並且這個是MR task,在另一個執行緒中執行。
// Launch it in the parallel mode, as a separate thread only for MR tasks
tskRun.start();
} else {
tskRun.runSequential(); // 主執行緒執行
}
running.put(tskRes, tskRun); // 放入running佇列
}
Task:
(1) ConditionalTask
(2) CopyTask
(3) DDLTask
(4) ExecDriver
(5) MapRedTask
(6) ExplainTask
(7) FetchTask
(8) FunctionTask
(9) MapredLocalTask
(10) MoveTask
核心之一:SemanticAnalyzer
ql/src/java目錄下面的: org.apache.hadoop.hive.ql.SemanticAnalyzer
SemanticAnalyzer. analyzeInternal(ASTNode ast)
{
// analyze create table command
if (ast.getToken().getType() == HiveParser.TOK_CREATETABLE) { //帶有create
isCreateTable = true;
// if it is not CTAS, we don't need to go further and just return
if ((child = analyzeCreateTable(ast, qb)) == null) { // create-table-as-select 返回查詢子樹
regular create-table or create-table-like statements 返回null
return;
}
}
doPhase1(child, qb, initPhase1Ctx());//分析AST樹
getMetaData(qb); //從資料庫中獲得表的資訊
Operator sinkOp = genPlan(qb);// AST-〉operator trees
Optimizer optm = new Optimizer();
pCtx = optm.optimize();// 優化 operator trees -〉operator trees
// At this point we have the complete operator tree
// from which we want to find the reduce operator
genMapRedTasks(qb); // operator trees-〉MapReduce Tasks
}
Hive原理分析:
(1) 從HQL語句到AST的轉化過程是很機械,使用ANTLR,根據Hive.g的語法分析規則,生成AST。
(2) 從AST轉化到QB,再到DAG圖不是那麼很容易明白,所以需要理清楚一下。
AST-〉QB就是把AST裡面的一些資訊和子查詢分析出來,如所有涉及的表和表的別名(如果這條HQL查詢語句中沒有為表取別名,那麼取別名為表名)的對應關係儲存到QB的aliasToTabs,目標表(目標表即輸出的table)的子AST。where子句,select子句,join子句,等一些子查詢的AST分析出來,儲存起來。
QBMetaData是查詢相關的後設資料資訊,如所有源表(源表即從哪些表取得輸入資料)到該表的Table關聯。表的Table用來記錄Table有哪些欄位,各個欄位的型別,表的分隔符等等資訊。目標表名(存放輸出結果的表)到表的Table的關聯,目標表可以有多個,因為輸出可能是寫入多個表。
QB-〉DAG圖的轉化過程。
從QB生成operator,從生成的QB中的子查詢生成Operator並儲存記錄它們之間的父子關係,還可能插入一些operator,這些operator是一些必要的輔助功能。
後面需要對這個DAG圖,即operator圖進行拆分,生成一些mapreduce作業(job),如有一個map階段可能有多個operator,完成這些operator的功能,如某個Job的map執行多個operator,TableScanOperator是第一個operator,從讀取一個表的資料開始(一條一條記錄,record),在接著可能就是跟據where生成的operator(FilterOperator),過濾哪些不符合規則的記錄(record,key/value),在接著是執行根據select生成的Select Operator(該operator選擇僅需要的欄位,過濾無關的欄位,從而減少中間資料),最後是一個Reduce Output Operator,該operator完成map的輸出,生成中間key和value。
作業的reduce也是可以執行多個operator的。
從QB生成的Operator裡面有父子關係,生成mapreduce時,會對這個具有父子關係的operator圖進行切分,生成一個個階段,有些階段是mapreduce作業,這些作業執行多個operator的功能。
ReduceSinkOperator是map的最後一個Operator,因為該operator需要生成一個map的輸出,即輸出key和輸出value。
生成Operator樹的過程:SemanticAnalyzer.genPlan(QB qb)
(1) 子查詢必須有一個別名即alias,遍歷所有的子查詢,出現多個子查詢在Join時出現,join兩邊的表都是來自子查詢。
(2) 遍歷所有的源表,出現多個在join時出現。
(3)處理join,在on條件中的過濾條件會推到join前即ReduceSinkOperator前,如果是一個join,那麼先生成兩個ReduceSinkOperator,然後再生成JoinOperator,join這兩個表。
下面的是在SemanticAnalyzer.genBodyPlan(QB qb, Operator input) 裡面完成。
(4) optimizeMultiGroupBy
(4.1)optimizeMultiGroupBy可以優化時走的路徑跟下面的不相同。
(4.2) 對每個select進行處理,多個select出現在Multi-Group-By Inserts、Multi Table/File Inserts、Dynamic-partition Insert等情況下。multi_insert.q
SemanticAnalyzer.doPhase1(ASTNode ast, QB qb, Phase1Ctx ctx_1) {
switch (ast.getToken().getType()) {
case HiveParser.TOK_SELECTDI:
case HiveParser.TOK_SELECT:
(1) 在QBParseInfo裡儲存select查詢子節點,Map<String, ASTNode> destToSelExpr;
(2)有hint,在QBParseInfo儲存hints子節點,ASTNode hints;
(3)處理ast子樹的聚合函式,HiveParser.TOK_FUNCTION、TOK_FUNCTIONDI、TOK_FUNCTIONSTAR,
(4)處理select中column別名Map<ASTNode, String> exprToColumnAlias;
(5)儲存聚合函式,QBParseInfo的LinkedHashMap<String, LinkedHashMap<String, ASTNode>> destToAggregationExprs;
(6)TOK_FUNCTIONDI,抽取儲存distinct聚合函式,HashMap<String, List<ASTNode>> destToDistinctFuncExprs;
case HiveParser.TOK_WHERE:
在QBParseInfo裡儲存where查詢子節點,HashMap<String, ASTNode> destToWhereExpr;
case HiveParser.TOK_DESTINATION:
在QBParseInfo裡面儲存目標地址子節點資訊,HashMap<String, ASTNode> nameToDest;
case HiveParser.TOK_FROM:
只有一個子節點,有四種子節點
(1) 一種是表,資料來源於一個表。processTable,處理別名,沒有別名錶名就是別名。
(2)一種是子查詢,資料來源於子查詢,processSubQuery,子查詢必須要有個別名,子查詢可能是單獨的一個query或者是兩個query的union。子查詢也是遞迴呼叫doPhase1來完成相關分析。
(3)一種是檢視,資料來源於一個檢視,processLateralView
(4)一種是Join,資料來源於幾個表的join,processJoin,join子節點的孩子節點是兩個或者三個,孩子節點可以是表、子查詢、join子節點,儲存join子查詢ASTNode joinExpr;
case HiveParser.TOK_CLUSTERBY:
在QBParseInfo裡儲存cluster by查詢子節點,HashMap<String, ASTNode> destToClusterby;
case HiveParser.TOK_DISTRIBUTEBY:
在QBParseInfo裡儲存distribute by查詢子節點,有distribute by的時候不能有cluster by和order by,HashMap<String, ASTNode> destToDistributeby;
case HiveParser.TOK_SORTBY:
在QBParseInfo裡儲存sort by查詢子節點,有sort by的時候不能有cluster by和order by,HashMap<String, ASTNode> destToSortby;
case HiveParser.TOK_ORDERBY:
在QBParseInfo裡儲存order by查詢子節點,有order by的時候不能有cluster by,HashMap<String, ASTNode> destToOrderby;
case HiveParser.TOK_GROUPBY:
在QBParseInfo裡儲存group by查詢子節點,HashMap<String, ASTNode> destToGroupby;
case HiveParser.TOK_LIMIT:
在QBParseInfo裡儲存limit查詢子節點,HashMap<String, Integer> destToLimit;
case HiveParser.TOK_UNION:
}
if (!skipRecursion) {
// Iterate over the rest of the children
int child_count = ast.getChildCount();
for (int child_pos = 0; child_pos < child_count; ++child_pos) {
// Recurse
doPhase1((ASTNode) ast.getChild(child_pos), qb, ctx_1); //遞迴處理各個孩子節點
}
}
}
SemanticAnalyzer.getMetaData(QB qb) {
(1)從資料庫中獲取表的資訊,這些表是記錄在QB的HashMap<String, String> aliasToTabs;中
表的別名和對應的org.apache.hadoop.hive.ql.metadata.Table儲存記錄在QB的QBMetaData qbm;的HashMap<String, Table> aliasToTable;中。
(2)如果有子查詢,遞迴呼叫getMetaData(QB qb)從資料庫獲取表的資訊
(3)獲取目的表的資訊
目的子節點儲存在QBParseInfo的HashMap<String, ASTNode> nameToDest;
目的節點有2種:(3.1)目的是表,表分分割槽表和非分割槽表(3.2)目的是本地目錄或者hdfs目錄,獲得設定一箇中間臨時目錄
}
SemanticAnalyzer.genPlan(QB qb){
(1)處理子查詢,生成子查詢的operator tree
(2)遍歷source tables,記錄儲存在QB的HashMap<String, String> aliasToTabs;裡面,對每個源表生成一個TableScanOperator,儲存到SemanticAnalyzer的HashMap<TableScanOperator, Table> topToTable;裡
(3)處理檢視
(4)處理join
(5)genBodyPlan,生成剩下的operator tree.
}
SemanticAnalyzer.genBodyPlan(QB qb, Operator input) {
(1)multi-group by優化
(2)遍歷所有的destination tables,儲存記錄在QBParseInfo的Map<String, ASTNode> destToSelExpr;裡,從select獲得。
(2.1)有where語句生成FilterOperator,從QBParseInfo.destToWhereExpr裡查詢
(2.2)有group by或者聚合函式,根據相關配置生成相應operator tree.
(2.3)生成SelectOperator,選取相應欄位,來自select語句
(2.4)有cluster by 或者distribute by或者order by或者sort by生成相應的ReduceSinkOperator和ExtractOperator,如果是order by設定reduce數為1
(2.5)分兩種情況,qbp是子查詢與qbp不是子查詢
(2.5.1)是子查詢
(2.5.2)不是子查詢
有limit,生成相應的LimitOperator,這裡需要分情況,是否需要兩個MR
如果需要進行型別轉換則生成相應的SelectOperator,生成FileSinkOperator
}
Optimizer.optimize() {
}
SemanticAnalyzer.genMapRedTasks(QB qb) {
}
核心之二:MapRedTask
TaskRunner:
public void runSequential() {
int exitVal = -101;
try {
exitVal = tsk.executeTask(); //執行Task.executeTask()
} catch (Throwable t) {
t.printStackTrace();
}
result.setExitVal(exitVal);
}
Task:
public int executeTask() {
int retval = execute(driverContext); //各個子類實現該方法
}
protected abstract int execute(DriverContext driverContext);
這裡介紹MapRedTask這個Task.
MapRedTask:
public int execute(DriverContext driverContext) {
(1) setNumberOfReducers(); // estimate number of reducers 推測reduce個數
(2) if (!ctx.isLocalOnlyExecutionMode() &&
conf.getBoolVar(HiveConf.ConfVars.LOCALMODEAUTO)) { //HiveConf.ConfVars.HADOOPJT不是local,並且LOCALMODEAUTO("hive.exec.mode.local.auto", true)開啟
//hive.exec.mode.local.auto用於小job自動轉換為本地執行,should hive determine whether to run in local mode automatically
判斷job能否本地執行,目前的判斷條件是:(一)輸入資料小於等於128M (二)map數小於等於4 (三) reduce數小於等於1,這3個條件都滿足,該任務就在本地執行。
}
(3)計算得到 runningViaChild
runningViaChild =
"local".equals(conf.getVar(HiveConf.ConfVars.HADOOPJT)) ||
conf.getBoolVar(HiveConf.ConfVars.SUBMITVIACHILD);
//如果是本地執行或者通過子程式提交作業,runningViaChild為true
(3.1) 如果runningViaChild為false,super.execute(driverContext); ExecDriver.execute完成task。
(3.2) 如果runningViaChild為true,通過子程式完成
executor = Runtime.getRuntime().exec(cmdLine, env, new File(workDir));
子程式的入口main函式是ExecDriver.main()
}
核心之三:ExecDriver
/home/tianzhao/apache/hive-0.6.0/build/hadoopcore/hadoop-0.19.1/bin/hadoop jar /home/tianzhao/apache/hive-0.6.0/build/ql/hive-exec-0.6.0.jar org.apache.hadoop.hive.ql.exec.ExecDriver -plan /tmp/hive-tianzhao/hive_2011-05-31_09-30-02_222_4000721282829102058/plan5577504731701425227.xml -jobconf datanucleus.connectionPoolingType=DBCP
使用hadoop jar hive-exec-0.6.0.jar org.apache.hadoop.hive.ql.exec.ExecDriver提交job給hadoop,作業的資訊諸如operator等資訊序列化到了 -plan plan5577504731701425227.xml裡面。
ExecMapper、ExecReducer在configure(JobConf job)執行的時候會反序列化出來。
hive提交給hadoop的MapReduce作業,map階段執行ExecMapper,reduce階段執行ExecReducer。
add jar/add file/add archive,這些archive、jar和檔案會寫入 Distributed Cache裡面。在MapTask和ReduceTask執行的時候讀取呼叫。 寫入Distributed Cache參考ExecDriver。
ExecDriver使用JobClient提交Job後,定期檢視Job的進展情況,Job完成後,呼叫operator的jobClose()方法。
hive.exec.plan
org.apache.hadoop.hive.ql.exec.Utilities.setMapRedWork() 會設定plan的ID
ExecDriver.execute(DriverContext driverContext) {
(1) 建立ScratchDir目錄
(2) 設定mapper類,reducer類等等
job.setMapperClass(ExecMapper.class);
(3) 如果有MapredLocalWork,並且不是localMode,那麼上傳檔案到HDFS,該檔案加入DistributedCache。場景用於auto map join,auto map join會產生兩個Task:MapredLocalTask+MapRedTask。 MapredLocalTask將小表的資料從hdfs fetch下來,put到一個HashTable,寫入到本地的一個檔案中。在MapRedTask中把本地的這個檔案寫入hdfs,add到DistributedCache,就是當前的部分。這裡的小表寫入HashTable合併相同的key,只需要在client端做一次,在map端只需要讀取使用即可。加入DistributedCache是為了一個TaskTracker多次執行MapTask使用到一個檔案時不需要多次下載,只需一次下載即可。
(4)MapredWork寫入hdfs,副本數設定為10,加入DistributedCache
Utilities.setMapRedWork(job, work, ctx.getMRTmpFileURI());
(5)建立JobClient
JobClient jc = new JobClient(job);
(6)執行PrejobHooks
runPreJobHooks(); // Call the Pre-job hooks' list
(7)提交job
orig_rj = rj = jc.submitJob(job);
(8)定時檢測job時候執行完成
private void progress(ExecDriverTaskHandle th) throws IOException {
while (!rj.isComplete()) {
Thread.sleep(pullInterval); // pullInterval預設是1000L,HIVECOUNTERSPULLINTERVAL("hive.exec.counters.pull.interval", 1000L),通過hive.exec.counters.pull.interval可以設定
updateCounters(th); //獲取更新進度資訊
String report = " " + getId() + " map = " + mapProgress + "%, reduce = " + reduceProgress
+ "%"; // 列印這行進度資訊
}
// while迴圈外,job已經結束
runPostJobHooks(rj); //執行PostJobHooks
}
(9)清理操作
(10)執行Operator的jobClose方法
for (Operator<? extends Serializable> op : work.getAliasToWork().values()) {
op.jobClose(job, success, feedBack);
}
work.getReducer().jobClose(job, success, feedBack);
(11)return (returnVal); 返回
}
ExecDriver.main( )
兩個地方呼叫
(1)MapRedTask :
在本地執行或者通過子程式提交兩種方式下會呼叫。
ExecDriver.main(String[] args) {
} else {
MapredWork plan = Utilities.deserializeMapRedWork(pathData, conf);
ExecDriver ed = new ExecDriver(plan, conf, isSilent);
ret = ed.execute(new DriverContext());
}
}
(2)MapredLocalTask :
啟動子程式執行
ExecDriver.main(String[] args) {
if (localtask) {
memoryMXBean = ManagementFactory.getMemoryMXBean();
MapredLocalWork plan = Utilities.deserializeMapRedLocalWork(pathData, conf);
MapredLocalTask ed = new MapredLocalTask(plan, conf, isSilent);
ret = ed.executeFromChildJVM(new DriverContext()); // 從hdfs上面獲取小表資料,寫到HashTable中,然後dump到本地的一個檔案。
}
}
http://bupt04406.iteye.com/blog/1096504
相關文章
- Hive 日期處理Hive
- Java Struts2 的請求處理流程詳解Java
- 使用Hive處理WordCountHive
- Hive處理Json資料HiveJSON
- 架構設計 | 非同步處理流程,多種實現模式詳解架構非同步模式
- Hive JOIN使用詳解Hive
- 詳解Bash命令列處理命令列
- JavaScript之事件處理詳解JavaScript事件
- 批處理中的for詳解
- Hive學習之六 《Hive進階— —hive jdbc》 詳解HiveJDBC
- Java註解處理器使用詳解Java
- Nginx請求處理流程你瞭解嗎?Nginx
- 非同步流程處理非同步
- KafkaSpout的處理流程Kafka
- BIEE 之 處理流程
- Hive sql語法詳解HiveSQL
- Hive中的UDF詳解Hive
- Hive的Transform功能詳解HiveORM
- 詳解C#異常處理C#
- MySQL 動態字串處理詳解MySql字串
- Kafka流處理內幕詳解Kafka
- 文字預處理技術詳解
- Reactor詳解之:異常處理React
- Mysql字元處理函式詳解MySql字元函式
- python時間處理詳解Python
- 詳解Bash命令列處理(轉)命令列
- python異常處理詳解Python
- Hive學習之三 《Hive的表的詳解和應用案例詳解》Hive
- hive 初始化變數Hive變數
- hive:初始化報錯Hive
- RxJava2 錯誤處理詳解RxJava
- Ceph pg unfound處理過程詳解
- Python Excel處理庫openpyxl詳解PythonExcel
- TCP通訊處理粘包詳解TCP
- 批處理中的for詳解(轉載)
- 遙感影像處理流程
- Mongodb請求處理流程MongoDB
- 關於Disruptor處理流程