基本介绍:
什么是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);
|