基本介紹:
什麼是Metric?
metric就是Druid運行過程中產生的一些指標,如查詢時間、查詢成功數量、JVM參數、任務成功數等。
Metric有什麼用?
對Druid進行異常監控報警,對指標數據進行分析等。
Metric發送到哪?
發送位置可以配置,包括日誌、http等。
send metric流程(以查詢爲例):
- 查詢並得到查詢結果
- 生成QueryMetrics,裏面包含全部要發送信息
- 通過QueryMetrics的bulid得到metric event
- 將event加入batch
- batch裝滿了就包裝起來(seal)
- 將batch加入隊列並喚醒EmittingThread線程發送
源碼分析:
1.查詢結束後調用QueryLifecycle的emitLogsAndMetrics函數發送metrics
QueryResource是查詢的入口,查詢會調用其中的doPost函數
在doPost函數中會首先創建QueryLifecycle用於管理查詢的生命週期:
1
|
final QueryLifecycle queryLifecycle = queryLifecycleFactory.factorize();
|
通過QueryLifecycle的execute函數得到查詢結果:
1
2
|
final QueryLifecycle.QueryResponse queryResponse = queryLifecycle.execute();
final Sequence<?> results = queryResponse.getResults();
|
通過QueryLifecycle的emitLogsAndMetrics函數發送metrics:
1
|
queryLifecycle.emitLogsAndMetrics( null , req.getRemoteAddr(), - 1 );
|
接下來我們來看下QueryLifecycle的emitLogsAndMetrics函數。
2.生成QueryMetrics
首先生成QueryMetrics,其包含全部要發送的信息,生成QueryMetrics的代碼如下:
1
2
3
4
5
6
7
8
|
QueryMetrics queryMetrics = DruidMetrics.makeRequestMetrics(
queryMetricsFactory,
toolChest,
baseQuery,
StringUtils.nullToEmptyNonDruidDataString(remoteAddress)
);
queryMetrics.success(success);
queryMetrics.reportQueryTime(queryTimeNs);
|
接下來我們看一下DruidMetrics的makeRequestMetrics函數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public static <T> QueryMetrics<?> makeRequestMetrics(
final GenericQueryMetricsFactory queryMetricsFactory,
final QueryToolChest<T, Query<T>> toolChest,
final Query<T> query,
final String remoteAddr
)
{
QueryMetrics<? super Query<T>> queryMetrics;
if (toolChest != null ) {
queryMetrics = toolChest.makeMetrics(query);
} else {
queryMetrics = queryMetricsFactory.makeMetrics(query);
}
queryMetrics.context(query);
queryMetrics.remoteAddress(remoteAddr);
return queryMetrics;
}
|
在makeRequestMetrics函數中,根據toolChest是否存在決定使用何種方式初始化一個queryMetrics,如果toolChest存在,調用toolChest的makeMetrics函數,我們首先看一下這個函數:
1
2
3
4
5
6
7
|
@Override
public TimeseriesQueryMetrics makeMetrics(TimeseriesQuery query)
{
TimeseriesQueryMetrics queryMetrics = queryMetricsFactory.makeMetrics();
queryMetrics.query(query);
return queryMetrics;
}
|
如果toolChest不存在,則調用queryMetricsFactory的makeMetrics函數初始化,接下來看一下這個函數:
1
2
3
4
5
6
7
|
@Override
public QueryMetrics<Query<?>> makeMetrics(Query<?> query)
{
DefaultQueryMetrics<Query<?>> queryMetrics = new DefaultQueryMetrics<>(jsonMapper);
queryMetrics.query(query);
return queryMetrics;
}
|
通過觀察,我們發現這兩種方法都初始化一個queryMetrics,然後調用query方法,接下來我們看一下query方法如果將查詢所帶有的信息存儲在queryMetrics中
1
2
3
4
5
6
7
8
9
10
11
|
@Override
public void query(QueryType query)
{
dataSource(query);
queryType(query);
interval(query);
hasFilters(query);
duration(query);
queryId(query);
sqlQueryId(query);
}
|
在query函數中,通過上面所列函數將query所帶的dataSource、queryType、interval等信息放入queryMetrics的bulider屬性中,以dataSource爲例:
1
2
3
4
5
6
7
8
9
10
|
@Override
public void dataSource(QueryType query)
{
setDimension(DruidMetrics.DATASOURCE, DataSourceUtil.getMetricName(query.getDataSource()));
}
protected void setDimension(String dimension, String value)
{
checkModifiedFromOwnerThread();
builder.setDimension(dimension, value);
}
|
我們現在再回到QueryLifecycle的emitLogsAndMetrics函數,下面語句會將查詢時間信息存儲在queryMetrics的metrics屬性裏。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
queryMetrics.reportQueryTime(queryTimeNs);
@Override
public QueryMetrics<QueryType> reportQueryTime( long timeNs)
{
return reportMillisTimeMetric( "query/time" , timeNs);
}
private QueryMetrics<QueryType> reportMillisTimeMetric(String metricName, long timeNs)
{
return reportMetric(metricName, TimeUnit.NANOSECONDS.toMillis(timeNs));
}
protected QueryMetrics<QueryType> reportMetric(String metricName, Number value)
{
checkModifiedFromOwnerThread();
metrics.put(metricName, value);
return this ;
}
|
現在我們就創建好了queryMetrics,其屬性bulider和metrics中含有一條metrics所需要的全部信息。
3.queryMetrics通過bulid函數得到metric event(event表示一條metric數據)
我們回到QueryLifecycle的emitLogsAndMetrics函數,我們現在構建好了queryMetrics,將使用queryMetrics的emit函數發射metrics
1
|
queryMetrics.emit(emitter);
|
我們查看一下emit這個函數:
1
2
3
4
5
6
7
8
9
10
11
|
@Override
public void emit(ServiceEmitter emitter)
{
checkModifiedFromOwnerThread();
for (Map.Entry<String, Number> metric : metrics.entrySet()) {
//builder.build(metric.getKey(), metric.getValue()) = ServiceEventBuilder
//ServiceEventBuilder裏有bulid方法返回一個ServiceMetricEvent
emitter.emit(builder.build(metric.getKey(), metric.getValue()));
}
metrics.clear();
}
|
在這個函數中,對於metrics中的每一個metric,取出它的key和value作爲builder的bulid函數的參數,builder的bulid函數會返回一個ServiceEventBuilder,ServiceEventBuilder有一個函數bulid函數會返回一個ServiceMetricEvent,ServiceMetricEvent表示一條metric數據。我們可以i看一下相關代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public ServiceEventBuilder<ServiceMetricEvent> build(
final String metric,
final Number value
)
{
return build( null , metric, value);
}
public ServiceEventBuilder<ServiceMetricEvent> build(
final DateTime createdTime,
final String metric,
final Number value
)
{
if (Double.isNaN(value.doubleValue())) {
throw new ISE( "Value of NaN is not allowed!" );
}
if (Double.isInfinite(value.doubleValue())) {
throw new ISE( "Value of Infinite is not allowed!" );
}
return new ServiceEventBuilder<ServiceMetricEvent>()
{
@Override
public ServiceMetricEvent build(ImmutableMap<String, String> serviceDimensions)
{
return new ServiceMetricEvent(
createdTime,
serviceDimensions,
userDims,
feed,
metric,
value
);
}
};
}
|
我們現在再回到emit函數,其中會調用ServiceEmitter的emit函數,其參數是我們剛纔得到的ServiceEventBuilder,我們看一下ServiceEmitter的emit函數:
1
2
3
4
5
|
public void emit(ServiceEventBuilder builder)
{
//builder.build(serviceDimensions) = ServiceMetricEvent
emit(builder.build(serviceDimensions));
}
|
首先我們會使用builder.build(serviceDimensions)得到ServiceMetricEvent,然後將其作爲參數,調用emit函數:
1
2
3
4
5
|
@Override
public void emit(Event event)
{
emitter.emit(event);
}
|
4.將event加入batch
這裏會調用Emitter的emit函數,Emitter是一個接口,因此我們會以HttpPostEmitter爲例來代替Emitter進行介紹(HttpPostEmitter是Emitter的子類)。
1
2
3
4
5
|
@Override
public void emit(Event event)
{
emitAndReturnBatch(event);
}
|
HttpPostEmitter的emit函數會調用emitAndReturnBatch函數,這個函數會將metric event放到batch中,batch是一個緩存區,會在裝滿後加入隊列,並通過http發送。將event加入到batch的邏輯在如下語句中:
1
2
3
|
if (batch.tryAddEvent(eventBytes)) {
return batch;
}
|
接下來看一下tryAddEvent函數中的部分代碼,如下,如果batch中還沒有event,則調用tryAddFirstEvent函數;如果batch中已經有event,但是還沒裝滿,則調用tryAddNonFirstEvent函數;否則,也就是batch被裝滿了,我們將調用seal函數進行發送:
1
2
3
4
5
6
7
8
9
10
11
12
|
if (bufferWatermark == 0 ) {
if (tryAddFirstEvent(event)) {
return true ;
}
} else if (newBufferWatermark(bufferWatermark, event) <= emitter.maxBufferWatermark) {
if (tryAddNonFirstEvent(state, event)) {
return true ;
}
} else {
seal();
return false ;
}
|
這裏以tryAddFirstEvent函數爲例,介紹如何將event加入batch,tryAddNonFirstEvent函數原理類似,這裏不在過多介紹。
在tryAddFirstEvent函數中有如下語句,第一句將根據配置,向batch的buffer中寫入"[",第二句調用函數writeEvent將event寫入batch的buffer。
1
2
|
int bufferOffset = emitter.batchingStrategy.writeBatchStart(buffer);
writeEvent(event, bufferOffset);
|
在writeEvent中,使用如下語句將event寫入buffer:
1
|
System.arraycopy(event, 0 , buffer, bufferOffset, event.length);
|
5.將batch加入隊列並喚醒EmittingThread線程發送
在上一步驟,我們學習瞭如何將event放入batch,接下來介紹如何將已經裝滿的batch包裝起來,加入隊列,並喚醒EmittingThread線程發送
首先看seal函數:
1
2
3
4
|
void seal()
{
releaseShared(SEAL_TAG);
}
|
seal函數會調用AQS的releaseShared函數,然後會調用tryReleaseShared函數,在tryReleaseShared函數中可能需要處理三種情況,分別是解鎖、解鎖並打包和打包,這裏主要介紹打包的情況,也就是tag == SEAL_TAG的情況,其主要邏輯是如下語句,它會調用emitter的onSealExclusive函數,其中第一個函數是當前batch,第二個參數與超時時間有關:
1
2
3
4
|
emitter.onSealExclusive(
this ,
firstEventTimestamp > 0 ? System.currentTimeMillis() - firstEventTimestamp : - 1
);
|
在onSealExclusive函數中,會調用doOnSealExclusive函數,這個函數的主要邏輯是如下兩個語句,第一行將將batch加入發射隊列,第二行喚醒EmittingThread發射batch:
1
2
3
4
|
//將batch加入隊列
addBatchToEmitQueue(batch);
//喚醒emit線程進行發射
wakeUpEmittingThread();
|
6.EmittingThread線程
接下來介紹一下EmittingThread如何工作,即如何將batch通過http方式發送。
如下代碼是EmittingThread的run函數,其會在HttpPostEmitter啓動的時候被調用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@Override
public void run()
{
//一直執行,執行到最後會被阻塞,等待喚醒
while ( true ) {
boolean needsToShutdown = needsToShutdown();
try {
emitLargeEvents();
emitBatches();
tryEmitOneFailedBuffer();
if (needsToShutdown) {
tryEmitAndDrainAllFailedBuffers();
// Make GC life easier
drainBuffersToReuse();
return ;
}
}
catch (Throwable t) {
log.error(t, "Uncaught exception in EmittingThread.run()" );
}
if (failedBuffers.isEmpty()) {
// Waiting for 1/2 of config.getFlushMillis() in order to flush events not more than 50% later than specified.
// If nanos=0 parkNanos() doesn't wait at all, then we don't want.
long waitNanos = Math.max(TimeUnit.MILLISECONDS.toNanos(config.getFlushMillis()) / 2 , 1 );
//阻塞,等待喚醒
LockSupport.parkNanos(HttpPostEmitter. this , waitNanos);
}
}
}
|
在run函數中,最外層是一個while循環,且沒有終止條件,所以函數裏的邏輯會一直循環執行,而循環體的最後一行代碼是LockSupport.parkNanos(HttpPostEmitter.this, waitNanos);這將使改線程堵塞,等待被喚醒。也就是說,循環體中的邏輯啓動後會執行一次,然後被阻塞,直到我們在上一步驟中提到的wakeUpEmittingThread函數來歡迎該線程。即每次喚醒操作會執行一次上述函數中循環體中的代碼一次,接下來我們看一下循環體中的代碼,其中最重要的是如下三行:
1
2
3
|
emitLargeEvents();
emitBatches();
tryEmitOneFailedBuffer();
|
第一行將發射大的event(不能裝入batch的event),第二行是最重要的發射batch,第三行會嘗試發射失敗的buffer,它們三個的發射邏輯相同,這裏以emitBatches爲例進行介紹。
1
2
3
4
5
6
|
private void emitBatches()
{
for (Batch batch; (batch = pollBatchFromEmitQueue()) != null ; ) {
emit(batch);
}
}
|
emitBatches函數中會從發射隊列中取出所有batch逐個進行發射,在emit函數中有如下代碼。
1
2
3
4
5
6
7
8
|
if (sendWithRetries(batch.buffer, bufferEndOffset, eventCount, true )) {
buffersToReuse.add(batch.buffer);
approximateBuffersToReuseCount.incrementAndGet();
} else {
limitFailedBuffersSize();
failedBuffers.addLast( new FailedBuffer(batch.buffer, bufferEndOffset, eventCount));
approximateFailedBuffersCount.incrementAndGet();
}
|
上述代碼會使用sendWithRetries函數嘗試發送,如果發送失敗還會進行重試,如果發射成功,會講該batch的buffer回收利用,當重試次數用盡後依然失敗的,會將buffer加入失敗隊列等待過一會後重試。
在sendWithRetries函數中,會調用RetryUtils的retry函數,該函數有三個參數,第一個參數是一個Task,其perform函數是主要執行邏輯;第二個參數爲一個Predicate,用來判斷是否滿足條件;第三個參數爲最大重試次數。
在第一個參數Task的perform函數中,最重要的邏輯是send函數。
在send函數中,會生成一個request,其body爲Batch的buffer中的內容,url從配置中取得,然後執行如下語句通過http發送Batch的buffer中的內容到指定位置:
1
|
ListenableFuture<Response> future = client.executeRequest(request);
|