Dubbo metrics学习总结
1 简介
metrics为微服务的监控提供数据基础;是一个标准度量库,对监控对象的数据采集进行了统一封装。
2 特性与功能
3 实现原理
4 Metric注册与定义
监控数据采集逻辑的实现取决于具体的指标类型
4.1 支持的指标类型
4.1.1 操作系统
支持Linux/Windows/Mac,包含cpu, load, disk, nettraffic, tcp。
对应的实现模块:metrics-os
4.1.2 Jvm
支持classload, gc次数和时间, 文件句柄,young/old区占用,线程状态, 堆外内存,编译时间,部分指标支持自动差值计算
实现模块:metrics-jvm
4.1.3 中间件
4.1.3.1 Tomcat
请求数,失败次数,处理时间,发送接收字节数,线程池活跃线程数等。
实现模块:metrics-tomcat
4.1.3.2 Druid
sql执行次数,错误数,执行时间,影响行数等;
实现模块:metrics-druid.
4.1.3.3 Nginx
接受,活跃连接数,读,写请求数,排队数,请求qps,平均rt等;
实现模块:metrics-nginx.
4.1.4 应用层
需要使用相关api去应用层埋点实现;如qps等。
4.2 度量器
用于指标的统计,监控数据的结构和特征会有所不同,所以根据不同的需求会对应一种度量器
4.2.1 Counter
counter统计基于时间滑动窗口算法实现。
Bucket类提供分桶计数功能,统计一定时间间隔内的计数
BucketDeque类可以理解为一个时间窗口,用于保存最近N个时间间隔内的计数
统计值的修改逻辑,如下:
/**
* update the counter to the given bucket
*/
public void update(long n) {
if (updateTotalCount) {
totalCount.inc(n);
}
// align current timestamp
long curTs = TimeUnit.MILLISECONDS.toSeconds(clock.getTime()) / interval * interval;
Bucket oldBucket = latestBucket.get();
if (curTs > latestBucket.get().timestamp) {
// create a new bucket and evict the oldest one
Bucket newBucket = new Bucket();
newBucket.timestamp = curTs;
if (latestBucket.compareAndSet(oldBucket, newBucket)) {
// this is a single thread operation
buckets.addLast(newBucket);
oldBucket = newBucket;
} else {
oldBucket = latestBucket.get();
}
}
// reduce the call to latestBucket.get() to avoid cache line invalidation
// because internally latestBucket is a volatile object
oldBucket.count.add(n);
}
获取整个时间窗口的统计值实现,如下:
/**
* Return the bucket count, keyed by timestamp
* @return the bucket count, keyed by timestamp
*/
public Map<Long, Long> getBucketCounts(long startTime) {
Map<Long, Long> counts = new LinkedHashMap<Long, Long>();
long curTs = calculateCurrentTimestamp(clock.getTime());
for (Bucket bucket: buckets.getBucketList()) {
if (1000L * bucket.timestamp >= startTime && bucket.timestamp <= curTs) {
counts.put(1000L * bucket.timestamp, bucket.count.sum());
}
}
return counts;
}
备注:至于其它度量器的底层实现大多是基于BucketCounter。可以理解为也是通过时间滑动窗口算法实现的。
4.2.2 Meter(吞吐率度量器)
EWMA类是对指数加权移动平均值算法的实现,指数移动加权平均较传统的平均法来说,一是不需要保存过去所有的数值;二是计算量显著减小。
EWMA 的表达式如下:
vt=βθt +(1−β)vt−1
上式中 θt
为时刻 t 的实际温度;系数 β 表示加权下降的速率,其值越小下降的越快;vt 为 t
时刻 EWMA 的值。
在上图中有两条不同颜色的线,分别对应着不同的 β
值。
β值为0<β<1区间;
当β=0.9,是最接近实际数据的;
β的取值可以指定一个常量,也可以受其它因素的影响来取值,比如时间范围,β=aT,T为1分钟、5分钟等。
4.3 metric命名规则
metric的名字抽象为MetricName,它由两部分组成,key和tag。
4.3.1 Key
key是一个由.分隔的字符串, 它描述了这个metric的基本含义.
4.3.2 Tag
引入tag是为了实现多维度统计数据。tag由两部分组成, tagKey和tagValue, 形式为{tagKey=tagValue}
- key和tag只支持:[a-z][A-Z][0-9][-_./], 不能有空格, 大小写敏感, key原则上不包含大写。
- 格式为app_name.category[.sub_category]*,category和sub_category里面如果有多个单词,用下划线’_‘连接, 不要用’.'连接
- 需要动态聚合的维度, 放在tag里面, 同时在tagKey也在key中体现。 不需要聚合的维度, 放在key里面。
- 不要使用太多的tag, 一般而言4-5个已经足够
4.3.3 规则解析
MetricName对象的创建方式有:new MetricName()、MetricName.build()、MetricName.join()
key的解析原理,如下:
Tag的解析原理,如下:
举例说明:
Key为dubbo.service.qps;
Tag 为 service=demoService,host=demo
实现方式有多种:
- 方式一
MetricName name = new
MetricName("dubbo.service.qps").tagged("service", "demoService");
name = name.tagged("host", "demo");
- 方式二
MetricName name = MetricName.build("dubbo")
.resolve("service.qps")
.tagged("service", "demoService","host", "demo");
还有其它的方式
5 单机metric数据落盘
数据如果完全存在内存里面,可能会出现因为拉取失败,或者应用本身抖动,导致数据丢失的可能。
为了解决该问题,metrics引入了数据落盘的模块,提供了日志方式和二进制方式两种方式的落盘。
日志方式默认通过JSON方式进行输出,可以通过日志组件进行拉取和聚合,文件的可读性也比较强,但是无法查询历史数据。
二进制方式则提供了一种更加紧凑的存储,同时支持了对历史数据进行查询。
6 metric数据暴露
外部可以通过什么方式查询相关数据,如指标信息、指标实时统计数据等
指标信息:默认的指标、动态新增的指标
代码实现模块:metrics-rest
6.1 HTTP Server实现原理
基于Jersey+ sun Http server的简单实现
6.2 http查询接口
目前查询的是本地内存的数据
详细的接口说明见:https://github.com/dubbo/metrics/wiki/query-from-http
7 metric数据上报
供外部收集数据
代码实现模块:metrics-reporter
7.1 上报类型
7.1.1 SLF4J上报:Slf4jReporter
7.1.1.1 实现原理
7.1.2 OpenTsdb上报:OpenTsdbReporter
7.1.2.1 实现原理
7.1.3 jmx上报:JmxReporter
7.1.3.1 实现原理
备注:待完善
7.1.4 控制台上报:ConsoleReporter
7.1.4.1 实现原理
备注:待完善
7.1.5 Csv文件上报:CsvReporter
7.1.5.1 实现原理
备注:待完善
8使用方式
8.1 埋点
定义或注册metric
- 定义metric
指定指标类型、统计维度、级别、度量器。如下:
Counter hello = MetricManager.getCounter("test", MetricName.build("test.my.counter"));
hello.inc();
- 注册metric
例如:tomcat、druid、nginx等
MetricsIntegrateUtils.registerAllMetrics();
8.2 metric数据上报
首先根据需要选择你要上报的类型,例如用ConsoleReporter上报到控制台,则:
private final ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
.outputTo(output)
.formattedFor(Locale.US)
.withClock(clock)
.formattedFor(TimeZone.getTimeZone("PST"))
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.filter(MetricFilter.ALL)
.build();
reporter.report(this.<Gauge>map(),
map("test.counter", counter),
this.<Histogram>map(),
this.<Meter>map(),
this.<Timer>map());
8.3 Rest http server 启动
metricsHttpServer = new MetricsHttpServer();
metricsHttpServer.start();