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)用對應(yīng)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í)行對應(yīng)的邏輯。
ServerOptionsExecutor只是一個接口九默,它的實現(xiàn)主要有:
比如如果傳入的ServiceName是hiveserver2,則會執(zhí)行StartOptionExecutor的execute方法罚随,這個方法中調(diào)用了HiveServer2的startHiveServer2方法。
這時,HiveServer才開始啟動它的各個組件并提供服務(wù)。
二指蚁、HiveServer2的各個服務(wù)組件
Hive定義了一個接口Service,很多服務(wù)組件都實現(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的一個服務(wù)組件缀磕。HiveServer2繼承了CompositeService類,這里用到了設(shè)計模式中的組合模式劣光。這樣袜蚕,HiveServer2可以同時持有多個服務(wù)組件。在HiveServer2啟動時绢涡,會相應(yīng)啟動它的那些服務(wù)組件牲剃。
HiveServer2的服務(wù)組件主要有如下這些:
1、ThriftCLIService
一個Server的服務(wù)組件雄可,它會監(jiān)聽指定的端口來對外提供服務(wù)凿傅,主要基于Thrift實現(xiàn)的rpc服務(wù)。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ā)送命令時將對應(yīng)的SessionHandle帶上潜叛,SessionManager就可以根據(jù)這個SessionHandle獲取到具體的HiveSession對象。
拿到對應(yīng)的HiveSession對象后壶硅,CLIService把具體的操作繼續(xù)委托給HiveSession的方法執(zhí)行威兜。
4、OperationManager
SessionManager在啟動時庐椒,會初始化一個OperationManager椒舵,主要用來生成Operation(每一條命令都可以理解為是一個Operation)。
SessionManager在創(chuàng)建新的HiveSession時约谈,會將OperationManager的實例也傳給HiveSession笔宿。后面HiveSession根據(jù)命令執(zhí)行對應(yīng)的操作時會通過OperationManager獲取到對應(yīng)的Operation,之后調(diào)用Operation#run()執(zhí)行具體的邏輯棱诱。
也就是說泼橘,客戶端發(fā)來的命令最終生成一個Operation對象然后執(zhí)行。不同的命令會對應(yīng)不同的Operation的實現(xiàn):
很明顯迈勋,執(zhí)行Sql最終會調(diào)用SQLOperation#run()方法炬灭。
三、一個命令的具體處理過程
1靡菇、一個命令的處理流程
- ThriftCLIService服務(wù)啟動后重归,會監(jiān)聽指定的端口,之后有請求進來厦凤,就會獲取對應(yīng)的Processor處理(可以參考thrift的架構(gòu)設(shè)計)
- 如果沒有配置kerberos鼻吮,最終的Processor會是TSetIpAddressProcessor。TSetIpAddressProcessor繼承自TCLIService.Processor较鼓,TCLIService.Processor中定義了具體的命令要執(zhí)行哪些ProcessFunction
- 通過具體的命令找到對應(yīng)的ProcessFunction后狈网,就執(zhí)行ProcessFunction的getResult()方法。之后,各個ProcessFunction實現(xiàn)類的getResult()方法最終又會調(diào)用ThriftCLIService的相關(guān)方法
- ThriftCLIService從請求中獲取到SessionHandle后拓哺,就委托給CLIService的相關(guān)方法來處理
- CLIService拿著SessionHandle從SessionManager獲取到對應(yīng)的HiveSession勇垛,之后繼續(xù)把命令委托給HiveSession處理
- HiveSession根據(jù)具體的命令從OperationManager中獲取到對應(yīng)的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
設(shè)置為true,才會在Beeline的控制臺輸出HiveServer2那邊的相關(guān)操作日志。另外板惑,通過hive.server2.logging.operation.level
還可以調(diào)整輸出級別橄镜。
如果開啟了operation的log日志功能,Driver組件在輸出日志時就會往另外一個臨時的日志文件也輸出一份冯乘。這個臨時的日志文件是一個Operation一份洽胶。后面客戶端根據(jù)OperationHandle發(fā)送fetchResult(fetchType=1)請求來獲取對應(yīng)的日志信息。這時HiveServer2只要直接去Operation對應(yīng)的臨時日志文件中拉取數(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線程池提交一個異步任務(wù)肢预,用來處理sql(也就是調(diào)用Driver#run()的邏輯),提交任務(wù)到線程池后會立馬返回一個OperationHandle洼哎,后續(xù)客戶端可以根據(jù)這個唯一標識實時的查詢?nèi)蝿?wù)運行日志烫映。
當然,如果請求沒要求異步操作噩峦,Driver#run()的操作將在HiveServer2-Handler-Pool的線程中執(zhí)行锭沟,這時會一直阻塞到任務(wù)執(zhí)行完才返回,也就是說识补,客戶端要等任務(wù)執(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的日志中悼沿,各種任務(wù)的日志會交替出現(xiàn)等舔,日志又雜又多,因此理解好上面三種線程可以方便我們排查問題糟趾,快速定位任務(wù)相關(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對應(yīng)的客戶端實現(xiàn)就是CliDriver靴寂,后來出現(xiàn)了HiveServer2磷蜀,完全取代了第一代。HiveServer2對應(yīng)的客戶端是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服務(wù)虫埂,和HiveCli在同一個進程內(nèi)。其他的邏輯都和Beeline相同圃验。
3掉伏、總結(jié)
- beeline需要連接遠程的HiveServer2來交互
- HiveCli直接在本進程執(zhí)行命令,不用進行遠程通信
在2.3.3版本中澳窑,HiveCli默認還是使用CliDriver的實現(xiàn)方式斧散。可以通過在hive-env.sh
中設(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的介紹: