Hive常用的長服務主要有HiveServer2和MetaStore,這兩者都可以配置一些監控數據。HiveServer2可以配置若干監控,有關HiveServer2的更多介紹可以查看文檔Setting Up Hiveser2。
一、HiveServer2 UI
從Hive 2.0.0版本以後,提供了一個Hiveser2的UI界面,默認通過10002端口訪問。在該頁面上可以看到當前活躍的HiveServe2連接Sessions,以及日誌,Metrics信息等。
http://host:10002/jmx 路徑下可以獲取到HiveServer2一些監控項,但主要是jvm相關的監控,通過參數配置可以增加其他監控項。Hive中可配置的Metrics相關信息可以參考Hive Metrics,大致列舉如下,
參數 | 默認值 | 含義 |
---|---|---|
hive.metastore.metrics.enabled | false | 打開metastore的監控項 |
hive.server2.metrics.enabled | false | 打開hiveserver2的監控項 |
hive.service.metrics.class | org.apache.hadoop.hive.common.metrics.metrics2.CodahaleMetrics | hive監控體系實現類 |
hive.service.metrics.reporter | JSON_FILE, JMX | 監控信息的返回格式,可選JSON_FILE, CONSOLE, JMX多個選項用逗號分隔 |
hive.service.metrics.file.frequency | 5秒 | 監控項更新時間間隔 |
hive.service.metrics.file.location | /tmp/report.json | 當使用metrics2的默認類,並且返回JSON_FILE格式時,監控信息會輸出到該文件中,文件內容會覆蓋,時間間隔由參數控制 |
將參數hive.metastore.metrics.enabled
打開後,再次訪問jmx路徑會看到多了很多metrics:name=
開頭的監控項,
hive的Metrics信息如下所示,
二、Hive源代碼
如上圖所示,可以看到有metrics:name=api_
以及metrics:name=active_calls_
開頭的監控項明細信息,這些參數都在org.apache.hadoop.hive.common.metrics.metrics2.CodahaleMetrics
類中定義,正如上面提到的,這個類在Hive-2版本中是參數hive.service.metrics.class
的默認值。
1、MetricsFactory類
CodahaleMetrics
類是在org.apache.hadoop.hive.common.metrics.common.MetricsFactory
類中通過init
方法構造出來的,過程如下所示,通過參數來選擇具體的實現類。
public synchronized static void init(HiveConf conf) throws Exception {
if (metrics == null) {
Class metricsClass = conf.getClassByName(
conf.getVar(HiveConf.ConfVars.HIVE_METRICS_CLASS));
Constructor constructor = metricsClass.getConstructor(HiveConf.class);
metrics = (Metrics) constructor.newInstance(conf);
}
}
JvmPauseMonitor, HiveMetastore, PerfLogger, Operation, SQLOperation, HiveServer2
等類通過調用MetricsFactory.getInstance
方法獲取Metrics
對象。
1、Metrics收集
上面提到主要有5個類都會通過Metrics
對象收集監控數據,接下來逐個分析。
(1)org.apache.hadoop.hive.common.JvmPauseMonitor
從類註釋看,該類是基於Hadoop中的JvmPauseMonitor
類實現的。當Hive啓動JVM進程時會生成JvmPauserMonitor
對象來收集JVM相關的監控數據,比較常見的是HiveServer2
和HiveMetaStore
進程。使用方法如下,
JvmPauseMonitor pauseMonitor = new JvmPauseMonitor(conf);
pauseMonitor.start();
接下來看一下start
方法,在該方法中會生成一個守護線程,Monitor
是JvmPauserMonitor
內部類。
public void start() {
Preconditions.checkState(monitorThread == null,
"JvmPauseMonitor thread is Already started");
monitorThread = new Daemon(new Monitor());
monitorThread.start();
}
在Monitor
類中,三次調用incrementMetricsCounter
方法調用Metrics.incrementCounter
方法來累加指定監控項的數值,
private void incrementMetricsCounter(String name, long count) {
Metrics metrics = MetricsFactory.getInstance();
if (metrics != null) {
try {
metrics.incrementCounter(name, count);
} catch (Exception e) {
LOG.warn("Error Reporting JvmPauseMonitor to Metrics system", e);
}
}
}
這三次調用的監控項定義在MetricsConstant
中,
public static final String JVM_PAUSE_INFO = "jvm.pause.info-threshold";
public static final String JVM_PAUSE_WARN = "jvm.pause.warn-threshold";
public static final String JVM_EXTRA_SLEEP = "jvm.pause.extraSleepTime";
(2)HiveMetaStore
這個類中最主要的方法是startFunction
,如下所示,
private String startFunction(String function, String extraLogInfo) {
incrementCounter(function);
logInfo((getThreadLocalIpAddress() == null ? "" : "source:" + getThreadLocalIpAddress() + " ") +
function + extraLogInfo);
if (MetricsFactory.getInstance() != null) {
try {
MetricsFactory.getInstance().startStoredScope(function);
} catch (IOException e) {
LOG.debug("Exception when starting metrics scope"
+ e.getClass().getName() + " " + e.getMessage(), e);
}
}
return function;
}
調用該方法的入口大致如下,每一次對元數據的操作,比如建表,建庫,查表等都有對應的處理函數,同時會通過該方法更新一下Metrics信息。
所有通過CodahaleMetrics.startStoredScope
方法進行監控的指標,都會在方法名前加一個“api_”前綴。下面這段代碼位於CodahaleMetrics
中。
public static final String API_PREFIX = "api_";
public void startStoredScope(String name) throws IOException {
name = API_PREFIX + name;
if (threadLocalScopes.get().containsKey(name)) {
threadLocalScopes.get().get(name).open();
} else {
threadLocalScopes.get().put(name, new CodahaleMetricsScope(name, this));
}
}
(3)PerfLogger
在PerfLogger
中,主要在beginMetrics
方法中用到了Metrics
,而beginMetrics
方法的入口又是PerfLogBegin
。
public void PerfLogBegin(String callerName, String method) {
long startTime = System.currentTimeMillis();
startTimes.put(method, new Long(startTime));
if (LOG.isDebugEnabled()) {
LOG.debug("<PERFLOG method=" + method + " from=" + callerName + ">");
}
beginMetrics(method);
}
PerfLogger
是一個輔助類,可以用於監控代碼片段的執行效率,如上面的代碼所示,當日志調整到DEBUG級別時,調用PerfLogger
相關方法將會打印方法的耗時信息等,在PerfLogEnd
方法中,將會輸出一個方法完整的運行耗時等性能信息。當Hive需要排查性能問題時,可以使用本方法。
Hive中有多個類使用了PerfLogger
,比如org.apache.hadoop.hive.ql.Driver
,在compile
方法中的調用如下所示,
perfLogger.PerfLogBegin(CLASS_NAME, PerfLogger.PARSE);
ParseDriver pd = new ParseDriver();
ASTNode tree = pd.parse(command, ctx);
tree = ParseUtils.findRootNonNullToken(tree);
perfLogger.PerfLogEnd(CLASS_NAME, PerfLogger.PARSE);
在解析之前,調用PerfLogBegin
方法,傳入類名,以及對應的parse
標識符,在這段邏輯結束時,調用PerfLogEnd
方法,傳入相同參數,日誌中將會顯示這一段邏輯從開始到結束的執行時長等信息。
(4)HiveServer2
在HiveServer2中,直接操作Metrics
的地方只有兩處,一處是在init
方法中,如下所示,當參數hive.server2.metrics.enabled
打開時,初始化MetricsFactory
生成對應的Metrics
類。
public synchronized void init(HiveConf hiveConf) {
//Initialize metrics first, as some metrics are for initialization stuff.
try {
if (hiveConf.getBoolVar(ConfVars.HIVE_SERVER2_METRICS_ENABLED)) {
MetricsFactory.init(hiveConf);
}
} catch (Throwable t) {
LOG.warn("Could not initiate the HiveServer2 Metrics system. Metrics may not be reported.", t);
}
cliService = new CLIService(this);
addService(cliService);
...
}
另一處是在stop
方法中,停止HiveServer2服務時,調用MetricsFactory.close()
方法關閉Metrics。
上面代碼中,啓動HiveServer2後,會生成CLIService
對象,後續連接該Server的提交的SQL任務,具體的Metrics信息都由下面的Operation
處理。
(4)Operation
及其子類
Operation
有兩個直接子類MetadataOperation
和ExecuteStagementOperation
,子類繼承關係如下圖所示,
2、CodahaleMetrics
類
該類繼承自org.apache.hadoop.hive.common.metrics.common.Metrics
類,
(1)incrementCounter(String name, long increment)
該方法在前面的代碼中已經提到,根據監控項的名稱name,找到對應監控項的值,然後增加該監控項的數值increment。
(2)decrementCounter(String name, long decrement)
與上面的方法功能相反,減少監控項的數值。
(2)startStoredScope
前面HiveMetaStore.startFunction
方法,連接該metastore執行不同操作時調用的方法,全部進入這個方法中,該方法的監控值加1,然後將數據存入StoredScope
對象中,這個對象中存儲的數值可以由JMXReporter
或者JsonFileReporter
向外部暴露。
private String startFunction(String function, String extraLogInfo) {
incrementCounter(function);
logInfo((getThreadLocalIpAddress() == null ? "" : "source:" + getThreadLocalIpAddress() + " ") +
function + extraLogInfo);
if (MetricsFactory.getInstance() != null) {
try {
MetricsFactory.getInstance().startStoredScope(function);
} catch (IOException e) {
LOG.debug("Exception when starting metrics scope"
+ e.getClass().getName() + " " + e.getMessage(), e);
}
}
return function;
}
(3)endStoredScope
從HiveMetaStore中開始過程與StartStoredScope
相反。
從當前線程中移除指定監控項。
public void endStoredScope(String name) throws IOException {
name = API_PREFIX + name;
if (threadLocalScopes.get().containsKey(name)) {
threadLocalScopes.get().get(name).close();
threadLocalScopes.get().remove(name);
}
}
(5)addGauge
將監控項加入到監控存儲對象metricRegistry
中,該對象是MetricRegistry
類型,後面有提到。
public void addGauge(String name, final MetricsVariable variable) {
Gauge gauge = new Gauge() {
@Override
public Object getValue() {
return variable.getValue();
}
};
try {
gaugesLock.lock();
gauges.put(name, gauge);
// Metrics throws an Exception if we don't do this when the key already exists
if (metricRegistry.getGauges().containsKey(name)) {
LOGGER.warn("A Gauge with name [" + name + "] already exists. "
+ " The old gauge will be overwritten, but this is not recommended");
metricRegistry.remove(name);
}
metricRegistry.register(name, gauge);
} finally {
gaugesLock.unlock();
}
}
(6)initReporting
初始化Reporter對象,由參數hive.service.metrics.reporter
控制,常用的是JMX, JSON_FILE。
3、Reporter
這裏是向外部暴露Metrics信息的地方,常用的有以下兩個實現,由參數hive.service.metrics.reporter
配置具體的輸出方式。在上面的initReporting
來構造對應的Reporter。
(1)JMXReporter
略,位於metrics-core-3.1.0.jar
的com.codahale.metrics
路徑下。構造好該對象,調用start
方法。
(2)JsonFileReporter
JsonFileReporter
是CodahaleMetrics
的內部類。start
方法定時從MetricRegistry
類中取出收集到的監控數據,輸出到hive.service.metrics.file.location
路徑下。
上面的Gauge
對象也通過metricRegistry.register(name, gauge)
方法註冊進來。