https://blog.csdn.net/u013332124/article/details/94182021
以下內(nèi)容都基于hive-2.3.3版本。
一、HiveServer2的啟動
通常我們是通過調(diào)用${hive_home}/bin下的start-hiveserver2.sh腳本來啟動HiveServer2的。start-hiveserver2.sh腳本實際是調(diào)用${hive_home}/bin/hive腳本:
#! /bin/bash
export HIVE_HOME=/www/hive
${HIVE_HOME}/bin/hive --service hiveserver2 >> ${HIVE_HOME}/logs/hiveserver2.out 2>&1 &
之后hive腳本中會根據(jù)傳入的--service的參數(shù)去調(diào)用${hive_home}/bin/ext/hiveserver2.sh腳本,最終在hiveserver2.sh腳本中才調(diào)用了HiveServer2類的main方法。
//HiveServer2.java
public static void main(String[] args) {
HiveConf.setLoadHiveServer2Config(true);
try {
ServerOptionsProcessor oproc = new ServerOptionsProcessor("hiveserver2");
//解析傳入的參數(shù)
ServerOptionsProcessorResponse oprocResponse = oproc.parse(args);
String initLog4jMessage = LogUtils.initHiveLog4j();
LOG.debug(initLog4jMessage);
HiveStringUtils.startupShutdownMessage(HiveServer2.class, args, LOG);
LOG.debug(oproc.getDebugMessage().toString());
//調(diào)用對應Executor的execute方法
oprocResponse.getServerOptionsExecutor().execute();
} catch (LogInitializationException e) {
LOG.error("Error initializing log: " + e.getMessage(), e);
System.exit(-1);
}
}
HiveServer2的main函數(shù)執(zhí)行時會先解析傳入的參數(shù),然后返回一個response,里面封裝了一個ServerOptionsExecutor。之后調(diào)用ServerOptionsExecutor#execute方法即可執(zhí)行對應的邏輯。
ServerOptionsExecutor只是一個接口,它的實現(xiàn)主要有:

比如如果傳入的ServiceName是hiveserver2,則會執(zhí)行StartOptionExecutor的execute方法,這個方法中調(diào)用了HiveServer2的startHiveServer2方法。
這時,HiveServer才開始啟動它的各個組件并提供服務。
二、HiveServer2的各個服務組件
Hive定義了一個接口Service,很多服務組件都實現(xiàn)了這個接口。這個接口定義了一個組件基本的生命周期,以及組件的各種狀態(tài):
public interface Service {
public enum STATE {
NOTINITED,
INITED,
STARTED,
STOPPED
}
void init(HiveConf conf);
void start();
void stop();
void register(ServiceStateChangeListener listener);
void unregister(ServiceStateChangeListener listener);
String getName();
HiveConf getHiveConf();
STATE getServiceState();
long getStartTime();
}
Service接口的具體實現(xiàn)有:
可以看到,包括HiveServer2其實也算Hive的一個服務組件。HiveServer2繼承了CompositeService類,這里用到了設計模式中的組合模式。這樣,HiveServer2可以同時持有多個服務組件。在HiveServer2啟動時,會相應啟動它的那些服務組件。
HiveServer2的服務組件主要有如下這些:
1、ThriftCLIService
一個Server的服務組件,它會監(jiān)聽指定的端口來對外提供服務,主要基于Thrift實現(xiàn)的rpc服務。HiveServer2在啟動ThriftCLIService時,會將CLIService的實例也傳給它。這樣,ThriftCLIService收到請求后,就可以委托給CLIService處理了。
另外,ThriftCLIService啟動時,根據(jù)hive.server2.transport.mode參數(shù)的值來決定是啟動ThriftHttpCLIService還是ThriftBinaryCLIService。默認是ThriftBinaryCLIService:
if (isHTTPTransportMode(hiveConf)) {
thriftCLIService = new ThriftHttpCLIService(cliService, oomHook);
} else {
thriftCLIService = new ThriftBinaryCLIService(cliService, oomHook);
}
2、CLIService
CLIService主要封裝了處理命令的邏輯,一條命令發(fā)到HiveServer2后,ThriftCLIService會委托給CLIService來處理。不同的命令會調(diào)用不同的CLIService方法。比如執(zhí)行Sql就是調(diào)用CliService#executeStatementAsync()方法。
3、SessionManager
CLIService在啟動時,會初始化一個SessionManager,用來管理會話。
當要建立一個會話時,會調(diào)用SessionManager#createSession()來獲取到唯一的SessionHandle返回給客戶端(session的唯一性是通過SessionHandle來標識的)。之后客戶端發(fā)送命令時將對應的SessionHandle帶上,SessionManager就可以根據(jù)這個SessionHandle獲取到具體的HiveSession對象。
拿到對應的HiveSession對象后,CLIService把具體的操作繼續(xù)委托給HiveSession的方法執(zhí)行。
4、OperationManager
SessionManager在啟動時,會初始化一個OperationManager,主要用來生成Operation(每一條命令都可以理解為是一個Operation)。
SessionManager在創(chuàng)建新的HiveSession時,會將OperationManager的實例也傳給HiveSession。后面HiveSession根據(jù)命令執(zhí)行對應的操作時會通過OperationManager獲取到對應的Operation,之后調(diào)用Operation#run()執(zhí)行具體的邏輯。
也就是說,客戶端發(fā)來的命令最終生成一個Operation對象然后執(zhí)行。不同的命令會對應不同的Operation的實現(xiàn):
很明顯,執(zhí)行Sql最終會調(diào)用SQLOperation#run()方法。
三、一個命令的具體處理過程
1、一個命令的處理流程
- ThriftCLIService服務啟動后,會監(jiān)聽指定的端口,之后有請求進來,就會獲取對應的Processor處理(可以參考thrift的架構(gòu)設計)
- 如果沒有配置kerberos,最終的Processor會是TSetIpAddressProcessor。TSetIpAddressProcessor繼承自TCLIService.Processor,TCLIService.Processor中定義了具體的命令要執(zhí)行哪些ProcessFunction
- 通過具體的命令找到對應的ProcessFunction后,就執(zhí)行ProcessFunction的getResult()方法。之后,各個ProcessFunction實現(xiàn)類的getResult()方法最終又會調(diào)用ThriftCLIService的相關(guān)方法
- ThriftCLIService從請求中獲取到SessionHandle后,就委托給CLIService的相關(guān)方法來處理
- CLIService拿著SessionHandle從SessionManager獲取到對應的HiveSession,之后繼續(xù)把命令委托給HiveSession處理
- HiveSession根據(jù)具體的命令從OperationManager中獲取到對應的Operation,這個Operation就是真正的操作對象。
- 最終調(diào)用Operation#run()方法獲取到一個OperationHandle,后面客戶端還可以通過這個OperationHandle標識來獲取到此次操作的一些狀態(tài)信息。如果是要執(zhí)行Sql,就會走到SQLOperation#run()方法,之后就進入Driver.run()方法,然后開始編譯執(zhí)行sql了。
2、關(guān)于SessionHandle和OperationHandle
在hive中,一個session表示一個會話。我們可以理解為一個beeline控制臺就是一個session,在我們通過!conn命令連接到集群后,HiveServer2就會創(chuàng)建一個HiveSession,然后交給SessionManager管理,之后返回一個SessionHandle給客戶端,這個SessionHandle就是此次session的唯一標識了。后面客戶端發(fā)送命令的時候都需要帶上這個SessionHandle,這樣HiveServer2才可以辨認出是哪個session發(fā)來的請求。
在一個session中發(fā)起一個請求,HiveServer2收到請求進行處理后,會返回一個OperationHandle來作為此次操作的唯一標識。后面客戶端可以通過這個OperationHandle標識來獲取此次操作的具體信息(發(fā)請求時帶上這個OperationHandle信息),比如獲取操作的執(zhí)行狀態(tài)、日志等信息。
這也就是說,一個session其實可以發(fā)起多個操作,只要維護好返回的OperationHandle,我們可以并行查詢這些操作的相關(guān)狀態(tài)。在beeline的控制臺中,我們發(fā)送完一個命令后,會阻塞在那里,給我們的感覺好像一個session只能同時處理一個命令。
另外,在阻塞的過程中,beeline客戶端其實也不斷的再想HiveServer2發(fā)送請求獲取日志并輸出。
beeline客戶端是如何獲取日志輸出的
只有當hive.server2.logging.operation.enabled設置為true,才會在Beeline的控制臺輸出HiveServer2那邊的相關(guān)操作日志。另外,通過hive.server2.logging.operation.level還可以調(diào)整輸出級別。
如果開啟了operation的log日志功能,Driver組件在輸出日志時就會往另外一個臨時的日志文件也輸出一份。這個臨時的日志文件是一個Operation一份。后面客戶端根據(jù)OperationHandle發(fā)送fetchResult(fetchType=1)請求來獲取對應的日志信息。這時HiveServer2只要直接去Operation對應的臨時日志文件中拉取數(shù)據(jù)即可。
Operation臨時日志文件的存放路徑和hive.server2.logging.operation.log.location參數(shù)有關(guān)。
四、HiveServer2中的那些重要線程
1、rpc請求處理線程——HiveServer2-Handler-Pool
這個線程池主要是HiveServer2用來處理rpc請求的線程。線程池的coreSize和maxSize和參數(shù)hive.server2.thrift.min.worker.threads和hive.server2.thrift.max.worker.threads相關(guān),默認值分別是5和500。
這個線程和netty的worker線程類似,有客戶端發(fā)送請求給ThriftServer,最終都會由這個線程來處理。
2、sql異步執(zhí)行線程 —— HiveServer2-Background-Pool
在SQLOperation#runInternal()方法中,如果請求要求異步操作,就會向BackGroundPool線程池提交一個異步任務,用來處理sql(也就是調(diào)用Driver#run()的邏輯),提交任務到線程池后會立馬返回一個OperationHandle,后續(xù)客戶端可以根據(jù)這個唯一標識實時的查詢?nèi)蝿者\行日志。
當然,如果請求沒要求異步操作,Driver#run()的操作將在HiveServer2-Handler-Pool的線程中執(zhí)行,這時會一直阻塞到任務執(zhí)行完才返回,也就是說,客戶端要等任務執(zhí)行完才能看到全部運行日志。
BackGroundPool線程池的coreSize和maxSize都由參數(shù)hive.server2.async.exec.threads來決定,默認值是100。
是否要異步執(zhí)行Driver#run()由thrift請求體TExecuteStatementReq中的參數(shù)runAsync來決定。目前看hive-jdbc的代碼,這個參數(shù)默認都是true。
另外,目前只有SQLOperation才支持異步執(zhí)行,其他的Operation都不支持
3、執(zhí)行task的線程
Driver在編譯完sql后,會生成物理執(zhí)行計劃,這個物理執(zhí)行計劃中包含了一系列的task。Driver執(zhí)行task的方式是將task放到一個線程中執(zhí)行。這個線程沒有特別指定名稱,通過日志我們看到的是[Thread-id],其中id會不斷自增。
因為task是在線程中執(zhí)行的,因此一個Operation是允許多個task并行的。單個Operation的并行度由配置hive.exec.parallel.thread.number來決定,默認值是8。
4、總結(jié)
在hive的日志中,各種任務的日志會交替出現(xiàn),日志又雜又多,因此理解好上面三種線程可以方便我們排查問題,快速定位任務相關(guān)的線程和日志。
五、Beeline和HiveCli的區(qū)別
在hive中,有兩種方式可以執(zhí)行hiveQL。分別是beeline和hiveCli。
1、Beeline
beeline客戶端執(zhí)行的主類是Beeline.java。
Beeline需要連接上HiveServer2后才可以執(zhí)行命令,之后通過jdbc協(xié)議往hiveServer2發(fā)送相關(guān)請求來執(zhí)行用戶的命令。
2、HiveCli
舊版的HiveCli執(zhí)行的主類是CliDriver.java,目前新版的主類是HiveCli.java(底層還是調(diào)用Beeline.java類)。
CliDriver.java的執(zhí)行流程詳解可以看下面這篇文章:
https://segmentfault.com/a/1190000002766035 —— 主要就是創(chuàng)建一個進程,在當前進程中執(zhí)行hive的相關(guān)命令,比如執(zhí)行Sql就初始化一個Driver,然后執(zhí)行run方法。
在早期的版本中,第一代HiveServer對應的客戶端實現(xiàn)就是CliDriver,后來出現(xiàn)了HiveServer2,完全取代了第一代。HiveServer2對應的客戶端是Beeline,但是由于早期使用CliDriver的用戶太多了,因此CliDriver版本一直沒有被下掉。
后面社區(qū)又開發(fā)了新版的HiveCli,其底層其實也是調(diào)用了Beeline,這樣社區(qū)只需要維護一份客戶端的代碼即可:
public static void main(String[] args) throws IOException {
int status = new HiveCli().runWithArgs(args, null);
System.exit(status);
}
public int runWithArgs(String[] cmd, InputStream inputStream) throws IOException {
beeLine = new BeeLine(false);
try {
return beeLine.begin(cmd, inputStream);
} finally {
beeLine.close();
}
}
Beeline類有個屬性isBeeline就表示這個Beeline實例是算真正的beeline客戶端還是HiveCli客戶端。
當isBeeline屬性為false時,beeline客戶端連接的是一個內(nèi)嵌的HiveServer2服務,和HiveCli在同一個進程內(nèi)。其他的邏輯都和Beeline相同。
3、總結(jié)
- beeline需要連接遠程的HiveServer2來交互
- HiveCli直接在本進程執(zhí)行命令,不用進行遠程通信
在2.3.3版本中,HiveCli默認還是使用CliDriver的實現(xiàn)方式??梢酝ㄟ^在hive-env.sh中設置USE_DEPRECATED_CLI=false來將實現(xiàn)變成HiveCli.java的形式。因為HiveCli.java會啟動內(nèi)置的HiveServer2,執(zhí)行的邏輯基本和HiveServer2一樣,所以在做一些測試時會更容易發(fā)現(xiàn)HiveServer2的一些問題。
官網(wǎng)關(guān)于Beeline和HiveCli的介紹: